diff --git a/src/index.tsx b/src/index.tsx index 884c5f3..132e1dd 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -216,4 +216,5 @@ export function define(nameOrDef: string | TagDef, defIfNamed?: TagDef) { } } +// shortcut so you only have to import one thing, if you want define.Styles = Styles \ No newline at end of file diff --git a/src/tests/index.test.tsx b/src/tests/index.test.tsx index 9a130c6..486c098 100644 --- a/src/tests/index.test.tsx +++ b/src/tests/index.test.tsx @@ -744,6 +744,147 @@ describe('variants on parts via props', () => { }) }) +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({}) @@ -777,13 +918,12 @@ describe('edge cases', () => { expect(html).toContain('size-small') }) - test('does not duplicate styles when registered multiple times', () => { - define('NoDuplicate', { width: 100 }) - define('NoDuplicate', { width: 200 }) + test('throws error when defining duplicate component names', () => { + define('NoDuplicateTest', { width: 100 }) - const styles = parseCSS(getStylesCSS()) - // Should keep first value (??= operator) - expect(styles['NoDuplicate']?.['width']).toBe('100px') + expect(() => { + define('NoDuplicateTest', { width: 200 }) + }).toThrow('NoDuplicateTest is already defined! Must use unique names.') }) test('handles complex nested structures', () => {