@@ -106,4 +106,226 @@ describe('hierarchy', () => {
106106 spyMixinClass . mockReset ( )
107107 spyMixinClass . mockRestore ( )
108108 } )
109+
110+ // Memory optimization tests - ancestors stored as array
111+ it ( 'getAncestors should return array directly' , async ( ) => {
112+ const hierarchy = prepare ( )
113+ const ancestors = hierarchy . getAncestors ( core . class . TxCreateDoc )
114+
115+ // Verify it's an array
116+ expect ( Array . isArray ( ancestors ) ) . toBeTruthy ( )
117+
118+ // Verify it contains expected ancestors
119+ expect ( ancestors ) . toContain ( core . class . TxCreateDoc )
120+ expect ( ancestors ) . toContain ( core . class . TxCUD )
121+ expect ( ancestors ) . toContain ( core . class . Tx )
122+ expect ( ancestors ) . toContain ( core . class . Doc )
123+ expect ( ancestors ) . toContain ( core . class . Obj )
124+
125+ // Verify order is consistent
126+ const indexTx = ancestors . indexOf ( core . class . Tx )
127+ const indexDoc = ancestors . indexOf ( core . class . Doc )
128+ expect ( indexDoc ) . toBeGreaterThan ( indexTx )
129+ } )
130+
131+ it ( 'isDerived should work with array-based ancestors' , async ( ) => {
132+ const hierarchy = prepare ( )
133+
134+ // Test various inheritance chains
135+ expect ( hierarchy . isDerived ( core . class . TxCreateDoc , core . class . Tx ) ) . toBeTruthy ( )
136+ expect ( hierarchy . isDerived ( core . class . TxCreateDoc , core . class . TxCUD ) ) . toBeTruthy ( )
137+ expect ( hierarchy . isDerived ( core . class . TxCreateDoc , core . class . Doc ) ) . toBeTruthy ( )
138+ expect ( hierarchy . isDerived ( core . class . TxCreateDoc , core . class . Obj ) ) . toBeTruthy ( )
139+
140+ // Test self-derivation (class is in its own ancestors)
141+ expect ( hierarchy . isDerived ( core . class . TxCreateDoc , core . class . TxCreateDoc ) ) . toBeTruthy ( )
142+
143+ // Test non-derived classes
144+ expect ( hierarchy . isDerived ( core . class . TxCreateDoc , core . class . Space ) ) . toBeFalsy ( )
145+ expect ( hierarchy . isDerived ( core . class . Space , core . class . Tx ) ) . toBeFalsy ( )
146+
147+ // Test with mixins
148+ expect ( hierarchy . isDerived ( test . class . TestComment , core . class . AttachedDoc ) ) . toBeTruthy ( )
149+ expect ( hierarchy . isDerived ( test . mixin . TaskMixinTodos , test . class . Task ) ) . toBeTruthy ( )
150+ } )
151+
152+ it ( 'should handle deep inheritance chains efficiently' , async ( ) => {
153+ const hierarchy = prepare ( )
154+
155+ // TxCreateDoc has a chain: TxCreateDoc -> TxCUD -> Tx -> Doc -> Obj
156+ const ancestors = hierarchy . getAncestors ( core . class . TxCreateDoc )
157+ expect ( ancestors . length ) . toBeGreaterThanOrEqual ( 5 )
158+
159+ // All intermediate classes should be present
160+ expect ( hierarchy . isDerived ( core . class . TxCreateDoc , core . class . TxCUD ) ) . toBeTruthy ( )
161+ expect ( hierarchy . isDerived ( core . class . TxCUD , core . class . Tx ) ) . toBeTruthy ( )
162+ expect ( hierarchy . isDerived ( core . class . Tx , core . class . Doc ) ) . toBeTruthy ( )
163+ expect ( hierarchy . isDerived ( core . class . Doc , core . class . Obj ) ) . toBeTruthy ( )
164+ } )
165+
166+ // Classifier properties tests - Map-based storage
167+ it ( 'getClassifierProp and setClassifierProp should work with Map' , async ( ) => {
168+ const hierarchy = prepare ( )
169+
170+ // Set a property
171+ hierarchy . setClassifierProp ( core . class . Space , 'testProp' , 'testValue' )
172+
173+ // Get the property
174+ const value = hierarchy . getClassifierProp ( core . class . Space , 'testProp' )
175+ expect ( value ) . toBe ( 'testValue' )
176+
177+ // Update the property
178+ hierarchy . setClassifierProp ( core . class . Space , 'testProp' , 'updatedValue' )
179+ const updatedValue = hierarchy . getClassifierProp ( core . class . Space , 'testProp' )
180+ expect ( updatedValue ) . toBe ( 'updatedValue' )
181+ } )
182+
183+ it ( 'should handle multiple properties per classifier' , async ( ) => {
184+ const hierarchy = prepare ( )
185+
186+ // Set multiple properties
187+ hierarchy . setClassifierProp ( core . class . Space , 'prop1' , 'value1' )
188+ hierarchy . setClassifierProp ( core . class . Space , 'prop2' , 'value2' )
189+ hierarchy . setClassifierProp ( core . class . Space , 'prop3' , 42 )
190+ hierarchy . setClassifierProp ( core . class . Space , 'prop4' , { nested : 'object' } )
191+
192+ // Verify all properties are stored correctly
193+ expect ( hierarchy . getClassifierProp ( core . class . Space , 'prop1' ) ) . toBe ( 'value1' )
194+ expect ( hierarchy . getClassifierProp ( core . class . Space , 'prop2' ) ) . toBe ( 'value2' )
195+ expect ( hierarchy . getClassifierProp ( core . class . Space , 'prop3' ) ) . toBe ( 42 )
196+ expect ( hierarchy . getClassifierProp ( core . class . Space , 'prop4' ) ) . toEqual ( { nested : 'object' } )
197+
198+ // Verify undefined for non-existent property
199+ expect ( hierarchy . getClassifierProp ( core . class . Space , 'nonExistent' ) ) . toBeUndefined ( )
200+ } )
201+
202+ it ( 'should isolate properties between different classifiers' , async ( ) => {
203+ const hierarchy = prepare ( )
204+
205+ // Set properties on different classifiers
206+ hierarchy . setClassifierProp ( core . class . Space , 'name' , 'Space' )
207+ hierarchy . setClassifierProp ( core . class . Doc , 'name' , 'Doc' )
208+ hierarchy . setClassifierProp ( test . class . Task , 'name' , 'Task' )
209+
210+ // Verify isolation
211+ expect ( hierarchy . getClassifierProp ( core . class . Space , 'name' ) ) . toBe ( 'Space' )
212+ expect ( hierarchy . getClassifierProp ( core . class . Doc , 'name' ) ) . toBe ( 'Doc' )
213+ expect ( hierarchy . getClassifierProp ( test . class . Task , 'name' ) ) . toBe ( 'Task' )
214+ } )
215+
216+ it ( 'should handle property updates without creating new objects' , async ( ) => {
217+ const hierarchy = prepare ( )
218+
219+ // Set initial value
220+ hierarchy . setClassifierProp ( core . class . Space , 'counter' , 0 )
221+
222+ // Update multiple times (testing that we're not creating new objects each time)
223+ for ( let i = 1 ; i <= 100 ; i ++ ) {
224+ hierarchy . setClassifierProp ( core . class . Space , 'counter' , i )
225+ }
226+
227+ // Verify final value
228+ expect ( hierarchy . getClassifierProp ( core . class . Space , 'counter' ) ) . toBe ( 100 )
229+ } )
230+
231+ // Edge cases and integration tests
232+ it ( 'should handle interface implementation checks correctly' , async ( ) => {
233+ const hierarchy = prepare ( )
234+
235+ // Task implements DummyWithState which extends WithState
236+ expect ( hierarchy . isImplements ( test . class . Task , test . interface . WithState ) ) . toBeTruthy ( )
237+ expect ( hierarchy . isImplements ( test . class . Task , test . interface . DummyWithState ) ) . toBeTruthy ( )
238+
239+ // TaskCheckItem directly implements WithState
240+ expect ( hierarchy . isImplements ( test . class . TaskCheckItem , test . interface . WithState ) ) . toBeTruthy ( )
241+
242+ // Negative cases
243+ expect ( hierarchy . isImplements ( core . class . Space , test . interface . WithState ) ) . toBeFalsy ( )
244+ } )
245+
246+ it ( 'should maintain consistency after multiple hierarchy operations' , async ( ) => {
247+ const hierarchy = prepare ( )
248+
249+ // Perform multiple operations
250+ const ancestors1 = hierarchy . getAncestors ( test . class . Task )
251+ const isDerived1 = hierarchy . isDerived ( test . class . Task , core . class . Doc )
252+
253+ // Set some properties
254+ hierarchy . setClassifierProp ( test . class . Task , 'test' , 'value' )
255+
256+ // Verify operations still work correctly
257+ const ancestors2 = hierarchy . getAncestors ( test . class . Task )
258+ const isDerived2 = hierarchy . isDerived ( test . class . Task , core . class . Doc )
259+
260+ expect ( ancestors1 ) . toEqual ( ancestors2 )
261+ expect ( isDerived1 ) . toBe ( isDerived2 )
262+ expect ( isDerived2 ) . toBeTruthy ( )
263+ } )
264+
265+ it ( 'should handle getDescendants correctly' , async ( ) => {
266+ const hierarchy = prepare ( )
267+
268+ // Get descendants of Doc (should include many classes)
269+ const descendants = hierarchy . getDescendants ( core . class . Doc )
270+
271+ expect ( descendants ) . toContain ( core . class . Space )
272+ expect ( descendants ) . toContain ( core . class . Tx )
273+ expect ( descendants ) . toContain ( test . class . Task )
274+ expect ( Array . isArray ( descendants ) ) . toBeTruthy ( )
275+ } )
276+
277+ it ( 'should work with getBaseClass' , async ( ) => {
278+ const hierarchy = prepare ( )
279+
280+ // Get base class of a mixin
281+ const baseClass = hierarchy . getBaseClass ( test . mixin . TaskMixinTodos )
282+ expect ( baseClass ) . toBe ( test . class . Task )
283+
284+ // Get base class of a regular class (should return itself)
285+ const baseClass2 = hierarchy . getBaseClass ( test . class . Task )
286+ expect ( baseClass2 ) . toBe ( test . class . Task )
287+ } )
288+
289+ it ( 'should handle getAllAttributes correctly' , async ( ) => {
290+ const hierarchy = prepare ( )
291+
292+ // Get all attributes for a class
293+ const attributes = hierarchy . getAllAttributes ( core . class . TxCreateDoc )
294+
295+ // Should return a Map
296+ expect ( attributes instanceof Map ) . toBeTruthy ( )
297+
298+ // Test with to parameter
299+ const attributesTo = hierarchy . getAllAttributes ( core . class . TxCreateDoc , core . class . Tx )
300+ expect ( attributesTo instanceof Map ) . toBeTruthy ( )
301+ } )
302+
303+ it ( 'should maintain immutability of returned ancestors array' , async ( ) => {
304+ const hierarchy = prepare ( )
305+
306+ // Get ancestors
307+ const ancestors = hierarchy . getAncestors ( test . class . Task )
308+ const originalLength = ancestors . length
309+
310+ // The returned array should be the internal array, but modifying it shouldn't break hierarchy
311+ // (This is a trade-off for memory optimization - callers should treat it as read-only)
312+ expect ( ancestors . length ) . toBe ( originalLength )
313+ expect ( ancestors ) . toContain ( core . class . Doc )
314+ } )
315+
316+ it ( 'should handle performance for multiple isDerived checks' , async ( ) => {
317+ const hierarchy = prepare ( )
318+
319+ // Perform many isDerived checks to ensure array-based lookup is performant
320+ const startTime = Date . now ( )
321+ for ( let i = 0 ; i < 1000 ; i ++ ) {
322+ hierarchy . isDerived ( core . class . TxCreateDoc , core . class . Tx )
323+ hierarchy . isDerived ( test . class . Task , core . class . Doc )
324+ hierarchy . isDerived ( test . class . TaskCheckItem , core . class . AttachedDoc )
325+ }
326+ const endTime = Date . now ( )
327+
328+ // Should complete in reasonable time (< 100ms for 3000 checks)
329+ expect ( endTime - startTime ) . toBeLessThan ( 100 )
330+ } )
109331} )
0 commit comments