|
|
|
|
@ -5,6 +5,12 @@ import { describe, expect, it } from 'vitest'
|
|
|
|
|
const COMPONENTS_DIR = 'src/uix/eidos/components'
|
|
|
|
|
const ROOT_FILE_EXCEPTIONS = new Set(['svg'])
|
|
|
|
|
|
|
|
|
|
interface NamespaceContract {
|
|
|
|
|
component: string
|
|
|
|
|
properties: readonly string[]
|
|
|
|
|
assignments: readonly string[]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function stripComments(source: string): string {
|
|
|
|
|
return source.replace(/\/\*[\s\S]*?\*\//g, '').replace(/(^|[^:])\/\/.*$/gm, '$1')
|
|
|
|
|
}
|
|
|
|
|
@ -20,6 +26,22 @@ 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+Root\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[] = []
|
|
|
|
|
@ -48,4 +70,28 @@ describe('Eidos component API contract', () => {
|
|
|
|
|
|
|
|
|
|
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([])
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|