@@ -90,6 +90,52 @@ describe('SymbolRegistry', () => {
9090 expect ( registry . isRegistered ( symRegistered . id ) ) . toBe ( true ) ;
9191 } ) ;
9292
93+ it ( 'indexes symbols and supports querying by meta' , ( ) => {
94+ const registry = new SymbolRegistry ( ) ;
95+
96+ // register a couple of symbols with meta
97+ const symA = registry . register ( {
98+ meta : { bar : 'type' , foo : { bar : true } } ,
99+ name : 'A' ,
100+ } ) ;
101+ const symB = registry . register ( {
102+ meta : { bar : 'value' , foo : { bar : false } } ,
103+ name : 'B' ,
104+ } ) ;
105+
106+ // query by top-level meta key
107+ const types = registry . query ( { bar : 'type' } ) ;
108+ expect ( types ) . toEqual ( [ symA ] ) ;
109+
110+ // query by nested meta key
111+ const nestedTrue = registry . query ( { foo : { bar : true } } ) ;
112+ expect ( nestedTrue ) . toEqual ( [ symA ] ) ;
113+
114+ const nestedFalse = registry . query ( { foo : { bar : false } } ) ;
115+ expect ( nestedFalse ) . toEqual ( [ symB ] ) ;
116+ } ) ;
117+
118+ it ( 'replaces stubs after registering' , ( ) => {
119+ const registry = new SymbolRegistry ( ) ;
120+
121+ const refA = registry . reference ( { a : 0 } ) ;
122+ const refAB = registry . reference ( { a : 0 , b : 0 } ) ;
123+ const refB = registry . reference ( { b : - 1 } ) ;
124+ const symC = registry . register ( {
125+ meta : { a : 0 , b : 0 , c : 0 } ,
126+ name : 'C' ,
127+ } ) ;
128+ const refAD = registry . reference ( { a : 0 , d : 0 } ) ;
129+ const refAC = registry . reference ( { a : 0 , c : 0 } ) ;
130+
131+ expect ( symC ) . toEqual ( refA ) ;
132+ expect ( symC ) . toEqual ( refAB ) ;
133+ expect ( symC ) . toEqual ( refAC ) ;
134+ expect ( symC ) . not . toEqual ( refAD ) ;
135+ expect ( symC ) . not . toEqual ( refB ) ;
136+ expect ( symC . meta ) . toEqual ( { a : 0 , b : 0 , c : 0 } ) ;
137+ } ) ;
138+
93139 it ( 'throws on invalid register or reference' , ( ) => {
94140 const registry = new SymbolRegistry ( ) ;
95141 // Register with id that does not exist
@@ -103,4 +149,58 @@ describe('SymbolRegistry', () => {
103149 'Symbol with ID 42 not found. The selector ["missing"] matched an ID, but there was no result. This is likely an issue with the application logic.' ,
104150 ) ;
105151 } ) ;
152+
153+ it ( 'caches query results and invalidates on relevant updates' , ( ) => {
154+ const registry = new SymbolRegistry ( ) ;
155+ const symA = registry . register ( { meta : { foo : 'bar' } , name : 'A' } ) ;
156+
157+ // first query populates cache
158+ const result1 = registry . query ( { foo : 'bar' } ) ;
159+ expect ( result1 ) . toEqual ( [ symA ] ) ;
160+ expect ( registry [ 'queryCache' ] . size ) . toBe ( 1 ) ;
161+
162+ // same query should hit cache, no change in cache size
163+ const result2 = registry . query ( { foo : 'bar' } ) ;
164+ expect ( result2 ) . toEqual ( [ symA ] ) ;
165+ expect ( registry [ 'queryCache' ] . size ) . toBe ( 1 ) ;
166+
167+ // register another symbol with matching key should invalidate cache
168+ registry . register ( { meta : { foo : 'bar' } , name : 'B' } ) ;
169+ expect ( registry [ 'queryCache' ] . size ) . toBe ( 0 ) ;
170+
171+ // new query repopulates cache
172+ const result3 = registry . query ( { foo : 'bar' } ) ;
173+ expect ( result3 . map ( ( r ) => r . name ) . sort ( ) ) . toEqual ( [ 'A' , 'B' ] ) ;
174+ expect ( registry [ 'queryCache' ] . size ) . toBe ( 1 ) ;
175+ } ) ;
176+
177+ it ( 'invalidates only affected cache entries' , ( ) => {
178+ const registry = new SymbolRegistry ( ) ;
179+ const symA = registry . register ( { meta : { foo : 'bar' } , name : 'A' } ) ;
180+ const symX = registry . register ( { meta : { x : 'y' } , name : 'X' } ) ;
181+
182+ // Seed multiple cache entries
183+ const resultFoo = registry . query ( { foo : 'bar' } ) ;
184+ const resultX = registry . query ( { x : 'y' } ) ;
185+ expect ( resultFoo ) . toEqual ( [ symA ] ) ;
186+ expect ( resultX ) . toEqual ( [ symX ] ) ;
187+ const initialCacheKeys = Array . from ( registry [ 'queryCache' ] . keys ( ) ) ;
188+ expect ( initialCacheKeys . length ) . toBe ( 2 ) ;
189+
190+ // Add new symbol that should only affect foo:bar queries
191+ registry . register ( { meta : { foo : 'bar' } , name : 'B' } ) ;
192+
193+ // Cache entry for foo:bar should be invalidated, x:y should remain
194+ const cacheKeysAfter = Array . from ( registry [ 'queryCache' ] . keys ( ) ) ;
195+ expect ( cacheKeysAfter . length ) . toBe ( 1 ) ;
196+ const remainingKey = cacheKeysAfter [ 0 ] ;
197+ expect ( remainingKey ) . toBe (
198+ initialCacheKeys . find ( ( k ) => k . includes ( 'x:"y"' ) ) ,
199+ ) ;
200+
201+ // Query foo:bar again to repopulate it
202+ const resultFoo2 = registry . query ( { foo : 'bar' } ) ;
203+ expect ( resultFoo2 . map ( ( r ) => r . name ) . sort ( ) ) . toEqual ( [ 'A' , 'B' ] ) ;
204+ expect ( registry [ 'queryCache' ] . size ) . toBe ( 2 ) ;
205+ } ) ;
106206} ) ;
0 commit comments