import { afterEach, describe, expect, it } from 'vitest' import { act } from 'react' import { byId, click, setup } from './harness' import type { Harness } from './harness' import { attachDialog, registerEditor, registerFieldEditor } from '../src/registry' import { diffData } from '../src/diff' import { allowedChildren, fieldKind } from '../src/schema' let h: Harness afterEach(() => h?.cleanup()) describe('extension points', () => { it('registerEditor adds a toolbar button for matching types only, and disposes cleanly', async () => { h = await setup() const off = registerEditor({ id: 't-heading', label: 'Heading tools', icon: 'H', match: 'heading', component: () =>

heading dialog

}) await act(async () => void click(byId('h1'))) await h.settle() expect(h.shadow.querySelector('[aria-label="Heading tools"]')).not.toBeNull() await act(async () => void click(byId('t1'))) await h.settle() expect(h.shadow.querySelector('[aria-label="Heading tools"]')).toBeNull() off() await act(async () => void click(byId('h1'))) await h.settle() expect(h.shadow.querySelector('[aria-label="Heading tools"]')).toBeNull() }) it('attachDialog attaches by selector and opens with the live node + schema', async () => { h = await setup() let seen: any const off = attachDialog('.prose', ({ node, schema }) => ((seen = { id: node.id, type: node.dataType, props: Object.keys(schema.properties) }),

custom body editor

), { id: 'body-ed', label: 'Body' }) await act(async () => void click(byId('t1'))) await h.settle() await h.press('Body') expect(h.shadow.querySelector('.cms-dialog')!.textContent).toContain('custom body editor') expect(seen).toEqual({ id: 't1', type: 'text', props: ['body'] }) off() }) it('registerEditor with the same id replaces a built-in', async () => { h = await setup() registerEditor({ id: 'properties', label: 'Properties', match: '*', priority: 100, onDoubleClick: true, component: () =>

my properties

}) await act(async () => void click(byId('t1'))) await h.press('Properties') expect(h.shadow.querySelector('.cms-dialog')!.textContent).toContain('my properties') }) it('field editors override form widgets by schema', async () => { h = await setup() const off = registerFieldEditor({ id: 'tone-swatches', match: (s, path) => path === 'tone', component: ({ value, onChange }) => (
{['light', 'dark'].map((t) => ( ))}
), }) await act(async () => void click(byId('s1'))) await h.press('Properties') expect(h.shadow.querySelector('.swatches')).not.toBeNull() off() }) }) describe('helpers', () => { it('diffData: changed → value, removed → null, equal → omitted', () => { expect(diffData({ a: 1, b: 2, c: { x: 1 } }, { a: 1, b: 3, c: { x: 1 } })).toEqual({ b: 3 }) expect(diffData({ a: 1, b: 2 }, { a: 1 })).toEqual({ b: null }) expect(diffData({}, { n: { x: 1 } })).toEqual({ n: { x: 1 } }) }) it('allowedChildren honours x-graph and "*"', () => { expect(allowedChildren({ 'x-graph': { children: ['a'] } }, ['a', 'b'])).toEqual(['a']) expect(allowedChildren({ 'x-graph': { children: ['*'] } }, ['a', 'b'])).toEqual(['a', 'b']) expect(allowedChildren({}, ['a'])).toEqual([]) }) it('fieldKind picks widgets', () => { expect(fieldKind({ type: 'string', format: 'markdown' })).toBe('markdown') expect(fieldKind({ type: 'string', enum: ['a'] })).toBe('enum') expect(fieldKind({ type: 'array' })).toBe('json') expect(fieldKind({ type: ['string', 'null'] })).toBe('string') }) })