diff --git a/src/uix/contracts.test.ts b/src/uix/contracts.test.ts index 21e8d573d..47f6a66c1 100644 --- a/src/uix/contracts.test.ts +++ b/src/uix/contracts.test.ts @@ -112,6 +112,22 @@ function collectDataAttrs(source: string): Set { return attrs; } +function toPascalName(kebab: string): string { + return kebab + .split('-') + .map((segment) => `${segment.charAt(0).toUpperCase()}${segment.slice(1)}`) + .join(''); +} + +function collectPublicSomaComponentDirs(): string[] { + const root = join(HERE, 'soma', 'components'); + return readdirSync(root, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => entry.name) + .filter((name) => name !== 'internal' && existsSync(join(root, name, 'index.ts'))) + .sort(); +} + function collectComponentLangPathViolations(source: string, file: string): string[] { const clean = stripComments(source); const violations: string[] = []; @@ -276,6 +292,20 @@ describe('UIX layer contracts', () => { expect(violations).toEqual([]); }); + it('guards Soma component barrel exports all public component modules', () => { + const indexSource = readFileSync(join(HERE, 'soma', 'components', 'index.ts'), 'utf8'); + const missingExports = collectPublicSomaComponentDirs() + .map((dir) => ({ + dir, + name: toPascalName(dir), + exportRe: new RegExp(`export \\* as ${toPascalName(dir)} from ['"]\\./${dir}['"];`) + })) + .filter(({ exportRe }) => !exportRe.test(indexSource)) + .map(({ dir, name }) => `${dir} -> ${name}`); + + expect(missingExports).toEqual([]); + }); + it('guards Soma from direct mutable DOM writes', () => { const violations = grepSources( join(HERE, 'soma'), diff --git a/src/uix/soma/components/index.ts b/src/uix/soma/components/index.ts index 0a0a29b6d..26d5bcf70 100644 --- a/src/uix/soma/components/index.ts +++ b/src/uix/soma/components/index.ts @@ -18,6 +18,7 @@ export * as DateRangeField from './date-range-field'; export * as DateRangePicker from './date-range-picker'; export * as Dialog from './dialog'; export * as DragDrop from './drag-drop'; +export * as Drawer from './drawer'; export * as DropdownMenu from './dropdown-menu'; export * as Editable from './editable'; export * as Feed from './feed';