import { describe, test, expect } from 'bun:test' import { define } from '../index' import { renderToString, getStylesCSS, parseCSS } from './test_helpers' describe('define - basic functionality', () => { test('creates a component function', () => { const Component = define({ display: 'flex' }) expect(typeof Component).toBe('function') }) test('component returns a JSX element', () => { const Component = define({ display: 'flex' }) const result = Component({}) expect(result).toBeDefined() expect(typeof result).toBe('object') }) test('applies className to rendered element', () => { const Component = define('MyComponent', { display: 'flex' }) const html = renderToString(Component({})) expect(html).toContain('class="MyComponent"') }) test('generates unique anonymous component names', () => { const Component1 = define({ display: 'flex' }) const Component2 = define({ display: 'block' }) const html1 = renderToString(Component1({})) const html2 = renderToString(Component2({})) // Should have different auto-generated names expect(html1).toMatch(/class="Div\d*"/) expect(html2).toMatch(/class="Div\d*"/) expect(html1).not.toBe(html2) }) test('renders default div element', () => { const Component = define('DivTest', { display: 'flex' }) const html = renderToString(Component({})) expect(html).toContain('') }) test('respects custom base element', () => { const Component = define('ButtonTest', { base: 'button', color: 'blue' }) const html = renderToString(Component({})) expect(html).toContain('') }) test('passes children through to component', () => { const Component = define({}) const html = renderToString(Component({ children: 'Hello World' })) expect(html).toContain('Hello World') }) test('passes additional props through to component', () => { const Component = define({}) const html = renderToString(Component({ id: 'test-id', 'data-test': 'value' })) expect(html).toContain('id="test-id"') expect(html).toContain('data-test="value"') }) }) describe('CSS generation - camelCase to kebab-case', () => { test('converts camelCase properties to kebab-case', () => { define('CamelTest', { flexDirection: 'column', alignItems: 'center', justifyContent: 'space-between' }) const css = getStylesCSS() expect(css).toContain('flex-direction: column') expect(css).toContain('align-items: center') expect(css).toContain('justify-content: space-between') }) test('handles consecutive capital letters', () => { define('ConsecutiveTest', { backgroundColor: 'red', borderRadius: 5 }) const css = getStylesCSS() expect(css).toContain('background-color: red') expect(css).toContain('border-radius: 5px') }) }) describe('CSS generation - numeric values and units', () => { test('adds px unit to numeric layout values', () => { define('NumericTest', { width: 100, height: 200, padding: 16, margin: 8 }) const styles = parseCSS(getStylesCSS()) expect(styles['NumericTest']?.['width']).toBe('100px') expect(styles['NumericTest']?.['height']).toBe('200px') expect(styles['NumericTest']?.['padding']).toBe('16px') expect(styles['NumericTest']?.['margin']).toBe('8px') }) test('preserves string values without adding px', () => { define('StringTest', { width: '100%', height: 'auto', margin: '0 auto', padding: '1rem' }) const styles = parseCSS(getStylesCSS()) expect(styles['StringTest']?.['width']).toBe('100%') expect(styles['StringTest']?.['height']).toBe('auto') expect(styles['StringTest']?.['margin']).toBe('0 auto') expect(styles['StringTest']?.['padding']).toBe('1rem') }) test('does not add px to unitless properties', () => { define('UnitlessTest', { flex: 1, flexGrow: 2, flexShrink: 1, zIndex: 10, order: 3, opacity: 0.5, fontWeight: 700, lineHeight: 1.5 }) const styles = parseCSS(getStylesCSS()) expect(styles['UnitlessTest']?.['flex']).toBe('1') expect(styles['UnitlessTest']?.['flex-grow']).toBe('2') expect(styles['UnitlessTest']?.['flex-shrink']).toBe('1') expect(styles['UnitlessTest']?.['z-index']).toBe('10') expect(styles['UnitlessTest']?.['order']).toBe('3') expect(styles['UnitlessTest']?.['opacity']).toBe('0.5') expect(styles['UnitlessTest']?.['font-weight']).toBe('700') expect(styles['UnitlessTest']?.['line-height']).toBe('1.5') }) test('handles numeric zero values correctly', () => { define('ZeroTest', { margin: 0, padding: 0, zIndex: 0, opacity: 0 }) const styles = parseCSS(getStylesCSS()) expect(styles['ZeroTest']?.['margin']).toBe('0px') expect(styles['ZeroTest']?.['padding']).toBe('0px') expect(styles['ZeroTest']?.['z-index']).toBe('0') expect(styles['ZeroTest']?.['opacity']).toBe('0') }) }) describe('CSS generation - layout and look', () => { test('generates CSS for layout properties', () => { define('LayoutTest', { display: 'flex', flexDirection: 'column', gap: 16, padding: 20 }) const css = getStylesCSS() expect(css).toContain('.LayoutTest') expect(css).toContain('display: flex') expect(css).toContain('flex-direction: column') expect(css).toContain('gap: 16px') expect(css).toContain('padding: 20px') }) test('generates CSS for look properties', () => { define('LookTest', { color: 'blue', backgroundColor: 'white', fontSize: 16, fontWeight: 600 }) const css = getStylesCSS() expect(css).toContain('.LookTest') expect(css).toContain('color: blue') expect(css).toContain('background-color: white') expect(css).toContain('font-size: 16px') expect(css).toContain('font-weight: 600') }) test('combines layout and look properties', () => { define('CombinedTest', { display: 'flex', padding: 16, color: 'blue', backgroundColor: 'white' }) const styles = parseCSS(getStylesCSS()) const combined = styles['CombinedTest']! expect(combined['display']).toBe('flex') expect(combined['padding']).toBe('16px') expect(combined['color']).toBe('blue') expect(combined['background-color']).toBe('white') }) }) describe('CSS generation - parts', () => { test('generates separate CSS for each part', () => { define('PartTest', { display: 'flex', parts: { Header: { base: 'header', color: 'red', fontSize: 20 }, Body: { base: 'main', padding: 20 }, Footer: { base: 'footer', fontSize: 12 } } }) const styles = parseCSS(getStylesCSS()) expect(styles['PartTest_Header']?.['color']).toBe('red') expect(styles['PartTest_Header']?.['font-size']).toBe('20px') expect(styles['PartTest_Body']?.['padding']).toBe('20px') expect(styles['PartTest_Footer']?.['font-size']).toBe('12px') }) test('part className format is ComponentName_PartName', () => { define('ComponentWithParts', { parts: { MyPart: { color: 'green' } } }) const css = getStylesCSS() expect(css).toContain('.ComponentWithParts_MyPart') }) }) describe('components with parts', () => { test('creates part components accessible through render function', () => { let capturedParts: any const Component = define('PartRenderTest', { parts: { Header: { base: 'header' }, Body: { base: 'main' } }, render: ({ props, parts }) => { capturedParts = parts return {props.children} } }) Component({}) expect(typeof capturedParts.Header).toBe('function') expect(typeof capturedParts.Body).toBe('function') expect(typeof capturedParts.Root).toBe('function') }) test('part components render with correct className', () => { const Component = define('PartClassTest', { parts: { Header: { base: 'header' } }, render: ({ props, parts }) => { return Header Content } }) const html = renderToString(Component({})) expect(html).toContain('class="PartClassTest_Header"') expect(html).toContain(' { const Component = define('PartBaseTest', { parts: { Header: { base: 'header' }, Main: { base: 'main' }, Footer: { base: 'footer' } }, render: ({ props, parts }) => { return (
Header Main Footer
) } }) const html = renderToString(Component({})) expect(html).toContain(' { test('applies boolean variant class when true', () => { const Component = define('BoolVariant', { display: 'flex', variants: { primary: { color: 'blue' } } }) const html = renderToString(Component({ primary: true })) // Boolean variants add just the variant name as a class expect(html).toContain('class="BoolVariant primary"') }) test('does not apply boolean variant class when false or absent', () => { const Component = define('BoolVariantFalse', { variants: { active: { backgroundColor: 'green' } } }) const htmlFalse = renderToString(Component({ active: false })) const htmlAbsent = renderToString(Component({})) // When false or absent, variant class should not be added (check the class attribute) expect(htmlFalse).toContain('class="BoolVariantFalse"') expect(htmlFalse).not.toContain('class="BoolVariantFalse active') expect(htmlAbsent).toContain('class="BoolVariantFalse"') expect(htmlAbsent).not.toContain(' active') }) test('generates CSS for component with boolean variant', () => { define('BoolVariantCSS', { display: 'block', variants: { active: { backgroundColor: 'green' } } }) const css = getStylesCSS() expect(css).toContain('.BoolVariantCSS') expect(css).toContain('display: block') }) }) describe('variants - string/enum variants', () => { test('applies string variant class', () => { const Component = define('StringVariant', { variants: { size: { small: { padding: 8 }, medium: { padding: 16 }, large: { padding: 24 } } } }) const htmlSmall = renderToString(Component({ size: 'small' })) const htmlLarge = renderToString(Component({ size: 'large' })) expect(htmlSmall).toContain('class="StringVariant size-small"') expect(htmlLarge).toContain('class="StringVariant size-large"') }) test('generates CSS for each variant option', () => { define('ColorVariant', { variants: { color: { red: { color: 'red', backgroundColor: '#ffeeee' }, blue: { color: 'blue', backgroundColor: '#eeeeff' }, green: { color: 'green', backgroundColor: '#eeffee' } } } }) const styles = parseCSS(getStylesCSS()) expect(styles['ColorVariant.color-red']?.['color']).toBe('red') expect(styles['ColorVariant.color-blue']?.['color']).toBe('blue') expect(styles['ColorVariant.color-green']?.['color']).toBe('green') }) test('applies multiple variant classes', () => { const Component = define('MultiVariant', { variants: { size: { small: { padding: 8 }, large: { padding: 24 } }, color: { red: { color: 'red' }, blue: { color: 'blue' } } } }) const html = renderToString(Component({ size: 'small', color: 'blue' })) expect(html).toContain('size-small') expect(html).toContain('color-blue') }) test('ignores undefined variant values', () => { const Component = define('UndefinedVariant', { variants: { size: { small: { padding: 8 } } } }) const html = renderToString(Component({ size: 'nonexistent' })) expect(html).toContain('class="UndefinedVariant"') expect(html).not.toContain('size-nonexistent') }) test('ignores non-variant props', () => { const Component = define('NonVariantProps', { variants: { size: { small: { padding: 8 } } } }) const html = renderToString(Component({ size: 'small', randomProp: 'value', id: 'test' })) expect(html).toContain('size-small') expect(html).toContain('id="test"') expect(html).toContain('randomProp="value"') }) }) describe('variants with parts', () => { test('generates CSS for variant parts', () => { define('VariantParts', { parts: { Header: { base: 'header' } }, variants: { theme: { dark: { parts: { Header: { color: 'white', backgroundColor: 'black' } } }, light: { parts: { Header: { color: 'black', backgroundColor: 'white' } } } } } }) const styles = parseCSS(getStylesCSS()) expect(styles['VariantParts_Header.theme-dark']?.['color']).toBe('white') expect(styles['VariantParts_Header.theme-dark']?.['background-color']).toBe('black') expect(styles['VariantParts_Header.theme-light']?.['color']).toBe('black') expect(styles['VariantParts_Header.theme-light']?.['background-color']).toBe('white') }) test('part elements include variant classes', () => { const Component = define('VariantPartClass', { parts: { Header: { base: 'header' } }, variants: { size: { large: { padding: 20 } } }, render: ({ props, parts }) => { return Header Content } }) const html = renderToString(Component({ size: 'large' })) expect(html).toContain('VariantPartClass_Header') expect(html).toContain('size-large') }) }) describe('custom render function', () => { test('uses custom render when provided', () => { const Component = define('CustomRender', { display: 'flex', render: ({ props, parts }) => { return
{props.children}
} }) const html = renderToString(Component({ children: 'Content' })) expect(html).toContain('class="custom-wrapper"') expect(html).toContain('Content') }) test('custom render receives props', () => { let receivedProps: any const Component = define({ render: ({ props, parts }) => { receivedProps = props return {props.children} } }) Component({ testProp: 'value', children: 'Test' }) expect(receivedProps.testProp).toBe('value') expect(receivedProps.children).toBe('Test') }) test('custom render can compose parts', () => { const Component = define('ComposeParts', { parts: { Header: { base: 'header' }, Body: { base: 'main' }, Footer: { base: 'footer' } }, render: ({ props, parts }) => { return ( Header {props.children} Footer ) } }) const html = renderToString(Component({ children: 'Main Content' })) expect(html).toContain(' { test('Styles is accessible via define.Styles', () => { expect(typeof define.Styles).toBe('function') }) test('Styles returns a style element', () => { const result = define.Styles() as any expect(result).toBeDefined() expect(result.props.dangerouslySetInnerHTML).toBeDefined() }) test('Styles renders to HTML with CSS', () => { define('StylesComp1', { width: 100 }) const html = renderToString(define.Styles()) expect(html).toContain('') }) test('Styles includes CSS for all registered components', () => { define('StylesComp2', { width: 100 }) define('StylesComp3', { height: 200 }) const css = getStylesCSS() expect(css).toContain('.StylesComp2') expect(css).toContain('width: 100px') expect(css).toContain('.StylesComp3') expect(css).toContain('height: 200px') }) test('Styles includes variant CSS', () => { define('StylesVariant', { variants: { size: { small: { padding: 8 }, large: { padding: 24 } } } }) const css = getStylesCSS() expect(css).toContain('.StylesVariant.size-small') expect(css).toContain('padding: 8px') expect(css).toContain('.StylesVariant.size-large') expect(css).toContain('padding: 24px') }) test('Styles includes part CSS', () => { define('StylesPart', { parts: { Header: { color: 'red' }, Body: { color: 'blue' } } }) const css = getStylesCSS() expect(css).toContain('.StylesPart_Header') expect(css).toContain('color: red') expect(css).toContain('.StylesPart_Body') expect(css).toContain('color: blue') }) }) describe('variants on parts via props', () => { test('applies boolean variant to part when passed as prop', () => { const Component = define('PartVariantBool', { parts: { Tab: { base: 'button', color: 'gray' } }, variants: { active: { parts: { Tab: { color: 'blue' } } } }, render: ({ props, parts }) => { return ( Active Tab Inactive Tab ) } }) const html = renderToString(Component({})) // Should have one tab with active class, one without expect(html).toContain('PartVariantBool_Tab active') // Count occurrences - should have 2 tabs total const tabCount = (html.match(/PartVariantBool_Tab/g) || []).length expect(tabCount).toBe(2) }) test('applies string variant to part when passed as prop', () => { const Component = define('PartVariantString', { parts: { Pill: { base: 'button' } }, variants: { size: { small: { parts: { Pill: { padding: 8 } } }, large: { parts: { Pill: { padding: 24 } } } } }, render: ({ props, parts }) => { return ( Small Large ) } }) const html = renderToString(Component({})) expect(html).toContain('PartVariantString_Pill size-small') expect(html).toContain('PartVariantString_Pill size-large') }) test('does not pass variant props through to HTML', () => { const Component = define('NoVariantLeakage', { parts: { Item: { base: 'div' } }, variants: { active: { parts: { Item: { color: 'blue' } } } }, render: ({ props, parts }) => { return ( Item ) } }) const html = renderToString(Component({})) // Should have the class, but not the attribute expect(html).toContain('class="NoVariantLeakage_Item active"') expect(html).not.toContain('active="true"') expect(html).not.toContain('active="false"') }) test('combines root and part level variants', () => { const Component = define('CombinedVariants', { parts: { NavItem: { base: 'a' } }, variants: { theme: { dark: { backgroundColor: 'black', parts: { NavItem: { color: 'white' } } } }, active: { parts: { NavItem: { fontWeight: 700 } } } }, render: ({ props, parts }) => { return ( Active Link Inactive Link ) } }) const html = renderToString(Component({ theme: 'dark' })) // Root should have theme variant expect(html).toContain('CombinedVariants theme-dark') // Active NavItem should have both theme and active classes expect(html).toContain('CombinedVariants_NavItem theme-dark active') // Inactive NavItem should have only theme class expect(html).toMatch(/CombinedVariants_NavItem theme-dark"[^>]*>Inactive/) }) }) describe('base with attributes', () => { test('extracts element name from base with attributes', () => { const Component = define('InputRadio', { base: 'input[type=radio]', display: 'block' }) const html = renderToString(Component({})) expect(html).toContain(' { const Component = define('InputCheckbox', { base: 'input[type=checkbox]' }) const html = renderToString(Component({})) expect(html).toContain('type="checkbox"') }) test('works without attributes', () => { const Component = define('PlainInput', { base: 'input', padding: 10 }) const html = renderToString(Component({})) expect(html).toContain(' { const Component = define('OverridableInput', { base: 'input[type=text]' }) const html = renderToString(Component({ type: 'email' })) expect(html).toContain('type="email"') }) }) describe('selectors with @ and &', () => { test('generates CSS for selectors with @PartName', () => { define('SelectorTest', { parts: { Input: { base: 'input[type=checkbox]', display: 'none' }, Label: { base: 'label', color: '#666', selectors: { '@Input:checked + &': { color: 'blue' } } } } }) const css = getStylesCSS() expect(css).toContain('.SelectorTest_Input:checked + .SelectorTest_Label') expect(css).toContain('color: blue') }) test('selectors support general sibling combinator ~', () => { define('SiblingTest', { parts: { Trigger: { base: 'input[type=checkbox]' }, Content: { display: 'none', selectors: { '@Trigger:checked ~ &': { display: 'block' } } } } }) const css = getStylesCSS() expect(css).toContain('.SiblingTest_Trigger:checked ~ .SiblingTest_Content') expect(css).toContain('display: block') }) test('selectors can include pseudo-classes on &', () => { define('PseudoTest', { parts: { Radio: { base: 'input[type=radio]', display: 'none' }, Label: { base: 'label', selectors: { '@Radio:checked + &:hover': { background: 'lightblue' } } } } }) const css = getStylesCSS() expect(css).toContain('.PseudoTest_Radio:checked + .PseudoTest_Label:hover') expect(css).toContain('background: lightblue') }) test('selectors work on root level', () => { define('RootSelectorTest', { color: 'black', selectors: { '&:hover': { color: 'blue' } } }) const css = getStylesCSS() expect(css).toContain('.RootSelectorTest:hover') expect(css).toContain('color: blue') }) test('supports multiple selectors', () => { define('MultiSelectorTest', { parts: { Input: { base: 'input[type=checkbox]' }, Label: { selectors: { '@Input:checked + &': { color: 'green' }, '@Input:disabled + &': { opacity: 0.5 } } } } }) const css = getStylesCSS() expect(css).toContain('.MultiSelectorTest_Input:checked + .MultiSelectorTest_Label') expect(css).toContain('color: green') expect(css).toContain('.MultiSelectorTest_Input:disabled + .MultiSelectorTest_Label') expect(css).toContain('opacity: 0.5') }) }) describe('edge cases', () => { test('handles empty definition', () => { const Component = define({}) const html = renderToString(Component({})) expect(html).toBeDefined() expect(html).toMatch(/class="Div\d*"/) }) test('handles definition with only parts', () => { const Component = define({ parts: { Header: { base: 'header' } } }) const result = Component({}) expect(result).toBeDefined() }) test('handles definition with only variants', () => { const Component = define({ variants: { size: { small: { padding: 8 } } } }) const html = renderToString(Component({ size: 'small' })) expect(html).toContain('size-small') }) test('throws error when defining duplicate component names', () => { define('NoDuplicateTest', { width: 100 }) expect(() => { define('NoDuplicateTest', { width: 200 }) }).toThrow('NoDuplicateTest is already defined! Must use unique names.') }) test('handles complex nested structures', () => { define('ComplexNested', { display: 'grid', parts: { Container: { padding: 16 }, Item: { fontSize: 14 } }, variants: { theme: { dark: { backgroundColor: 'black', parts: { Container: { backgroundColor: '#222' }, Item: { color: 'white' } } } } } }) const styles = parseCSS(getStylesCSS()) expect(styles['ComplexNested']?.['display']).toBe('grid') expect(styles['ComplexNested_Container']?.['padding']).toBe('16px') expect(styles['ComplexNested_Item']?.['font-size']).toBe('14px') expect(styles['ComplexNested.theme-dark']?.['background-color']).toBe('black') expect(styles['ComplexNested_Container.theme-dark']?.['background-color']).toBe('#222') expect(styles['ComplexNested_Item.theme-dark']?.['color']).toBe('white') }) test('handles JSX children correctly', () => { const Component = define('ChildrenTest', {}) const html = renderToString(Component({ children: Nested Element })) expect(html).toContain('Nested Element') }) test('handles multiple children', () => { const Component = define('MultiChildren', { render: ({ props, parts }) => { return {props.children} } }) const html = renderToString(Component({ children: [
First
,
Second
] })) expect(html).toContain('First') expect(html).toContain('Second') }) })