From ea08ea1370c51c700f93aac2a05b0735663cdee6 Mon Sep 17 00:00:00 2001 From: Danila Poyarkov Date: Fri, 22 May 2026 23:52:47 +0300 Subject: [PATCH] test(canvas): cover luminance masks and tile transforms --- tests/e2e/canvas/renderer-visuals.spec.ts | 120 ++++++++++++++++++ ...ansformed-tile-fills-openpencil-darwin.png | Bin 0 -> 19763 bytes 2 files changed, 120 insertions(+) create mode 100644 tests/e2e/canvas/renderer-visuals.spec.ts-snapshots/luminance-masks-and-transformed-tile-fills-openpencil-darwin.png diff --git a/tests/e2e/canvas/renderer-visuals.spec.ts b/tests/e2e/canvas/renderer-visuals.spec.ts index e7bea16a4..d033d8e04 100644 --- a/tests/e2e/canvas/renderer-visuals.spec.ts +++ b/tests/e2e/canvas/renderer-visuals.spec.ts @@ -166,3 +166,123 @@ test('gradients and image fill modes', async () => { await editor.canvas.waitForRender() await expectCanvas('gradients-and-image-fill-modes') }) + +test('luminance masks and transformed tile fills', async () => { + await editor.page.evaluate(async () => { + const store = window.openPencil?.getStore?.() + if (!store) throw new Error('OpenPencil store not initialized') + const pageId = store.state.currentPageId + + const imageCanvas = document.createElement('canvas') + imageCanvas.width = 32 + imageCanvas.height = 32 + const ctx = imageCanvas.getContext('2d') + if (!ctx) throw new Error('Cannot create tile fixture canvas') + ctx.fillStyle = '#111827' + ctx.fillRect(0, 0, 32, 32) + ctx.fillStyle = '#f97316' + ctx.fillRect(0, 0, 16, 16) + ctx.fillStyle = '#38bdf8' + ctx.fillRect(16, 16, 16, 16) + ctx.fillStyle = '#facc15' + ctx.fillRect(20, 4, 8, 8) + const blob = await new Promise((resolve, reject) => { + imageCanvas.toBlob((result) => { + if (result) { + resolve(result) + return + } + reject(new Error('Failed to encode tile fixture')) + }, 'image/png') + }) + const imageHash = store.storeImage(new Uint8Array(await blob.arrayBuffer())) + + store.graph.createNode('RECTANGLE', pageId, { + name: 'Transformed tile fill visual', + x: 80, + y: 76, + width: 180, + height: 130, + cornerRadius: 18, + fills: [ + { + type: 'IMAGE', + color: { r: 0, g: 0, b: 0, a: 1 }, + visible: true, + opacity: 1, + imageHash, + imageScaleMode: 'TILE', + imageTransform: { m00: 0.35, m01: 0, m02: 0.15, m10: 0, m11: 0.35, m12: 0.1 } + } + ], + strokes: [ + { + color: { r: 1, g: 1, b: 1, a: 0.85 }, + weight: 2, + visible: true, + opacity: 1, + align: 'INSIDE' + } + ] + }) + + const frame = store.graph.createNode('FRAME', pageId, { + name: 'Luminance mask stack visual', + x: 320, + y: 76, + width: 240, + height: 130, + cornerRadius: 18, + fills: [{ type: 'SOLID', color: { r: 0.08, g: 0.1, b: 0.18, a: 1 }, visible: true, opacity: 1 }] + }) + store.graph.createNode('RECTANGLE', frame.id, { + name: 'Luminance mask gradient', + x: 24, + y: 20, + width: 192, + height: 90, + isMask: true, + maskType: 'LUMINANCE', + fills: [ + { + type: 'GRADIENT_LINEAR', + color: { r: 0, g: 0, b: 0, a: 1 }, + visible: true, + opacity: 1, + gradientStops: [ + { color: { r: 0, g: 0, b: 0, a: 1 }, position: 0 }, + { color: { r: 1, g: 1, b: 1, a: 1 }, position: 1 } + ] + } + ] + }) + for (let index = 0; index < 5; index++) { + store.graph.createNode('RECTANGLE', frame.id, { + name: `Masked color stripe ${index + 1}`, + x: 24 + index * 38, + y: 20, + width: 34, + height: 90, + fills: [ + { + type: 'SOLID', + color: [ + { r: 0.96, g: 0.35, b: 0.35, a: 1 }, + { r: 0.96, g: 0.64, b: 0.16, a: 1 }, + { r: 0.08, g: 0.73, b: 0.73, a: 1 }, + { r: 0.23, g: 0.51, b: 0.96, a: 1 }, + { r: 0.58, g: 0.27, b: 0.95, a: 1 } + ][index], + visible: true, + opacity: 1 + } + ] + }) + } + + store.clearSelection() + store.requestRender() + }) + await editor.canvas.waitForRender() + await expectCanvas('luminance-masks-and-transformed-tile-fills') +}) diff --git a/tests/e2e/canvas/renderer-visuals.spec.ts-snapshots/luminance-masks-and-transformed-tile-fills-openpencil-darwin.png b/tests/e2e/canvas/renderer-visuals.spec.ts-snapshots/luminance-masks-and-transformed-tile-fills-openpencil-darwin.png new file mode 100644 index 0000000000000000000000000000000000000000..4f3c623b9fbf48999233032b0a306b6cb514712c GIT binary patch literal 19763 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 K9_{t{?LPqWLUd~Y literal 0 HcmV?d00001