openpencil/tests/engine/editor/selection/semantics.test.ts
Danila Poyarkov de8578c435
feat(app): expand Figma menu and selection parity (#459)
- Add Figma-style object, component, selection, view, settings, and browser Move to Page actions
- Add undoable z-order, transformed distribution, inverse selection, and single/bulk rename behavior
- Keep browser and native menus, shortcuts, generated schema, localization, and documentation aligned
2026-08-03 00:23:05 +03:00

85 lines
3.4 KiB
TypeScript

import { describe, expect, test } from 'bun:test'
import { createEditor } from '@open-pencil/core/editor'
function renameOptions(replacement: string, match = '', startNumber = 1) {
return { match, replacement, startNumber }
}
describe('selection semantics', () => {
test('selectInverse selects unselected top-level layers', () => {
const editor = createEditor()
const pageId = editor.state.currentPageId
const first = editor.graph.createNode('RECTANGLE', pageId, { name: 'First' })
const second = editor.graph.createNode('RECTANGLE', pageId, { name: 'Second' })
const third = editor.graph.createNode('RECTANGLE', pageId, { name: 'Third' })
editor.select([second.id])
editor.selectInverse()
expect(editor.state.selectedIds).toEqual(new Set([first.id, third.id]))
})
test('renameSelected applies one name and supports undo', () => {
const editor = createEditor()
const pageId = editor.state.currentPageId
const first = editor.graph.createNode('RECTANGLE', pageId, { name: 'First' })
const second = editor.graph.createNode('ELLIPSE', pageId, { name: 'Second' })
editor.select([first.id, second.id])
editor.renameSelected(renameOptions('Shared name'))
expect(editor.graph.getNode(first.id)?.name).toBe('Shared name')
expect(editor.graph.getNode(second.id)?.name).toBe('Shared name')
editor.undoAction()
expect(editor.graph.getNode(first.id)?.name).toBe('First')
expect(editor.graph.getNode(second.id)?.name).toBe('Second')
})
test('renameSelected supports matching, current names, and numbered sequences', () => {
const editor = createEditor()
const pageId = editor.state.currentPageId
const first = editor.graph.createNode('RECTANGLE', pageId, { name: 'Icon/Home' })
const second = editor.graph.createNode('RECTANGLE', pageId, { name: 'Icon/Search' })
editor.select([first.id, second.id])
editor.renameSelected(renameOptions('Image/', '^Icon/'))
expect(editor.graph.getNode(first.id)?.name).toBe('Image/Home')
expect(editor.graph.getNode(second.id)?.name).toBe('Image/Search')
editor.renameSelected(renameOptions('$nn $&', '', 7))
expect(editor.graph.getNode(first.id)?.name).toBe('07 Image/Home')
expect(editor.graph.getNode(second.id)?.name).toBe('08 Image/Search')
editor.undoAction()
editor.renameSelected(renameOptions('$NN $&', '', 7))
expect(editor.graph.getNode(first.id)?.name).toBe('08 Image/Home')
expect(editor.graph.getNode(second.id)?.name).toBe('07 Image/Search')
})
test('renameSelected rejects invalid regular expressions', () => {
const editor = createEditor()
const node = editor.graph.createNode('RECTANGLE', editor.state.currentPageId, { name: 'Home' })
editor.select([node.id])
const preview = editor.previewRenameSelected(renameOptions('Icon', '['))
editor.renameSelected(renameOptions('Icon', '['))
expect(preview.error).toBe('invalid-pattern')
expect(editor.graph.getNode(node.id)?.name).toBe('Home')
expect(editor.undo.canUndo).toBe(false)
})
test('blank results restore readable default names', () => {
const editor = createEditor()
const componentSet = editor.graph.createNode('COMPONENT_SET', editor.state.currentPageId, {
name: 'Custom'
})
editor.select([componentSet.id])
editor.renameSelected(renameOptions(''))
expect(editor.graph.getNode(componentSet.id)?.name).toBe('Component set')
})
})