openpencil/tests/e2e/canvas/drop-files.spec.ts
Danila Poyarkov 1f2f2468cc feat(svg): flatten compatible imported fills
- Merge adjacent filled SVG paths into editable multi-color vector networks
- Preserve stroke ordering and remap gradients into flattened bounds
- Export path-level solid and gradient fills without losing paint fidelity
- Cover real provider artwork, SVG round trips, and CanvasKit output

Co-authored-by: Rob Coenen <753704+rcoenen@users.noreply.github.com>
2026-08-01 15:30:13 +03:00

99 lines
3.2 KiB
TypeScript

import { expect, test, useEditorSetupWithClear } from '#tests/e2e/fixtures'
const editor = useEditorSetupWithClear('/?test&no-chrome&no-rulers')
async function dispatchMixedFileDrop() {
return editor.page.getByTestId('canvas-element').evaluate(async (canvas) => {
const bounds = canvas.getBoundingClientRect()
const svg = new File(
[
'<svg width="40" height="20" viewBox="0 0 40 20"><rect width="20" height="20" fill="#f00"/><rect x="20" width="20" height="20" fill="#0c8"/></svg>'
],
'mark.svg'
)
const rasterCanvas = document.createElement('canvas')
rasterCanvas.width = 20
rasterCanvas.height = 10
const context = rasterCanvas.getContext('2d')
if (!context) throw new Error('Canvas context unavailable')
context.fillStyle = '#00f'
context.fillRect(0, 0, 20, 10)
const blob = await new Promise<Blob>((resolve, reject) => {
rasterCanvas.toBlob((value) => {
if (value) resolve(value)
else reject(new Error('PNG failed'))
})
})
const image = new File([blob], 'photo.png', { type: 'image/png' })
const transfer = new DataTransfer()
transfer.items.add(svg)
transfer.items.add(image)
const eventOptions = {
bubbles: true,
cancelable: true,
clientX: bounds.left + 300,
clientY: bounds.top + 200,
dataTransfer: transfer
}
const dragoverAccepted = !canvas.dispatchEvent(new DragEvent('dragover', eventOptions))
canvas.dispatchEvent(new DragEvent('drop', eventOptions))
return dragoverAccepted
})
}
test('mixed SVG and raster drops share placement, selection, and undo', async () => {
expect(await dispatchMixedFileDrop()).toBe(true)
await expect
.poll(() =>
editor.page.evaluate(() => {
const store = window.openPencil?.getStore?.()
return store?.graph.getChildren(store.state.currentPageId).length ?? 0
})
)
.toBe(2)
const placed = await editor.page.evaluate(() => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
return {
nodes: store.graph
.getChildren(store.state.currentPageId)
.map((node) => ({ name: node.name, type: node.type })),
selected: store.state.selectedIds.size,
undoLabel: store.undo.undoLabel
}
})
expect(placed).toEqual({
nodes: [
{ name: 'mark', type: 'FRAME' },
{ name: 'photo', type: 'RECTANGLE' }
],
selected: 2,
undoLabel: 'Place files'
})
await editor.canvas.waitForRender()
expect(await editor.canvas.canvas.screenshot()).toMatchSnapshot('dropped-svg-multicolor.png')
await editor.page.evaluate(() => window.openPencil?.getStore?.().undoAction())
await expect
.poll(() =>
editor.page.evaluate(() => {
const store = window.openPencil?.getStore?.()
return store?.graph.getChildren(store.state.currentPageId).length ?? 0
})
)
.toBe(0)
await editor.page.evaluate(() => window.openPencil?.getStore?.().redoAction())
await expect
.poll(() =>
editor.page.evaluate(() => {
const store = window.openPencil?.getStore?.()
return store?.graph.getChildren(store.state.currentPageId).length ?? 0
})
)
.toBe(2)
editor.canvas.assertNoErrors()
})