import { describe, expect, it } from 'vitest'; import * as registryModule from '../src/data/machineRegistry'; type MachineLike = Record; type RegistryCandidate = { label: string; machines: MachineLike[]; }; const STRING_VALUE_KEYS = [ 'label', 'name', 'title', 'displayName', 'id', 'slug', 'value', 'level', 'rank', ] as const; function isRecord(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value); } function getStringFromUnknown(value: unknown): string { if (typeof value === 'string' || typeof value === 'number') { return String(value).trim(); } if (Array.isArray(value)) { return value.map(getStringFromUnknown).find(Boolean) ?? ''; } if (isRecord(value)) { for (const key of STRING_VALUE_KEYS) { const candidate = getStringFromUnknown(value[key]); if (candidate) { return candidate; } } } return ''; } function getStringField(machine: MachineLike, keys: readonly string[]): string { for (const key of keys) { const candidate = getStringFromUnknown(machine[key]); if (candidate) { return candidate; } } return ''; } function valueToStringArray(value: unknown): string[] { if (typeof value === 'string') { return value .split(',') .map((entry) => entry.trim()) .filter(Boolean); } if (typeof value === 'number') { return [String(value)]; } if (Array.isArray(value)) { return value.map(getStringFromUnknown).filter(Boolean); } if (value instanceof Set) { return Array.from(value).map(getStringFromUnknown).filter(Boolean); } if (isRecord(value)) { const candidate = getStringFromUnknown(value); return candidate ? [candidate] : []; } return []; } function getArrayField(machine: MachineLike, keys: readonly string[]): string[] { for (const key of keys) { const values = valueToStringArray(machine[key]); if (values.length > 0) { return values; } } return []; } function normaliseLabel(value: string): string { return value.trim().toLowerCase(); } function getRouteKey(machine: MachineLike): string { return getStringField(machine, ['slug', 'id', 'routeSlug', 'routeId', 'key']); } function getMachineName(machine: MachineLike): string { return getStringField(machine, ['name', 'title', 'label', 'displayName']); } function getMachineSummary(machine: MachineLike): string { return getStringField(machine, ['summary', 'shortDescription', 'description', 'overview']); } function isMachineLike(value: unknown): value is MachineLike { if (!isRecord(value)) { return false; } const hasName = Boolean(getMachineName(value)); const hasCategory = Boolean( getStringField(value, ['category', 'categoryId', 'categoryName', 'categorySlug', 'type']), ); const hasDifficulty = Boolean( getStringField(value, ['difficulty', 'difficultyLabel', 'difficultyLevel', 'complexity']), ); const hasDescription = Boolean(getMachineSummary(value)); const hasTags = getArrayField(value, ['tags', 'keywords', 'labels']).length > 0; return hasName && hasCategory && (hasDifficulty || hasDescription || hasTags); } function collectCandidates( label: string, value: unknown, candidates: RegistryCandidate[], visited = new WeakSet(), depth = 0, ): void { if (depth > 4) { return; } if (Array.isArray(value)) { const machineItems = value.filter(isMachineLike); if (machineItems.length >= 2 && machineItems.length >= value.length * 0.6) { candidates.push({ label, machines: machineItems }); } for (const [index, entry] of value.entries()) { collectCandidates(`${label}[${index}]`, entry, candidates, visited, depth + 1); } return; } if (!isRecord(value) || visited.has(value)) { return; } visited.add(value); const objectValues = Object.values(value); const recordValues = objectValues.filter(isRecord); const machineValues = objectValues.filter(isMachineLike); if (machineValues.length >= 2 && machineValues.length >= Math.max(2, recordValues.length * 0.5)) { candidates.push({ label, machines: machineValues }); } for (const [nestedKey, nestedValue] of Object.entries(value)) { collectCandidates(`${label}.${nestedKey}`, nestedValue, candidates, visited, depth + 1); } } function dedupeMachines(machines: MachineLike[]): MachineLike[] { const seen = new Set(); const deduped: MachineLike[] = []; for (const [index, machine] of machines.entries()) { const identity = normaliseLabel(getRouteKey(machine) || getMachineName(machine) || `#${index}`); if (!identity || seen.has(identity)) { continue; } seen.add(identity); deduped.push(machine); } return deduped; } function extractMachineRegistry(): MachineLike[] { const candidates: RegistryCandidate[] = []; for (const [exportName, exportedValue] of Object.entries(registryModule)) { collectCandidates(exportName, exportedValue, candidates); } const mergedCandidates = dedupeMachines(candidates.flatMap((candidate) => candidate.machines)); const largestCandidate = candidates.sort((left, right) => right.machines.length - left.machines.length)[0]; return mergedCandidates.length >= (largestCandidate?.machines.length ?? 0) ? mergedCandidates : largestCandidate?.machines ?? []; } function displayName(machine: MachineLike, index: number): string { return getMachineName(machine) || getRouteKey(machine) || `machine #${index + 1}`; } const machines = extractMachineRegistry(); const machineCases = machines.map((machine, index) => ({ label: displayName(machine, index), machine, })); describe('machine registry contract', () => { it('publishes a substantial core-and-stretch catalogue', () => { expect(machines.length).toBeGreaterThanOrEqual(12); }); it('uses unique route-safe ids or slugs for deep links', () => { const routeKeys = machines.map(getRouteKey); for (const routeKey of routeKeys) { expect(routeKey).not.toBe(''); expect(routeKey).toMatch(/^[a-z0-9]+(?:[-_][a-z0-9]+)*$/); } expect(new Set(routeKeys).size).toBe(routeKeys.length); }); it.each(machineCases)('$label has complete card metadata', ({ machine }) => { const name = getMachineName(machine); const category = getStringField(machine, [ 'category', 'categoryId', 'categoryName', 'categorySlug', 'type', ]); const difficulty = getStringField(machine, [ 'difficulty', 'difficultyLabel', 'difficultyLevel', 'complexity', ]); const summary = getMachineSummary(machine); const tags = getArrayField(machine, ['tags', 'keywords', 'labels']); const uniqueTags = new Set(tags.map(normaliseLabel)); expect(name.length).toBeGreaterThanOrEqual(4); expect(category.length).toBeGreaterThanOrEqual(3); expect(difficulty.length).toBeGreaterThanOrEqual(3); expect(summary.length).toBeGreaterThanOrEqual(40); expect(tags.length).toBeGreaterThanOrEqual(2); expect(uniqueTags.size).toBe(tags.length); }); it('spans the major mechanical categories and multiple learning levels', () => { const categories = new Set( machines .map((machine) => getStringField(machine, ['category', 'categoryId', 'categoryName', 'categorySlug', 'type']), ) .filter(Boolean) .map(normaliseLabel), ); const difficulties = new Set( machines .map((machine) => getStringField(machine, [ 'difficulty', 'difficultyLabel', 'difficultyLevel', 'complexity', ]), ) .filter(Boolean) .map(normaliseLabel), ); expect(categories.size).toBeGreaterThanOrEqual(4); expect(difficulties.size).toBeGreaterThanOrEqual(3); }); });