import { describe, expect, it } from 'vitest'; import { compileBdcBlock, compileBdcToWire } from './compile-bdc-to-wire'; import type { BdcElement } from './jsx-runtime'; import { compileBdcToWire as viaIndex } from './index'; function screen(props: Record, children: BdcElement[]): BdcElement { return { type: 'screen', props, children }; } describe('compileBdcToWire', () => { it('compiles a minimal screen tree', () => { const wire = compileBdcToWire( screen({ id: 'demo.home', data: { n: 1 } }, [ { type: 'text', props: { text: 'Hello' }, children: [], }, ]), ); expect(wire.schema_version).toBe(1); expect(wire.screen_id).toBe('demo.home'); expect(wire.data).toEqual({ n: 1 }); expect(wire.blocks[0]).toMatchObject({ type: 'text', props: { text: 'Hello' }, }); }); it('maps camelCase props to snake_case', () => { const wire = compileBdcToWire( screen({ id: 'demo.home' }, [ { type: 'button', props: { actionId: 'go', label: 'Go' }, children: [], }, ]), ); expect(wire.blocks[0].props).toMatchObject({ action_id: 'go', label: 'Go' }); }); it('rejects non-screen roots', () => { expect(() => compileBdcToWire({ type: 'stack', props: {}, children: [] }), ).toThrow(/bdc_root_must_be_screen/); }); it('derives button/text labels from children and maps style keys', () => { const wire = compileBdcToWire( screen({ id: 'demo.home' }, [ { type: 'button', props: { actionId: 'go', style: { paddingTop: 8, marginBottom: 4 } }, children: [ { type: '_text_node', props: { value: ' Press ' }, children: [] }, { type: 'text', props: { text: 'me' }, children: [] }, ], }, { type: 'text', props: {}, children: [{ type: '_text_node', props: { value: 'Body' }, children: [] }], }, { type: '_fragment', props: {}, children: [], }, { type: 'column', props: { id: 'col-1', children: 'ignored' }, children: [ { type: '_text_node', props: { value: 'skip' }, children: [] }, { type: 'spacer', props: {}, children: [] }, ], }, ]), ); expect(wire.blocks).toHaveLength(3); expect(wire.blocks[0]?.props).toMatchObject({ action_id: 'go', label: 'Press me', style: { padding_top: 8, margin_bottom: 4 }, }); expect(wire.blocks[1]?.props?.text).toBe('Body'); expect(wire.blocks[2]?.id).toBe('col-1'); expect(wire.blocks[2]?.children).toHaveLength(1); expect(wire.blocks[2]?.children?.[0]?.type).toBe('spacer'); }); it('compiles header slot blocks and defaults meta/actions/data', () => { const slot: BdcElement = { type: 'chip_row', props: { id: 'slot' }, children: [], }; const wire = compileBdcToWire( screen( { id: 'demo.home', header: { title: 'Home', headerSearch: true, slotBlocks: [{ type: 'ignored' }], headerSlot: 'ignored', }, headerSlot: slot, }, [], ), ); expect(wire.header).toMatchObject({ title: 'Home', header_search: true, }); expect(wire.header?.slot_blocks?.[0]).toMatchObject({ id: 'slot', type: 'chip_row' }); expect(wire.data).toEqual({}); expect(wire.actions).toEqual({}); expect(wire.meta).toMatchObject({ cache_ttl_ms: 30_000, offline_policy: 'cache_only' }); }); it('passes meta.chrome through to wire unchanged (snake_case)', () => { const wire = compileBdcToWire( screen( { id: 'discover.home', meta: { cache_ttl_ms: 45_000, chrome: { full_bleed_scroll: true, header_slot: true, pull_refresh: true, surface: 'discover', }, }, }, [], ), ); expect(wire.meta).toMatchObject({ cache_ttl_ms: 45_000, chrome: { full_bleed_scroll: true, header_slot: true, pull_refresh: true, surface: 'discover', }, }); }); it('accepts headerSlot arrays and maps header camelCase', () => { const wire = compileBdcToWire( screen( { id: 'demo.home', header: { brandMode: true }, headerSlot: [ { type: 'chip', props: {}, children: [] }, { type: 'chip', props: {}, children: [] }, ], }, [], ), ); expect(wire.header).toMatchObject({ brand_mode: true }); expect(wire.header?.slot_blocks).toHaveLength(2); }); it('compileBdcBlock and index re-export work', () => { const block = compileBdcBlock({ type: '_text_node', props: { value: 'x' }, children: [], }); expect(block).toEqual({ id: '0', type: 'text', props: { text: 'x' } }); expect(viaIndex(screen({ id: 'i' }, [])).screen_id).toBe('i'); }); it('flattens _fragment children instead of dropping them', () => { const wire = compileBdcToWire( screen({ id: 'demo.fragment' }, [ { type: 'column', props: {}, children: [ { type: '_fragment', props: {}, children: [ { type: 'text', props: { text: 'a' }, children: [] }, { type: 'text', props: { text: 'b' }, children: [] }, ], }, ], }, ]), ); expect(wire.blocks[0]?.children?.map((c) => c.props?.text)).toEqual(['a', 'b']); }); });