openpencil/tests/e2e/code/panel.spec.ts
Danila Poyarkov d90e640e89
perf(app): defer inactive Code panel generation (#514)
* perf(app): defer inactive Code panel generation

- Skip JSX serialization and syntax highlighting while the Code tab is hidden

- Restore code generation when desktop or mobile users activate the tab

- Cover large Design-tab selections without hidden Code-panel work

* test(app): cover deferred Code panel updates

- Keep mobile JSX generation inactive while the drawer is closed

- Measure the complete two-frame inactive selection flow

- Verify mobile Code output refreshes when the drawer reopens
2026-08-14 14:24:10 +03:00

180 lines
5.9 KiB
TypeScript

import { expect, test, useEditorSetup } from '#tests/e2e/fixtures'
const editor = useEditorSetup()
function codeTab() {
return editor.page.getByTestId('properties-tab-code')
}
function designTab() {
return editor.page.getByTestId('properties-tab-design')
}
function codePanel() {
return editor.page.getByTestId('code-panel')
}
function codePanelEmpty() {
return editor.page.getByTestId('code-panel-empty')
}
function formatToggle() {
return editor.page.getByTestId('code-panel-format-toggle')
}
function copyButton() {
return editor.page.getByTestId('code-panel-copy')
}
test('inactive Code tab skips JSX generation for large selections', async () => {
const selectionDuration = await editor.page.evaluate(async () => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
const pageId = store.state.currentPageId
const ids: string[] = []
for (let frameIndex = 0; frameIndex < 50; frameIndex++) {
const frame = store.graph.createNode('FRAME', pageId, { name: `Frame ${frameIndex}` })
ids.push(frame.id)
for (let childIndex = 0; childIndex < 100; childIndex++) {
store.graph.createNode('RECTANGLE', frame.id, { name: `Child ${childIndex}` })
}
}
const startedAt = performance.now()
store.select(ids)
await new Promise<void>((resolve) => {
requestAnimationFrame(() => requestAnimationFrame(() => resolve()))
})
return performance.now() - startedAt
})
expect(selectionDuration).toBeLessThan(1000)
await expect(designTab()).toHaveAttribute('data-state', 'active')
await codeTab().click()
await expect(codePanel()).toContainText('Frame')
await editor.page.evaluate(() => window.openPencil?.getStore?.().clearSelection())
await designTab().click()
})
test('Code tab shows empty state with no selection', async () => {
await codeTab().click()
await expect(codePanelEmpty()).toBeVisible()
await expect(codePanelEmpty()).toContainText('Select a layer')
})
test('selecting a rectangle shows JSX code', async () => {
await editor.canvas.drawRect(100, 100, 200, 150)
await editor.canvas.waitForRender()
await expect(codePanel()).toBeVisible()
const code = await codePanel().textContent()
expect(code).toContain('Rectangle')
})
test('format toggle switches between OpenPencil and Tailwind', async () => {
await expect(formatToggle()).toBeVisible()
const initialFormat = await formatToggle().textContent()
expect(initialFormat).toContain('OpenPencil')
await formatToggle().click()
await expect(formatToggle()).toContainText('Tailwind')
const code = await codePanel().textContent()
expect(code).toContain('div')
await formatToggle().click()
await expect(formatToggle()).toContainText('OpenPencil')
})
test('copy button works and shows confirmation', async () => {
await copyButton().click()
await expect(copyButton()).toContainText('Copied')
await editor.page.waitForTimeout(2500)
await expect(copyButton()).toContainText('Copy')
})
test('deselecting shows empty state again', async () => {
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 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 editor.canvas.waitForRender()
const code = await codePanel().textContent()
expect(code).toContain('Frame')
})
test('switching back to Design tab works', async () => {
await designTab().click()
await expect(
editor.page
.getByTestId('design-panel-single')
.or(editor.page.getByTestId('design-panel-empty'))
.first()
).toBeVisible()
})
test('shows import errors in the Code panel', async () => {
await codeTab().click()
await editor.page.getByTestId('code-panel-import-toggle').click()
await editor.page.evaluate(() => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
const importDOMText = store.importDOMText
store.importDOMText = async () => {
store.importDOMText = importDOMText
throw new Error('CSS import failed')
}
})
await editor.page.getByTestId('code-panel-import-html').fill('<div class="card">Broken DOM</div>')
await editor.page.getByTestId('code-panel-import').click()
await expect(editor.page.getByTestId('code-panel-import-error')).toBeVisible()
await expect(editor.page.getByTestId('code-panel-import-error')).toContainText(
'CSS import failed'
)
await editor.page.getByTestId('code-panel-import-html').fill('<div class="card">Recovered</div>')
await expect(editor.page.getByTestId('code-panel-import-error')).toBeHidden()
await editor.page.getByTestId('code-panel-import-toggle').click()
})
test('imports HTML and CSS into the canvas', async () => {
await codeTab().click()
await editor.page.getByTestId('code-panel-import-toggle').click()
await editor.page.getByTestId('code-panel-import-html').fill('<div class="card">Hello DOM</div>')
await editor.page
.getByTestId('code-panel-import-css')
.fill('.card { width: 240px; height: 120px; padding: 16px; background: #ffffff; }')
await editor.page.getByTestId('code-panel-import').click()
await editor.page.waitForFunction(() => {
const store = window.openPencil?.getStore?.()
return store?.graph.getAllNodes().some((node) => node.name.includes('Hello DOM'))
})
await editor.canvas.waitForRender()
const imported = await editor.page.evaluate(() => {
const store = window.openPencil?.getStore?.()
if (!store) throw new Error('OpenPencil store not initialized')
return store.graph.getAllNodes().some((node) => node.name.includes('Hello DOM'))
})
expect(imported).toBe(true)
})