import { buildComponent } from '../../../components/utils/buildComponent' import ComponentRegistry from './ComponentRegistry' import ScopedComponentRegistry from './ScopedComponentRegistry' describe('ScopedComponentRegistry', () => { function comp(variant: string, html = `<${variant} />`) { return buildComponent(variant, () => html) } describe('get()', () => { it('should return local component when it exists', () => { // Arrange const parent = new ComponentRegistry() const scoped = new ScopedComponentRegistry(parent) scoped.registerMany([comp('local-input', '')]) // Act & Assert expect(scoped.get('local-input')?.variant).toBe('local-input') }) it('should fall back to parent when component is not in local scope', () => { // Arrange const parent = new ComponentRegistry() parent.registerMany([comp('parent-input')]) const scoped = new ScopedComponentRegistry(parent) // Act & Assert expect(scoped.get('parent-input')?.variant).toBe('parent-input') }) it('should return undefined when component exists in neither scope', () => { // Arrange const parent = new ComponentRegistry() const scoped = new ScopedComponentRegistry(parent) // Act & Assert expect(scoped.get('nonexistent')).toBeUndefined() }) it('should shadow parent component with local component of same variant', () => { // Arrange const parent = new ComponentRegistry() parent.registerMany([comp('text-input', '')]) const scoped = new ScopedComponentRegistry(parent) scoped.registerMany([comp('text-input', '')]) // Act const result = scoped.get('text-input') // Assert expect(result?.render({} as any)).toBe('') }) }) describe('has()', () => { it('should return true for local component', () => { // Arrange const parent = new ComponentRegistry() const scoped = new ScopedComponentRegistry(parent) scoped.registerMany([comp('local-comp')]) // Act & Assert expect(scoped.has('local-comp')).toBe(true) }) it('should return true for parent component', () => { // Arrange const parent = new ComponentRegistry() parent.registerMany([comp('parent-comp')]) const scoped = new ScopedComponentRegistry(parent) // Act & Assert expect(scoped.has('parent-comp')).toBe(true) }) it('should return false when component exists in neither scope', () => { // Arrange const parent = new ComponentRegistry() const scoped = new ScopedComponentRegistry(parent) // Act & Assert expect(scoped.has('nonexistent')).toBe(false) }) }) describe('getAll()', () => { it('should merge local and parent components', () => { // Arrange const parent = new ComponentRegistry() parent.registerMany([comp('parent-comp')]) const scoped = new ScopedComponentRegistry(parent) scoped.registerMany([comp('local-comp')]) // Act const all = scoped.getAll() // Assert expect(all.size).toBe(2) expect(all.has('parent-comp')).toBe(true) expect(all.has('local-comp')).toBe(true) }) it('should prefer local over parent when variants overlap', () => { // Arrange const parent = new ComponentRegistry() parent.registerMany([comp('text-input', '')]) const scoped = new ScopedComponentRegistry(parent) scoped.registerMany([comp('text-input', '')]) // Act const all = scoped.getAll() // Assert expect(all.size).toBe(1) expect(all.get('text-input')?.render({} as any)).toBe('') }) it('should return a copy that does not affect the registry', () => { // Arrange const parent = new ComponentRegistry() parent.registerMany([comp('parent-comp')]) const scoped = new ScopedComponentRegistry(parent) scoped.registerMany([comp('local-comp')]) // Act const all = scoped.getAll() all.clear() // Assert expect(scoped.size()).toBe(2) }) }) describe('size()', () => { it('should count both local and parent components', () => { // Arrange const parent = new ComponentRegistry() parent.registerMany([comp('parent-comp')]) const scoped = new ScopedComponentRegistry(parent) scoped.registerMany([comp('local-comp')]) // Act & Assert expect(scoped.size()).toBe(2) }) it('should not double-count shadowed components', () => { // Arrange const parent = new ComponentRegistry() parent.registerMany([comp('text-input', '')]) const scoped = new ScopedComponentRegistry(parent) scoped.registerMany([comp('text-input', '')]) // Act & Assert expect(scoped.size()).toBe(1) }) }) describe('isolation between scoped registries', () => { it('should allow two scoped registries to define same-variant components', () => { // Arrange const parent = new ComponentRegistry() const scopeA = new ScopedComponentRegistry(parent) scopeA.registerMany([comp('custom-input', '')]) const scopeB = new ScopedComponentRegistry(parent) scopeB.registerMany([comp('custom-input', '')]) // Act & Assert expect(scopeA.get('custom-input')?.render({} as any)).toBe('') expect(scopeB.get('custom-input')?.render({} as any)).toBe('') }) it('should share parent components across scoped registries', () => { // Arrange const parent = new ComponentRegistry() parent.registerMany([comp('shared-comp')]) const scopeA = new ScopedComponentRegistry(parent) const scopeB = new ScopedComponentRegistry(parent) // Act & Assert expect(scopeA.has('shared-comp')).toBe(true) expect(scopeB.has('shared-comp')).toBe(true) }) it('should not leak local components between scoped registries', () => { // Arrange const parent = new ComponentRegistry() const scopeA = new ScopedComponentRegistry(parent) scopeA.registerMany([comp('only-in-a')]) const scopeB = new ScopedComponentRegistry(parent) scopeB.registerMany([comp('only-in-b')]) // Act & Assert expect(scopeA.has('only-in-a')).toBe(true) expect(scopeA.has('only-in-b')).toBe(false) expect(scopeB.has('only-in-b')).toBe(true) expect(scopeB.has('only-in-a')).toBe(false) }) }) })