test(e2e): extract shared editor setup fixture
- useEditorSetup() / useEditorSetupWithClear() in tests/e2e/fixtures.ts - Migrated 17 specs to shared fixture, removing ~300 lines of boilerplate - Moved getSelectedNode/getSelectedNodes to shared store helpers - Replaced inline getSelectedNode in 4 specs with shared import - Test duplication: 329 → 308 clones (10.12% → 9.08%)
This commit is contained in:
parent
e7b610b228
commit
11e38b626a
|
|
@ -193,7 +193,8 @@ const strictTestFilePlacement = createFileRule('open-pencil/strict-test-file-pla
|
|||
return 'Temporary/profile test files must not be committed. Move exploratory specs to scratch/ or delete them.'
|
||||
}
|
||||
if (sourceRel.startsWith('tests/e2e/')) {
|
||||
return sourceRel.endsWith('.spec.ts') ? null : 'E2E tests must live under tests/e2e/** and use *.spec.ts.'
|
||||
if (sourceRel.endsWith('.spec.ts') || sourceRel.endsWith('/fixtures.ts')) return null
|
||||
return 'E2E tests must live under tests/e2e/** and use *.spec.ts.'
|
||||
}
|
||||
if (sourceRel.startsWith('tests/figma/')) {
|
||||
return sourceRel.endsWith('.spec.ts') ? null : 'Figma Playwright tests must live under tests/figma/** and use *.spec.ts.'
|
||||
|
|
|
|||
|
|
@ -1,37 +1,21 @@
|
|||
import { expect, test, type Page } from '@playwright/test'
|
||||
import { expect, test, useEditorSetup } from '#tests/e2e/fixtures'
|
||||
|
||||
import { CanvasHelper } from '#tests/helpers/canvas'
|
||||
|
||||
let page: Page
|
||||
let canvas: CanvasHelper
|
||||
|
||||
test.describe.configure({ mode: 'serial' })
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
page = await browser.newPage()
|
||||
await page.goto('/')
|
||||
canvas = new CanvasHelper(page)
|
||||
await canvas.waitForInit()
|
||||
})
|
||||
|
||||
test.afterAll(async () => {
|
||||
await page.close()
|
||||
})
|
||||
const editor = useEditorSetup()
|
||||
|
||||
test('menu bar is visible in browser mode', async () => {
|
||||
const menubar = page.locator('[role="menubar"]')
|
||||
const menubar = editor.page.locator('[role="menubar"]')
|
||||
await expect(menubar).toBeVisible()
|
||||
})
|
||||
|
||||
test('menu bar has all top-level menus', async () => {
|
||||
const triggers = page.locator('[role="menubar"] [role="menuitem"]')
|
||||
const triggers = editor.page.locator('[role="menubar"] [role="menuitem"]')
|
||||
const labels = await triggers.allTextContents()
|
||||
expect(labels).toEqual(['File', 'Edit', 'View', 'Object', 'Text', 'Arrange'])
|
||||
})
|
||||
|
||||
test('File menu opens and shows items', async () => {
|
||||
await page.locator('[role="menubar"] [role="menuitem"]', { hasText: 'File' }).click()
|
||||
const menu = page.locator('[role="menu"]')
|
||||
await editor.page.locator('[role="menubar"] [role="menuitem"]', { hasText: 'File' }).click()
|
||||
const menu = editor.page.locator('[role="menu"]')
|
||||
await expect(menu).toBeVisible()
|
||||
|
||||
const items = await menu.locator('[role="menuitem"]').allTextContents()
|
||||
|
|
@ -39,12 +23,12 @@ test('File menu opens and shows items', async () => {
|
|||
expect(items.some((t) => t.includes('Save'))).toBe(true)
|
||||
expect(items.some((t) => t.includes('Save As'))).toBe(true)
|
||||
|
||||
await page.keyboard.press('Escape')
|
||||
await editor.page.keyboard.press('Escape')
|
||||
})
|
||||
|
||||
test('Edit menu shows Undo/Redo/Delete', async () => {
|
||||
await page.locator('[role="menubar"] [role="menuitem"]', { hasText: 'Edit' }).click()
|
||||
const menu = page.locator('[role="menu"]')
|
||||
await editor.page.locator('[role="menubar"] [role="menuitem"]', { hasText: 'Edit' }).click()
|
||||
const menu = editor.page.locator('[role="menu"]')
|
||||
await expect(menu).toBeVisible()
|
||||
|
||||
const items = await menu.locator('[role="menuitem"]').allTextContents()
|
||||
|
|
@ -53,12 +37,12 @@ test('Edit menu shows Undo/Redo/Delete', async () => {
|
|||
expect(items.some((t) => t.includes('Delete'))).toBe(true)
|
||||
expect(items.some((t) => t.includes('Select all'))).toBe(true)
|
||||
|
||||
await page.keyboard.press('Escape')
|
||||
await editor.page.keyboard.press('Escape')
|
||||
})
|
||||
|
||||
test('View menu shows zoom options', async () => {
|
||||
await page.locator('[role="menubar"] [role="menuitem"]', { hasText: 'View' }).click()
|
||||
const menu = page.locator('[role="menu"]')
|
||||
await editor.page.locator('[role="menubar"] [role="menuitem"]', { hasText: 'View' }).click()
|
||||
const menu = editor.page.locator('[role="menu"]')
|
||||
await expect(menu).toBeVisible()
|
||||
|
||||
const items = await menu.locator('[role="menuitem"]').allTextContents()
|
||||
|
|
@ -66,12 +50,12 @@ test('View menu shows zoom options', async () => {
|
|||
expect(items.some((t) => t.includes('Zoom In'))).toBe(true)
|
||||
expect(items.some((t) => t.includes('Zoom Out'))).toBe(true)
|
||||
|
||||
await page.keyboard.press('Escape')
|
||||
await editor.page.keyboard.press('Escape')
|
||||
})
|
||||
|
||||
test('Object menu shows Group/Ungroup/Component', async () => {
|
||||
await page.locator('[role="menubar"] [role="menuitem"]', { hasText: 'Object' }).click()
|
||||
const menu = page.locator('[role="menu"]')
|
||||
await editor.page.locator('[role="menubar"] [role="menuitem"]', { hasText: 'Object' }).click()
|
||||
const menu = editor.page.locator('[role="menu"]')
|
||||
await expect(menu).toBeVisible()
|
||||
|
||||
const items = await menu.locator('[role="menuitem"]').allTextContents()
|
||||
|
|
@ -81,11 +65,11 @@ test('Object menu shows Group/Ungroup/Component', async () => {
|
|||
expect(items.some((t) => t.includes('Bring to front'))).toBe(true)
|
||||
expect(items.some((t) => t.includes('Send to back'))).toBe(true)
|
||||
|
||||
await page.keyboard.press('Escape')
|
||||
await editor.page.keyboard.press('Escape')
|
||||
})
|
||||
|
||||
function getStoreStateNumber(key: 'selectedIds' | 'zoom') {
|
||||
return page.evaluate((stateKey) => {
|
||||
return editor.page.evaluate((stateKey) => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
if (stateKey === 'selectedIds') return store.state.selectedIds.size
|
||||
|
|
@ -94,32 +78,32 @@ function getStoreStateNumber(key: 'selectedIds' | 'zoom') {
|
|||
}
|
||||
|
||||
test('Undo via Edit menu works', async () => {
|
||||
await canvas.drawRect(200, 200, 100, 100)
|
||||
await editor.canvas.drawRect(200, 200, 100, 100)
|
||||
const beforeUndo = await getStoreStateNumber('selectedIds')
|
||||
expect(beforeUndo).toBe(1)
|
||||
|
||||
await page.locator('[role="menubar"] [role="menuitem"]', { hasText: 'Edit' }).click()
|
||||
await page.locator('[role="menu"] [role="menuitem"]', { hasText: 'Undo' }).click()
|
||||
await canvas.waitForRender()
|
||||
await editor.page.locator('[role="menubar"] [role="menuitem"]', { hasText: 'Edit' }).click()
|
||||
await editor.page.locator('[role="menu"] [role="menuitem"]', { hasText: 'Undo' }).click()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const afterUndo = await getStoreStateNumber('selectedIds')
|
||||
expect(afterUndo).toBe(0)
|
||||
})
|
||||
|
||||
test('Duplicate via Edit menu works', async () => {
|
||||
await canvas.drawRect(300, 300, 80, 80)
|
||||
await editor.canvas.drawRect(300, 300, 80, 80)
|
||||
|
||||
const countBefore = await page.evaluate(() => {
|
||||
const countBefore = await editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
return store.graph.getChildren(store.state.currentPageId).length
|
||||
})
|
||||
|
||||
await page.locator('[role="menubar"] [role="menuitem"]', { hasText: 'Edit' }).click()
|
||||
await page.locator('[role="menu"] [role="menuitem"]', { hasText: 'Duplicate' }).click()
|
||||
await canvas.waitForRender()
|
||||
await editor.page.locator('[role="menubar"] [role="menuitem"]', { hasText: 'Edit' }).click()
|
||||
await editor.page.locator('[role="menu"] [role="menuitem"]', { hasText: 'Duplicate' }).click()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const countAfter = await page.evaluate(() => {
|
||||
const countAfter = await editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
return store.graph.getChildren(store.state.currentPageId).length
|
||||
|
|
@ -129,16 +113,16 @@ test('Duplicate via Edit menu works', async () => {
|
|||
})
|
||||
|
||||
test('Zoom to fit via View menu works', async () => {
|
||||
await page.locator('[role="menubar"] [role="menuitem"]', { hasText: 'View' }).click()
|
||||
await page.locator('[role="menu"] [role="menuitem"]', { hasText: 'Zoom in' }).click()
|
||||
await canvas.waitForRender()
|
||||
await editor.page.locator('[role="menubar"] [role="menuitem"]', { hasText: 'View' }).click()
|
||||
await editor.page.locator('[role="menu"] [role="menuitem"]', { hasText: 'Zoom in' }).click()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const zoomBefore = await getStoreStateNumber('zoom')
|
||||
expect(zoomBefore).toBeGreaterThan(1)
|
||||
|
||||
await page.locator('[role="menubar"] [role="menuitem"]', { hasText: 'View' }).click()
|
||||
await page.locator('[role="menu"] [role="menuitem"]', { hasText: 'Zoom to fit' }).click()
|
||||
await canvas.waitForRender()
|
||||
await editor.page.locator('[role="menubar"] [role="menuitem"]', { hasText: 'View' }).click()
|
||||
await editor.page.locator('[role="menu"] [role="menuitem"]', { hasText: 'Zoom to fit' }).click()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const zoomAfter = await getStoreStateNumber('zoom')
|
||||
expect(zoomAfter).not.toBe(zoomBefore)
|
||||
|
|
|
|||
|
|
@ -1,25 +1,10 @@
|
|||
import { expect, test, type Page } from '@playwright/test'
|
||||
|
||||
import { expect, test, useEditorSetup } from '#tests/e2e/fixtures'
|
||||
import { CanvasHelper } from '#tests/helpers/canvas'
|
||||
|
||||
let page: Page
|
||||
let canvas: CanvasHelper
|
||||
|
||||
test.describe.configure({ mode: 'serial' })
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
page = await browser.newPage()
|
||||
await page.goto('/')
|
||||
canvas = new CanvasHelper(page)
|
||||
await canvas.waitForInit()
|
||||
})
|
||||
|
||||
test.afterAll(async () => {
|
||||
await page.close()
|
||||
})
|
||||
const editor = useEditorSetup()
|
||||
|
||||
test('autosave triggers after scene changes with a file handle', async () => {
|
||||
const writeCount = await page.evaluate(() => {
|
||||
const writeCount = await editor.page.evaluate(() => {
|
||||
let writes = 0
|
||||
const mockWritable = {
|
||||
write: async () => {
|
||||
|
|
@ -41,7 +26,7 @@ test('autosave triggers after scene changes with a file handle', async () => {
|
|||
|
||||
// Inject the file handle into the store's internal state
|
||||
// We do this by calling a save first to establish the handle
|
||||
await page.evaluate(() => {
|
||||
await editor.page.evaluate(() => {
|
||||
// Directly set the fileHandle via a test hook
|
||||
// Since fileHandle is a closure variable, we need to trigger the save path
|
||||
// The cleanest way: mock showSaveFilePicker to return our handle
|
||||
|
|
@ -56,14 +41,14 @@ test('autosave triggers after scene changes with a file handle', async () => {
|
|||
})
|
||||
|
||||
// Trigger Save As to establish the file handle
|
||||
await page.keyboard.press('Meta+Shift+s')
|
||||
await page.waitForTimeout(500)
|
||||
await editor.page.keyboard.press('Meta+Shift+s')
|
||||
await editor.page.waitForTimeout(500)
|
||||
|
||||
// Now draw a shape — this should trigger autosave after 3s
|
||||
await canvas.drawRect(400, 400, 60, 60)
|
||||
await editor.canvas.drawRect(400, 400, 60, 60)
|
||||
|
||||
// Check that the scene version changed
|
||||
const versionAfterDraw = await page.evaluate(() => {
|
||||
const versionAfterDraw = await editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
return store.state.sceneVersion
|
||||
|
|
@ -71,10 +56,10 @@ test('autosave triggers after scene changes with a file handle', async () => {
|
|||
expect(versionAfterDraw).toBeGreaterThan(0)
|
||||
|
||||
// Wait for autosave debounce (3s) + buffer
|
||||
await page.waitForTimeout(4000)
|
||||
await editor.page.waitForTimeout(4000)
|
||||
|
||||
// Verify a write happened by checking the mock was called
|
||||
const writeHappened = await page.evaluate(() => {
|
||||
const writeHappened = await editor.page.evaluate(() => {
|
||||
// The handle's createWritable should have been called
|
||||
const handle = window.showSaveFilePicker
|
||||
return handle !== undefined
|
||||
|
|
|
|||
|
|
@ -1,35 +1,15 @@
|
|||
import { test, expect, type Page } from '@playwright/test'
|
||||
import { expect, test, useEditorSetupWithClear } from '#tests/e2e/fixtures'
|
||||
|
||||
import { CanvasHelper } from '#tests/helpers/canvas'
|
||||
|
||||
let page: Page
|
||||
let canvas: CanvasHelper
|
||||
|
||||
test.describe.configure({ mode: 'serial' })
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
page = await browser.newPage()
|
||||
await page.goto('/?test&no-chrome&no-rulers')
|
||||
canvas = new CanvasHelper(page)
|
||||
await canvas.waitForInit()
|
||||
})
|
||||
|
||||
test.afterAll(async () => {
|
||||
await page.close()
|
||||
})
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await canvas.clearCanvas()
|
||||
})
|
||||
const editor = useEditorSetupWithClear('/?test&no-chrome&no-rulers')
|
||||
|
||||
async function expectCanvas(name: string) {
|
||||
canvas.assertNoErrors()
|
||||
const buffer = await canvas.canvas.screenshot()
|
||||
editor.canvas.assertNoErrors()
|
||||
const buffer = await editor.canvas.canvas.screenshot()
|
||||
expect(buffer).toMatchSnapshot(`${name}.png`)
|
||||
}
|
||||
|
||||
async function createOverlayDemo(rotation: number) {
|
||||
await page.evaluate((frameRotation) => {
|
||||
await editor.page.evaluate((frameRotation) => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
const pageId = store.state.currentPageId
|
||||
|
|
@ -91,7 +71,7 @@ async function createOverlayDemo(rotation: number) {
|
|||
store.requestRender()
|
||||
}, rotation)
|
||||
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
}
|
||||
|
||||
test('rotated frame selection labels render with hovered child', async () => {
|
||||
|
|
@ -102,7 +82,7 @@ test('rotated frame selection labels render with hovered child', async () => {
|
|||
test('rotation preview updates frame labels before mouse up', async () => {
|
||||
await createOverlayDemo(0)
|
||||
|
||||
await page.evaluate(() => {
|
||||
await editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
const frameId = [...store.state.selectedIds][0]
|
||||
|
|
@ -110,7 +90,7 @@ test('rotation preview updates frame labels before mouse up', async () => {
|
|||
store.requestRepaint()
|
||||
})
|
||||
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
await expectCanvas('rotated-frame-selection-labels-preview')
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { expect, test, type Page } from '@playwright/test'
|
||||
|
||||
import { CanvasHelper } from '#tests/helpers/canvas'
|
||||
import { getSelectedNodes } from '#tests/helpers/store'
|
||||
|
||||
let page: Page
|
||||
let canvas: CanvasHelper
|
||||
|
|
@ -34,26 +35,6 @@ function getSelectedCount() {
|
|||
})
|
||||
}
|
||||
|
||||
function getSelectedNodes() {
|
||||
return page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
return [...store.state.selectedIds].map((id) => {
|
||||
const n = store.graph.getNode(id)
|
||||
if (!n) throw new Error(`Selected node ${id} not found`)
|
||||
return {
|
||||
id: n.id,
|
||||
name: n.name,
|
||||
type: n.type,
|
||||
x: n.x,
|
||||
y: n.y,
|
||||
width: n.width,
|
||||
height: n.height,
|
||||
fills: n.fills
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
test('copy + paste via store duplicates a shape', async () => {
|
||||
await canvas.drawRect(100, 100, 120, 80)
|
||||
|
|
@ -76,7 +57,7 @@ test('copy + paste via store duplicates a shape', async () => {
|
|||
})
|
||||
|
||||
test('pasted node is offset from original', async () => {
|
||||
const nodes = await getSelectedNodes()
|
||||
const nodes = await getSelectedNodes(page)
|
||||
expect(nodes).toHaveLength(1)
|
||||
|
||||
const pasted = nodes[0]
|
||||
|
|
@ -123,7 +104,7 @@ test('duplicate preserves fills', async () => {
|
|||
|
||||
await canvas.duplicate()
|
||||
|
||||
const nodes = await getSelectedNodes()
|
||||
const nodes = await getSelectedNodes(page)
|
||||
expect(nodes[0].fills[0].color.b).toBeCloseTo(1, 1)
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,25 +1,9 @@
|
|||
import { expect, test, type Page } from '@playwright/test'
|
||||
import { expect, test, useEditorSetup } from '#tests/e2e/fixtures'
|
||||
|
||||
import { CanvasHelper } from '#tests/helpers/canvas'
|
||||
|
||||
let page: Page
|
||||
let canvas: CanvasHelper
|
||||
|
||||
test.describe.configure({ mode: 'serial' })
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
page = await browser.newPage()
|
||||
await page.goto('/')
|
||||
canvas = new CanvasHelper(page)
|
||||
await canvas.waitForInit()
|
||||
})
|
||||
|
||||
test.afterAll(async () => {
|
||||
await page.close()
|
||||
})
|
||||
const editor = useEditorSetup()
|
||||
|
||||
function getNodeChildren(nodeId: string) {
|
||||
return page.evaluate((id) => {
|
||||
return editor.page.evaluate((id) => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
return store.graph.getChildren(id).map((n) => ({
|
||||
|
|
@ -32,7 +16,7 @@ function getNodeChildren(nodeId: string) {
|
|||
}
|
||||
|
||||
function getSelectedParent() {
|
||||
return page.evaluate(() => {
|
||||
return editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
const id = [...store.state.selectedIds][0]
|
||||
|
|
@ -43,7 +27,7 @@ function getSelectedParent() {
|
|||
}
|
||||
|
||||
function createFrameWithChild() {
|
||||
return page.evaluate(() => {
|
||||
return editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
const pageId = store.state.currentPageId
|
||||
|
|
@ -57,7 +41,7 @@ function createFrameWithChild() {
|
|||
}
|
||||
|
||||
function selectNode(nodeId: string) {
|
||||
return page.evaluate((id) => {
|
||||
return editor.page.evaluate((id) => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
store.select([id])
|
||||
|
|
@ -66,7 +50,7 @@ function selectNode(nodeId: string) {
|
|||
}
|
||||
|
||||
function copyAndPaste() {
|
||||
return page.evaluate(async () => {
|
||||
return editor.page.evaluate(async () => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
const data = new DataTransfer()
|
||||
|
|
@ -77,20 +61,20 @@ function copyAndPaste() {
|
|||
}
|
||||
|
||||
test('paste into selected frame places node as child', async () => {
|
||||
await canvas.clearCanvas()
|
||||
await editor.canvas.clearCanvas()
|
||||
const { frameId } = await createFrameWithChild()
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
await canvas.drawRect(400, 50, 60, 60)
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.drawRect(400, 50, 60, 60)
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
await copyAndPaste()
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
await selectNode(frameId)
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
await page.evaluate(async () => {
|
||||
await editor.page.evaluate(async () => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
const rect = [...store.graph.nodes.values()].find(
|
||||
|
|
@ -109,7 +93,7 @@ test('paste into selected frame places node as child', async () => {
|
|||
|
||||
if (html) await store.pasteFromHTML(html)
|
||||
})
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const children = await getNodeChildren(frameId)
|
||||
const pastedChild = children.find((c) => c.name === 'Rectangle')
|
||||
|
|
@ -118,14 +102,14 @@ test('paste into selected frame places node as child', async () => {
|
|||
})
|
||||
|
||||
test('paste with child selected places node as sibling in parent frame', async () => {
|
||||
await canvas.clearCanvas()
|
||||
await editor.canvas.clearCanvas()
|
||||
const { frameId, childId } = await createFrameWithChild()
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
await canvas.drawRect(400, 50, 60, 60)
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.drawRect(400, 50, 60, 60)
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
await page.evaluate(
|
||||
await editor.page.evaluate(
|
||||
async ({ childId: cid }) => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
|
|
@ -144,7 +128,7 @@ test('paste with child selected places node as sibling in parent frame', async (
|
|||
},
|
||||
{ childId }
|
||||
)
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const parent = await getSelectedParent()
|
||||
expect(parent).toBe(frameId)
|
||||
|
|
@ -154,11 +138,11 @@ test('paste with child selected places node as sibling in parent frame', async (
|
|||
})
|
||||
|
||||
test('paste with no selection places on page', async () => {
|
||||
await canvas.clearCanvas()
|
||||
await canvas.drawRect(100, 100, 60, 60)
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.clearCanvas()
|
||||
await editor.canvas.drawRect(100, 100, 60, 60)
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
await page.evaluate(async () => {
|
||||
await editor.page.evaluate(async () => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
const data = new DataTransfer()
|
||||
|
|
@ -167,15 +151,15 @@ test('paste with no selection places on page', async () => {
|
|||
store.clearSelection()
|
||||
if (html) await store.pasteFromHTML(html)
|
||||
})
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const parent = await getSelectedParent()
|
||||
const pageId = await page.evaluate(() => {
|
||||
const pageId = await editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
return store.state.currentPageId
|
||||
})
|
||||
expect(parent).toBe(pageId)
|
||||
|
||||
canvas.assertNoErrors()
|
||||
editor.canvas.assertNoErrors()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,45 +1,29 @@
|
|||
import { expect, test, type Page } from '@playwright/test'
|
||||
import { expect, test, useEditorSetup } from '#tests/e2e/fixtures'
|
||||
|
||||
import { CanvasHelper } from '#tests/helpers/canvas'
|
||||
|
||||
let page: Page
|
||||
let canvas: CanvasHelper
|
||||
|
||||
test.describe.configure({ mode: 'serial' })
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
page = await browser.newPage()
|
||||
await page.goto('/')
|
||||
canvas = new CanvasHelper(page)
|
||||
await canvas.waitForInit()
|
||||
})
|
||||
|
||||
test.afterAll(async () => {
|
||||
await page.close()
|
||||
})
|
||||
const editor = useEditorSetup()
|
||||
|
||||
function codeTab() {
|
||||
return page.getByTestId('properties-tab-code')
|
||||
return editor.page.getByTestId('properties-tab-code')
|
||||
}
|
||||
|
||||
function designTab() {
|
||||
return page.getByTestId('properties-tab-design')
|
||||
return editor.page.getByTestId('properties-tab-design')
|
||||
}
|
||||
|
||||
function codePanel() {
|
||||
return page.getByTestId('code-panel')
|
||||
return editor.page.getByTestId('code-panel')
|
||||
}
|
||||
|
||||
function codePanelEmpty() {
|
||||
return page.getByTestId('code-panel-empty')
|
||||
return editor.page.getByTestId('code-panel-empty')
|
||||
}
|
||||
|
||||
function formatToggle() {
|
||||
return page.getByTestId('code-panel-format-toggle')
|
||||
return editor.page.getByTestId('code-panel-format-toggle')
|
||||
}
|
||||
|
||||
function copyButton() {
|
||||
return page.getByTestId('code-panel-copy')
|
||||
return editor.page.getByTestId('code-panel-copy')
|
||||
}
|
||||
|
||||
test('Code tab shows empty state with no selection', async () => {
|
||||
|
|
@ -49,8 +33,8 @@ test('Code tab shows empty state with no selection', async () => {
|
|||
})
|
||||
|
||||
test('selecting a rectangle shows JSX code', async () => {
|
||||
await canvas.drawRect(100, 100, 200, 150)
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.drawRect(100, 100, 200, 150)
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
await expect(codePanel()).toBeVisible()
|
||||
|
||||
|
|
@ -79,26 +63,26 @@ test('copy button works and shows confirmation', async () => {
|
|||
|
||||
await expect(copyButton()).toContainText('Copied')
|
||||
|
||||
await page.waitForTimeout(2500)
|
||||
await editor.page.waitForTimeout(2500)
|
||||
await expect(copyButton()).toContainText('Copy')
|
||||
})
|
||||
|
||||
test('deselecting shows empty state again', async () => {
|
||||
await page.keyboard.press('Escape')
|
||||
await canvas.waitForRender()
|
||||
await editor.page.keyboard.press('Escape')
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
await expect(codePanelEmpty()).toBeVisible()
|
||||
})
|
||||
|
||||
test('selecting a frame shows Frame in JSX', async () => {
|
||||
// Create a frame via store to avoid click-targeting issues
|
||||
await page.evaluate(() => {
|
||||
await editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
const id = store.createShape('FRAME', 300, 100, 200, 200)
|
||||
store.select([id])
|
||||
})
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const code = await codePanel().textContent()
|
||||
expect(code).toContain('Frame')
|
||||
|
|
@ -107,5 +91,5 @@ test('selecting a frame shows Frame in JSX', async () => {
|
|||
test('switching back to Design tab works', async () => {
|
||||
await designTab().click()
|
||||
|
||||
await expect(page.getByTestId('design-panel-single').or(page.getByTestId('design-panel-empty')).first()).toBeVisible()
|
||||
await expect(editor.page.getByTestId('design-panel-single').or(editor.page.getByTestId('design-panel-empty')).first()).toBeVisible()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,25 +1,9 @@
|
|||
import { expect, test, type Page } from '@playwright/test'
|
||||
import { expect, test, useEditorSetup } from '#tests/e2e/fixtures'
|
||||
|
||||
import { CanvasHelper } from '#tests/helpers/canvas'
|
||||
|
||||
let page: Page
|
||||
let canvas: CanvasHelper
|
||||
|
||||
test.describe.configure({ mode: 'serial' })
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
page = await browser.newPage()
|
||||
await page.goto('/')
|
||||
canvas = new CanvasHelper(page)
|
||||
await canvas.waitForInit()
|
||||
})
|
||||
|
||||
test.afterAll(async () => {
|
||||
await page.close()
|
||||
})
|
||||
const editor = useEditorSetup()
|
||||
|
||||
async function getSelectedFill() {
|
||||
return page.evaluate(() => {
|
||||
return editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
const id = [...store.state.selectedIds][0]
|
||||
|
|
@ -30,36 +14,36 @@ async function getSelectedFill() {
|
|||
}
|
||||
|
||||
async function openFillPicker() {
|
||||
const solidTab = page.getByTestId('fill-picker-tab-solid')
|
||||
const solidTab = editor.page.getByTestId('fill-picker-tab-solid')
|
||||
if (await solidTab.isVisible().catch(() => false)) return
|
||||
const swatch = page.getByTestId('fill-picker-swatch').first()
|
||||
const swatch = editor.page.getByTestId('fill-picker-swatch').first()
|
||||
await swatch.click()
|
||||
await expect(solidTab).toBeVisible()
|
||||
}
|
||||
|
||||
async function chooseFormat(label: 'RGB' | 'HSL' | 'HSB' | 'OkHCL') {
|
||||
await page.getByTestId('color-format-select').click()
|
||||
await page.getByRole('option', { name: label, exact: true }).click()
|
||||
await editor.page.getByTestId('color-format-select').click()
|
||||
await editor.page.getByRole('option', { name: label, exact: true }).click()
|
||||
}
|
||||
|
||||
async function dragSlider(testId: string, ratio: number) {
|
||||
const slider = page.getByTestId(testId).locator('input[type="range"]')
|
||||
const slider = editor.page.getByTestId(testId).locator('input[type="range"]')
|
||||
const box = await slider.boundingBox()
|
||||
if (!box) throw new Error(`Missing slider: ${testId}`)
|
||||
const y = box.y + box.height / 2
|
||||
await page.mouse.move(box.x + 2, y)
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(box.x + Math.max(2, Math.min(box.width - 2, box.width * ratio)), y, {
|
||||
await editor.page.mouse.move(box.x + 2, y)
|
||||
await editor.page.mouse.down()
|
||||
await editor.page.mouse.move(box.x + Math.max(2, Math.min(box.width - 2, box.width * ratio)), y, {
|
||||
steps: 20
|
||||
})
|
||||
await page.mouse.up()
|
||||
await canvas.waitForRender()
|
||||
await editor.page.mouse.up()
|
||||
await editor.canvas.waitForRender()
|
||||
}
|
||||
|
||||
test('rgb hue slider updates selected fill color', async () => {
|
||||
await canvas.clearCanvas()
|
||||
await canvas.drawRect(100, 100, 160, 120)
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.clearCanvas()
|
||||
await editor.canvas.drawRect(100, 100, 160, 120)
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
await openFillPicker()
|
||||
const before = await getSelectedFill()
|
||||
|
|
|
|||
|
|
@ -1,26 +1,10 @@
|
|||
import { expect, test, type Page } from '@playwright/test'
|
||||
|
||||
import { expect, test, useEditorSetup } from '#tests/e2e/fixtures'
|
||||
import { expectDefined } from '#tests/helpers/assert'
|
||||
import { CanvasHelper } from '#tests/helpers/canvas'
|
||||
|
||||
let page: Page
|
||||
let canvas: CanvasHelper
|
||||
|
||||
test.describe.configure({ mode: 'serial' })
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
page = await browser.newPage()
|
||||
await page.goto('/')
|
||||
canvas = new CanvasHelper(page)
|
||||
await canvas.waitForInit()
|
||||
})
|
||||
|
||||
test.afterAll(async () => {
|
||||
await page.close()
|
||||
})
|
||||
const editor = useEditorSetup()
|
||||
|
||||
function getNodeById(id: string) {
|
||||
return page.evaluate((nodeId) => {
|
||||
return editor.page.evaluate((nodeId) => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
const n = store.graph.getNode(nodeId)
|
||||
|
|
@ -30,7 +14,7 @@ function getNodeById(id: string) {
|
|||
}
|
||||
|
||||
function getSelectedIds() {
|
||||
return page.evaluate(() => {
|
||||
return editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
return [...store.state.selectedIds]
|
||||
|
|
@ -38,7 +22,7 @@ function getSelectedIds() {
|
|||
}
|
||||
|
||||
function getPageChildren() {
|
||||
return page.evaluate(() => {
|
||||
return editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
return store.graph.getChildren(store.state.currentPageId).map((n) => ({
|
||||
|
|
@ -53,11 +37,11 @@ function getPageChildren() {
|
|||
let componentId: string
|
||||
|
||||
test('create component from selection (⌘⌥K)', async () => {
|
||||
await canvas.drawRect(100, 100, 120, 80)
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.drawRect(100, 100, 120, 80)
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
await page.keyboard.press('Meta+Alt+k')
|
||||
await canvas.waitForRender()
|
||||
await editor.page.keyboard.press('Meta+Alt+k')
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const ids = await getSelectedIds()
|
||||
expect(ids).toHaveLength(1)
|
||||
|
|
@ -69,16 +53,16 @@ test('create component from selection (⌘⌥K)', async () => {
|
|||
})
|
||||
|
||||
test('component shows purple label in design panel', async () => {
|
||||
const header = page.getByTestId('design-node-header')
|
||||
const header = editor.page.getByTestId('design-node-header')
|
||||
await expect(header).toContainText('COMPONENT')
|
||||
})
|
||||
|
||||
test('component visible in layers panel', async () => {
|
||||
const layers = page.locator('[data-node-id]')
|
||||
const layers = editor.page.locator('[data-node-id]')
|
||||
const count = await layers.count()
|
||||
expect(count).toBeGreaterThan(0)
|
||||
|
||||
const types = await page.evaluate(() => {
|
||||
const types = await editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
return store.graph.getChildren(store.state.currentPageId).map((n) => n.type)
|
||||
|
|
@ -92,12 +76,12 @@ test('create instance from component (context menu)', async () => {
|
|||
expect(comp).toBeTruthy()
|
||||
|
||||
// Use store directly to create instance
|
||||
await page.evaluate((compId) => {
|
||||
await editor.page.evaluate((compId) => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
store.createInstanceFromComponent(compId, 300, 100)
|
||||
}, expectDefined(comp, 'component').id)
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const updated = await getPageChildren()
|
||||
const instance = updated.find((c) => c.type === 'INSTANCE')
|
||||
|
|
@ -112,38 +96,38 @@ test('instance shows INSTANCE type in design panel', async () => {
|
|||
'instance node'
|
||||
)
|
||||
|
||||
await page.evaluate((id) => {
|
||||
await editor.page.evaluate((id) => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
store.select([id])
|
||||
}, instance.id)
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const header = page.getByTestId('design-node-header')
|
||||
const header = editor.page.getByTestId('design-node-header')
|
||||
await expect(header).toContainText('INSTANCE')
|
||||
})
|
||||
|
||||
test('instance has "Go to Main Component" button', async () => {
|
||||
const goToBtn = page.getByTestId('design-go-to-component')
|
||||
const goToBtn = editor.page.getByTestId('design-go-to-component')
|
||||
await expect(goToBtn).toBeVisible()
|
||||
})
|
||||
|
||||
test('instance has "Detach" button', async () => {
|
||||
const detachBtn = page.getByTestId('design-detach-instance')
|
||||
const detachBtn = editor.page.getByTestId('design-detach-instance')
|
||||
await expect(detachBtn).toBeVisible()
|
||||
})
|
||||
|
||||
test('modifying component propagates to instance', async () => {
|
||||
// Select the component
|
||||
await page.evaluate((id) => {
|
||||
await editor.page.evaluate((id) => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
store.select([id])
|
||||
}, componentId)
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
// Change component fill
|
||||
await page.evaluate((id) => {
|
||||
await editor.page.evaluate((id) => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
store.updateNodeWithUndo(
|
||||
|
|
@ -162,7 +146,7 @@ test('modifying component propagates to instance', async () => {
|
|||
'Change fill'
|
||||
)
|
||||
}, componentId)
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
// Check instance got the same fill
|
||||
const children = await getPageChildren()
|
||||
|
|
@ -170,7 +154,7 @@ test('modifying component propagates to instance', async () => {
|
|||
children.find((c) => c.type === 'INSTANCE'),
|
||||
'instance node'
|
||||
)
|
||||
const instanceNode = await page.evaluate((id) => {
|
||||
const instanceNode = await editor.page.evaluate((id) => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
const n = store.graph.getNode(id)
|
||||
|
|
@ -188,23 +172,23 @@ test('detach instance converts to frame', async () => {
|
|||
'instance node'
|
||||
)
|
||||
|
||||
await page.evaluate((id) => {
|
||||
await editor.page.evaluate((id) => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
store.select([id])
|
||||
}, instance.id)
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
await page.evaluate(() => {
|
||||
await editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
store.detachInstance()
|
||||
})
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const ids = await getSelectedIds()
|
||||
const detached = await getNodeById(expectDefined(ids[0], 'detached selected id'))
|
||||
expect(detached?.type).toBe('FRAME')
|
||||
|
||||
canvas.assertNoErrors()
|
||||
editor.canvas.assertNoErrors()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,26 +1,10 @@
|
|||
import { expect, test, type Page } from '@playwright/test'
|
||||
|
||||
import { expect, test, useEditorSetup } from '#tests/e2e/fixtures'
|
||||
import { expectDefined } from '#tests/helpers/assert'
|
||||
import { CanvasHelper } from '#tests/helpers/canvas'
|
||||
|
||||
let page: Page
|
||||
let canvas: CanvasHelper
|
||||
|
||||
test.describe.configure({ mode: 'serial' })
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
page = await browser.newPage()
|
||||
await page.goto('/')
|
||||
canvas = new CanvasHelper(page)
|
||||
await canvas.waitForInit()
|
||||
})
|
||||
|
||||
test.afterAll(async () => {
|
||||
await page.close()
|
||||
})
|
||||
const editor = useEditorSetup()
|
||||
|
||||
function getPageChildren() {
|
||||
return page.evaluate(() => {
|
||||
return editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
return store.graph.getChildren(store.state.currentPageId).map((n) => ({
|
||||
|
|
@ -34,7 +18,7 @@ function getPageChildren() {
|
|||
}
|
||||
|
||||
function getSelectedCount() {
|
||||
return page.evaluate(() => {
|
||||
return editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
return store.state.selectedIds.size
|
||||
|
|
@ -42,16 +26,16 @@ function getSelectedCount() {
|
|||
}
|
||||
|
||||
async function rightClickShape(x: number, y: number) {
|
||||
const box = expectDefined(await canvas.canvas.boundingBox(), 'canvas bounds')
|
||||
await page.mouse.click(box.x + x, box.y + y, { button: 'right' })
|
||||
const box = expectDefined(await editor.canvas.canvas.boundingBox(), 'canvas bounds')
|
||||
await editor.page.mouse.click(box.x + x, box.y + y, { button: 'right' })
|
||||
}
|
||||
|
||||
function contextMenu() {
|
||||
return page.locator('[role="menu"]')
|
||||
return editor.page.locator('[role="menu"]')
|
||||
}
|
||||
|
||||
function contextItem(testId: string) {
|
||||
return page.getByTestId(testId)
|
||||
return editor.page.getByTestId(testId)
|
||||
}
|
||||
|
||||
test('right-click on empty canvas shows context menu without selection items disabled', async () => {
|
||||
|
|
@ -62,16 +46,16 @@ test('right-click on empty canvas shows context menu without selection items dis
|
|||
const copyItem = contextItem('context-copy')
|
||||
await expect(copyItem).toBeVisible()
|
||||
|
||||
await page.keyboard.press('Escape')
|
||||
await editor.page.keyboard.press('Escape')
|
||||
})
|
||||
|
||||
test('draw shape and right-click selects it', async () => {
|
||||
await canvas.drawRect(200, 200, 120, 80)
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.drawRect(200, 200, 120, 80)
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
// Deselect first
|
||||
await page.keyboard.press('Escape')
|
||||
await canvas.waitForRender()
|
||||
await editor.page.keyboard.press('Escape')
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
// Right-click the shape
|
||||
await rightClickShape(250, 230)
|
||||
|
|
@ -91,7 +75,7 @@ test('context menu shows expected items', async () => {
|
|||
await expect(contextItem('context-toggle-visibility')).toBeVisible()
|
||||
await expect(contextItem('context-toggle-lock')).toBeVisible()
|
||||
|
||||
await page.keyboard.press('Escape')
|
||||
await editor.page.keyboard.press('Escape')
|
||||
})
|
||||
|
||||
test('duplicate via context menu works', async () => {
|
||||
|
|
@ -99,17 +83,17 @@ test('duplicate via context menu works', async () => {
|
|||
|
||||
await rightClickShape(250, 230)
|
||||
await contextItem('context-duplicate').click()
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const countAfter = (await getPageChildren()).length
|
||||
expect(countAfter).toBe(countBefore + 1)
|
||||
})
|
||||
|
||||
test('toggle visibility via context menu', async () => {
|
||||
await canvas.click(250, 230)
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.click(250, 230)
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const nodeId = await page.evaluate(() => {
|
||||
const nodeId = await editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
return [...store.state.selectedIds][0]
|
||||
|
|
@ -117,9 +101,9 @@ test('toggle visibility via context menu', async () => {
|
|||
|
||||
await rightClickShape(250, 230)
|
||||
await contextItem('context-toggle-visibility').click()
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const hidden = await page.evaluate((id) => {
|
||||
const hidden = await editor.page.evaluate((id) => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
const n = store.graph.getNode(id)
|
||||
|
|
@ -128,14 +112,14 @@ test('toggle visibility via context menu', async () => {
|
|||
expect(hidden?.visible).toBe(false)
|
||||
|
||||
// Toggle back: select via store since invisible nodes can't be hit-tested
|
||||
await page.evaluate(() => {
|
||||
await editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
store.toggleVisibility()
|
||||
})
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const restored = await page.evaluate((id) => {
|
||||
const restored = await editor.page.evaluate((id) => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
const n = store.graph.getNode(id)
|
||||
|
|
@ -147,7 +131,7 @@ test('toggle visibility via context menu', async () => {
|
|||
test('toggle lock via context menu', async () => {
|
||||
await rightClickShape(250, 230)
|
||||
await contextItem('context-toggle-lock').click()
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const children = await getPageChildren()
|
||||
const locked = children.find((c) => c.locked)
|
||||
|
|
@ -156,7 +140,7 @@ test('toggle lock via context menu', async () => {
|
|||
// Unlock
|
||||
await rightClickShape(250, 230)
|
||||
await contextItem('context-toggle-lock').click()
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const after = await getPageChildren()
|
||||
expect(after.every((c) => !c.locked)).toBe(true)
|
||||
|
|
@ -167,22 +151,22 @@ test('delete via context menu removes node', async () => {
|
|||
|
||||
await rightClickShape(250, 230)
|
||||
await contextItem('context-delete').click()
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const countAfter = (await getPageChildren()).length
|
||||
expect(countAfter).toBe(countBefore - 1)
|
||||
})
|
||||
|
||||
test('group via context menu', async () => {
|
||||
await canvas.clearCanvas()
|
||||
await canvas.drawRect(100, 100, 60, 60)
|
||||
await canvas.drawRect(200, 100, 60, 60)
|
||||
await canvas.selectAll()
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.clearCanvas()
|
||||
await editor.canvas.drawRect(100, 100, 60, 60)
|
||||
await editor.canvas.drawRect(200, 100, 60, 60)
|
||||
await editor.canvas.selectAll()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
await rightClickShape(130, 130)
|
||||
await contextItem('context-group').click()
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const children = await getPageChildren()
|
||||
const group = children.find((c) => c.type === 'GROUP')
|
||||
|
|
@ -191,35 +175,35 @@ test('group via context menu', async () => {
|
|||
|
||||
test('ungroup via store after context-menu group', async () => {
|
||||
// Groups are click-through, so ungroup via store instead
|
||||
await page.evaluate(() => {
|
||||
await editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
const group = store.graph.getChildren(store.state.currentPageId).find((n) => n.type === 'GROUP')
|
||||
if (group) store.select([group.id])
|
||||
store.ungroupSelected()
|
||||
})
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const children = await getPageChildren()
|
||||
expect(children.every((c) => c.type !== 'GROUP')).toBe(true)
|
||||
expect(children.length).toBe(2)
|
||||
|
||||
canvas.assertNoErrors()
|
||||
editor.canvas.assertNoErrors()
|
||||
})
|
||||
|
||||
test('create component via context menu', async () => {
|
||||
await canvas.click(130, 130)
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.click(130, 130)
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
await rightClickShape(130, 130)
|
||||
await contextItem('context-create-component').click()
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const children = await getPageChildren()
|
||||
const comp = children.find((c) => c.type === 'COMPONENT')
|
||||
expect(comp).toBeTruthy()
|
||||
|
||||
canvas.assertNoErrors()
|
||||
editor.canvas.assertNoErrors()
|
||||
})
|
||||
|
||||
test('Copy/Paste as submenu exists', async () => {
|
||||
|
|
@ -229,10 +213,10 @@ test('Copy/Paste as submenu exists', async () => {
|
|||
await expect(submenuTrigger).toBeVisible()
|
||||
|
||||
await submenuTrigger.hover()
|
||||
await page.waitForTimeout(300)
|
||||
await editor.page.waitForTimeout(300)
|
||||
|
||||
await expect(contextItem('context-copy-as-svg')).toBeVisible()
|
||||
await expect(contextItem('context-copy-as-jsx')).toBeVisible()
|
||||
|
||||
await page.keyboard.press('Escape')
|
||||
await editor.page.keyboard.press('Escape')
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,50 +1,34 @@
|
|||
import { expect, test, type Page } from '@playwright/test'
|
||||
|
||||
import { expect, test, useEditorSetup } from '#tests/e2e/fixtures'
|
||||
import { expectDefined } from '#tests/helpers/assert'
|
||||
import { CanvasHelper } from '#tests/helpers/canvas'
|
||||
|
||||
let page: Page
|
||||
let canvas: CanvasHelper
|
||||
|
||||
test.describe.configure({ mode: 'serial' })
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
page = await browser.newPage()
|
||||
await page.goto('/')
|
||||
canvas = new CanvasHelper(page)
|
||||
await canvas.waitForInit()
|
||||
})
|
||||
|
||||
test.afterAll(async () => {
|
||||
await page.close()
|
||||
})
|
||||
const editor = useEditorSetup()
|
||||
|
||||
function designPanel() {
|
||||
return page.getByTestId('design-panel-single')
|
||||
return editor.page.getByTestId('design-panel-single')
|
||||
}
|
||||
|
||||
function nodeHeader() {
|
||||
return page.getByTestId('design-node-header')
|
||||
return editor.page.getByTestId('design-node-header')
|
||||
}
|
||||
|
||||
function fillSection() {
|
||||
return page.getByTestId('fill-section')
|
||||
return editor.page.getByTestId('fill-section')
|
||||
}
|
||||
|
||||
function strokeSection() {
|
||||
return page.getByTestId('stroke-section')
|
||||
return editor.page.getByTestId('stroke-section')
|
||||
}
|
||||
|
||||
function positionSection() {
|
||||
return page.getByTestId('position-section')
|
||||
return editor.page.getByTestId('position-section')
|
||||
}
|
||||
|
||||
function effectsSection() {
|
||||
return page.getByTestId('effects-section')
|
||||
return editor.page.getByTestId('effects-section')
|
||||
}
|
||||
|
||||
function getNode(id: string) {
|
||||
return page.evaluate((nodeId) => {
|
||||
return editor.page.evaluate((nodeId) => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
const n = store.graph.getNode(nodeId)
|
||||
|
|
@ -65,7 +49,7 @@ function getNode(id: string) {
|
|||
}
|
||||
|
||||
function getSelectedId() {
|
||||
return page.evaluate(() => {
|
||||
return editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
return [...store.state.selectedIds][0] ?? null
|
||||
|
|
@ -73,8 +57,8 @@ function getSelectedId() {
|
|||
}
|
||||
|
||||
test('selecting a rectangle shows design panel with type and name', async () => {
|
||||
await canvas.drawRect(100, 100, 120, 80)
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.drawRect(100, 100, 120, 80)
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
await expect(designPanel()).toBeVisible()
|
||||
await expect(nodeHeader()).toContainText('RECTANGLE')
|
||||
|
|
@ -108,16 +92,16 @@ test('clicking color area changes fill color', async () => {
|
|||
const swatch = fillSection().getByTestId('fill-picker-swatch').first()
|
||||
await swatch.click()
|
||||
|
||||
const colorArea = page.locator('.cursor-crosshair').first()
|
||||
const colorArea = editor.page.locator('.cursor-crosshair').first()
|
||||
await expect(colorArea).toBeVisible({ timeout: 5000 })
|
||||
|
||||
const box = await colorArea.boundingBox()
|
||||
await page.mouse.click(
|
||||
await editor.page.mouse.click(
|
||||
expectDefined(box, 'color area bounds').x + expectDefined(box, 'color area bounds').width - 10,
|
||||
expectDefined(box, 'color area bounds').y + 10
|
||||
)
|
||||
await canvas.waitForRender()
|
||||
await page.waitForTimeout(100)
|
||||
await editor.canvas.waitForRender()
|
||||
await editor.page.waitForTimeout(100)
|
||||
|
||||
const after = await getNode(expectDefined(id, 'selected id'))
|
||||
const c1 = expectDefined(before, 'before node').fills[0].color
|
||||
|
|
@ -126,13 +110,13 @@ test('clicking color area changes fill color', async () => {
|
|||
|
||||
// Close popover — click the swatch again to toggle it off
|
||||
await swatch.click()
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
})
|
||||
|
||||
test('adding a stroke creates stroke section item', async () => {
|
||||
const addBtn = strokeSection().getByTestId('stroke-section-add')
|
||||
await addBtn.click()
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const strokeItems = strokeSection().getByTestId('stroke-item')
|
||||
await expect(strokeItems.first()).toBeVisible()
|
||||
|
|
@ -145,7 +129,7 @@ test('adding a stroke creates stroke section item', async () => {
|
|||
test('adding an effect creates effect item', async () => {
|
||||
const addBtn = effectsSection().getByTestId('effects-section-add')
|
||||
await addBtn.click()
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const effectItems = effectsSection().getByTestId('effect-item')
|
||||
await expect(effectItems.first()).toBeVisible()
|
||||
|
|
@ -158,7 +142,7 @@ test('adding an effect creates effect item', async () => {
|
|||
test('adding a second fill shows two fill items', async () => {
|
||||
const addBtn = fillSection().getByTestId('fill-section-add')
|
||||
await addBtn.click()
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const fillItems = fillSection().getByTestId('fill-item')
|
||||
expect(await fillItems.count()).toBe(2)
|
||||
|
|
@ -169,7 +153,7 @@ test('adding a second fill shows two fill items', async () => {
|
|||
})
|
||||
|
||||
test('visibility toggle in appearance section works', async () => {
|
||||
const visBtn = page.getByTestId('appearance-visibility')
|
||||
const visBtn = editor.page.getByTestId('appearance-visibility')
|
||||
await expect(visBtn).toBeVisible()
|
||||
|
||||
const id = await getSelectedId()
|
||||
|
|
@ -177,13 +161,13 @@ test('visibility toggle in appearance section works', async () => {
|
|||
expect(expectDefined(before, 'before node').visible).toBe(true)
|
||||
|
||||
await visBtn.click()
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const after = await getNode(expectDefined(id, 'selected id'))
|
||||
expect(expectDefined(after, 'after node').visible).toBe(false)
|
||||
|
||||
await visBtn.click()
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const restored = await getNode(expectDefined(id, 'selected id'))
|
||||
expect(expectDefined(restored, 'restored node').visible).toBe(true)
|
||||
|
|
@ -193,14 +177,14 @@ test('fill stroke and effect visibility toggles update on repeated clicks and su
|
|||
const id = await getSelectedId()
|
||||
expect(id).toBeTruthy()
|
||||
|
||||
const fillButton = page.getByTestId('fill-visibility-0')
|
||||
const fillButton = editor.page.getByTestId('fill-visibility-0')
|
||||
await expect(fillButton).toBeVisible()
|
||||
|
||||
const initial = await getNode(expectDefined(id, 'selected id'))
|
||||
expect(expectDefined(initial, 'initial node').fills[0]?.visible).toBe(true)
|
||||
|
||||
await fillButton.click()
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
await expect(fillButton).toHaveAttribute('data-visible', 'false')
|
||||
expect(
|
||||
expectDefined(await getNode(expectDefined(id, 'selected id')), 'selected node').fills[0]
|
||||
|
|
@ -208,19 +192,19 @@ test('fill stroke and effect visibility toggles update on repeated clicks and su
|
|||
).toBe(false)
|
||||
|
||||
await fillButton.click()
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
await expect(fillButton).toHaveAttribute('data-visible', 'true')
|
||||
expect(
|
||||
expectDefined(await getNode(expectDefined(id, 'selected id')), 'selected node').fills[0]
|
||||
?.visible
|
||||
).toBe(true)
|
||||
|
||||
await canvas.undo()
|
||||
await editor.canvas.undo()
|
||||
expect(
|
||||
expectDefined(await getNode(expectDefined(id, 'selected id')), 'selected node').fills[0]
|
||||
?.visible
|
||||
).toBe(false)
|
||||
await canvas.redo()
|
||||
await editor.canvas.redo()
|
||||
expect(
|
||||
expectDefined(await getNode(expectDefined(id, 'selected id')), 'selected node').fills[0]
|
||||
?.visible
|
||||
|
|
@ -228,9 +212,9 @@ test('fill stroke and effect visibility toggles update on repeated clicks and su
|
|||
|
||||
const strokeAddButton = strokeSection().getByTestId('stroke-section-add')
|
||||
await strokeAddButton.click()
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const strokeButton = page.getByTestId('stroke-visibility-0')
|
||||
const strokeButton = editor.page.getByTestId('stroke-visibility-0')
|
||||
await expect(strokeButton).toBeVisible()
|
||||
expect(
|
||||
expectDefined(await getNode(expectDefined(id, 'selected id')), 'selected node').strokes[0]
|
||||
|
|
@ -238,7 +222,7 @@ test('fill stroke and effect visibility toggles update on repeated clicks and su
|
|||
).toBe(true)
|
||||
|
||||
await strokeButton.click()
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
await expect(strokeButton).toHaveAttribute('data-visible', 'false')
|
||||
expect(
|
||||
expectDefined(await getNode(expectDefined(id, 'selected id')), 'selected node').strokes[0]
|
||||
|
|
@ -246,19 +230,19 @@ test('fill stroke and effect visibility toggles update on repeated clicks and su
|
|||
).toBe(false)
|
||||
|
||||
await strokeButton.click()
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
await expect(strokeButton).toHaveAttribute('data-visible', 'true')
|
||||
expect(
|
||||
expectDefined(await getNode(expectDefined(id, 'selected id')), 'selected node').strokes[0]
|
||||
?.visible
|
||||
).toBe(true)
|
||||
|
||||
await canvas.undo()
|
||||
await editor.canvas.undo()
|
||||
expect(
|
||||
expectDefined(await getNode(expectDefined(id, 'selected id')), 'selected node').strokes[0]
|
||||
?.visible
|
||||
).toBe(false)
|
||||
await canvas.redo()
|
||||
await editor.canvas.redo()
|
||||
expect(
|
||||
expectDefined(await getNode(expectDefined(id, 'selected id')), 'selected node').strokes[0]
|
||||
?.visible
|
||||
|
|
@ -266,9 +250,9 @@ test('fill stroke and effect visibility toggles update on repeated clicks and su
|
|||
|
||||
const effectAddButton = effectsSection().getByTestId('effects-section-add')
|
||||
await effectAddButton.click()
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const effectButton = page.getByTestId('effect-visibility-0')
|
||||
const effectButton = editor.page.getByTestId('effect-visibility-0')
|
||||
await expect(effectButton).toBeVisible()
|
||||
expect(
|
||||
expectDefined(await getNode(expectDefined(id, 'selected id')), 'selected node').effects[0]
|
||||
|
|
@ -276,7 +260,7 @@ test('fill stroke and effect visibility toggles update on repeated clicks and su
|
|||
).toBe(true)
|
||||
|
||||
await effectButton.click()
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
await expect(effectButton).toHaveAttribute('data-visible', 'false')
|
||||
expect(
|
||||
expectDefined(await getNode(expectDefined(id, 'selected id')), 'selected node').effects[0]
|
||||
|
|
@ -284,19 +268,19 @@ test('fill stroke and effect visibility toggles update on repeated clicks and su
|
|||
).toBe(false)
|
||||
|
||||
await effectButton.click()
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
await expect(effectButton).toHaveAttribute('data-visible', 'true')
|
||||
expect(
|
||||
expectDefined(await getNode(expectDefined(id, 'selected id')), 'selected node').effects[0]
|
||||
?.visible
|
||||
).toBe(true)
|
||||
|
||||
await canvas.undo()
|
||||
await editor.canvas.undo()
|
||||
expect(
|
||||
expectDefined(await getNode(expectDefined(id, 'selected id')), 'selected node').effects[0]
|
||||
?.visible
|
||||
).toBe(false)
|
||||
await canvas.redo()
|
||||
await editor.canvas.redo()
|
||||
expect(
|
||||
expectDefined(await getNode(expectDefined(id, 'selected id')), 'selected node').effects[0]
|
||||
?.visible
|
||||
|
|
@ -304,19 +288,19 @@ test('fill stroke and effect visibility toggles update on repeated clicks and su
|
|||
})
|
||||
|
||||
test('deselecting shows empty design panel', async () => {
|
||||
await page.keyboard.press('Escape')
|
||||
await canvas.waitForRender()
|
||||
await editor.page.keyboard.press('Escape')
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
await expect(page.getByTestId('design-panel-empty')).toBeVisible()
|
||||
await expect(editor.page.getByTestId('design-panel-empty')).toBeVisible()
|
||||
})
|
||||
|
||||
test('multi-select shows mixed header', async () => {
|
||||
await canvas.drawRect(300, 100, 60, 60)
|
||||
await canvas.drawRect(400, 100, 60, 60)
|
||||
await canvas.selectAll()
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.drawRect(300, 100, 60, 60)
|
||||
await editor.canvas.drawRect(400, 100, 60, 60)
|
||||
await editor.canvas.selectAll()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const multiHeader = page.getByTestId('design-multi-header')
|
||||
const multiHeader = editor.page.getByTestId('design-multi-header')
|
||||
await expect(multiHeader).toBeVisible()
|
||||
await expect(multiHeader).toContainText('Mixed')
|
||||
await expect(multiHeader).toContainText('layers')
|
||||
|
|
|
|||
|
|
@ -1,30 +1,10 @@
|
|||
import { test, expect, type Page } from '@playwright/test'
|
||||
import { expect, test, useEditorSetupWithClear } from '#tests/e2e/fixtures'
|
||||
|
||||
import { CanvasHelper } from '#tests/helpers/canvas'
|
||||
|
||||
let page: Page
|
||||
let canvas: CanvasHelper
|
||||
|
||||
test.describe.configure({ mode: 'serial' })
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
page = await browser.newPage()
|
||||
await page.goto('/?test&no-chrome&no-rulers')
|
||||
canvas = new CanvasHelper(page)
|
||||
await canvas.waitForInit()
|
||||
})
|
||||
|
||||
test.afterAll(async () => {
|
||||
await page.close()
|
||||
})
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await canvas.clearCanvas()
|
||||
})
|
||||
const editor = useEditorSetupWithClear('/?test&no-chrome&no-rulers')
|
||||
|
||||
async function expectCanvas(name: string) {
|
||||
canvas.assertNoErrors()
|
||||
const buffer = await canvas.canvas.screenshot()
|
||||
editor.canvas.assertNoErrors()
|
||||
const buffer = await editor.canvas.canvas.screenshot()
|
||||
expect(buffer).toMatchSnapshot(`${name}.png`)
|
||||
}
|
||||
|
||||
|
|
@ -33,31 +13,31 @@ test('empty canvas', async () => {
|
|||
})
|
||||
|
||||
test('draw rectangle', async () => {
|
||||
await canvas.drawRect(100, 100, 200, 150)
|
||||
await editor.canvas.drawRect(100, 100, 200, 150)
|
||||
await expectCanvas('draw-rectangle')
|
||||
})
|
||||
|
||||
test('draw ellipse', async () => {
|
||||
await canvas.drawEllipse(100, 100, 200, 150)
|
||||
await editor.canvas.drawEllipse(100, 100, 200, 150)
|
||||
await expectCanvas('draw-ellipse')
|
||||
})
|
||||
|
||||
test('draw rectangle then move it', async () => {
|
||||
await canvas.drawRect(100, 100, 200, 150)
|
||||
await canvas.selectTool('select')
|
||||
await canvas.drag(200, 175, 400, 300)
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.drawRect(100, 100, 200, 150)
|
||||
await editor.canvas.selectTool('select')
|
||||
await editor.canvas.drag(200, 175, 400, 300)
|
||||
await editor.canvas.waitForRender()
|
||||
await expectCanvas('draw-rectangle-then-move-it')
|
||||
})
|
||||
|
||||
test('draw and delete', async () => {
|
||||
await canvas.drawRect(100, 100, 200, 150)
|
||||
await canvas.deleteSelection()
|
||||
await editor.canvas.drawRect(100, 100, 200, 150)
|
||||
await editor.canvas.deleteSelection()
|
||||
await expectCanvas('draw-and-delete')
|
||||
})
|
||||
|
||||
test('draw and undo', async () => {
|
||||
await canvas.drawRect(100, 100, 200, 150)
|
||||
await canvas.undo()
|
||||
await editor.canvas.drawRect(100, 100, 200, 150)
|
||||
await editor.canvas.undo()
|
||||
await expectCanvas('draw-and-undo')
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,29 +1,9 @@
|
|||
import { expect, test, type Page } from '@playwright/test'
|
||||
import { expect, test, useEditorSetupWithClear } from '#tests/e2e/fixtures'
|
||||
|
||||
import { CanvasHelper } from '#tests/helpers/canvas'
|
||||
|
||||
let page: Page
|
||||
let canvas: CanvasHelper
|
||||
|
||||
test.describe.configure({ mode: 'serial' })
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
page = await browser.newPage()
|
||||
await page.goto('/?test')
|
||||
canvas = new CanvasHelper(page)
|
||||
await canvas.waitForInit()
|
||||
})
|
||||
|
||||
test.afterAll(async () => {
|
||||
await page.close()
|
||||
})
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await canvas.clearCanvas()
|
||||
})
|
||||
const editor = useEditorSetupWithClear('/?test')
|
||||
|
||||
async function rectangleCount() {
|
||||
return page.evaluate(() => {
|
||||
return editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
return [...store.graph.nodes.values()].filter((node) => node.type === 'RECTANGLE').length
|
||||
|
|
@ -31,11 +11,11 @@ async function rectangleCount() {
|
|||
}
|
||||
|
||||
async function layerItems() {
|
||||
return page.getByTestId('layers-item').allTextContents()
|
||||
return editor.page.getByTestId('layers-item').allTextContents()
|
||||
}
|
||||
|
||||
async function historyState() {
|
||||
return page.evaluate(() => {
|
||||
return editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
return {
|
||||
|
|
@ -48,36 +28,36 @@ async function historyState() {
|
|||
}
|
||||
|
||||
test('undo after option-drag duplicate removes the copy', async () => {
|
||||
await canvas.drawRect(120, 120, 120, 90)
|
||||
await editor.canvas.drawRect(120, 120, 120, 90)
|
||||
await expect.poll(rectangleCount).toBe(1)
|
||||
|
||||
await canvas.altDrag(180, 165, 340, 165)
|
||||
await editor.canvas.altDrag(180, 165, 340, 165)
|
||||
await expect.poll(rectangleCount).toBe(2)
|
||||
await expect.poll(layerItems).toEqual(['Rectangle', 'Rectangle copy'])
|
||||
|
||||
await canvas.undo()
|
||||
await editor.canvas.undo()
|
||||
await expect.poll(rectangleCount).toBe(1)
|
||||
await expect.poll(layerItems).toEqual(['Rectangle'])
|
||||
await expect.poll(historyState).toMatchObject({ canRedo: true, redoLabel: 'Duplicate' })
|
||||
|
||||
await page.waitForTimeout(1500)
|
||||
await editor.page.waitForTimeout(1500)
|
||||
await expect.poll(rectangleCount).toBe(1)
|
||||
await expect.poll(layerItems).toEqual(['Rectangle'])
|
||||
await expect.poll(historyState).toMatchObject({ canRedo: true, redoLabel: 'Duplicate' })
|
||||
|
||||
await page.keyboard.down('Meta')
|
||||
await page.keyboard.down('Shift')
|
||||
await page.keyboard.press('KeyZ')
|
||||
await page.keyboard.up('Shift')
|
||||
await page.keyboard.up('Meta')
|
||||
await canvas.waitForRender()
|
||||
await editor.page.keyboard.down('Meta')
|
||||
await editor.page.keyboard.down('Shift')
|
||||
await editor.page.keyboard.press('KeyZ')
|
||||
await editor.page.keyboard.up('Shift')
|
||||
await editor.page.keyboard.up('Meta')
|
||||
await editor.canvas.waitForRender()
|
||||
await expect.poll(rectangleCount).toBe(2)
|
||||
await expect.poll(layerItems).toEqual(['Rectangle', 'Rectangle copy'])
|
||||
await expect.poll(historyState).toMatchObject({ canUndo: true, undoLabel: 'Duplicate' })
|
||||
|
||||
await page.waitForTimeout(1500)
|
||||
await editor.page.waitForTimeout(1500)
|
||||
await expect.poll(rectangleCount).toBe(2)
|
||||
await expect.poll(layerItems).toEqual(['Rectangle', 'Rectangle copy'])
|
||||
await expect.poll(historyState).toMatchObject({ canUndo: true, undoLabel: 'Duplicate' })
|
||||
canvas.assertNoErrors()
|
||||
editor.canvas.assertNoErrors()
|
||||
})
|
||||
|
|
|
|||
42
tests/e2e/fixtures.ts
Normal file
42
tests/e2e/fixtures.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { test, expect, type Page } from '@playwright/test'
|
||||
|
||||
import { CanvasHelper } from '#tests/helpers/canvas'
|
||||
|
||||
export function useEditorSetup(url = '/') {
|
||||
let page: Page
|
||||
let canvas: CanvasHelper
|
||||
|
||||
test.describe.configure({ mode: 'serial' })
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
page = await browser.newPage()
|
||||
await page.goto(url)
|
||||
canvas = new CanvasHelper(page)
|
||||
await canvas.waitForInit()
|
||||
})
|
||||
|
||||
test.afterAll(async () => {
|
||||
await page.close()
|
||||
})
|
||||
|
||||
return {
|
||||
get page() {
|
||||
return page
|
||||
},
|
||||
get canvas() {
|
||||
return canvas
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function useEditorSetupWithClear(url = '/') {
|
||||
const ctx = useEditorSetup(url)
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await ctx.canvas.clearCanvas()
|
||||
})
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
export { test, expect }
|
||||
|
|
@ -1,26 +1,10 @@
|
|||
import { expect, test, type Page } from '@playwright/test'
|
||||
|
||||
import { expect, test, useEditorSetup } from '#tests/e2e/fixtures'
|
||||
import { expectDefined } from '#tests/helpers/assert'
|
||||
import { CanvasHelper } from '#tests/helpers/canvas'
|
||||
|
||||
let page: Page
|
||||
let canvas: CanvasHelper
|
||||
|
||||
test.describe.configure({ mode: 'serial' })
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
page = await browser.newPage()
|
||||
await page.goto('/')
|
||||
canvas = new CanvasHelper(page)
|
||||
await canvas.waitForInit()
|
||||
})
|
||||
|
||||
test.afterAll(async () => {
|
||||
await page.close()
|
||||
})
|
||||
const editor = useEditorSetup()
|
||||
|
||||
function getActiveTool() {
|
||||
return page.evaluate(() => {
|
||||
return editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
return store.state.activeTool
|
||||
|
|
@ -28,7 +12,7 @@ function getActiveTool() {
|
|||
}
|
||||
|
||||
function getSelectedCount() {
|
||||
return page.evaluate(() => {
|
||||
return editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
return store.state.selectedIds.size
|
||||
|
|
@ -36,7 +20,7 @@ function getSelectedCount() {
|
|||
}
|
||||
|
||||
function getPageChildren() {
|
||||
return page.evaluate(() => {
|
||||
return editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
return store.graph.getChildren(store.state.currentPageId).map((n) => ({
|
||||
|
|
@ -49,7 +33,7 @@ function getPageChildren() {
|
|||
}
|
||||
|
||||
function getUIVisible() {
|
||||
return page.evaluate(() => {
|
||||
return editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
return store.state.showUI
|
||||
|
|
@ -57,7 +41,7 @@ function getUIVisible() {
|
|||
}
|
||||
|
||||
function getZoom() {
|
||||
return page.evaluate(() => {
|
||||
return editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
return store.state.zoom
|
||||
|
|
@ -66,74 +50,74 @@ function getZoom() {
|
|||
|
||||
test.describe('tool switching', () => {
|
||||
test('V → SELECT', async () => {
|
||||
await page.keyboard.press('v')
|
||||
await editor.page.keyboard.press('v')
|
||||
expect(await getActiveTool()).toBe('SELECT')
|
||||
})
|
||||
|
||||
test('R → RECTANGLE', async () => {
|
||||
await page.keyboard.press('r')
|
||||
await editor.page.keyboard.press('r')
|
||||
expect(await getActiveTool()).toBe('RECTANGLE')
|
||||
})
|
||||
|
||||
test('O → ELLIPSE', async () => {
|
||||
await page.keyboard.press('o')
|
||||
await editor.page.keyboard.press('o')
|
||||
expect(await getActiveTool()).toBe('ELLIPSE')
|
||||
})
|
||||
|
||||
test('F → FRAME', async () => {
|
||||
await page.keyboard.press('f')
|
||||
await editor.page.keyboard.press('f')
|
||||
expect(await getActiveTool()).toBe('FRAME')
|
||||
})
|
||||
|
||||
test('T → TEXT', async () => {
|
||||
await page.keyboard.press('t')
|
||||
await editor.page.keyboard.press('t')
|
||||
expect(await getActiveTool()).toBe('TEXT')
|
||||
})
|
||||
|
||||
test('L → LINE', async () => {
|
||||
await page.keyboard.press('l')
|
||||
await editor.page.keyboard.press('l')
|
||||
expect(await getActiveTool()).toBe('LINE')
|
||||
})
|
||||
|
||||
test('P → PEN', async () => {
|
||||
await page.keyboard.press('p')
|
||||
await editor.page.keyboard.press('p')
|
||||
expect(await getActiveTool()).toBe('PEN')
|
||||
})
|
||||
|
||||
test('H → HAND', async () => {
|
||||
await page.keyboard.press('h')
|
||||
await editor.page.keyboard.press('h')
|
||||
expect(await getActiveTool()).toBe('HAND')
|
||||
})
|
||||
|
||||
test('S → SECTION', async () => {
|
||||
await page.keyboard.press('s')
|
||||
await editor.page.keyboard.press('s')
|
||||
expect(await getActiveTool()).toBe('SECTION')
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('selection shortcuts', () => {
|
||||
test('⌘A selects all', async () => {
|
||||
await page.keyboard.press('v')
|
||||
await canvas.drawRect(100, 100, 60, 60)
|
||||
await canvas.drawRect(200, 100, 60, 60)
|
||||
await editor.page.keyboard.press('v')
|
||||
await editor.canvas.drawRect(100, 100, 60, 60)
|
||||
await editor.canvas.drawRect(200, 100, 60, 60)
|
||||
|
||||
await page.keyboard.press('Meta+a')
|
||||
await editor.page.keyboard.press('Meta+a')
|
||||
expect(await getSelectedCount()).toBe(2)
|
||||
})
|
||||
|
||||
test('Escape clears selection and resets to SELECT tool', async () => {
|
||||
await page.keyboard.press('Escape')
|
||||
await editor.page.keyboard.press('Escape')
|
||||
expect(await getSelectedCount()).toBe(0)
|
||||
expect(await getActiveTool()).toBe('SELECT')
|
||||
})
|
||||
|
||||
test('Backspace deletes selected', async () => {
|
||||
await canvas.selectAll()
|
||||
await editor.canvas.selectAll()
|
||||
const beforeCount = await getSelectedCount()
|
||||
expect(beforeCount).toBeGreaterThan(0)
|
||||
|
||||
await page.keyboard.press('Backspace')
|
||||
await canvas.waitForRender()
|
||||
await editor.page.keyboard.press('Backspace')
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const children = await getPageChildren()
|
||||
expect(children).toHaveLength(0)
|
||||
|
|
@ -142,22 +126,22 @@ test.describe('selection shortcuts', () => {
|
|||
|
||||
test.describe('z-order shortcuts', () => {
|
||||
test('] brings to front', async () => {
|
||||
await canvas.drawRect(100, 100, 60, 60)
|
||||
await canvas.drawRect(100, 100, 60, 60)
|
||||
await editor.canvas.drawRect(100, 100, 60, 60)
|
||||
await editor.canvas.drawRect(100, 100, 60, 60)
|
||||
|
||||
const childrenBefore = await getPageChildren()
|
||||
const firstId = expectDefined(childrenBefore[0], 'first page child').id
|
||||
|
||||
// Select the first (bottom) node
|
||||
await page.evaluate((id) => {
|
||||
await editor.page.evaluate((id) => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
store.select([id])
|
||||
}, firstId)
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
await page.keyboard.press(']')
|
||||
await canvas.waitForRender()
|
||||
await editor.page.keyboard.press(']')
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const childrenAfter = await getPageChildren()
|
||||
expect(childrenAfter[childrenAfter.length - 1].id).toBe(firstId)
|
||||
|
|
@ -168,15 +152,15 @@ test.describe('z-order shortcuts', () => {
|
|||
const lastId = expectDefined(children.at(-1), 'last page child').id
|
||||
|
||||
// Select the last (top) node
|
||||
await page.evaluate((id) => {
|
||||
await editor.page.evaluate((id) => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
store.select([id])
|
||||
}, lastId)
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
await page.keyboard.press('[')
|
||||
await canvas.waitForRender()
|
||||
await editor.page.keyboard.press('[')
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const after = await getPageChildren()
|
||||
expect(after[0].id).toBe(lastId)
|
||||
|
|
@ -185,11 +169,11 @@ test.describe('z-order shortcuts', () => {
|
|||
|
||||
test.describe('group/ungroup shortcuts', () => {
|
||||
test('⌘G groups selection', async () => {
|
||||
await canvas.selectAll()
|
||||
await editor.canvas.selectAll()
|
||||
expect(await getSelectedCount()).toBeGreaterThanOrEqual(2)
|
||||
|
||||
await page.keyboard.press('Meta+g')
|
||||
await canvas.waitForRender()
|
||||
await editor.page.keyboard.press('Meta+g')
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const children = await getPageChildren()
|
||||
const group = children.find((c) => c.type === 'GROUP')
|
||||
|
|
@ -198,8 +182,8 @@ test.describe('group/ungroup shortcuts', () => {
|
|||
})
|
||||
|
||||
test('⌘⇧G ungroups', async () => {
|
||||
await page.keyboard.press('Meta+Shift+g')
|
||||
await canvas.waitForRender()
|
||||
await editor.page.keyboard.press('Meta+Shift+g')
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const children = await getPageChildren()
|
||||
expect(children.every((c) => c.type !== 'GROUP')).toBe(true)
|
||||
|
|
@ -210,28 +194,28 @@ test.describe('UI toggles', () => {
|
|||
test('⌘\\ toggles UI visibility', async () => {
|
||||
const before = await getUIVisible()
|
||||
|
||||
await page.keyboard.press('Meta+\\')
|
||||
await canvas.waitForRender()
|
||||
await editor.page.keyboard.press('Meta+\\')
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const after = await getUIVisible()
|
||||
expect(after).toBe(!before)
|
||||
|
||||
// Restore
|
||||
await page.keyboard.press('Meta+\\')
|
||||
await canvas.waitForRender()
|
||||
await editor.page.keyboard.press('Meta+\\')
|
||||
await editor.canvas.waitForRender()
|
||||
expect(await getUIVisible()).toBe(before)
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('duplicate', () => {
|
||||
test('⌘D duplicates selection', async () => {
|
||||
await canvas.clearCanvas()
|
||||
await canvas.drawRect(100, 100, 60, 60)
|
||||
await canvas.selectAll()
|
||||
await editor.canvas.clearCanvas()
|
||||
await editor.canvas.drawRect(100, 100, 60, 60)
|
||||
await editor.canvas.selectAll()
|
||||
expect(await getSelectedCount()).toBe(1)
|
||||
|
||||
await page.keyboard.press('Meta+d')
|
||||
await canvas.waitForRender()
|
||||
await editor.page.keyboard.press('Meta+d')
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const children = await getPageChildren()
|
||||
expect(children).toHaveLength(2)
|
||||
|
|
@ -240,27 +224,27 @@ test.describe('duplicate', () => {
|
|||
|
||||
test.describe('zoom shortcuts', () => {
|
||||
test('⌘0 zooms to 100%', async () => {
|
||||
await canvas.clearCanvas()
|
||||
await canvas.drawRect(100, 100, 60, 60)
|
||||
await editor.canvas.clearCanvas()
|
||||
await editor.canvas.drawRect(100, 100, 60, 60)
|
||||
|
||||
// Set zoom to something other than 100%
|
||||
await page.evaluate(() => {
|
||||
await editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
store.state.zoom = 2
|
||||
})
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
await page.keyboard.press('Meta+0')
|
||||
await canvas.waitForRender()
|
||||
await editor.page.keyboard.press('Meta+0')
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const zoomAfter = await getZoom()
|
||||
expect(zoomAfter).toBe(1)
|
||||
})
|
||||
|
||||
test('⌘1 zooms to fit', async () => {
|
||||
await page.keyboard.press('Meta+1')
|
||||
await canvas.waitForRender()
|
||||
await editor.page.keyboard.press('Meta+1')
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const zoom = await getZoom()
|
||||
expect(zoom).toBeGreaterThan(0)
|
||||
|
|
@ -268,10 +252,10 @@ test.describe('zoom shortcuts', () => {
|
|||
})
|
||||
|
||||
test('⌘2 zooms to selection', async () => {
|
||||
await canvas.selectAll()
|
||||
await editor.canvas.selectAll()
|
||||
|
||||
await page.keyboard.press('Meta+2')
|
||||
await canvas.waitForRender()
|
||||
await editor.page.keyboard.press('Meta+2')
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const zoom = await getZoom()
|
||||
expect(zoom).toBeGreaterThan(0)
|
||||
|
|
@ -279,8 +263,8 @@ test.describe('zoom shortcuts', () => {
|
|||
})
|
||||
|
||||
test('⇧1 zooms to fit (same as ⌘1)', async () => {
|
||||
await page.keyboard.press('Shift+1')
|
||||
await canvas.waitForRender()
|
||||
await editor.page.keyboard.press('Shift+1')
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const zoom = await getZoom()
|
||||
expect(zoom).toBeGreaterThan(0)
|
||||
|
|
@ -288,10 +272,10 @@ test.describe('zoom shortcuts', () => {
|
|||
})
|
||||
|
||||
test('⇧2 zooms to selection (same as ⌘2)', async () => {
|
||||
await canvas.selectAll()
|
||||
await editor.canvas.selectAll()
|
||||
|
||||
await page.keyboard.press('Shift+2')
|
||||
await canvas.waitForRender()
|
||||
await editor.page.keyboard.press('Shift+2')
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const zoom = await getZoom()
|
||||
expect(zoom).toBeGreaterThan(0)
|
||||
|
|
@ -301,19 +285,19 @@ test.describe('zoom shortcuts', () => {
|
|||
|
||||
test.describe('undo/redo', () => {
|
||||
test('⌘Z undoes last action', async () => {
|
||||
await canvas.clearCanvas()
|
||||
await canvas.drawRect(100, 100, 60, 60)
|
||||
await editor.canvas.clearCanvas()
|
||||
await editor.canvas.drawRect(100, 100, 60, 60)
|
||||
expect((await getPageChildren()).length).toBe(1)
|
||||
|
||||
await page.keyboard.press('Meta+z')
|
||||
await canvas.waitForRender()
|
||||
await editor.page.keyboard.press('Meta+z')
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
expect((await getPageChildren()).length).toBe(0)
|
||||
})
|
||||
|
||||
test('⌘⇧Z redoes undone action', async () => {
|
||||
await page.keyboard.press('Meta+Shift+z')
|
||||
await canvas.waitForRender()
|
||||
await editor.page.keyboard.press('Meta+Shift+z')
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
expect((await getPageChildren()).length).toBe(1)
|
||||
})
|
||||
|
|
@ -321,20 +305,20 @@ test.describe('undo/redo', () => {
|
|||
|
||||
test.describe('auto-layout shortcut', () => {
|
||||
test('⇧A toggles auto-layout on frame', async () => {
|
||||
await canvas.clearCanvas()
|
||||
await canvas.drawRect(100, 100, 200, 200)
|
||||
await canvas.selectAll()
|
||||
await editor.canvas.clearCanvas()
|
||||
await editor.canvas.drawRect(100, 100, 200, 200)
|
||||
await editor.canvas.selectAll()
|
||||
|
||||
// Change to frame type for auto-layout
|
||||
await page.evaluate(() => {
|
||||
await editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
const nodes = [...store.state.selectedIds]
|
||||
if (nodes[0]) store.updateNode(nodes[0], { type: 'FRAME' })
|
||||
})
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const layoutBefore = await page.evaluate(() => {
|
||||
const layoutBefore = await editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
const nodes = [...store.state.selectedIds]
|
||||
|
|
@ -342,10 +326,10 @@ test.describe('auto-layout shortcut', () => {
|
|||
})
|
||||
expect(layoutBefore).toBe('NONE')
|
||||
|
||||
await page.keyboard.press('Shift+a')
|
||||
await canvas.waitForRender()
|
||||
await editor.page.keyboard.press('Shift+a')
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const layoutAfter = await page.evaluate(() => {
|
||||
const layoutAfter = await editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
const nodes = [...store.state.selectedIds]
|
||||
|
|
@ -354,10 +338,10 @@ test.describe('auto-layout shortcut', () => {
|
|||
expect(layoutAfter).toBe('VERTICAL')
|
||||
|
||||
// Toggle off
|
||||
await page.keyboard.press('Shift+a')
|
||||
await canvas.waitForRender()
|
||||
await editor.page.keyboard.press('Shift+a')
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const layoutFinal = await page.evaluate(() => {
|
||||
const layoutFinal = await editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
const nodes = [...store.state.selectedIds]
|
||||
|
|
|
|||
|
|
@ -1,25 +1,9 @@
|
|||
import { expect, test, type Page } from '@playwright/test'
|
||||
import { expect, test, useEditorSetup } from '#tests/e2e/fixtures'
|
||||
|
||||
import { CanvasHelper } from '#tests/helpers/canvas'
|
||||
|
||||
let page: Page
|
||||
let canvas: CanvasHelper
|
||||
|
||||
test.describe.configure({ mode: 'serial' })
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
page = await browser.newPage()
|
||||
await page.goto('/demo')
|
||||
canvas = new CanvasHelper(page)
|
||||
await canvas.waitForInit()
|
||||
})
|
||||
|
||||
test.afterAll(async () => {
|
||||
await page.close()
|
||||
})
|
||||
const editor = useEditorSetup('/demo')
|
||||
|
||||
function layerRows() {
|
||||
return page.locator('[data-node-id]')
|
||||
return editor.page.locator('[data-node-id]')
|
||||
}
|
||||
|
||||
async function getLayerNames(): Promise<string[]> {
|
||||
|
|
@ -40,7 +24,7 @@ interface SceneTreeNode {
|
|||
}
|
||||
|
||||
async function getSceneTree(): Promise<SceneTreeNode> {
|
||||
return page.evaluate(() => {
|
||||
return editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) return null
|
||||
|
||||
|
|
@ -60,7 +44,7 @@ async function getSceneTree(): Promise<SceneTreeNode> {
|
|||
}
|
||||
|
||||
async function getSelectedCount(): Promise<number> {
|
||||
return page.evaluate(() => {
|
||||
return editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
return store.state.selectedIds.size
|
||||
|
|
@ -82,8 +66,8 @@ test('clicking a node inside a frame does not reparent it', async () => {
|
|||
expect(sidebarBefore).toBeTruthy()
|
||||
|
||||
// Click inside the App Preview section area
|
||||
await canvas.click(350, 310)
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.click(350, 310)
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
// Sidebar should still be a child of Dashboard
|
||||
const afterTree = await getSceneTree()
|
||||
|
|
@ -91,17 +75,17 @@ test('clicking a node inside a frame does not reparent it', async () => {
|
|||
const afterDashboard = afterSection?.children.find((c) => c.name === 'Dashboard')
|
||||
expect(afterDashboard?.children.find((c) => c.name === 'Sidebar')).toBeTruthy()
|
||||
|
||||
canvas.assertNoErrors()
|
||||
editor.canvas.assertNoErrors()
|
||||
})
|
||||
|
||||
test('creating a shape updates layers', async () => {
|
||||
const before = await getLayerNames()
|
||||
await canvas.drawRect(600, 500, 50, 50)
|
||||
await editor.canvas.drawRect(600, 500, 50, 50)
|
||||
const names = await getLayerNames()
|
||||
expect(names).toContain('Rectangle')
|
||||
expect(names.length).toBe(before.length + 1)
|
||||
|
||||
await canvas.undo()
|
||||
await editor.canvas.undo()
|
||||
const after = await getLayerNames()
|
||||
expect(after.length).toBe(before.length)
|
||||
expect(after).not.toContain('Rectangle')
|
||||
|
|
@ -109,16 +93,16 @@ test('creating a shape updates layers', async () => {
|
|||
|
||||
test('Shift+A wraps selection in auto-layout frame', async () => {
|
||||
// Draw two loose rectangles for this test
|
||||
await canvas.drawRect(700, 600, 60, 60)
|
||||
await canvas.drawRect(800, 600, 60, 60)
|
||||
await canvas.selectAll()
|
||||
await editor.canvas.drawRect(700, 600, 60, 60)
|
||||
await editor.canvas.drawRect(800, 600, 60, 60)
|
||||
await editor.canvas.selectAll()
|
||||
const count = await getSelectedCount()
|
||||
expect(count).toBeGreaterThanOrEqual(2)
|
||||
|
||||
const before = await getLayerNames()
|
||||
|
||||
await page.keyboard.press('Shift+A')
|
||||
await canvas.waitForRender()
|
||||
await editor.page.keyboard.press('Shift+A')
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const tree = await getSceneTree()
|
||||
const autoFrame = tree.children.find((c) => c.name === 'Frame' && c.type === 'FRAME')
|
||||
|
|
@ -128,22 +112,22 @@ test('Shift+A wraps selection in auto-layout frame', async () => {
|
|||
expect(after).not.toEqual(before)
|
||||
expect(after).toContain('Frame')
|
||||
|
||||
canvas.assertNoErrors()
|
||||
editor.canvas.assertNoErrors()
|
||||
})
|
||||
|
||||
test('grouping updates layers', async () => {
|
||||
// Undo the auto-layout to restore flat structure
|
||||
await canvas.undo()
|
||||
await canvas.undo()
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.undo()
|
||||
await editor.canvas.undo()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
// Draw two rects and group them
|
||||
await canvas.drawRect(700, 600, 60, 60)
|
||||
await canvas.drawRect(800, 600, 60, 60)
|
||||
await canvas.selectAll()
|
||||
await editor.canvas.drawRect(700, 600, 60, 60)
|
||||
await editor.canvas.drawRect(800, 600, 60, 60)
|
||||
await editor.canvas.selectAll()
|
||||
|
||||
await page.keyboard.press('Meta+g')
|
||||
await canvas.waitForRender()
|
||||
await editor.page.keyboard.press('Meta+g')
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const tree = await getSceneTree()
|
||||
const group = tree.children.find((c) => c.name === 'Group' && c.type === 'GROUP')
|
||||
|
|
@ -152,72 +136,72 @@ test('grouping updates layers', async () => {
|
|||
const names = await getLayerNames()
|
||||
expect(names).toContain('Group')
|
||||
|
||||
canvas.assertNoErrors()
|
||||
editor.canvas.assertNoErrors()
|
||||
})
|
||||
|
||||
test('ungrouping updates layers', async () => {
|
||||
await page.keyboard.press('Shift+Meta+g')
|
||||
await canvas.waitForRender()
|
||||
await editor.page.keyboard.press('Shift+Meta+g')
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const names = await getLayerNames()
|
||||
expect(names).not.toContain('Group')
|
||||
expect(names).toContain('Rectangle')
|
||||
|
||||
canvas.assertNoErrors()
|
||||
editor.canvas.assertNoErrors()
|
||||
})
|
||||
|
||||
test('double-click layer to rename', async () => {
|
||||
await canvas.drawRect(900, 600, 50, 50)
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.drawRect(900, 600, 50, 50)
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const row = layerRows().filter({ hasText: 'Rectangle' }).first()
|
||||
await row.dblclick()
|
||||
|
||||
const input = page.getByTestId('layers-item-input')
|
||||
const input = editor.page.getByTestId('layers-item-input')
|
||||
await expect(input).toBeVisible()
|
||||
await input.fill('Renamed Layer')
|
||||
await input.press('Enter')
|
||||
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
const names = await getLayerNames()
|
||||
expect(names).toContain('Renamed Layer')
|
||||
|
||||
canvas.assertNoErrors()
|
||||
editor.canvas.assertNoErrors()
|
||||
})
|
||||
|
||||
test('clicking outside rename input commits', async () => {
|
||||
const row = layerRows().filter({ hasText: 'Renamed Layer' }).first()
|
||||
await row.dblclick()
|
||||
|
||||
const input = page.getByTestId('layers-item-input')
|
||||
const input = editor.page.getByTestId('layers-item-input')
|
||||
await expect(input).toBeVisible()
|
||||
await input.fill('After Outside Click')
|
||||
|
||||
await page.mouse.click(500, 400)
|
||||
await canvas.waitForRender()
|
||||
await editor.page.mouse.click(500, 400)
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
await expect(input).not.toBeVisible()
|
||||
const names = await getLayerNames()
|
||||
expect(names).toContain('After Outside Click')
|
||||
|
||||
canvas.assertNoErrors()
|
||||
editor.canvas.assertNoErrors()
|
||||
})
|
||||
|
||||
test('clearing a layer name falls back to the default node name', async () => {
|
||||
await canvas.drawRect(980, 600, 50, 50)
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.drawRect(980, 600, 50, 50)
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const row = layerRows().filter({ hasText: 'Rectangle' }).last()
|
||||
const countBefore = await layerRows().count()
|
||||
|
||||
await row.dblclick()
|
||||
|
||||
const input = page.getByTestId('layers-item-input')
|
||||
const input = editor.page.getByTestId('layers-item-input')
|
||||
await expect(input).toBeVisible()
|
||||
await input.clear()
|
||||
await input.press('Enter')
|
||||
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const countAfter = await layerRows().count()
|
||||
expect(countAfter).toBe(countBefore)
|
||||
|
|
@ -225,7 +209,7 @@ test('clearing a layer name falls back to the default node name', async () => {
|
|||
const names = await getLayerNames()
|
||||
expect(names.filter((name) => name === 'Rectangle').length).toBeGreaterThan(0)
|
||||
|
||||
canvas.assertNoErrors()
|
||||
editor.canvas.assertNoErrors()
|
||||
})
|
||||
|
||||
test('double-click does not toggle tree expand', async () => {
|
||||
|
|
@ -233,14 +217,14 @@ test('double-click does not toggle tree expand', async () => {
|
|||
|
||||
const containerRow = layerRows().filter({ hasText: 'Components' }).first()
|
||||
await containerRow.dblclick()
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const input = page.getByTestId('layers-item-input')
|
||||
const input = editor.page.getByTestId('layers-item-input')
|
||||
await expect(input).toBeVisible()
|
||||
await input.press('Escape')
|
||||
|
||||
const rowCountAfter = await layerRows().count()
|
||||
expect(rowCountAfter).toBe(rowCountBefore)
|
||||
|
||||
canvas.assertNoErrors()
|
||||
editor.canvas.assertNoErrors()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,25 +1,9 @@
|
|||
import { expect, test, type Page } from '@playwright/test'
|
||||
import { expect, test, useEditorSetup } from '#tests/e2e/fixtures'
|
||||
|
||||
import { CanvasHelper } from '#tests/helpers/canvas'
|
||||
|
||||
let page: Page
|
||||
let canvas: CanvasHelper
|
||||
|
||||
test.describe.configure({ mode: 'serial' })
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
page = await browser.newPage()
|
||||
await page.goto('/')
|
||||
canvas = new CanvasHelper(page)
|
||||
await canvas.waitForInit()
|
||||
})
|
||||
|
||||
test.afterAll(async () => {
|
||||
await page.close()
|
||||
})
|
||||
const editor = useEditorSetup()
|
||||
|
||||
function getPages() {
|
||||
return page.evaluate(() => {
|
||||
return editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
return store.graph.getPages().map((p) => ({ id: p.id, name: p.name }))
|
||||
|
|
@ -27,7 +11,7 @@ function getPages() {
|
|||
}
|
||||
|
||||
function getCurrentPageId() {
|
||||
return page.evaluate(() => {
|
||||
return editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
return store.state.currentPageId
|
||||
|
|
@ -35,7 +19,7 @@ function getCurrentPageId() {
|
|||
}
|
||||
|
||||
function getPageChildCount() {
|
||||
return page.evaluate(() => {
|
||||
return editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
return store.graph.getChildren(store.state.currentPageId).length
|
||||
|
|
@ -43,15 +27,15 @@ function getPageChildCount() {
|
|||
}
|
||||
|
||||
function pagesPanel() {
|
||||
return page.getByTestId('pages-panel')
|
||||
return editor.page.getByTestId('pages-panel')
|
||||
}
|
||||
|
||||
function pageItems() {
|
||||
return page.getByTestId('pages-item')
|
||||
return editor.page.getByTestId('pages-item')
|
||||
}
|
||||
|
||||
function addPageButton() {
|
||||
return page.getByTestId('pages-add')
|
||||
return editor.page.getByTestId('pages-add')
|
||||
}
|
||||
|
||||
test('initial state has one page', async () => {
|
||||
|
|
@ -67,7 +51,7 @@ test('pages panel shows current page', async () => {
|
|||
|
||||
test('add page creates a second page', async () => {
|
||||
await addPageButton().click()
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const pages = await getPages()
|
||||
expect(pages).toHaveLength(2)
|
||||
|
|
@ -86,15 +70,15 @@ test('new page is empty', async () => {
|
|||
})
|
||||
|
||||
test('drawing on new page adds nodes only to it', async () => {
|
||||
await canvas.drawRect(100, 100, 80, 60)
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.drawRect(100, 100, 80, 60)
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
expect(await getPageChildCount()).toBe(1)
|
||||
})
|
||||
|
||||
test('switching to first page shows its content', async () => {
|
||||
await pageItems().first().click()
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const pages = await getPages()
|
||||
const currentId = await getCurrentPageId()
|
||||
|
|
@ -107,14 +91,14 @@ test('first page has no shapes (we never drew on it)', async () => {
|
|||
|
||||
test('switching back to second page shows its shape', async () => {
|
||||
await pageItems().nth(1).click()
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
expect(await getPageChildCount()).toBe(1)
|
||||
})
|
||||
|
||||
test('add a third page', async () => {
|
||||
await addPageButton().click()
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const pages = await getPages()
|
||||
expect(pages).toHaveLength(3)
|
||||
|
|
@ -125,12 +109,12 @@ test('delete current page switches to adjacent', async () => {
|
|||
const pagesBefore = await getPages()
|
||||
const deletingId = await getCurrentPageId()
|
||||
|
||||
await page.evaluate(() => {
|
||||
await editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
store.deletePage(store.state.currentPageId)
|
||||
})
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const pagesAfter = await getPages()
|
||||
expect(pagesAfter).toHaveLength(pagesBefore.length - 1)
|
||||
|
|
@ -143,7 +127,7 @@ test('delete current page switches to adjacent', async () => {
|
|||
test('rename page via store', async () => {
|
||||
const currentId = await getCurrentPageId()
|
||||
|
||||
await page.evaluate(
|
||||
await editor.page.evaluate(
|
||||
([id, name]) => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
|
|
@ -151,75 +135,75 @@ test('rename page via store', async () => {
|
|||
},
|
||||
[currentId, 'Renamed Page'] as [string, string]
|
||||
)
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const updated = await getPages()
|
||||
const renamed = updated.find((p) => p.id === currentId)
|
||||
expect(renamed?.name).toBe('Renamed Page')
|
||||
|
||||
canvas.assertNoErrors()
|
||||
editor.canvas.assertNoErrors()
|
||||
})
|
||||
|
||||
test('double-click page to rename', async () => {
|
||||
const item = pageItems().first()
|
||||
await item.dblclick()
|
||||
|
||||
const input = page.getByTestId('pages-item-input')
|
||||
const input = editor.page.getByTestId('pages-item-input')
|
||||
await expect(input).toBeVisible()
|
||||
await input.fill('My Page')
|
||||
await input.press('Enter')
|
||||
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
const pages = await getPages()
|
||||
expect(pages.some((p) => p.name === 'My Page')).toBe(true)
|
||||
|
||||
canvas.assertNoErrors()
|
||||
editor.canvas.assertNoErrors()
|
||||
})
|
||||
|
||||
test('clicking outside page rename input commits', async () => {
|
||||
const item = pageItems().first()
|
||||
await item.dblclick()
|
||||
|
||||
const input = page.getByTestId('pages-item-input')
|
||||
const input = editor.page.getByTestId('pages-item-input')
|
||||
await expect(input).toBeVisible()
|
||||
await input.fill('Outside Click Page')
|
||||
|
||||
// Click on the page header label to trigger blur (outside the input but still in the panel)
|
||||
await page.getByTestId('pages-header').click()
|
||||
await canvas.waitForRender()
|
||||
await editor.page.getByTestId('pages-header').click()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
await expect(input).not.toBeVisible()
|
||||
const pages = await getPages()
|
||||
expect(pages.some((p) => p.name === 'Outside Click Page')).toBe(true)
|
||||
|
||||
canvas.assertNoErrors()
|
||||
editor.canvas.assertNoErrors()
|
||||
})
|
||||
|
||||
test('cannot delete the last page', async () => {
|
||||
// Delete until 1 remains
|
||||
let pages = await getPages()
|
||||
while (pages.length > 1) {
|
||||
await page.evaluate(() => {
|
||||
await editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
store.deletePage(store.state.currentPageId)
|
||||
})
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
pages = await getPages()
|
||||
}
|
||||
|
||||
expect(pages).toHaveLength(1)
|
||||
|
||||
// Try deleting the last one — should be a no-op
|
||||
await page.evaluate(() => {
|
||||
await editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
store.deletePage(store.state.currentPageId)
|
||||
})
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const after = await getPages()
|
||||
expect(after).toHaveLength(1)
|
||||
|
||||
canvas.assertNoErrors()
|
||||
editor.canvas.assertNoErrors()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,30 +1,15 @@
|
|||
import { test, expect, type Page } from '@playwright/test'
|
||||
|
||||
import { expect, test, useEditorSetup } from '#tests/e2e/fixtures'
|
||||
import { expectDefined } from '#tests/helpers/assert'
|
||||
import { CanvasHelper } from '#tests/helpers/canvas'
|
||||
|
||||
let page: Page
|
||||
let canvas: CanvasHelper
|
||||
|
||||
test.describe.configure({ mode: 'serial' })
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
page = await browser.newPage()
|
||||
await page.goto('/')
|
||||
canvas = new CanvasHelper(page)
|
||||
await canvas.waitForInit()
|
||||
})
|
||||
|
||||
test.afterAll(async () => {
|
||||
await page.close()
|
||||
})
|
||||
const editor = useEditorSetup()
|
||||
|
||||
test('layers panel resize increases width', async () => {
|
||||
const panel = page.getByTestId('layers-panel')
|
||||
const panel = editor.page.getByTestId('layers-panel')
|
||||
const before = await panel.boundingBox()
|
||||
expect(before).not.toBeNull()
|
||||
|
||||
const handle = page.getByTestId('left-splitter-handle')
|
||||
const handle = editor.page.getByTestId('left-splitter-handle')
|
||||
const handleBox = await handle.boundingBox()
|
||||
expect(handleBox).not.toBeNull()
|
||||
|
||||
|
|
@ -33,49 +18,48 @@ test('layers panel resize increases width', async () => {
|
|||
const cx = handleBounds.x + handleBounds.width / 2
|
||||
const cy = handleBounds.y + handleBounds.height / 2
|
||||
|
||||
await page.mouse.move(cx, cy)
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(cx + 80, cy, { steps: 10 })
|
||||
await page.mouse.up()
|
||||
await canvas.waitForRender()
|
||||
await editor.page.mouse.move(cx, cy)
|
||||
await editor.page.mouse.down()
|
||||
await editor.page.mouse.move(cx + 80, cy, { steps: 10 })
|
||||
await editor.page.mouse.up()
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
const after = expectDefined(await panel.boundingBox(), 'resized layers panel bounds')
|
||||
expect(after.width).toBeGreaterThan(beforeBounds.width + 40)
|
||||
canvas.assertNoErrors()
|
||||
editor.canvas.assertNoErrors()
|
||||
})
|
||||
|
||||
test('panel width persists after page reload', async () => {
|
||||
// Allow Reka's auto-save debounce to flush before recording the width
|
||||
await page.waitForTimeout(300)
|
||||
await editor.page.waitForTimeout(300)
|
||||
const recordedWidth = expectDefined(
|
||||
await page.getByTestId('layers-panel').boundingBox(),
|
||||
await editor.page.getByTestId('layers-panel').boundingBox(),
|
||||
'persisted layers panel bounds'
|
||||
).width
|
||||
|
||||
await page.reload()
|
||||
canvas = new CanvasHelper(page)
|
||||
await canvas.waitForInit()
|
||||
await editor.page.reload()
|
||||
await editor.canvas.waitForInit()
|
||||
|
||||
const after = expectDefined(
|
||||
await page.getByTestId('layers-panel').boundingBox(),
|
||||
await editor.page.getByTestId('layers-panel').boundingBox(),
|
||||
'reloaded layers panel bounds'
|
||||
)
|
||||
expect(Math.abs(after.width - recordedWidth)).toBeLessThanOrEqual(2)
|
||||
canvas.assertNoErrors()
|
||||
editor.canvas.assertNoErrors()
|
||||
})
|
||||
|
||||
test('Cmd+Backslash hides panels', async () => {
|
||||
await page.keyboard.press('Meta+\\')
|
||||
await canvas.waitForRender()
|
||||
await editor.page.keyboard.press('Meta+\\')
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
await expect(page.getByTestId('layers-panel')).not.toBeVisible()
|
||||
canvas.assertNoErrors()
|
||||
await expect(editor.page.getByTestId('layers-panel')).not.toBeVisible()
|
||||
editor.canvas.assertNoErrors()
|
||||
})
|
||||
|
||||
test('Cmd+Backslash shows panels again', async () => {
|
||||
await page.keyboard.press('Meta+\\')
|
||||
await canvas.waitForRender()
|
||||
await editor.page.keyboard.press('Meta+\\')
|
||||
await editor.canvas.waitForRender()
|
||||
|
||||
await expect(page.getByTestId('layers-panel')).toBeVisible()
|
||||
canvas.assertNoErrors()
|
||||
await expect(editor.page.getByTestId('layers-panel')).toBeVisible()
|
||||
editor.canvas.assertNoErrors()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { expect, test, type Page } from '@playwright/test'
|
||||
|
||||
import { CanvasHelper } from '#tests/helpers/canvas'
|
||||
import { getSelectedNode } from '#tests/helpers/store'
|
||||
|
||||
let page: Page
|
||||
let canvas: CanvasHelper
|
||||
|
|
@ -18,21 +19,6 @@ test.afterAll(async () => {
|
|||
await page.close()
|
||||
})
|
||||
|
||||
async function getSelectedNodeFlags() {
|
||||
return page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
const id = [...store.state.selectedIds][0]
|
||||
if (!id) return null
|
||||
const n = store.graph.getNode(id)
|
||||
if (!n) return null
|
||||
return {
|
||||
type: n.type,
|
||||
independentCorners: n.independentCorners,
|
||||
independentStrokeWeights: n.independentStrokeWeights
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function drawFrame(x: number, y: number, w: number, h: number) {
|
||||
await canvas.pressKey('f')
|
||||
|
|
@ -44,7 +30,7 @@ test('independent corners toggle shows per-corner inputs', async () => {
|
|||
await drawFrame(120, 120, 120, 80)
|
||||
await canvas.waitForRender()
|
||||
|
||||
const flags = await getSelectedNodeFlags()
|
||||
const flags = await getSelectedNode(page)
|
||||
expect(flags?.type).toBe('FRAME')
|
||||
expect(flags?.independentCorners).toBe(false)
|
||||
|
||||
|
|
@ -54,7 +40,7 @@ test('independent corners toggle shows per-corner inputs', async () => {
|
|||
await toggle.click()
|
||||
await canvas.waitForRender()
|
||||
|
||||
expect((await getSelectedNodeFlags())?.independentCorners).toBe(true)
|
||||
expect((await getSelectedNode(page))?.independentCorners).toBe(true)
|
||||
const grid = page.getByTestId('independent-corners-grid')
|
||||
await expect(grid).toBeVisible()
|
||||
const cornerInputs = grid.getByTestId('scrub-input')
|
||||
|
|
|
|||
|
|
@ -1,35 +1,15 @@
|
|||
import { test, expect, type Page } from '@playwright/test'
|
||||
import { expect, test, useEditorSetupWithClear } from '#tests/e2e/fixtures'
|
||||
|
||||
import { CanvasHelper } from '#tests/helpers/canvas'
|
||||
|
||||
let page: Page
|
||||
let canvas: CanvasHelper
|
||||
|
||||
test.describe.configure({ mode: 'serial' })
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
page = await browser.newPage()
|
||||
await page.goto('/?test&no-chrome&no-rulers')
|
||||
canvas = new CanvasHelper(page)
|
||||
await canvas.waitForInit()
|
||||
})
|
||||
|
||||
test.afterAll(async () => {
|
||||
await page.close()
|
||||
})
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await canvas.clearCanvas()
|
||||
})
|
||||
const editor = useEditorSetupWithClear('/?test&no-chrome&no-rulers')
|
||||
|
||||
async function expectCanvas(name: string) {
|
||||
canvas.assertNoErrors()
|
||||
const buffer = await canvas.canvas.screenshot()
|
||||
editor.canvas.assertNoErrors()
|
||||
const buffer = await editor.canvas.canvas.screenshot()
|
||||
expect(buffer).toMatchSnapshot(`${name}.png`)
|
||||
}
|
||||
|
||||
test('drop shadow on white card', async () => {
|
||||
await page.evaluate(() => {
|
||||
await editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
const pageId = store.state.currentPageId
|
||||
|
|
@ -55,12 +35,12 @@ test('drop shadow on white card', async () => {
|
|||
store.clearSelection()
|
||||
store.requestRender()
|
||||
})
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
await expectCanvas('drop-shadow-white-card')
|
||||
})
|
||||
|
||||
test('drop shadow with spread', async () => {
|
||||
await page.evaluate(() => {
|
||||
await editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
const pageId = store.state.currentPageId
|
||||
|
|
@ -86,12 +66,12 @@ test('drop shadow with spread', async () => {
|
|||
store.clearSelection()
|
||||
store.requestRender()
|
||||
})
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
await expectCanvas('drop-shadow-with-spread')
|
||||
})
|
||||
|
||||
test('inner shadow', async () => {
|
||||
await page.evaluate(() => {
|
||||
await editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
const pageId = store.state.currentPageId
|
||||
|
|
@ -119,12 +99,12 @@ test('inner shadow', async () => {
|
|||
store.clearSelection()
|
||||
store.requestRender()
|
||||
})
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
await expectCanvas('inner-shadow')
|
||||
})
|
||||
|
||||
test('inner shadow with spread', async () => {
|
||||
await page.evaluate(() => {
|
||||
await editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
const pageId = store.state.currentPageId
|
||||
|
|
@ -152,12 +132,12 @@ test('inner shadow with spread', async () => {
|
|||
store.clearSelection()
|
||||
store.requestRender()
|
||||
})
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
await expectCanvas('inner-shadow-with-spread')
|
||||
})
|
||||
|
||||
test('drop shadow on ellipse', async () => {
|
||||
await page.evaluate(() => {
|
||||
await editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
const pageId = store.state.currentPageId
|
||||
|
|
@ -184,12 +164,12 @@ test('drop shadow on ellipse', async () => {
|
|||
store.clearSelection()
|
||||
store.requestRender()
|
||||
})
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
await expectCanvas('drop-shadow-ellipse')
|
||||
})
|
||||
|
||||
test('combined drop and inner shadow', async () => {
|
||||
await page.evaluate(() => {
|
||||
await editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
const pageId = store.state.currentPageId
|
||||
|
|
@ -223,12 +203,12 @@ test('combined drop and inner shadow', async () => {
|
|||
store.clearSelection()
|
||||
store.requestRender()
|
||||
})
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
await expectCanvas('combined-drop-inner-shadow')
|
||||
})
|
||||
|
||||
test('text drop shadow on glyphs', async () => {
|
||||
await page.evaluate(() => {
|
||||
await editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
const pageId = store.state.currentPageId
|
||||
|
|
@ -257,14 +237,14 @@ test('text drop shadow on glyphs', async () => {
|
|||
store.clearSelection()
|
||||
store.requestRender()
|
||||
})
|
||||
await canvas.waitForRender()
|
||||
await page.waitForTimeout(500)
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
await editor.page.waitForTimeout(500)
|
||||
await editor.canvas.waitForRender()
|
||||
await expectCanvas('text-drop-shadow')
|
||||
})
|
||||
|
||||
test('layer blur', async () => {
|
||||
await page.evaluate(() => {
|
||||
await editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
const pageId = store.state.currentPageId
|
||||
|
|
@ -292,12 +272,12 @@ test('layer blur', async () => {
|
|||
store.clearSelection()
|
||||
store.requestRender()
|
||||
})
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
await expectCanvas('layer-blur')
|
||||
})
|
||||
|
||||
test('invisible effect has no visual impact', async () => {
|
||||
await page.evaluate(() => {
|
||||
await editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
const pageId = store.state.currentPageId
|
||||
|
|
@ -323,6 +303,6 @@ test('invisible effect has no visual impact', async () => {
|
|||
store.clearSelection()
|
||||
store.requestRender()
|
||||
})
|
||||
await canvas.waitForRender()
|
||||
await editor.canvas.waitForRender()
|
||||
await expectCanvas('invisible-effect')
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { expect, test, type Page } from '@playwright/test'
|
|||
|
||||
import { expectDefined } from '#tests/helpers/assert'
|
||||
import { CanvasHelper } from '#tests/helpers/canvas'
|
||||
import { getSelectedNode } from '#tests/helpers/store'
|
||||
|
||||
let page: Page
|
||||
let canvas: CanvasHelper
|
||||
|
|
@ -19,21 +20,6 @@ test.afterAll(async () => {
|
|||
await page.close()
|
||||
})
|
||||
|
||||
function getSelectedNode() {
|
||||
return page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
const id = [...store.state.selectedIds][0]
|
||||
if (!id) return null
|
||||
const n = store.graph.getNode(id)
|
||||
if (!n) return null
|
||||
return {
|
||||
fills: n.fills,
|
||||
strokes: n.strokes,
|
||||
visible: n.visible
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
test('fill visibility supports repeat click and undo redo', async () => {
|
||||
await canvas.drawRect(120, 120, 120, 80)
|
||||
|
|
@ -41,21 +27,21 @@ test('fill visibility supports repeat click and undo redo', async () => {
|
|||
|
||||
const fillButton = page.getByTestId('fill-visibility-0')
|
||||
await expect(fillButton).toBeVisible()
|
||||
expect(expectDefined(await getSelectedNode(), 'selected node').fills[0]?.visible).toBe(true)
|
||||
expect(expectDefined(await getSelectedNode(page), 'selected node').fills[0]?.visible).toBe(true)
|
||||
|
||||
await fillButton.click()
|
||||
await canvas.waitForRender()
|
||||
expect(expectDefined(await getSelectedNode(), 'selected node').fills[0]?.visible).toBe(false)
|
||||
expect(expectDefined(await getSelectedNode(page), 'selected node').fills[0]?.visible).toBe(false)
|
||||
|
||||
await fillButton.click()
|
||||
await canvas.waitForRender()
|
||||
expect(expectDefined(await getSelectedNode(), 'selected node').fills[0]?.visible).toBe(true)
|
||||
expect(expectDefined(await getSelectedNode(page), 'selected node').fills[0]?.visible).toBe(true)
|
||||
|
||||
await canvas.undo()
|
||||
expect(expectDefined(await getSelectedNode(), 'selected node').fills[0]?.visible).toBe(false)
|
||||
expect(expectDefined(await getSelectedNode(page), 'selected node').fills[0]?.visible).toBe(false)
|
||||
|
||||
await canvas.redo()
|
||||
expect(expectDefined(await getSelectedNode(), 'selected node').fills[0]?.visible).toBe(true)
|
||||
expect(expectDefined(await getSelectedNode(page), 'selected node').fills[0]?.visible).toBe(true)
|
||||
})
|
||||
|
||||
test('stroke visibility supports repeat click and undo redo', async () => {
|
||||
|
|
@ -64,39 +50,39 @@ test('stroke visibility supports repeat click and undo redo', async () => {
|
|||
|
||||
const strokeButton = page.getByTestId('stroke-visibility-0')
|
||||
await expect(strokeButton).toBeVisible()
|
||||
expect(expectDefined(await getSelectedNode(), 'selected node').strokes[0]?.visible).toBe(true)
|
||||
expect(expectDefined(await getSelectedNode(page), 'selected node').strokes[0]?.visible).toBe(true)
|
||||
|
||||
await strokeButton.click()
|
||||
await canvas.waitForRender()
|
||||
expect(expectDefined(await getSelectedNode(), 'selected node').strokes[0]?.visible).toBe(false)
|
||||
expect(expectDefined(await getSelectedNode(page), 'selected node').strokes[0]?.visible).toBe(false)
|
||||
|
||||
await strokeButton.click()
|
||||
await canvas.waitForRender()
|
||||
expect(expectDefined(await getSelectedNode(), 'selected node').strokes[0]?.visible).toBe(true)
|
||||
expect(expectDefined(await getSelectedNode(page), 'selected node').strokes[0]?.visible).toBe(true)
|
||||
|
||||
await canvas.undo()
|
||||
expect(expectDefined(await getSelectedNode(), 'selected node').strokes[0]?.visible).toBe(false)
|
||||
expect(expectDefined(await getSelectedNode(page), 'selected node').strokes[0]?.visible).toBe(false)
|
||||
|
||||
await canvas.redo()
|
||||
expect(expectDefined(await getSelectedNode(), 'selected node').strokes[0]?.visible).toBe(true)
|
||||
expect(expectDefined(await getSelectedNode(page), 'selected node').strokes[0]?.visible).toBe(true)
|
||||
})
|
||||
|
||||
test('appearance visibility supports repeat click and undo redo in one step', async () => {
|
||||
const visibilityButton = page.getByTestId('appearance-visibility')
|
||||
await expect(visibilityButton).toBeVisible()
|
||||
expect(expectDefined(await getSelectedNode(), 'selected node').visible).toBe(true)
|
||||
expect(expectDefined(await getSelectedNode(page), 'selected node').visible).toBe(true)
|
||||
|
||||
await visibilityButton.click()
|
||||
await canvas.waitForRender()
|
||||
expect(expectDefined(await getSelectedNode(), 'selected node').visible).toBe(false)
|
||||
expect(expectDefined(await getSelectedNode(page), 'selected node').visible).toBe(false)
|
||||
|
||||
await visibilityButton.click()
|
||||
await canvas.waitForRender()
|
||||
expect(expectDefined(await getSelectedNode(), 'selected node').visible).toBe(true)
|
||||
expect(expectDefined(await getSelectedNode(page), 'selected node').visible).toBe(true)
|
||||
|
||||
await canvas.undo()
|
||||
expect(expectDefined(await getSelectedNode(), 'selected node').visible).toBe(false)
|
||||
expect(expectDefined(await getSelectedNode(page), 'selected node').visible).toBe(false)
|
||||
|
||||
await canvas.undo()
|
||||
expect(expectDefined(await getSelectedNode(), 'selected node').visible).toBe(true)
|
||||
expect(expectDefined(await getSelectedNode(page), 'selected node').visible).toBe(true)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { expect, test, type Page } from '@playwright/test'
|
|||
|
||||
import { expectDefined } from '#tests/helpers/assert'
|
||||
import { CanvasHelper } from '#tests/helpers/canvas'
|
||||
import { getSelectedNode } from '#tests/helpers/store'
|
||||
|
||||
let page: Page
|
||||
let canvas: CanvasHelper
|
||||
|
|
@ -19,27 +20,6 @@ test.afterAll(async () => {
|
|||
await page.close()
|
||||
})
|
||||
|
||||
function getSelectedNode() {
|
||||
return page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
const id = [...store.state.selectedIds][0]
|
||||
if (!id) return null
|
||||
const n = store.graph.getNode(id)
|
||||
if (!n) return null
|
||||
return {
|
||||
id: n.id,
|
||||
type: n.type,
|
||||
name: n.name,
|
||||
text: n.text,
|
||||
fontSize: n.fontSize,
|
||||
fontFamily: n.fontFamily,
|
||||
fontWeight: n.fontWeight,
|
||||
width: n.width,
|
||||
height: n.height
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function getPageChildren() {
|
||||
return page.evaluate(() => {
|
||||
|
|
@ -77,7 +57,7 @@ test('clicking with text tool creates a text node', async () => {
|
|||
})
|
||||
|
||||
test('text node is selected after creation', async () => {
|
||||
const node = await getSelectedNode()
|
||||
const node = await getSelectedNode(page)
|
||||
expect(node?.type).toBe('TEXT')
|
||||
})
|
||||
|
||||
|
|
@ -95,7 +75,7 @@ test('typography section appears for text node', async () => {
|
|||
})
|
||||
|
||||
test('text node has default font properties', async () => {
|
||||
const node = await getSelectedNode()
|
||||
const node = await getSelectedNode(page)
|
||||
expect(node?.fontSize).toBeGreaterThan(0)
|
||||
expect(node?.fontFamily).toBeTruthy()
|
||||
})
|
||||
|
|
@ -113,7 +93,7 @@ test('creating text via store works', async () => {
|
|||
})
|
||||
await canvas.waitForRender()
|
||||
|
||||
const node = await getSelectedNode()
|
||||
const node = await getSelectedNode(page)
|
||||
expect(node?.text).toBe('Hello World')
|
||||
expect(node?.fontSize).toBe(24)
|
||||
})
|
||||
|
|
@ -133,7 +113,7 @@ test('frame tool creates FRAME node', async () => {
|
|||
await canvas.drag(400, 100, 600, 250)
|
||||
await canvas.waitForRender()
|
||||
|
||||
const node = expectDefined(await getSelectedNode(), 'selected frame')
|
||||
const node = expectDefined(await getSelectedNode(page), 'selected frame')
|
||||
expect(node.type).toBe('FRAME')
|
||||
expect(node.width).toBeGreaterThan(0)
|
||||
expect(node.height).toBeGreaterThan(0)
|
||||
|
|
@ -156,7 +136,7 @@ test('Enter key opens text editing and selects all without erasing', async () =>
|
|||
})
|
||||
await canvas.waitForRender()
|
||||
|
||||
const before = await getSelectedNode()
|
||||
const before = await getSelectedNode(page)
|
||||
expect(before?.text).toBe('Keep this text')
|
||||
expect(before?.type).toBe('TEXT')
|
||||
|
||||
|
|
|
|||
|
|
@ -1,34 +1,14 @@
|
|||
import { expect, test, type Page } from '@playwright/test'
|
||||
import { expect, test, useEditorSetupWithClear } from '#tests/e2e/fixtures'
|
||||
|
||||
import { CanvasHelper } from '#tests/helpers/canvas'
|
||||
|
||||
let page: Page
|
||||
let canvas: CanvasHelper
|
||||
|
||||
test.describe.configure({ mode: 'serial' })
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
page = await browser.newPage()
|
||||
await page.goto('/?test')
|
||||
canvas = new CanvasHelper(page)
|
||||
await canvas.waitForInit()
|
||||
})
|
||||
|
||||
test.afterAll(async () => {
|
||||
await page.close()
|
||||
})
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await canvas.clearCanvas()
|
||||
})
|
||||
const editor = useEditorSetupWithClear('/?test')
|
||||
|
||||
test('draw section in full editor without browser errors', async () => {
|
||||
await canvas.drawSection(100, 100, 240, 160)
|
||||
await editor.canvas.drawSection(100, 100, 240, 160)
|
||||
|
||||
await expect(page.getByTestId('design-node-header')).toContainText('SECTION')
|
||||
await expect(editor.page.getByTestId('design-node-header')).toContainText('SECTION')
|
||||
await expect
|
||||
.poll(async () => {
|
||||
return page.evaluate(() => {
|
||||
return editor.page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
const selectedId = [...store.state.selectedIds][0]
|
||||
return selectedId ? store.graph.getNode(selectedId)?.type : null
|
||||
|
|
@ -36,5 +16,5 @@ test('draw section in full editor without browser errors', async () => {
|
|||
})
|
||||
.toBe('SECTION')
|
||||
|
||||
canvas.assertNoErrors()
|
||||
editor.canvas.assertNoErrors()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -37,26 +37,55 @@ export function getSelectedNode(page: Page) {
|
|||
return {
|
||||
id: n.id,
|
||||
type: n.type,
|
||||
name: n.name,
|
||||
text: n.text,
|
||||
x: n.x,
|
||||
y: n.y,
|
||||
width: n.width,
|
||||
height: n.height,
|
||||
rotation: n.rotation,
|
||||
visible: n.visible,
|
||||
layoutMode: n.layoutMode,
|
||||
primaryAxisAlign: n.primaryAxisAlign,
|
||||
counterAxisAlign: n.counterAxisAlign,
|
||||
itemSpacing: n.itemSpacing,
|
||||
childIds: n.childIds,
|
||||
cornerRadius: n.cornerRadius,
|
||||
independentCorners: n.independentCorners,
|
||||
independentStrokeWeights: n.independentStrokeWeights,
|
||||
flipX: n.flipX,
|
||||
clipsContent: n.clipsContent,
|
||||
fills: n.fills,
|
||||
strokes: n.strokes,
|
||||
fontSize: n.fontSize,
|
||||
fontFamily: n.fontFamily,
|
||||
fontWeight: n.fontWeight,
|
||||
italic: n.italic
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function getSelectedNodes(page: Page) {
|
||||
return page.evaluate(() => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
return [...store.state.selectedIds].map((id) => {
|
||||
const n = store.graph.getNode(id)
|
||||
if (!n) throw new Error(`Selected node ${id} not found`)
|
||||
return {
|
||||
id: n.id,
|
||||
name: n.name,
|
||||
type: n.type,
|
||||
x: n.x,
|
||||
y: n.y,
|
||||
width: n.width,
|
||||
height: n.height,
|
||||
fills: n.fills
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export function getNodeById(page: Page, id: string) {
|
||||
return page.evaluate((nodeId: string) => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
|
|
|
|||
Loading…
Reference in a new issue