111 lines
3.7 KiB
JavaScript
111 lines
3.7 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { readFile } from 'node:fs/promises';
|
|
import test from 'node:test';
|
|
|
|
const canvasLayersSource = await readFile(
|
|
new URL('../js/canvas/canvas_layers.js', import.meta.url),
|
|
'utf8'
|
|
);
|
|
const canvasRendererSource = await readFile(
|
|
new URL('../js/canvas/canvas_renderer.js', import.meta.url),
|
|
'utf8'
|
|
);
|
|
const canvasInteractionsSource = await readFile(
|
|
new URL('../js/canvas/canvas_interactions.js', import.meta.url),
|
|
'utf8'
|
|
);
|
|
|
|
function extractMethod(methodName) {
|
|
const start = canvasLayersSource.indexOf(` ${methodName}(`);
|
|
assert.notEqual(start, -1, `compiled ${methodName} method should exist`);
|
|
|
|
const bodyStart = canvasLayersSource.indexOf('{', start);
|
|
assert.notEqual(bodyStart, -1, `${methodName} should have a body`);
|
|
|
|
let depth = 0;
|
|
let quote = null;
|
|
let escaped = false;
|
|
for (let index = bodyStart; index < canvasLayersSource.length; index += 1) {
|
|
const character = canvasLayersSource[index];
|
|
|
|
if (quote) {
|
|
if (escaped) {
|
|
escaped = false;
|
|
} else if (character === '\\') {
|
|
escaped = true;
|
|
} else if (character === quote) {
|
|
quote = null;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (character === '"' || character === "'" || character === '`') {
|
|
quote = character;
|
|
continue;
|
|
}
|
|
if (character === '{') depth += 1;
|
|
if (character === '}') {
|
|
depth -= 1;
|
|
if (depth === 0) {
|
|
return canvasLayersSource.slice(start, index + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
throw new Error(`Could not extract ${methodName}`);
|
|
}
|
|
|
|
const methodFactory = new Function(
|
|
`return {\n${[
|
|
extractMethod('isSourceOverBlendMode'),
|
|
extractMethod('getDragCanvasBlendMode'),
|
|
extractMethod('createDragStaticSegments'),
|
|
].join(',\n')}\n};`,
|
|
);
|
|
const methods = methodFactory();
|
|
|
|
test('drag scene cache groups source-over layers and replays non-normal modes', () => {
|
|
const context = {
|
|
isSourceOverBlendMode: methods.isSourceOverBlendMode,
|
|
getDragCanvasBlendMode: methods.getDragCanvasBlendMode,
|
|
};
|
|
const layers = [
|
|
{ id: 'lower', blendMode: 'normal', opacity: 0.8 },
|
|
{ id: 'middle', blendMode: 'source-over', opacity: 0.6 },
|
|
{ id: 'blend', blendMode: 'multiply', opacity: 0.5 },
|
|
{ id: 'upper', blendMode: 'screen', opacity: 0.7 },
|
|
{ id: 'top', blendMode: 'normal', opacity: 1 },
|
|
];
|
|
|
|
const segments = methods.createDragStaticSegments.call(context, layers);
|
|
|
|
assert.equal(segments.length, 4);
|
|
assert.deepEqual(segments.map(segment => segment.layers.map(layer => layer.id)), [
|
|
['lower', 'middle'],
|
|
['blend'],
|
|
['upper'],
|
|
['top'],
|
|
]);
|
|
assert.deepEqual(segments.map(({ blendMode, opacity, applyLayerOpacity }) => ({
|
|
blendMode,
|
|
opacity,
|
|
applyLayerOpacity,
|
|
})), [
|
|
{ blendMode: 'source-over', opacity: 1, applyLayerOpacity: true },
|
|
{ blendMode: 'multiply', opacity: 0.5, applyLayerOpacity: false },
|
|
{ blendMode: 'screen', opacity: 0.7, applyLayerOpacity: false },
|
|
{ blendMode: 'source-over', opacity: 1, applyLayerOpacity: true },
|
|
]);
|
|
|
|
assert.match(canvasRendererSource, /canReuseDragFrame/);
|
|
assert.match(canvasRendererSource, /getDragDirtyRects/);
|
|
assert.match(canvasRendererSource, /dirtyRects\.forEach/);
|
|
assert.match(canvasRendererSource, /this\.canvas\.offscreenCanvas,\s*rect\.x/);
|
|
assert.match(canvasRendererSource, /ctx\.clip\(\)/);
|
|
assert.match(canvasLayersSource, /getOrCreateDragLayerPreview/);
|
|
assert.match(canvasLayersSource, /prepareDragLayerPreviews/);
|
|
assert.match(canvasLayersSource, /dragPreview: \{ viewport, width, height \}/);
|
|
assert.match(canvasLayersSource, /ctx\.drawImage\(preview, -layer\.width \/ 2/);
|
|
assert.match(canvasInteractionsSource, /prepareDragLayerPreviews\(this\.canvas\.canvasSelection\.selectedLayers\)/);
|
|
});
|