openpencil/tests/engine/app/editor/panes.test.ts

67 lines
2.5 KiB
TypeScript
Raw Normal View History

feat(editor): model split canvas panes (#519) * refactor(editor): separate canvas view state - Classify shared and view-local editor state explicitly - Let canvas surfaces render supplied view state and report their viewport - Preserve the existing one-canvas behavior by default * fix(canvas): preserve loading render state * feat(editor): model split canvas panes - Add pure recursive split-tree operations with validated sizes - Add explicit pane registry and independent view-state cloning - Cap visible panes and cover close and split behavior * refactor(editor): group state ownership modules - Move shared and view state into the editor state namespace - Model the partition with explicit interfaces and default factories - Derive runtime view keys from the default view object * feat(editor): model split canvas panes - Add pure recursive split-tree operations with validated sizes - Add explicit pane registry and independent view-state cloning - Cap visible panes and cover close and split behavior * feat(editor): add split canvas views - Render recursive pane layouts with Reka UI splitters and pane-local headers - Route canvas input, selection, viewport state, and close actions to the active pane - Repaint every canvas during shared document previews and cover split lifecycle in tests * fix(editor): clean up inactive pane interactions - Cancel drag, padding preview, and text editing state when pane focus changes - Remove duplicate changelog entries introduced while updating master
2026-08-15 07:31:52 +00:00
import { describe, expect, test } from 'bun:test'
import { createDefaultEditorState } from '@open-pencil/core/editor'
import { createCanvasPaneRegistry } from '@/app/editor/panes/registry'
import {
closePaneNode,
leafPaneIds,
MAX_VISIBLE_CANVAS_PANES,
normalizeSplitSizes,
paneCount,
splitPaneNode,
updateSplitSizes
} from '@/app/editor/panes/split-tree'
import type { CanvasSplitNode } from '@/app/editor/panes/split-tree'
describe('canvas split tree', () => {
test('splits panes and collapses one-child parents', () => {
const initial: CanvasSplitNode = { type: 'pane', paneId: 'a' }
const split = splitPaneNode(initial, 'a', 'b', 'split-1', 'horizontal')
expect(paneCount(split)).toBe(2)
expect(leafPaneIds(split)).toEqual(['a', 'b'])
expect(closePaneNode(split, 'a')).toEqual({ type: 'pane', paneId: 'b' })
})
test('normalizes valid sizes and rejects invalid updates', () => {
const split = splitPaneNode({ type: 'pane', paneId: 'a' }, 'a', 'b', 'split-1', 'vertical')
expect(normalizeSplitSizes(2, [1, 3])).toEqual([25, 75])
expect(updateSplitSizes(split, 'split-1', [30, 70])).toMatchObject({ sizes: [30, 70] })
expect(updateSplitSizes(split, 'split-1', [100])).toEqual(split)
})
})
describe('canvas pane registry', () => {
test('clones view state without cloning selection or transient interaction state', () => {
const state = createDefaultEditorState('page')
state.selectedIds = new Set(['selected'])
state.hoveredNodeId = 'hovered'
const registry = createCanvasPaneRegistry(state)
const first = registry.getActivePane()
const second = registry.splitPane(first.id, 'horizontal')
expect(second?.currentPageId).toBe('page')
expect(second?.selectedIds.size).toBe(0)
expect(second?.hoveredNodeId).toBeNull()
expect(registry.visiblePaneCount.value).toBe(2)
expect(state.selectedIds.size).toBe(0)
state.panX = 120
expect(registry.setActivePane(first.id)).toBe(true)
expect(second?.panX).toBe(120)
expect(state.selectedIds).toEqual(new Set(['selected']))
expect(state.panX).toBe(0)
})
test('refuses the last close and enforces the pane cap', () => {
const registry = createCanvasPaneRegistry(createDefaultEditorState('page'))
expect(registry.closePane(registry.activePaneId.value)).toBe(false)
while (registry.visiblePaneCount.value < MAX_VISIBLE_CANVAS_PANES) {
expect(registry.splitPane(registry.activePaneId.value, 'horizontal')).not.toBeNull()
}
expect(registry.splitPane(registry.activePaneId.value, 'vertical')).toBeNull()
})
})