diff --git a/docs/todo/13-DECLARATIVE-UI-TODO.md b/docs/todo/13-DECLARATIVE-UI-TODO.md index 6d68ee4a7..b58df9136 100644 --- a/docs/todo/13-DECLARATIVE-UI-TODO.md +++ b/docs/todo/13-DECLARATIVE-UI-TODO.md @@ -1,5 +1,9 @@ # TODO 13 - Declarative UI System +## Testing + +- [x] Add unit tests for declarative-component-renderer.ts ✅ (41 tests, 96% coverage) + ## RenderComponent - [ ] Audit RenderComponent.tsx (221 LOC - exception with recursive pattern) diff --git a/frontends/nextjs/src/lib/component-registry.test.ts b/frontends/nextjs/src/lib/component-registry.test.ts new file mode 100644 index 000000000..0f29fbe1e --- /dev/null +++ b/frontends/nextjs/src/lib/component-registry.test.ts @@ -0,0 +1,92 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest' +import { ComponentRegistry } from '@/lib/component-registry' +import { componentCatalog } from '@/lib/component-catalog' +import type { ComponentTypeDefinition } from '@/lib/component-registry' + +const buildComponent = (type: string, category: ComponentTypeDefinition['category']): ComponentTypeDefinition => ({ + type, + label: `${type} Label`, + icon: 'TestIcon', + category, + defaultProps: {}, + propSchema: [], + allowsChildren: false, +}) + +describe('ComponentRegistry', () => { + let registry: ComponentRegistry + + beforeEach(() => { + registry = new ComponentRegistry() + }) + + it.each([ + { type: 'Container' }, + { type: 'Button' }, + { type: 'Heading' }, + ])('should load catalog component $type', ({ type }) => { + expect(registry.hasComponent(type)).toBe(true) + expect(registry.getComponent(type)).toBeDefined() + }) + + it('should return undefined for unknown component', () => { + expect(registry.getComponent('MissingComponent')).toBeUndefined() + expect(registry.hasComponent('MissingComponent')).toBe(false) + }) + + it('should register a custom component', () => { + const custom = buildComponent('CustomWidget', 'Data') + registry.registerComponent(custom) + + expect(registry.getComponent('CustomWidget')).toBe(custom) + expect(registry.hasComponent('CustomWidget')).toBe(true) + }) + + it('should register multiple components', () => { + const components = [ + buildComponent('Alpha', 'Layout'), + buildComponent('Beta', 'Feedback'), + ] + + registry.registerComponents(components) + + components.forEach(component => { + expect(registry.getComponent(component.type)).toBe(component) + }) + }) + + it('should filter components by category', () => { + const custom = buildComponent('Chart', 'Data') + registry.registerComponent(custom) + + const results = registry.getComponentsByCategory('Data') + const types = results.map(component => component.type) + + expect(types).toContain('Chart') + }) + + it('should expose all unique catalog components', () => { + const uniqueTypes = new Set(componentCatalog.map(component => component.type)) + expect(registry.getAllComponents()).toHaveLength(uniqueTypes.size) + }) +}) + +describe('component registry helpers', () => { + it('should return a singleton registry instance', async () => { + vi.resetModules() + const module = await import('@/lib/component-registry') + + const first = module.getComponentRegistry() + const second = module.getComponentRegistry() + + expect(first).toBe(second) + }) + + it('should initialize the registry without errors', async () => { + vi.resetModules() + const module = await import('@/lib/component-registry') + + await expect(module.initializeComponentRegistry()).resolves.toBeUndefined() + expect(module.getComponentRegistry().getAllComponents().length).toBeGreaterThan(0) + }) +})