/** * Unit tests for FieldRegistry (SM-20). * * FieldRegistry is the simplest of the three owner-scoped registries — * one flat Map keyed on `ownerNodeId\0fieldName`. These tests pin the * basic register/lookup/clear contract and the owner-scope isolation. */ import { describe, it, expect } from 'vitest'; import { createFieldRegistry } from '../../../src/core/ingestion/model/field-registry.js'; import type { SymbolDefinition } from 'gitnexus-shared'; import { makeDef as makeBaseDef } from './helpers.js'; const makeDef = (overrides: Partial = {}): SymbolDefinition => makeBaseDef({ nodeId: 'prop:test', type: 'Property', ...overrides }); describe('FieldRegistry', () => { it('lookupFieldByOwner returns undefined when the registry is empty', () => { const reg = createFieldRegistry(); expect(reg.lookupFieldByOwner('class:User', 'name')).toBeUndefined(); }); it('register + lookup round-trips the exact def reference', () => { const reg = createFieldRegistry(); const def = makeDef({ nodeId: 'prop:User.name', declaredType: 'string' }); reg.register('class:User', 'name', def); expect(reg.lookupFieldByOwner('class:User', 'name')).toBe(def); }); it('isolates fields by ownerNodeId — same field name on two classes does not collide', () => { const reg = createFieldRegistry(); const userName = makeDef({ nodeId: 'prop:User.name' }); const orderName = makeDef({ nodeId: 'prop:Order.name' }); reg.register('class:User', 'name', userName); reg.register('class:Order', 'name', orderName); expect(reg.lookupFieldByOwner('class:User', 'name')?.nodeId).toBe('prop:User.name'); expect(reg.lookupFieldByOwner('class:Order', 'name')?.nodeId).toBe('prop:Order.name'); }); it('accumulates multiple defs under the same (ownerNodeId, fieldName)', () => { const reg = createFieldRegistry(); const first = makeDef({ nodeId: 'prop:User.name#first', type: 'Property' }); const second = makeDef({ nodeId: 'def:User.name#var', type: 'Variable' }); reg.register('class:User', 'name', first); reg.register('class:User', 'name', second); expect(reg.lookupFieldByOwner('class:User', 'name')?.nodeId).toBe('prop:User.name#first'); expect(reg.lookupAllByOwner('class:User', 'name')).toEqual([first, second]); }); it('clear() empties the registry', () => { const reg = createFieldRegistry(); reg.register('class:User', 'name', makeDef()); reg.register('class:Order', 'total', makeDef()); reg.clear(); expect(reg.lookupFieldByOwner('class:User', 'name')).toBeUndefined(); expect(reg.lookupFieldByOwner('class:Order', 'total')).toBeUndefined(); }); it('allows re-registration after clear', () => { const reg = createFieldRegistry(); reg.register('class:User', 'name', makeDef({ nodeId: 'prop:first' })); reg.clear(); reg.register('class:User', 'name', makeDef({ nodeId: 'prop:second' })); expect(reg.lookupFieldByOwner('class:User', 'name')?.nodeId).toBe('prop:second'); }); it('returns the same frozen empty array on miss', () => { const reg = createFieldRegistry(); expect(reg.lookupAllByOwner('class:Nope', 'x')).toBe(reg.lookupAllByOwner('class:Nope', 'y')); expect(Object.isFrozen(reg.lookupAllByOwner('class:Nope', 'x'))).toBe(true); }); });