openpencil/tests/e2e/app/command-palette.spec.ts
Danila Poyarkov 79fee711c7
feat(app): jump between pages from the command palette (#801)
* fix(vue): keep the command palette open when a command opens a step

CommandPaletteRoot emitted select for every item, including one that only opens its children, so a host that closes on select closed the palette instead of showing the step. useCommandPalette.select now reports whether a command ran, and the root emits only then.

Disabled items were marked only with Reka's data-disabled; expose aria-disabled so assistive technology announces them.

* feat(app): jump between pages from the command palette

The palette had no way to reach a page. It now lists the pages visited recently in the tab, offers a Go to page step with every page, and finds any page by name.

Recent pages are tracked per editor session from page changes and reset when the document is replaced. Palette items can be search-only, so pages beyond the recent ones appear only when the query matches them. The divider-page rule moves out of PageListRoot so the palette skips dividers the same way, and useCommandPalette is exported from the package root.

* fix(vue): list every item in a command palette step

The result limit also applied to a step's unfiltered list, so Go to page showed only the first 12 pages. A step the user opened now lists all of its items until they search; search results and the top-level list keep the limit.

The palette spec's page setup and current-page observation move to tests/helpers/pages as a setup mutation, a probe, and a Pages-panel driver.

* test(pages): match page rows by exact name

* refactor(app): express page lists with es-toolkit

Recent pages are take(uniq([visited, ...previous]), max); the palette builds its lookup with keyBy and its lists with without, compact, take, and difference instead of hand-built maps, sets, and slices. Tests count with range.
2026-10-01 20:26:35 +04:00

90 lines
3.6 KiB
TypeScript

import { range } from 'es-toolkit'
import { expect, test, useEditorSetup } from '#tests/e2e/fixtures'
import { addPages, currentPageName, visitPage } from '#tests/helpers/pages'
const editor = useEditorSetup()
function commandPaletteShortcut() {
return process.platform === 'darwin' ? 'Meta+KeyK' : 'Control+KeyK'
}
test('command palette opens, searches, and closes', async () => {
await editor.page.keyboard.press(commandPaletteShortcut())
const palette = editor.page.getByRole('dialog', { name: 'Command palette' })
await expect(palette).toBeVisible()
const search = palette.getByRole('searchbox', { name: 'Search commands' })
await expect(search).toBeFocused()
await search.fill('zoom')
await expect(palette.getByRole('option', { name: /Zoom to fit/ })).toBeVisible()
await expect(palette.getByRole('option', { name: 'New' })).not.toBeVisible()
await editor.page.keyboard.press('Escape')
await expect(palette).not.toBeVisible()
})
test('command palette exposes contextual export labels', async () => {
await editor.page.keyboard.press(commandPaletteShortcut())
const palette = editor.page.getByRole('dialog', { name: 'Command palette' })
await palette.getByRole('searchbox', { name: 'Search commands' }).fill('export')
await expect(palette.getByText('Export selection as PNG')).toBeVisible()
await expect(palette.getByText('Export selection as SVG')).toBeVisible()
await editor.page.keyboard.press('Escape')
})
function openPalette() {
return editor.page.keyboard
.press(commandPaletteShortcut())
.then(() => editor.page.getByRole('dialog', { name: 'Command palette' }))
}
test('command palette lists recent pages and finds other pages by name', async () => {
await addPages(editor.page, ['Alpha', 'Beta', 'Gamma'])
await visitPage(editor.page, 'Alpha')
await visitPage(editor.page, 'Beta')
const palette = await openPalette()
await expect(palette.getByRole('option', { name: 'Alpha Recent' })).toBeVisible()
await expect(palette.getByRole('option', { name: 'Page 1 Recent' })).toBeVisible()
await expect(palette.getByRole('option', { name: /^Beta/ })).toHaveCount(0)
await expect(palette.getByRole('option', { name: /^Gamma/ })).toHaveCount(0)
await palette.getByRole('searchbox', { name: 'Search commands' }).fill('gamma')
await palette.getByRole('option', { name: 'Gamma' }).click()
await expect(palette).not.toBeVisible()
await expect.poll(() => currentPageName(editor.page)).toBe('Gamma')
})
test('command palette goes to any page in a page step', async () => {
await addPages(editor.page, ['Delta', 'Epsilon'])
const current = await currentPageName(editor.page)
const palette = await openPalette()
await palette.getByRole('option', { name: 'Go to page…' }).click()
await expect(palette.getByRole('option', { name: `${current} Current page` })).toHaveAttribute(
'aria-disabled',
'true'
)
await palette.getByRole('button', { name: 'Back' }).click()
await expect(palette.getByRole('option', { name: 'Go to page…' })).toBeVisible()
await palette.getByRole('option', { name: 'Go to page…' }).click()
await palette.getByRole('option', { name: 'Epsilon' }).click()
await expect(palette).not.toBeVisible()
await expect.poll(() => currentPageName(editor.page)).toBe('Epsilon')
})
test('command palette page step lists every page, beyond the search result limit', async () => {
const names = range(1, 15).map((n) => `Chapter ${n}`)
await addPages(editor.page, names)
const palette = await openPalette()
await palette.getByRole('option', { name: 'Go to page…' }).click()
await expect(palette.getByRole('option', { name: /^Chapter 14/ })).toBeVisible()
await editor.page.keyboard.press('Escape')
})