From 8ec70330b46bee662c2cf55d2b7a67580b0c45f3 Mon Sep 17 00:00:00 2001 From: Danila Poyarkov Date: Wed, 6 May 2026 10:38:03 +0300 Subject: [PATCH] chore(lint): centralize OpenPencil window API - Route browser globals through src/app/window-api.ts instead of private __OPEN_PENCIL fields - Move E2E tests to the public window.openPencil test API - Add an oxlint rule banning direct window.__OPEN_PENCIL* access --- lint/plugin.js | 28 +++++++++++++++ oxlint.json | 1 + src/app/ai/chat/use.ts | 5 +-- src/app/shell/menu/files.ts | 7 ++-- src/app/tabs/index.ts | 3 +- src/app/window-api.ts | 34 +++++++++++++++++++ src/global.d.ts | 6 +--- tests/e2e/app/menu.spec.ts | 6 ++-- tests/e2e/autosave.spec.ts | 2 +- tests/e2e/canvas/frame-overlays.spec.ts | 4 +-- tests/e2e/canvas/manipulation.spec.ts | 24 ++++++------- tests/e2e/chat/panel.spec.ts | 2 +- tests/e2e/clipboard/copy-paste.spec.ts | 12 +++---- tests/e2e/code/panel.spec.ts | 2 +- tests/e2e/color-picker/basic.spec.ts | 2 +- tests/e2e/color-picker/demo-card.spec.ts | 4 +-- tests/e2e/components.spec.ts | 26 ++++++++------ tests/e2e/context-menu/basic.spec.ts | 14 ++++---- tests/e2e/design/panel.spec.ts | 4 +-- tests/e2e/editor/auto-layout.spec.ts | 2 +- tests/e2e/editor/duplicate-undo.spec.ts | 4 +-- tests/e2e/fonts/settings.spec.ts | 2 +- tests/e2e/keyboard/shortcuts.spec.ts | 24 ++++++------- tests/e2e/layers/panel.spec.ts | 4 +-- tests/e2e/pages/multi-page.spec.ts | 14 ++++---- tests/e2e/perf/basic.spec.ts | 6 ++-- .../properties/corner-stroke-toggles.spec.ts | 2 +- tests/e2e/properties/effects.spec.ts | 18 +++++----- tests/e2e/properties/panel.spec.ts | 12 +++---- tests/e2e/properties/visibility.spec.ts | 2 +- tests/e2e/scene/cache.spec.ts | 30 ++++++++-------- tests/e2e/snap/guides.spec.ts | 2 +- tests/e2e/stroke-picker/basic.spec.ts | 4 +-- tests/e2e/text/editing.spec.ts | 14 ++++---- tests/e2e/text/formatting.spec.ts | 4 +-- tests/e2e/tools/section.spec.ts | 2 +- tests/e2e/variables/dialog.spec.ts | 4 +-- tests/e2e/viewport/zoom-pan.spec.ts | 26 +++++++------- tests/helpers/store.ts | 10 +++--- 39 files changed, 217 insertions(+), 155 deletions(-) create mode 100644 src/app/window-api.ts diff --git a/lint/plugin.js b/lint/plugin.js index a49da002c..3c2ed0f48 100644 --- a/lint/plugin.js +++ b/lint/plugin.js @@ -1096,6 +1096,33 @@ const noFunctionAliasImports = { } } +const noDirectOpenPencilWindowInternals = { + meta: { + docs: { + description: 'Disallow direct access to private OpenPencil window internals' + } + }, + create(context) { + function propertyName(property) { + if (property?.type === 'Identifier') return property.name + if (property?.type === 'Literal' && typeof property.value === 'string') return property.value + return null + } + + return { + MemberExpression(node) { + const name = propertyName(node.property) + if (!name?.startsWith('__OPEN_PENCIL')) return + context.report({ + node, + message: + 'Do not access window.__OPEN_PENCIL* directly. Use src/app/window-api.ts or tests/helpers/store.ts instead.' + }) + } + } + } +} + const noTopLevelPrefixedTestFiles = createProgramFilenameRule({ description: 'Disallow top-level test files that encode domains as filename prefixes', check(file) { @@ -1151,6 +1178,7 @@ const plugin = { 'no-function-type': noFunctionType, 'no-reflect-delete-global-this-outside-tests': noReflectDeleteGlobalThisOutsideTests, 'no-core-browser-globals': noCoreBrowserGlobals, + 'no-direct-open-pencil-window-internals': noDirectOpenPencilWindowInternals, 'no-direct-graph-emitter-subscriptions': noDirectGraphEmitterSubscriptions, 'no-on-unmounted-in-composition-roots': noOnUnmountedInCompositionRoots, 'no-composable-state-wrappers': noComposableStateWrappers, diff --git a/oxlint.json b/oxlint.json index 26c93c833..c5ec31773 100644 --- a/oxlint.json +++ b/oxlint.json @@ -125,6 +125,7 @@ "open-pencil/no-function-type": "error", "open-pencil/no-reflect-delete-global-this-outside-tests": "error", "open-pencil/no-core-browser-globals": "error", + "open-pencil/no-direct-open-pencil-window-internals": "error", "open-pencil/no-function-alias-imports": "error", "open-pencil/max-composition-root-lines": [ "error", diff --git a/src/app/ai/chat/use.ts b/src/app/ai/chat/use.ts index 26c707a65..950c74d46 100644 --- a/src/app/ai/chat/use.ts +++ b/src/app/ai/chat/use.ts @@ -20,6 +20,7 @@ import { } from '@/app/ai/chat/storage' import { createChatSessionManager } from '@/app/ai/chat/transports' import { getActiveEditorStore } from '@/app/editor/active-store' +import { setOpenPencilTransportFactorySetter } from '@/app/window-api' const activeTab = ref<'design' | 'code' | 'ai'>('design') @@ -39,9 +40,9 @@ const chatSession = createChatSessionManager({ registerAIChatEffects(chatSession.markTransportDirty) if (IS_BROWSER) { - window.__OPEN_PENCIL_SET_TRANSPORT__ = (factory) => { + setOpenPencilTransportFactorySetter((factory) => { chatSession.setOverrideTransport(factory) - } + }) } export function useAIChat() { diff --git a/src/app/shell/menu/files.ts b/src/app/shell/menu/files.ts index 1a1235a9e..941ff06bd 100644 --- a/src/app/shell/menu/files.ts +++ b/src/app/shell/menu/files.ts @@ -2,6 +2,7 @@ import { useFileDialog } from '@vueuse/core' import { openFileInNewTab } from '@/app/tabs' import { isTauri } from '@/app/tauri/env' +import { setOpenPencilOpenFileHandler } from '@/app/window-api' import { IS_BROWSER } from '@/constants' const fileDialog = useFileDialog({ accept: '.fig,.pen', multiple: false, reset: true }) @@ -12,15 +13,13 @@ fileDialog.onChange((files) => { }) if (IS_BROWSER && 'window' in globalThis) { - ;( - globalThis.window as Window & { __OPEN_PENCIL_OPEN_FILE__?: (path: string) => Promise } - ).__OPEN_PENCIL_OPEN_FILE__ = async (path: string) => { + setOpenPencilOpenFileHandler(async (path: string) => { const response = await fetch(path) const blob = await response.blob() const name = path.split('/').pop() ?? 'file.fig' const file = new File([blob], name, { type: 'application/octet-stream' }) await openFileInNewTab(file, undefined, path) - } + }) } export async function readTauriDesignFile(path: string): Promise { diff --git a/src/app/tabs/index.ts b/src/app/tabs/index.ts index 046b7c280..ba2f5926b 100644 --- a/src/app/tabs/index.ts +++ b/src/app/tabs/index.ts @@ -7,6 +7,7 @@ import type { SceneGraph } from '@open-pencil/core/scene-graph' import { setActiveEditorStore } from '@/app/editor/active-store' import { createEditorStore } from '@/app/editor/session' import type { EditorStore } from '@/app/editor/session' +import { setOpenPencilStore } from '@/app/window-api' export interface Tab { id: string @@ -52,7 +53,7 @@ function activateTab(tab: Tab) { activeTabId.value = tab.id setActiveEditorStore(tab.store) triggerRef(tabsRef) - window.__OPEN_PENCIL_STORE__ = tab.store + setOpenPencilStore(tab.store) } export function switchTab(tabId: string) { diff --git a/src/app/window-api.ts b/src/app/window-api.ts new file mode 100644 index 000000000..84c3269fb --- /dev/null +++ b/src/app/window-api.ts @@ -0,0 +1,34 @@ +import type { ChatTransport, UIMessage } from 'ai' + +import type { EditorStore } from '@/app/editor/session/create' + +export interface OpenPencilWindowAPI { + store?: EditorStore + setTransport?: (factory: () => ChatTransport) => void + openFile?: (path: string) => Promise +} + +declare global { + interface Window { + openPencil?: OpenPencilWindowAPI + } +} + +function windowApi(): OpenPencilWindowAPI { + window.openPencil ??= {} + return window.openPencil +} + +export function setOpenPencilStore(store: EditorStore) { + windowApi().store = store +} + +export function setOpenPencilTransportFactorySetter( + setTransport: (factory: () => ChatTransport) => void +) { + windowApi().setTransport = setTransport +} + +export function setOpenPencilOpenFileHandler(openFile: (path: string) => Promise) { + windowApi().openFile = openFile +} diff --git a/src/global.d.ts b/src/global.d.ts index a554eaf04..a12bf4a75 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -1,6 +1,4 @@ -import type { ChatTransport, UIMessage } from 'ai' - -import type { EditorStore } from '@/app/editor/session/create' +export {} declare global { interface Uint8ArrayConstructor { @@ -40,8 +38,6 @@ declare global { blob(): Promise }[] > - __OPEN_PENCIL_SET_TRANSPORT__?(factory: () => ChatTransport): void - __OPEN_PENCIL_STORE__?: EditorStore __TEST_WRITE_COUNT__?(): number __TEST_MOCK_HANDLE__?: FileSystemFileHandle __savedOpen?: Window['open'] diff --git a/tests/e2e/app/menu.spec.ts b/tests/e2e/app/menu.spec.ts index f6daaa4da..f7eb456ca 100644 --- a/tests/e2e/app/menu.spec.ts +++ b/tests/e2e/app/menu.spec.ts @@ -86,7 +86,7 @@ test('Object menu shows Group/Ungroup/Component', async () => { function getStoreStateNumber(key: 'selectedIds' | 'zoom') { return page.evaluate((stateKey) => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') if (stateKey === 'selectedIds') return store.state.selectedIds.size return store.state.zoom @@ -110,7 +110,7 @@ test('Duplicate via Edit menu works', async () => { await canvas.drawRect(300, 300, 80, 80) const countBefore = await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return store.graph.getChildren(store.state.currentPageId).length }) @@ -120,7 +120,7 @@ test('Duplicate via Edit menu works', async () => { await canvas.waitForRender() const countAfter = await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return store.graph.getChildren(store.state.currentPageId).length }) diff --git a/tests/e2e/autosave.spec.ts b/tests/e2e/autosave.spec.ts index ad5412a0c..8a5fcfa57 100644 --- a/tests/e2e/autosave.spec.ts +++ b/tests/e2e/autosave.spec.ts @@ -64,7 +64,7 @@ test('autosave triggers after scene changes with a file handle', async () => { // Check that the scene version changed const versionAfterDraw = await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return store.state.sceneVersion }) diff --git a/tests/e2e/canvas/frame-overlays.spec.ts b/tests/e2e/canvas/frame-overlays.spec.ts index a55af39c9..2ae325abf 100644 --- a/tests/e2e/canvas/frame-overlays.spec.ts +++ b/tests/e2e/canvas/frame-overlays.spec.ts @@ -30,7 +30,7 @@ async function expectCanvas(name: string) { async function createOverlayDemo(rotation: number) { await page.evaluate((frameRotation) => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const pageId = store.state.currentPageId @@ -103,7 +103,7 @@ test('rotation preview updates frame labels before mouse up', async () => { await createOverlayDemo(0) await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const frameId = [...store.state.selectedIds][0] store.state.rotationPreview = { nodeId: frameId, angle: 28 } diff --git a/tests/e2e/canvas/manipulation.spec.ts b/tests/e2e/canvas/manipulation.spec.ts index 50b556003..6bee1603e 100644 --- a/tests/e2e/canvas/manipulation.spec.ts +++ b/tests/e2e/canvas/manipulation.spec.ts @@ -87,7 +87,7 @@ test('resize corner handle drag increases node dimensions', async () => { expect(before).not.toBeNull() const viewport = await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const id = [...store.state.selectedIds][0] const n = store.graph.getNode(id) @@ -132,7 +132,7 @@ test('rotation handle drag rotates node', async () => { const initialRotation = before!.rotation ?? 0 const viewport = await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const id = [...store.state.selectedIds][0] const n = store.graph.getNode(id) @@ -185,7 +185,7 @@ async function setupFrameChild(rotation: number) { await canvas.clearCanvas() const setup = await page.evaluate((frameRotation) => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const frameId = store.createShape('FRAME', 180, 160, 240, 160) if (!frameId) return null @@ -203,7 +203,7 @@ async function setupFrameChild(rotation: number) { await canvas.waitForRender() const state = await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const pageId = store.state.currentPageId const pageNode = store.graph.getNode(pageId) @@ -243,7 +243,7 @@ test('frame children keep correct hover and click hit area without rotation', as const state = await setupFrameChild(0) await canvas.hover(state.hitX, state.hitY) - const hoveredId = await page.evaluate(() => window.__OPEN_PENCIL_STORE__!.state.hoveredNodeId) + const hoveredId = await page.evaluate(() => window.openPencil?.store!.state.hoveredNodeId) expect(hoveredId).toBe(state.childId) await canvas.click(state.hitX, state.hitY) @@ -252,7 +252,7 @@ test('frame children keep correct hover and click hit area without rotation', as expect(selected?.id).toBe(state.childId) await canvas.hover(state.missX, state.missY) - const hoveredMiss = await page.evaluate(() => window.__OPEN_PENCIL_STORE__!.state.hoveredNodeId) + const hoveredMiss = await page.evaluate(() => window.openPencil?.store!.state.hoveredNodeId) expect(hoveredMiss).not.toBe(state.childId) canvas.assertNoErrors() }) @@ -261,7 +261,7 @@ test('rotated frame children keep correct hover and click hit area', async () => const state = await setupFrameChild(35) await canvas.hover(state.hitX, state.hitY) - const hoveredId = await page.evaluate(() => window.__OPEN_PENCIL_STORE__!.state.hoveredNodeId) + const hoveredId = await page.evaluate(() => window.openPencil?.store!.state.hoveredNodeId) expect(hoveredId).toBe(state.childId) await canvas.click(state.hitX, state.hitY) @@ -270,7 +270,7 @@ test('rotated frame children keep correct hover and click hit area', async () => expect(selected?.id).toBe(state.childId) await canvas.hover(state.missX, state.missY) - const hoveredMiss = await page.evaluate(() => window.__OPEN_PENCIL_STORE__!.state.hoveredNodeId) + const hoveredMiss = await page.evaluate(() => window.openPencil?.store!.state.hoveredNodeId) expect(hoveredMiss).not.toBe(state.childId) canvas.assertNoErrors() }) @@ -282,7 +282,7 @@ test('rotation drag exposes live rotation preview state', async () => { await canvas.waitForRender() const viewport = await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const id = [...store.state.selectedIds][0] const n = store.graph.getNode(id) @@ -309,15 +309,13 @@ test('rotation drag exposes live rotation preview state', async () => { await page.mouse.move(rx + 60, ry + 60, { steps: 15 }) await canvas.waitForRender() - const preview = await page.evaluate(() => window.__OPEN_PENCIL_STORE__!.state.rotationPreview) + const preview = await page.evaluate(() => window.openPencil?.store!.state.rotationPreview) expect(preview).not.toBeNull() await page.mouse.up() await canvas.waitForRender() - const clearedPreview = await page.evaluate( - () => window.__OPEN_PENCIL_STORE__!.state.rotationPreview - ) + const clearedPreview = await page.evaluate(() => window.openPencil?.store!.state.rotationPreview) expect(clearedPreview).toBeNull() canvas.assertNoErrors() }) diff --git a/tests/e2e/chat/panel.spec.ts b/tests/e2e/chat/panel.spec.ts index 63dd933ab..7f63e56d2 100644 --- a/tests/e2e/chat/panel.spec.ts +++ b/tests/e2e/chat/panel.spec.ts @@ -27,7 +27,7 @@ test.afterAll(async () => { async function injectMockTransport(page: Page) { await page.evaluate(() => { - const setTransport = window.__OPEN_PENCIL_SET_TRANSPORT__ + const setTransport = window.openPencil?.setTransport if (!setTransport) throw new Error('Transport override not available') let msgCounter = 0 diff --git a/tests/e2e/clipboard/copy-paste.spec.ts b/tests/e2e/clipboard/copy-paste.spec.ts index f4f1b5ca3..9c1b7cec5 100644 --- a/tests/e2e/clipboard/copy-paste.spec.ts +++ b/tests/e2e/clipboard/copy-paste.spec.ts @@ -20,7 +20,7 @@ test.afterAll(async () => { function getPageChildCount() { return page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return store.graph.getChildren(store.state.currentPageId).length }) @@ -28,7 +28,7 @@ function getPageChildCount() { function getSelectedCount() { return page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return store.state.selectedIds.size }) @@ -36,7 +36,7 @@ function getSelectedCount() { function getSelectedNodes() { return page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return [...store.state.selectedIds].map((id) => { const n = store.graph.getNode(id) @@ -62,7 +62,7 @@ test('copy + paste via store duplicates a shape', async () => { const countBefore = await getPageChildCount() await page.evaluate(async () => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const data = new DataTransfer() await store.writeCopyData(data) @@ -100,7 +100,7 @@ test('⌘D duplicates in place', async () => { test('duplicate preserves fills', async () => { // Set a custom fill on the selected node await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const id = [...store.state.selectedIds][0] store.updateNodeWithUndo( @@ -136,7 +136,7 @@ test('cut removes original', async () => { // Cut via store await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const data = new DataTransfer() store.writeCopyData(data) diff --git a/tests/e2e/code/panel.spec.ts b/tests/e2e/code/panel.spec.ts index 8b37c0665..8c07b8982 100644 --- a/tests/e2e/code/panel.spec.ts +++ b/tests/e2e/code/panel.spec.ts @@ -99,7 +99,7 @@ test('deselecting shows empty state again', async () => { test('selecting a frame shows Frame in JSX', async () => { // Create a frame via store to avoid click-targeting issues await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const id = store.createShape('FRAME', 300, 100, 200, 200) store.select([id]) diff --git a/tests/e2e/color-picker/basic.spec.ts b/tests/e2e/color-picker/basic.spec.ts index 4a966c86e..2ce0323a1 100644 --- a/tests/e2e/color-picker/basic.spec.ts +++ b/tests/e2e/color-picker/basic.spec.ts @@ -20,7 +20,7 @@ test.afterAll(async () => { async function getSelectedFill() { return page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const id = [...store.state.selectedIds][0] if (!id) return null diff --git a/tests/e2e/color-picker/demo-card.spec.ts b/tests/e2e/color-picker/demo-card.spec.ts index c6bc5eede..5064cc567 100644 --- a/tests/e2e/color-picker/demo-card.spec.ts +++ b/tests/e2e/color-picker/demo-card.spec.ts @@ -26,7 +26,7 @@ async function selectDemoCard(page: Parameters[0]['page'], canvas: await canvas.waitForInit() await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const nodes = Array.from(store.graph.nodes.values()) const card = @@ -50,7 +50,7 @@ async function selectDemoCard(page: Parameters[0]['page'], canvas: async function getSelectedFill(page: Parameters[0]['page']) { return page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const id = [...store.state.selectedIds][0] const node = store.graph.getNode(id) diff --git a/tests/e2e/components.spec.ts b/tests/e2e/components.spec.ts index ad537e4b4..fb2ddfeb3 100644 --- a/tests/e2e/components.spec.ts +++ b/tests/e2e/components.spec.ts @@ -20,7 +20,7 @@ test.afterAll(async () => { function getNodeById(id: string) { return page.evaluate((nodeId) => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const n = store.graph.getNode(nodeId) if (!n) return null @@ -29,12 +29,16 @@ function getNodeById(id: string) { } function getSelectedIds() { - return page.evaluate(() => [...window.__OPEN_PENCIL_STORE__!.state.selectedIds]) + return page.evaluate(() => { + const store = window.openPencil?.store + if (!store) throw new Error('OpenPencil store not initialized') + return [...store.state.selectedIds] + }) } function getPageChildren() { return page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return store.graph.getChildren(store.state.currentPageId).map((n) => ({ id: n.id, @@ -73,7 +77,7 @@ test('component visible in layers panel', async () => { expect(count).toBeGreaterThan(0) const types = await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return store.graph.getChildren(store.state.currentPageId).map((n) => n.type) }) @@ -87,7 +91,7 @@ test('create instance from component (context menu)', async () => { // Use store directly to create instance await page.evaluate((compId) => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') store.createInstanceFromComponent(compId, 300, 100) }, comp!.id) @@ -104,7 +108,7 @@ test('instance shows INSTANCE type in design panel', async () => { const instance = children.find((c) => c.type === 'INSTANCE')! await page.evaluate((id) => { - window.__OPEN_PENCIL_STORE__!.select([id]) + window.openPencil?.store!.select([id]) }, instance.id) await canvas.waitForRender() @@ -125,13 +129,13 @@ test('instance has "Detach" button', async () => { test('modifying component propagates to instance', async () => { // Select the component await page.evaluate((id) => { - window.__OPEN_PENCIL_STORE__!.select([id]) + window.openPencil?.store!.select([id]) }, componentId) await canvas.waitForRender() // Change component fill await page.evaluate((id) => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') store.updateNodeWithUndo( id, @@ -155,7 +159,7 @@ test('modifying component propagates to instance', async () => { const children = await getPageChildren() const instance = children.find((c) => c.type === 'INSTANCE')! const instanceNode = await page.evaluate((id) => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const n = store.graph.getNode(id) const child = store.graph.getChildren(id)[0] @@ -170,12 +174,12 @@ test('detach instance converts to frame', async () => { const instance = children.find((c) => c.type === 'INSTANCE')! await page.evaluate((id) => { - window.__OPEN_PENCIL_STORE__!.select([id]) + window.openPencil?.store!.select([id]) }, instance.id) await canvas.waitForRender() await page.evaluate(() => { - window.__OPEN_PENCIL_STORE__!.detachInstance() + window.openPencil?.store!.detachInstance() }) await canvas.waitForRender() diff --git a/tests/e2e/context-menu/basic.spec.ts b/tests/e2e/context-menu/basic.spec.ts index 30dc1af27..b13b650f9 100644 --- a/tests/e2e/context-menu/basic.spec.ts +++ b/tests/e2e/context-menu/basic.spec.ts @@ -21,7 +21,7 @@ test.afterAll(async () => { function getPageChildren() { return page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return store.graph.getChildren(store.state.currentPageId).map((n) => ({ id: n.id, @@ -35,7 +35,7 @@ function getPageChildren() { function getSelectedCount() { return page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return store.state.selectedIds.size }) @@ -110,7 +110,7 @@ test('toggle visibility via context menu', async () => { await canvas.waitForRender() const nodeId = await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return [...store.state.selectedIds][0] }) @@ -120,7 +120,7 @@ test('toggle visibility via context menu', async () => { await canvas.waitForRender() const hidden = await page.evaluate((id) => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const n = store.graph.getNode(id) return n ? { visible: n.visible } : null @@ -129,14 +129,14 @@ test('toggle visibility via context menu', async () => { // Toggle back: select via store since invisible nodes can't be hit-tested await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') store.toggleVisibility() }) await canvas.waitForRender() const restored = await page.evaluate((id) => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const n = store.graph.getNode(id) return n ? { visible: n.visible } : null @@ -192,7 +192,7 @@ 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(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store 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]) diff --git a/tests/e2e/design/panel.spec.ts b/tests/e2e/design/panel.spec.ts index a47c7bbbb..22a880066 100644 --- a/tests/e2e/design/panel.spec.ts +++ b/tests/e2e/design/panel.spec.ts @@ -44,7 +44,7 @@ function effectsSection() { function getNode(id: string) { return page.evaluate((nodeId) => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const n = store.graph.getNode(nodeId) if (!n) return null @@ -65,7 +65,7 @@ function getNode(id: string) { function getSelectedId() { return page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return [...store.state.selectedIds][0] ?? null }) diff --git a/tests/e2e/editor/auto-layout.spec.ts b/tests/e2e/editor/auto-layout.spec.ts index 7c556619e..8ba7874ce 100644 --- a/tests/e2e/editor/auto-layout.spec.ts +++ b/tests/e2e/editor/auto-layout.spec.ts @@ -24,7 +24,7 @@ test.afterAll(async () => { async function selectFrame() { expect(frameId, 'frameId must be set — did the Shift+A test run?').toBeTruthy() await page.evaluate((id: string) => { - window.__OPEN_PENCIL_STORE__!.select([id]) + window.openPencil?.store!.select([id]) }, frameId) await canvas.waitForRender() } diff --git a/tests/e2e/editor/duplicate-undo.spec.ts b/tests/e2e/editor/duplicate-undo.spec.ts index ff13aa3a2..86ef3df23 100644 --- a/tests/e2e/editor/duplicate-undo.spec.ts +++ b/tests/e2e/editor/duplicate-undo.spec.ts @@ -24,7 +24,7 @@ test.beforeEach(async () => { async function rectangleCount() { return page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return [...store.graph.nodes.values()].filter((node) => node.type === 'RECTANGLE').length }) @@ -36,7 +36,7 @@ async function layerItems() { async function historyState() { return page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return { canUndo: store.undo.canUndo, diff --git a/tests/e2e/fonts/settings.spec.ts b/tests/e2e/fonts/settings.spec.ts index 2a3f7b4ca..6ffcc5d7e 100644 --- a/tests/e2e/fonts/settings.spec.ts +++ b/tests/e2e/fonts/settings.spec.ts @@ -8,7 +8,7 @@ test('font settings popover is available from typography panel', async ({ page } await canvas.waitForInit() await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const id = store.createShape('TEXT', 120, 120, 240, 40) store.updateNode(id, { characters: 'Font settings smoke' }) diff --git a/tests/e2e/keyboard/shortcuts.spec.ts b/tests/e2e/keyboard/shortcuts.spec.ts index 873ca2ee1..4c8c233d5 100644 --- a/tests/e2e/keyboard/shortcuts.spec.ts +++ b/tests/e2e/keyboard/shortcuts.spec.ts @@ -21,7 +21,7 @@ test.afterAll(async () => { function getActiveTool() { return page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return store.state.activeTool }) @@ -29,7 +29,7 @@ function getActiveTool() { function getSelectedCount() { return page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return store.state.selectedIds.size }) @@ -37,7 +37,7 @@ function getSelectedCount() { function getPageChildren() { return page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return store.graph.getChildren(store.state.currentPageId).map((n) => ({ id: n.id, @@ -50,7 +50,7 @@ function getPageChildren() { function getUIVisible() { return page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return store.state.showUI }) @@ -58,7 +58,7 @@ function getUIVisible() { function getZoom() { return page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return store.state.zoom }) @@ -150,7 +150,7 @@ test.describe('z-order shortcuts', () => { // Select the first (bottom) node await page.evaluate((id) => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') store.select([id]) }, firstId) @@ -169,7 +169,7 @@ test.describe('z-order shortcuts', () => { // Select the last (top) node await page.evaluate((id) => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') store.select([id]) }, lastId) @@ -245,7 +245,7 @@ test.describe('zoom shortcuts', () => { // Set zoom to something other than 100% await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') store.state.zoom = 2 }) @@ -327,7 +327,7 @@ test.describe('auto-layout shortcut', () => { // Change to frame type for auto-layout await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const nodes = [...store.state.selectedIds] if (nodes[0]) store.updateNode(nodes[0], { type: 'FRAME' }) @@ -335,7 +335,7 @@ test.describe('auto-layout shortcut', () => { await canvas.waitForRender() const layoutBefore = await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const nodes = [...store.state.selectedIds] return store.graph.getNode(nodes[0])?.layoutMode @@ -346,7 +346,7 @@ test.describe('auto-layout shortcut', () => { await canvas.waitForRender() const layoutAfter = await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const nodes = [...store.state.selectedIds] return store.graph.getNode(nodes[0])?.layoutMode @@ -358,7 +358,7 @@ test.describe('auto-layout shortcut', () => { await canvas.waitForRender() const layoutFinal = await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const nodes = [...store.state.selectedIds] return store.graph.getNode(nodes[0])?.layoutMode diff --git a/tests/e2e/layers/panel.spec.ts b/tests/e2e/layers/panel.spec.ts index 157f2b401..15c8c1c5e 100644 --- a/tests/e2e/layers/panel.spec.ts +++ b/tests/e2e/layers/panel.spec.ts @@ -41,7 +41,7 @@ interface SceneTreeNode { async function getSceneTree(): Promise { return page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) return null function nodeTree(id: string): SceneTreeNode | null { @@ -61,7 +61,7 @@ async function getSceneTree(): Promise { async function getSelectedCount(): Promise { return page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return store.state.selectedIds.size }) diff --git a/tests/e2e/pages/multi-page.spec.ts b/tests/e2e/pages/multi-page.spec.ts index ef2d9a4da..791890392 100644 --- a/tests/e2e/pages/multi-page.spec.ts +++ b/tests/e2e/pages/multi-page.spec.ts @@ -20,7 +20,7 @@ test.afterAll(async () => { function getPages() { return page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return store.graph.getPages().map((p) => ({ id: p.id, name: p.name })) }) @@ -28,7 +28,7 @@ function getPages() { function getCurrentPageId() { return page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return store.state.currentPageId }) @@ -36,7 +36,7 @@ function getCurrentPageId() { function getPageChildCount() { return page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return store.graph.getChildren(store.state.currentPageId).length }) @@ -126,7 +126,7 @@ test('delete current page switches to adjacent', async () => { const deletingId = await getCurrentPageId() await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') store.deletePage(store.state.currentPageId) }) @@ -145,7 +145,7 @@ test('rename page via store', async () => { await page.evaluate( ([id, name]) => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') store.renamePage(id, name) }, @@ -200,7 +200,7 @@ test('cannot delete the last page', async () => { let pages = await getPages() while (pages.length > 1) { await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') store.deletePage(store.state.currentPageId) }) @@ -212,7 +212,7 @@ test('cannot delete the last page', async () => { // Try deleting the last one — should be a no-op await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') store.deletePage(store.state.currentPageId) }) diff --git a/tests/e2e/perf/basic.spec.ts b/tests/e2e/perf/basic.spec.ts index a5b901423..76ab8514f 100644 --- a/tests/e2e/perf/basic.spec.ts +++ b/tests/e2e/perf/basic.spec.ts @@ -15,7 +15,7 @@ test.describe('Render performance', () => { await helper.waitForInit() await page.evaluate((count: number) => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const arr = new Uint8Array(count * 3) crypto.getRandomValues(arr) @@ -87,7 +87,7 @@ test.describe('Render performance', () => { test('benchmark: synchronous render throughput', async () => { const results = await helper.page.evaluate((iterations: number) => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const renderer = store.renderer if (!renderer) throw new Error('OpenPencil renderer not initialized') @@ -180,7 +180,7 @@ test.describe('Render performance', () => { const results = await helper.page.evaluate( ({ count, iterations }) => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const renderer = store.renderer if (!renderer) throw new Error('OpenPencil renderer not initialized') diff --git a/tests/e2e/properties/corner-stroke-toggles.spec.ts b/tests/e2e/properties/corner-stroke-toggles.spec.ts index e6cb1b1b7..1cb95c6e1 100644 --- a/tests/e2e/properties/corner-stroke-toggles.spec.ts +++ b/tests/e2e/properties/corner-stroke-toggles.spec.ts @@ -20,7 +20,7 @@ test.afterAll(async () => { async function getSelectedNodeFlags() { return page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const id = [...store.state.selectedIds][0] if (!id) return null diff --git a/tests/e2e/properties/effects.spec.ts b/tests/e2e/properties/effects.spec.ts index bde686716..20b37ff4d 100644 --- a/tests/e2e/properties/effects.spec.ts +++ b/tests/e2e/properties/effects.spec.ts @@ -30,7 +30,7 @@ async function expectCanvas(name: string) { test('drop shadow on white card', async () => { await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const pageId = store.state.currentPageId store.graph.createNode('FRAME', pageId, { @@ -61,7 +61,7 @@ test('drop shadow on white card', async () => { test('drop shadow with spread', async () => { await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const pageId = store.state.currentPageId store.graph.createNode('FRAME', pageId, { @@ -92,7 +92,7 @@ test('drop shadow with spread', async () => { test('inner shadow', async () => { await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const pageId = store.state.currentPageId store.graph.createNode('FRAME', pageId, { @@ -125,7 +125,7 @@ test('inner shadow', async () => { test('inner shadow with spread', async () => { await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const pageId = store.state.currentPageId store.graph.createNode('FRAME', pageId, { @@ -158,7 +158,7 @@ test('inner shadow with spread', async () => { test('drop shadow on ellipse', async () => { await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const pageId = store.state.currentPageId store.graph.createNode('ELLIPSE', pageId, { @@ -190,7 +190,7 @@ test('drop shadow on ellipse', async () => { test('combined drop and inner shadow', async () => { await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const pageId = store.state.currentPageId store.graph.createNode('FRAME', pageId, { @@ -229,7 +229,7 @@ test('combined drop and inner shadow', async () => { test('text drop shadow on glyphs', async () => { await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const pageId = store.state.currentPageId store.graph.createNode('TEXT', pageId, { @@ -265,7 +265,7 @@ test('text drop shadow on glyphs', async () => { test('layer blur', async () => { await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const pageId = store.state.currentPageId store.graph.createNode('RECTANGLE', pageId, { @@ -298,7 +298,7 @@ test('layer blur', async () => { test('invisible effect has no visual impact', async () => { await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const pageId = store.state.currentPageId store.graph.createNode('FRAME', pageId, { diff --git a/tests/e2e/properties/panel.spec.ts b/tests/e2e/properties/panel.spec.ts index daa43773b..3fa6b56a5 100644 --- a/tests/e2e/properties/panel.spec.ts +++ b/tests/e2e/properties/panel.spec.ts @@ -96,7 +96,7 @@ test('variable bind badge appears on fill', async () => { await canvas.drawRect(200, 200, 80, 80) await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const col = store.graph.createCollection('Colors') const v = store.graph.createVariable('brand-red', 'COLOR', col.id, { r: 1, g: 0, b: 0, a: 1 }) @@ -116,7 +116,7 @@ test('fill color can bind an existing variable', async () => { await canvas.drawRect(200, 200, 80, 80) await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const col = store.graph.createCollection('Colors') const variable = store.graph.createVariable('test-brand-red', 'COLOR', col.id, { @@ -145,7 +145,7 @@ test('fill color can bind an existing variable', async () => { await canvas.waitForRender() await expect(page.locator('[data-test-id="fill-unbind-variable"]')).toBeHidden() const boundVariableId = await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const id = [...store.state.selectedIds][0] return id ? (store.getNode(id)?.boundVariables['fills/0/color'] ?? null) : null @@ -167,7 +167,7 @@ test('fill color can create and bind a variable', async () => { await expect(page.locator('[data-test-id="fill-unbind-variable"]')).toBeVisible() const boundVariable = await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const id = [...store.state.selectedIds][0] if (!id) return null @@ -193,7 +193,7 @@ test('width can create, bind, and detach a number variable', async () => { await expect(page.locator('[data-test-id="layout-width-unbind-variable"]')).toBeVisible() const boundVariable = await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const id = [...store.state.selectedIds][0] if (!id) return null @@ -212,7 +212,7 @@ test('width can create, bind, and detach a number variable', async () => { await expect(page.locator('[data-test-id="layout-width-unbind-variable"]')).toBeHidden() const directWidth = await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const id = [...store.state.selectedIds][0] const node = id ? store.getNode(id) : null diff --git a/tests/e2e/properties/visibility.spec.ts b/tests/e2e/properties/visibility.spec.ts index 49c68239d..ee265b47e 100644 --- a/tests/e2e/properties/visibility.spec.ts +++ b/tests/e2e/properties/visibility.spec.ts @@ -20,7 +20,7 @@ test.afterAll(async () => { function getSelectedNode() { return page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const id = [...store.state.selectedIds][0] if (!id) return null diff --git a/tests/e2e/scene/cache.spec.ts b/tests/e2e/scene/cache.spec.ts index 81a034d0c..8e489dc3e 100644 --- a/tests/e2e/scene/cache.spec.ts +++ b/tests/e2e/scene/cache.spec.ts @@ -12,7 +12,7 @@ test.describe('SkPicture scene caching', () => { await helper.waitForInit() await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const pageId = store.state.currentPageId @@ -75,7 +75,7 @@ test.describe('SkPicture scene caching', () => { // We simulate this by calling invalidateScenePicture() to force the next // render to re-record, then verify hover on/off doesn't lose text. await helper.page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') // Simulate what loadFonts() does: invalidate the cached picture store.renderer!.invalidateScenePicture() @@ -85,7 +85,7 @@ test.describe('SkPicture scene caching', () => { // Extra render to stabilize the SkPicture cache await helper.page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') store.requestRender() }) @@ -96,7 +96,7 @@ test.describe('SkPicture scene caching', () => { // Hover frame → un-hover: replays the newly recorded picture await helper.page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const pg = store.graph.getNode(store.state.currentPageId)! const frame = pg.childIds.find((id: string) => store.graph.getNode(id)?.type === 'FRAME') @@ -105,7 +105,7 @@ test.describe('SkPicture scene caching', () => { await helper.waitForRender() await helper.page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') store.setHoveredNode(null) }) @@ -118,7 +118,7 @@ test.describe('SkPicture scene caching', () => { test('text survives hover on/off cycle', async () => { // 1. Baseline: no hover — this records the SkPicture cache await helper.page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') store.setHoveredNode(null) store.requestRender() @@ -126,7 +126,7 @@ test.describe('SkPicture scene caching', () => { await helper.waitForRender() // Extra render cycle to ensure SkPicture cache is fully recorded await helper.page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') store.requestRender() }) @@ -135,7 +135,7 @@ test.describe('SkPicture scene caching', () => { // 2. Hover a frame — uses requestRepaint (only renderVersion, not sceneVersion) await helper.page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const page = store.graph.getNode(store.state.currentPageId)! const frame = page.childIds.find((id: string) => store.graph.getNode(id)?.type === 'FRAME') @@ -145,7 +145,7 @@ test.describe('SkPicture scene caching', () => { // 3. Hover off — should replay cached SkPicture (the critical transition) await helper.page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') store.setHoveredNode(null) }) @@ -160,7 +160,7 @@ test.describe('SkPicture scene caching', () => { test('text survives multiple hover cycles', async () => { // Rapid hover on/off using the real setHoveredNode path (requestRepaint) await helper.page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const page = store.graph.getNode(store.state.currentPageId)! const frame = page.childIds.find((id: string) => store.graph.getNode(id)?.type === 'FRAME') @@ -178,7 +178,7 @@ test.describe('SkPicture scene caching', () => { test('text survives real mouse hover on/off', async () => { // Use actual mouse movement instead of programmatic setHoveredNode await helper.page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') store.setHoveredNode(null) store.requestRender() @@ -194,7 +194,7 @@ test.describe('SkPicture scene caching', () => { // Move mouse to empty area (far from any node) await helper.page.mouse.move(box!.x + 800, box!.y + 600) - await helper.page.evaluate(() => window.__OPEN_PENCIL_STORE__!.setHoveredNode(null)) + await helper.page.evaluate(() => window.openPencil?.store!.setHoveredNode(null)) await helper.waitForRender() await helper.page.waitForTimeout(100) @@ -206,7 +206,7 @@ test.describe('SkPicture scene caching', () => { test('text survives scene change then hover cycle', async () => { // Mutate scene to invalidate picture cache await helper.page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const page = store.graph.getNode(store.state.currentPageId)! const frame = page.childIds.find((id: string) => store.graph.getNode(id)?.type === 'FRAME') @@ -217,7 +217,7 @@ test.describe('SkPicture scene caching', () => { // Hover on then off using real path await helper.page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const page = store.graph.getNode(store.state.currentPageId)! const frame = page.childIds.find((id: string) => store.graph.getNode(id)?.type === 'FRAME') @@ -226,7 +226,7 @@ test.describe('SkPicture scene caching', () => { await helper.waitForRender() await helper.page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') store.setHoveredNode(null) }) diff --git a/tests/e2e/snap/guides.spec.ts b/tests/e2e/snap/guides.spec.ts index 07a9c3c8f..77d9d9f6e 100644 --- a/tests/e2e/snap/guides.spec.ts +++ b/tests/e2e/snap/guides.spec.ts @@ -22,7 +22,7 @@ test.afterAll(async () => { async function createRects() { await canvas.clearCanvas() await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') store.createShape('RECTANGLE', 100, 100, 80, 80) const b = store.createShape('RECTANGLE', 300, 100, 80, 80) diff --git a/tests/e2e/stroke-picker/basic.spec.ts b/tests/e2e/stroke-picker/basic.spec.ts index e11e0cecc..5df90ca48 100644 --- a/tests/e2e/stroke-picker/basic.spec.ts +++ b/tests/e2e/stroke-picker/basic.spec.ts @@ -32,7 +32,7 @@ async function chooseFormat(page: Page, label: 'RGB' | 'HSL' | 'HSB' | 'OkHCL') async function getSelectedStroke(page: Page) { return page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const id = [...store.state.selectedIds][0] const node = store.graph.getNode(id) @@ -88,7 +88,7 @@ test('stroke picker hsb saturation and brightness sliders update stroke color on await canvas.waitForInit() await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const nodes = Array.from(store.graph.nodes.values()) const card = diff --git a/tests/e2e/text/editing.spec.ts b/tests/e2e/text/editing.spec.ts index 7016e9184..e341a4ab5 100644 --- a/tests/e2e/text/editing.spec.ts +++ b/tests/e2e/text/editing.spec.ts @@ -21,7 +21,7 @@ test.afterAll(async () => { function getSelectedNode() { return page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const id = [...store.state.selectedIds][0] if (!id) return null @@ -43,7 +43,7 @@ function getSelectedNode() { function getPageChildren() { return page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return store.graph.getChildren(store.state.currentPageId).map((n) => ({ type: n.type, @@ -57,7 +57,7 @@ test('pressing T activates text tool', async () => { await page.keyboard.press('t') const tool = await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return store.state.activeTool }) @@ -105,7 +105,7 @@ test('creating text via store works', async () => { await canvas.waitForRender() await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const id = store.createShape('TEXT', 100, 300, 200, 30) store.graph.updateNode(id, { text: 'Hello World', fontSize: 24, fontFamily: 'Inter' }) @@ -146,7 +146,7 @@ test('Enter key opens text editing and selects all without erasing', async () => await canvas.waitForRender() const textId = await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const id = store.createShape('TEXT', 300, 300, 200, 30) store.graph.updateNode(id, { text: 'Keep this text' }) @@ -164,14 +164,14 @@ test('Enter key opens text editing and selects all without erasing', async () => await page.waitForTimeout(200) const editing = await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return store.state.editingTextId }) expect(editing).toBe(textId) const after = await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const id = store.state.editingTextId if (!id) return null diff --git a/tests/e2e/text/formatting.spec.ts b/tests/e2e/text/formatting.spec.ts index b4e25d26e..00ded1fe6 100644 --- a/tests/e2e/text/formatting.spec.ts +++ b/tests/e2e/text/formatting.spec.ts @@ -17,7 +17,7 @@ test.beforeAll(async ({ browser }) => { await canvas.clearCanvas() await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') store.state.zoom = 1 store.state.panX = 0 @@ -54,7 +54,7 @@ test('bold button toggles fontWeight to 700 then back to 400', async () => { // ensure starting weight is 400 via undo-safe store method await page.evaluate(async (id: string) => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') store.updateNodeWithUndo(id, { fontWeight: 400 }, 'reset') store.state.sceneVersion++ diff --git a/tests/e2e/tools/section.spec.ts b/tests/e2e/tools/section.spec.ts index 7cc1e09cc..e2d9fd8eb 100644 --- a/tests/e2e/tools/section.spec.ts +++ b/tests/e2e/tools/section.spec.ts @@ -29,7 +29,7 @@ test('draw section in full editor without browser errors', async () => { await expect .poll(async () => { return page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store const selectedId = [...store.state.selectedIds][0] return selectedId ? store.graph.getNode(selectedId)?.type : null }) diff --git a/tests/e2e/variables/dialog.spec.ts b/tests/e2e/variables/dialog.spec.ts index cd9e7fed7..9dd84b4ab 100644 --- a/tests/e2e/variables/dialog.spec.ts +++ b/tests/e2e/variables/dialog.spec.ts @@ -20,7 +20,7 @@ test.afterAll(async () => { async function createColorVariable(name: string) { return page.evaluate((varName: string) => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const existing = [...store.graph.variableCollections.values()] const col = existing.length > 0 ? existing[0] : store.graph.createCollection('Test Collection') @@ -44,7 +44,7 @@ test('variables dialog opens', async () => { test('search filters variable rows', async () => { await page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const col = [...store.graph.variableCollections.values()][0] store.graph.createVariable('beta-spacing', 'FLOAT', col.id, 8) diff --git a/tests/e2e/viewport/zoom-pan.spec.ts b/tests/e2e/viewport/zoom-pan.spec.ts index c0a7fba99..6c576f5eb 100644 --- a/tests/e2e/viewport/zoom-pan.spec.ts +++ b/tests/e2e/viewport/zoom-pan.spec.ts @@ -15,7 +15,7 @@ test.describe('Zoom and pan', () => { await helper.waitForInit() await page.evaluate((count: number) => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const cols = Math.ceil(Math.sqrt(count)) for (let i = 0; i < count; i++) { @@ -40,7 +40,7 @@ test.describe('Zoom and pan', () => { test('wheel zoom updates viewport correctly', async () => { const before = await helper.page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return { panX: store.state.panX, panY: store.state.panY, zoom: store.state.zoom } }) @@ -55,7 +55,7 @@ test.describe('Zoom and pan', () => { await helper.page.waitForTimeout(50) const after = await helper.page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return { panX: store.state.panX, panY: store.state.panY, zoom: store.state.zoom } }) @@ -66,7 +66,7 @@ test.describe('Zoom and pan', () => { test('wheel pan updates viewport correctly', async () => { const before = await helper.page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return { panX: store.state.panX, panY: store.state.panY } }) @@ -80,7 +80,7 @@ test.describe('Zoom and pan', () => { await helper.page.waitForTimeout(50) const after = await helper.page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return { panX: store.state.panX, panY: store.state.panY } }) @@ -102,7 +102,7 @@ test.describe('Zoom and pan', () => { await helper.page.waitForTimeout(50) const state = await helper.page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return { zoom: store.state.zoom } }) @@ -114,7 +114,7 @@ test.describe('Zoom and pan', () => { test('shallowReactive: selection replace triggers UI update', async () => { const result = await helper.page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const pageNode = store.graph.getNode(store.state.currentPageId) if (!pageNode) throw new Error(`Page ${store.state.currentPageId} not found`) @@ -139,7 +139,7 @@ test.describe('Zoom and pan', () => { test('useRafFn loop picks up renderVersion changes', async () => { // Ensure clean state, wait for any pending renders await helper.page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') store.state.panX = 0 store.state.panY = 0 @@ -153,7 +153,7 @@ test.describe('Zoom and pan', () => { // Change fill color — always visible await helper.page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const pageNode = store.graph.getNode(store.state.currentPageId) if (!pageNode) throw new Error(`Page ${store.state.currentPageId} not found`) @@ -172,7 +172,7 @@ test.describe('Zoom and pan', () => { // Restore await helper.page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const pageNode = store.graph.getNode(store.state.currentPageId) if (!pageNode) throw new Error(`Page ${store.state.currentPageId} not found`) @@ -192,7 +192,7 @@ test.describe('Zoom and pan', () => { const before = await helper.screenshotCanvas() await helper.page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const pageNode = store.graph.getNode(store.state.currentPageId) if (!pageNode) throw new Error(`Page ${store.state.currentPageId} not found`) @@ -208,7 +208,7 @@ test.describe('Zoom and pan', () => { expect(Buffer.from(before)).not.toEqual(Buffer.from(after)) await helper.page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') store.clearSelection() }) @@ -219,7 +219,7 @@ test.describe('Zoom and pan', () => { const ITERATIONS = 500 const results = await helper.page.evaluate((iterations: number) => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') // Reset viewport diff --git a/tests/helpers/store.ts b/tests/helpers/store.ts index b256d7de0..800103241 100644 --- a/tests/helpers/store.ts +++ b/tests/helpers/store.ts @@ -2,7 +2,7 @@ import type { Page } from '@playwright/test' export function getSelectedIds(page: Page) { return page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return store.state.selectedIds.size }) @@ -10,7 +10,7 @@ export function getSelectedIds(page: Page) { export function getPageChildren(page: Page) { return page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return store.graph.getChildren(store.state.currentPageId).map((n) => ({ id: n.id, @@ -28,7 +28,7 @@ export function getPageChildren(page: Page) { export function getSelectedNode(page: Page) { return page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const id = [...store.state.selectedIds][0] if (!id) return null @@ -59,7 +59,7 @@ export function getSelectedNode(page: Page) { export function getNodeById(page: Page, id: string) { return page.evaluate((nodeId: string) => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') const n = store.graph.getNode(nodeId) if (!n) return null @@ -99,7 +99,7 @@ export function getNodeById(page: Page, id: string) { export function getEditingTextId(page: Page) { return page.evaluate(() => { - const store = window.__OPEN_PENCIL_STORE__ + const store = window.openPencil?.store if (!store) throw new Error('OpenPencil store not initialized') return store.state.editingTextId })