import { expect, test, useEditorSetup } from '#tests/e2e/fixtures' import { expectDefined } from '#tests/helpers/assert' const editor = useEditorSetup() function getActiveTool() { return editor.page.evaluate(() => { const store = window.openPencil?.getStore?.() if (!store) throw new Error('OpenPencil store not initialized') return store.state.activeTool }) } function getSelectedCount() { return editor.page.evaluate(() => { const store = window.openPencil?.getStore?.() if (!store) throw new Error('OpenPencil store not initialized') return store.state.selectedIds.size }) } function getPageChildren() { 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) => ({ id: n.id, type: n.type, name: n.name, childIds: n.childIds })) }) } function getUIVisible() { return editor.page.evaluate(() => { const store = window.openPencil?.getStore?.() if (!store) throw new Error('OpenPencil store not initialized') return store.state.showUI }) } function getZoom() { return editor.page.evaluate(() => { const store = window.openPencil?.getStore?.() if (!store) throw new Error('OpenPencil store not initialized') return store.state.zoom }) } test.describe('tool switching', () => { test('V → SELECT', async () => { await editor.page.keyboard.press('v') expect(await getActiveTool()).toBe('SELECT') }) test('R → RECTANGLE', async () => { await editor.page.keyboard.press('r') expect(await getActiveTool()).toBe('RECTANGLE') }) test('O → ELLIPSE', async () => { await editor.page.keyboard.press('o') expect(await getActiveTool()).toBe('ELLIPSE') }) test('F → FRAME', async () => { await editor.page.keyboard.press('f') expect(await getActiveTool()).toBe('FRAME') }) test('T → TEXT', async () => { await editor.page.keyboard.press('t') expect(await getActiveTool()).toBe('TEXT') }) test('L → LINE', async () => { await editor.page.keyboard.press('l') expect(await getActiveTool()).toBe('LINE') }) test('P → PEN', async () => { await editor.page.keyboard.press('p') expect(await getActiveTool()).toBe('PEN') }) test('H → HAND', async () => { await editor.page.keyboard.press('h') expect(await getActiveTool()).toBe('HAND') }) test('S → SECTION', async () => { await editor.page.keyboard.press('s') expect(await getActiveTool()).toBe('SECTION') }) }) test.describe('selection shortcuts', () => { test('⌘A selects all', async () => { await editor.page.keyboard.press('v') await editor.canvas.drawRect(100, 100, 60, 60) await editor.canvas.drawRect(200, 100, 60, 60) await editor.page.keyboard.press('Meta+a') expect(await getSelectedCount()).toBe(2) }) test('Escape clears selection and resets to SELECT tool', async () => { await editor.page.keyboard.press('Escape') expect(await getSelectedCount()).toBe(0) expect(await getActiveTool()).toBe('SELECT') }) test('Backspace deletes selected', async () => { await editor.canvas.selectAll() const beforeCount = await getSelectedCount() expect(beforeCount).toBeGreaterThan(0) await editor.page.keyboard.press('Backspace') await editor.canvas.waitForRender() const children = await getPageChildren() expect(children).toHaveLength(0) }) }) test.describe('z-order shortcuts', () => { test('] brings to front', async () => { 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 editor.page.evaluate((id) => { const store = window.openPencil?.getStore?.() if (!store) throw new Error('OpenPencil store not initialized') store.select([id]) }, firstId) await editor.canvas.waitForRender() await editor.page.keyboard.press(']') await editor.canvas.waitForRender() const childrenAfter = await getPageChildren() expect(childrenAfter[childrenAfter.length - 1].id).toBe(firstId) }) test('[ sends to back', async () => { const children = await getPageChildren() const lastId = expectDefined(children.at(-1), 'last page child').id // Select the last (top) node await editor.page.evaluate((id) => { const store = window.openPencil?.getStore?.() if (!store) throw new Error('OpenPencil store not initialized') store.select([id]) }, lastId) await editor.canvas.waitForRender() await editor.page.keyboard.press('[') await editor.canvas.waitForRender() const after = await getPageChildren() expect(after[0].id).toBe(lastId) }) }) test.describe('group/ungroup shortcuts', () => { test('⌘G groups selection', async () => { await editor.canvas.selectAll() expect(await getSelectedCount()).toBeGreaterThanOrEqual(2) await editor.page.keyboard.press('Meta+g') await editor.canvas.waitForRender() const children = await getPageChildren() const group = children.find((c) => c.type === 'GROUP') expect(group).toBeTruthy() expect(group?.childIds.length).toBeGreaterThanOrEqual(2) }) test('⌘⇧G ungroups', async () => { 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) }) }) test.describe('UI toggles', () => { test('⌘\\ toggles UI visibility', async () => { const before = await getUIVisible() await editor.page.keyboard.press('Meta+\\') await editor.canvas.waitForRender() const after = await getUIVisible() expect(after).toBe(!before) // Restore await editor.page.keyboard.press('Meta+\\') await editor.canvas.waitForRender() expect(await getUIVisible()).toBe(before) }) }) test.describe('duplicate', () => { test('⌘D duplicates selection', async () => { await editor.canvas.clearCanvas() await editor.canvas.drawRect(100, 100, 60, 60) await editor.canvas.selectAll() expect(await getSelectedCount()).toBe(1) await editor.page.keyboard.press('Meta+d') await editor.canvas.waitForRender() const children = await getPageChildren() expect(children).toHaveLength(2) }) }) test.describe('zoom shortcuts', () => { test('⌘0 zooms to 100%', async () => { await editor.canvas.clearCanvas() await editor.canvas.drawRect(100, 100, 60, 60) // Set zoom to something other than 100% await editor.page.evaluate(() => { const store = window.openPencil?.getStore?.() if (!store) throw new Error('OpenPencil store not initialized') store.state.zoom = 2 }) await editor.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 editor.page.keyboard.press('Meta+1') await editor.canvas.waitForRender() const zoom = await getZoom() expect(zoom).toBeGreaterThan(0) expect(zoom).toBeLessThanOrEqual(1) }) test('⌘2 zooms to selection', async () => { await editor.canvas.selectAll() await editor.page.keyboard.press('Meta+2') await editor.canvas.waitForRender() const zoom = await getZoom() expect(zoom).toBeGreaterThan(0) expect(zoom).toBeLessThanOrEqual(1) }) test('⇧1 zooms to fit (same as ⌘1)', async () => { await editor.page.keyboard.press('Shift+1') await editor.canvas.waitForRender() const zoom = await getZoom() expect(zoom).toBeGreaterThan(0) expect(zoom).toBeLessThanOrEqual(1) }) test('⇧2 zooms to selection (same as ⌘2)', async () => { await editor.canvas.selectAll() await editor.page.keyboard.press('Shift+2') await editor.canvas.waitForRender() const zoom = await getZoom() expect(zoom).toBeGreaterThan(0) expect(zoom).toBeLessThanOrEqual(1) }) }) test.describe('undo/redo', () => { test('⌘Z undoes last action', async () => { await editor.canvas.clearCanvas() await editor.canvas.drawRect(100, 100, 60, 60) expect((await getPageChildren()).length).toBe(1) await editor.page.keyboard.press('Meta+z') await editor.canvas.waitForRender() expect((await getPageChildren()).length).toBe(0) }) test('⌘⇧Z redoes undone action', async () => { await editor.page.keyboard.press('Meta+Shift+z') await editor.canvas.waitForRender() expect((await getPageChildren()).length).toBe(1) }) }) test.describe('auto-layout shortcut', () => { test('⇧A toggles auto-layout on frame', async () => { await editor.canvas.clearCanvas() await editor.canvas.drawRect(100, 100, 200, 200) await editor.canvas.selectAll() // Change to frame type for auto-layout 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 editor.canvas.waitForRender() 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] return store.graph.getNode(nodes[0])?.layoutMode }) expect(layoutBefore).toBe('NONE') await editor.page.keyboard.press('Shift+a') await editor.canvas.waitForRender() 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] return store.graph.getNode(nodes[0])?.layoutMode }) expect(layoutAfter).toBe('VERTICAL') // Toggle off await editor.page.keyboard.press('Shift+a') await editor.canvas.waitForRender() 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] return store.graph.getNode(nodes[0])?.layoutMode }) expect(layoutFinal).toBe('NONE') }) })