openpencil/tests/e2e/canvas/vector/editing.spec.ts
Danila Poyarkov cb2c4aa3d3 fix(vector): edit vectors from opened files
- Resolve the active graph at action time and map edit geometry through full world transforms
- Regenerate live path fills and discard stale imported stroke outlines after edits
- Preserve fill and stroke geometry through resize previews, undo, and redo
- Cover graph replacement, nested transforms, edit history, resizing, and CanvasKit output

Co-authored-by: Rob Coenen <753704+rcoenen@users.noreply.github.com>
2026-07-28 12:30:21 +03:00

103 lines
3.5 KiB
TypeScript

import { expect, test, useEditorSetupWithClear } from '#tests/e2e/fixtures'
const editor = useEditorSetupWithClear('/?test&no-chrome&no-rulers')
test('vector edit overlay follows nested transforms and live path fills', async () => {
await editor.page.evaluate(() => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
const pageId = store.state.currentPageId
const rectBlob = (x: number, y: number, width: number, height: number) => {
const blob = new Uint8Array(38)
const view = new DataView(blob.buffer)
const points = [
{ command: 1, x, y },
{ command: 2, x: x + width, y },
{ command: 2, x: x + width, y: y + height },
{ command: 2, x, y: y + height }
]
let offset = 0
for (const point of points) {
blob[offset] = point.command
view.setFloat32(offset + 1, point.x, true)
view.setFloat32(offset + 5, point.y, true)
offset += 9
}
blob[offset] = 0
return blob
}
const blue = {
type: 'SOLID' as const,
color: { r: 0.23, g: 0.51, b: 0.96, a: 1 },
visible: true,
opacity: 1
}
const orange = {
type: 'SOLID' as const,
color: { r: 1, g: 0.4, b: 0, a: 1 },
visible: true,
opacity: 1
}
const frame = store.graph.createNode('FRAME', pageId, {
name: 'Edit host',
x: 220,
y: 120,
width: 400,
height: 300,
rotation: 30,
fills: [
{ type: 'SOLID', color: { r: 0.95, g: 0.95, b: 0.97, a: 1 }, visible: true, opacity: 1 }
]
})
const vector = store.graph.createNode('VECTOR', frame.id, {
name: 'Edited vector',
x: 60,
y: 80,
width: 200,
height: 100,
rotation: 20,
fills: [blue],
fillGeometry: [
{ windingRule: 'NONZERO', commandsBlob: rectBlob(0, 0, 100, 100) },
{ windingRule: 'NONZERO', commandsBlob: rectBlob(100, 0, 100, 100), fills: [orange] }
],
vectorNetwork: {
vertices: [
{ x: 0, y: 0 },
{ x: 100, y: 0 },
{ x: 100, y: 100 },
{ x: 0, y: 100 },
{ x: 100, y: 0 },
{ x: 200, y: 0 },
{ x: 200, y: 100 },
{ x: 100, y: 100 }
],
segments: [
{ start: 0, end: 1, tangentStart: { x: 20, y: 20 }, tangentEnd: { x: -20, y: 0 } },
{ start: 1, end: 2, tangentStart: { x: 0, y: 0 }, tangentEnd: { x: 0, y: 0 } },
{ start: 2, end: 3, tangentStart: { x: 0, y: 0 }, tangentEnd: { x: 0, y: 0 } },
{ start: 3, end: 0, tangentStart: { x: 0, y: 0 }, tangentEnd: { x: 0, y: 0 } },
{ start: 4, end: 5, tangentStart: { x: 0, y: 0 }, tangentEnd: { x: 0, y: 0 } },
{ start: 5, end: 6, tangentStart: { x: 0, y: 0 }, tangentEnd: { x: 0, y: 0 } },
{ start: 6, end: 7, tangentStart: { x: 0, y: 0 }, tangentEnd: { x: 0, y: 0 } },
{ start: 7, end: 4, tangentStart: { x: 0, y: 0 }, tangentEnd: { x: 0, y: 0 } }
],
regions: [
{ windingRule: 'NONZERO', loops: [[0, 1, 2, 3]] },
{ windingRule: 'NONZERO', loops: [[4, 5, 6, 7]] }
]
}
})
store.enterNodeEditMode(vector.id)
const editState = store.getNodeEditState()
if (editState) editState.vertices[6].x += 30
store.requestRender()
})
await editor.canvas.waitForRender()
editor.canvas.assertNoErrors()
const buffer = await editor.canvas.canvas.screenshot()
expect(buffer).toMatchSnapshot('vector-edit-mode-overlay.png')
})