Skip to content

Commit f6a4aa4

Browse files
committed
add tests for memoized selectors
1 parent edb9b9f commit f6a4aa4

1 file changed

Lines changed: 150 additions & 0 deletions

File tree

tests/unit/useOnyxTest.ts

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,156 @@ describe('useOnyx', () => {
386386
expect(result.current[0]).toEqual('id - test_id, name - test_name - selector changed');
387387
expect(result.current[1].status).toEqual('loaded');
388388
});
389+
390+
it('should memoize selector output and return same reference when input unchanged', async () => {
391+
Onyx.set(ONYXKEYS.TEST_KEY, {id: 'test_id', name: 'test_name', count: 1});
392+
393+
const {result} = renderHook(() =>
394+
useOnyx(ONYXKEYS.TEST_KEY, {
395+
// @ts-expect-error bypass
396+
selector: (entry: OnyxEntry<{id: string; name: string; count: number}>) => ({
397+
id: entry?.id,
398+
name: entry?.name,
399+
}),
400+
}),
401+
);
402+
403+
await act(async () => waitForPromisesToResolve());
404+
405+
const firstResult = result.current[0];
406+
407+
// Trigger another render without changing the data
408+
await act(async () => waitForPromisesToResolve());
409+
410+
// Should return the exact same reference due to memoization
411+
expect(result.current[0]).toBe(firstResult);
412+
});
413+
414+
it('should return new reference when selector input changes', async () => {
415+
Onyx.set(ONYXKEYS.TEST_KEY, {id: 'test_id', name: 'test_name'});
416+
417+
const {result} = renderHook(() =>
418+
useOnyx(ONYXKEYS.TEST_KEY, {
419+
// @ts-expect-error bypass
420+
selector: (entry: OnyxEntry<{id: string; name: string}>) => ({
421+
id: entry?.id,
422+
name: entry?.name,
423+
}),
424+
}),
425+
);
426+
427+
await act(async () => waitForPromisesToResolve());
428+
429+
const firstResult = result.current[0];
430+
431+
// Change the data
432+
await act(async () => Onyx.merge(ONYXKEYS.TEST_KEY, {id: 'changed_id'}));
433+
434+
// Should return a new reference since data changed
435+
expect(result.current[0]).not.toBe(firstResult);
436+
expect(result.current[0]).toEqual({id: 'changed_id', name: 'test_name'});
437+
});
438+
439+
it('should memoize selector output using deep equality check', async () => {
440+
let selectorCallCount = 0;
441+
442+
Onyx.set(ONYXKEYS.TEST_KEY, {id: 'test_id', name: 'test_name'});
443+
444+
const {result} = renderHook(() =>
445+
useOnyx(ONYXKEYS.TEST_KEY, {
446+
// @ts-expect-error bypass
447+
selector: (entry: OnyxEntry<{id: string; name: string}>) => {
448+
selectorCallCount++;
449+
return {id: entry?.id, name: entry?.name};
450+
},
451+
}),
452+
);
453+
454+
await act(async () => waitForPromisesToResolve());
455+
456+
const firstResult = result.current[0];
457+
const initialCallCount = selectorCallCount;
458+
459+
// Add a property that will change object reference but keep selected data same
460+
await act(async () => Onyx.merge(ONYXKEYS.TEST_KEY, {extraProp: 'new'}));
461+
462+
// Selector should be called again due to input object reference change
463+
expect(selectorCallCount).toBe(initialCallCount + 1);
464+
// But output should be the same reference due to deep equality check in memoized selector
465+
expect(result.current[0]).toBe(firstResult);
466+
});
467+
468+
it('should use reference equality for memoized selectors instead of deep equality', async () => {
469+
// This test verifies the optimization where memoized selectors use reference equality
470+
const complexObject = {
471+
nested: {
472+
array: [1, 2, 3],
473+
object: {prop: 'value'},
474+
},
475+
};
476+
477+
Onyx.set(ONYXKEYS.TEST_KEY, complexObject);
478+
479+
const {result} = renderHook(() =>
480+
useOnyx(ONYXKEYS.TEST_KEY, {
481+
// @ts-expect-error bypass
482+
selector: (entry: OnyxEntry<typeof complexObject>) => entry?.nested,
483+
}),
484+
);
485+
486+
await act(async () => waitForPromisesToResolve());
487+
488+
const firstResult = result.current[0];
489+
490+
// Set the exact same object reference
491+
await act(async () => Onyx.set(ONYXKEYS.TEST_KEY, complexObject));
492+
493+
// Should return same reference due to memoization
494+
expect(result.current[0]).toBe(firstResult);
495+
496+
// Set different object with same content
497+
const differentObjectSameContent = {
498+
nested: {
499+
array: [1, 2, 3],
500+
object: {prop: 'value'},
501+
},
502+
};
503+
504+
await act(async () => Onyx.set(ONYXKEYS.TEST_KEY, differentObjectSameContent));
505+
506+
// Should return same reference due to deep equality in memoized selector
507+
expect(result.current[0]).toBe(firstResult);
508+
});
509+
510+
it('should memoize primitive selector results correctly', async () => {
511+
Onyx.set(ONYXKEYS.TEST_KEY, {count: 5, name: 'test'});
512+
513+
const {result} = renderHook(() =>
514+
useOnyx(ONYXKEYS.TEST_KEY, {
515+
// @ts-expect-error bypass
516+
selector: (entry: OnyxEntry<{count: number; name: string}>) => entry?.count || 0,
517+
}),
518+
);
519+
520+
await act(async () => waitForPromisesToResolve());
521+
522+
const firstResult = result.current[0];
523+
expect(firstResult).toBe(5);
524+
525+
// Change unrelated property
526+
await act(async () => Onyx.merge(ONYXKEYS.TEST_KEY, {name: 'changed'}));
527+
528+
// Should return the same primitive value (number 5)
529+
expect(result.current[0]).toBe(firstResult);
530+
expect(result.current[0]).toBe(5);
531+
532+
// Change the selected property
533+
await act(async () => Onyx.merge(ONYXKEYS.TEST_KEY, {count: 10}));
534+
535+
// Should return new value
536+
expect(result.current[0]).not.toBe(firstResult);
537+
expect(result.current[0]).toBe(10);
538+
});
389539
});
390540

391541
describe('allowStaleData', () => {

0 commit comments

Comments
 (0)