import { existsSync, readdirSync, readFileSync } from 'node:fs' import { join } from 'node:path' import { describe, expect, it } from 'vitest' const COMPONENTS_DIR = 'src/uix/eidos/components' const ROOT_FILE_EXCEPTIONS = new Set(['svg']) const COMPONENT_SOURCE_FILE = /\.(css|svelte|ts)$/ interface NamespaceContract { component: string properties: readonly string[] assignments: readonly string[] } function stripComments(source: string): string { return source.replace(/\/\*[\s\S]*?\*\//g, '').replace(/(^|[^:])\/\/.*$/gm, '$1') } function readComponentDirs(): readonly string[] { return readdirSync(COMPONENTS_DIR, { withFileTypes: true }) .filter((entry) => entry.isDirectory()) .map((entry) => entry.name) .sort() } function readComponentSourceFiles(dir = COMPONENTS_DIR): readonly { path: string; source: string }[] { return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => { const path = join(dir, entry.name) if (entry.isDirectory()) return readComponentSourceFiles(path) if (!entry.isFile() || !COMPONENT_SOURCE_FILE.test(entry.name)) return [] return [{ path, source: stripComments(readFileSync(path, 'utf8')) }] }) } function readIndexSource(component: string): string { return stripComments(readFileSync(join(COMPONENTS_DIR, component, 'index.ts'), 'utf8')) } function readNamespaceContract(source: string): NamespaceContract | undefined { const match = /type\s+([A-Z][A-Za-z0-9]+)Namespace\s*=\s*typeof\s+\w+\s*&\s*\{([\s\S]*?)\n\}/.exec( source ) if (!match) return undefined const component = match[1] const body = match[2] const properties = [...body.matchAll(/^\s*([A-Z][A-Za-z0-9]+)\s*:/gm)].map((m) => m[1]) const assignments = [ ...source.matchAll(new RegExp(`\\b${component}\\.([A-Z][A-Za-z0-9]+)\\s*=`, 'g')) ].map((m) => m[1]) return { component, properties, assignments } } describe('Eidos component API contract', () => { it('keeps component barrels on the disciplined option C surface', () => { const violations: string[] = [] for (const component of readComponentDirs()) { const source = readIndexSource(component) if (/\bObject\.assign\s*\(/.test(source)) { violations.push(`${component}: Object.assign`) } if (/export\s+(?:type\s+)?\{[^}]*\bProvider\b[^}]*\}/s.test(source)) { violations.push(`${component}: public Provider export`) } if (/from\s+['"]\.\/.*provider\.svelte['"]/.test(source)) { violations.push(`${component}: provider component import`) } } expect(violations).toEqual([]) }) it('keeps removed flat API markers out of component source', () => { const violations = readComponentSourceFiles() .filter(({ source }) => /\bdata-[a-z0-9-]+-flat\b/.test(source)) .map(({ path }) => path) expect(violations).toEqual([]) }) it('keeps component source free of Soma-prefixed runtime contracts', () => { const violations = readComponentSourceFiles() .filter(({ source }) => /\bdata-soma-|--soma-/.test(source)) .map(({ path }) => path) expect(violations).toEqual([]) }) it('keeps every public Eidos component rooted at {component}.svelte', () => { const missingRoots = readComponentDirs() .filter((component) => !ROOT_FILE_EXCEPTIONS.has(component)) .filter((component) => !existsSync(join(COMPONENTS_DIR, component, `${component}.svelte`))) expect(missingRoots).toEqual([]) }) it('keeps namespace type members and explicit assignments in sync', () => { const violations: string[] = [] for (const component of readComponentDirs()) { const contract = readNamespaceContract(readIndexSource(component)) if (!contract) continue const declared = new Set(contract.properties) const assigned = new Set(contract.assignments) for (const property of declared) { if (!assigned.has(property)) { violations.push(`${component}: ${contract.component}.${property} declared but not assigned`) } } for (const property of assigned) { if (!declared.has(property)) { violations.push(`${component}: ${contract.component}.${property} assigned but not declared`) } } } expect(violations).toEqual([]) }) it('keeps component wrappers free of legacy lifecycle and scoped styles', () => { const violations: string[] = [] for (const component of readComponentDirs()) { const dir = join(COMPONENTS_DIR, component) for (const entry of readdirSync(dir, { withFileTypes: true })) { if (!entry.isFile() || !entry.name.endsWith('.svelte')) continue const path = join(dir, entry.name) const source = readFileSync(path, 'utf8') if (/\bonMount\b/.test(source)) violations.push(`${component}/${entry.name}: onMount`) if (/)/.test(source)) violations.push(`${component}/${entry.name}: