Skip to content

Commit c0c2628

Browse files
committed
add unit tests
1 parent ce8ae85 commit c0c2628

1 file changed

Lines changed: 216 additions & 0 deletions

File tree

tests/unit/useOnyxTest.ts

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,222 @@ describe('useOnyx', () => {
536536
expect(result.current[0]).not.toBe(firstResult);
537537
expect(result.current[0]).toBe(10);
538538
});
539+
540+
it('should recompute selector when dependencies change even if input data stays the same', async () => {
541+
const testCollection = {
542+
[`${ONYXKEYS.COLLECTION.TEST_KEY}1`]: {id: '1', value: 'item1'},
543+
[`${ONYXKEYS.COLLECTION.TEST_KEY}2`]: {id: '2', value: 'item2'},
544+
[`${ONYXKEYS.COLLECTION.TEST_KEY}3`]: {id: '3', value: 'item3'},
545+
};
546+
547+
await act(async () => Onyx.mergeCollection(ONYXKEYS.COLLECTION.TEST_KEY, testCollection as GenericCollection));
548+
549+
let filterIds = ['1'];
550+
let selectorCallCount = 0;
551+
552+
const {result, rerender} = renderHook(() =>
553+
useOnyx(
554+
ONYXKEYS.COLLECTION.TEST_KEY,
555+
{
556+
selector: (collection) => {
557+
selectorCallCount++;
558+
return filterIds.map((id) => (collection as OnyxCollection<GenericCollection>)?.[`${ONYXKEYS.COLLECTION.TEST_KEY}${id}`]).filter(Boolean);
559+
},
560+
},
561+
[filterIds],
562+
),
563+
);
564+
565+
await act(async () => waitForPromisesToResolve());
566+
567+
// Record count after initial stabilization
568+
const initialCallCount = selectorCallCount;
569+
const initialResult = result.current[0];
570+
571+
// Should return item with id '1'
572+
expect(initialResult).toEqual([{id: '1', value: 'item1'}]);
573+
574+
// Change dependencies without changing underlying data
575+
await act(async () => {
576+
filterIds = ['1', '2'];
577+
rerender(ONYXKEYS.COLLECTION.TEST_KEY);
578+
});
579+
580+
// Selector should recompute and return items with id '1' and '2'
581+
expect(result.current[0]).toEqual([
582+
{id: '1', value: 'item1'},
583+
{id: '2', value: 'item2'},
584+
]);
585+
expect(selectorCallCount).toBeGreaterThan(initialCallCount);
586+
587+
// Record count after first dependency change
588+
const firstChangeCallCount = selectorCallCount;
589+
590+
// Change dependencies again
591+
await act(async () => {
592+
filterIds = ['2', '3'];
593+
rerender(ONYXKEYS.COLLECTION.TEST_KEY);
594+
});
595+
596+
// Selector should recompute and return items with id '2' and '3'
597+
expect(result.current[0]).toEqual([
598+
{id: '2', value: 'item2'},
599+
{id: '3', value: 'item3'},
600+
]);
601+
expect(selectorCallCount).toBeGreaterThan(firstChangeCallCount);
602+
});
603+
604+
it('should handle complex dependency scenarios with multiple values', async () => {
605+
type TestItem = {id: string; category: string; priority: number};
606+
const testData = {
607+
[`${ONYXKEYS.COLLECTION.TEST_KEY}item1`]: {id: 'item1', category: 'A', priority: 1},
608+
[`${ONYXKEYS.COLLECTION.TEST_KEY}item2`]: {id: 'item2', category: 'B', priority: 2},
609+
[`${ONYXKEYS.COLLECTION.TEST_KEY}item3`]: {id: 'item3', category: 'A', priority: 3},
610+
[`${ONYXKEYS.COLLECTION.TEST_KEY}item4`]: {id: 'item4', category: 'B', priority: 4},
611+
};
612+
613+
await act(async () => Onyx.mergeCollection(ONYXKEYS.COLLECTION.TEST_KEY, testData as GenericCollection));
614+
615+
let categoryFilter = 'A';
616+
let sortAscending = true;
617+
618+
const {result, rerender} = renderHook(() =>
619+
useOnyx(
620+
ONYXKEYS.COLLECTION.TEST_KEY,
621+
{
622+
selector: (collection) => {
623+
const typedCollection = collection as OnyxCollection<TestItem>;
624+
if (!typedCollection) return [];
625+
626+
const filtered = Object.values(typedCollection).filter((item) => item?.category === categoryFilter);
627+
628+
return filtered.sort((a, b) => (sortAscending ? (a?.priority ?? 0) - (b?.priority ?? 0) : (b?.priority ?? 0) - (a?.priority ?? 0)));
629+
},
630+
},
631+
[categoryFilter, sortAscending],
632+
),
633+
);
634+
635+
await act(async () => waitForPromisesToResolve());
636+
637+
// Should return category A items sorted ascending
638+
expect(result.current[0]).toEqual([
639+
{id: 'item1', category: 'A', priority: 1},
640+
{id: 'item3', category: 'A', priority: 3},
641+
]);
642+
643+
// Change sort order only
644+
await act(async () => {
645+
sortAscending = false;
646+
rerender(ONYXKEYS.COLLECTION.TEST_KEY);
647+
});
648+
649+
// Should return category A items sorted descending
650+
expect(result.current[0]).toEqual([
651+
{id: 'item3', category: 'A', priority: 3},
652+
{id: 'item1', category: 'A', priority: 1},
653+
]);
654+
655+
// Change category filter
656+
await act(async () => {
657+
categoryFilter = 'B';
658+
rerender(ONYXKEYS.COLLECTION.TEST_KEY);
659+
});
660+
661+
// Should return category B items sorted descending
662+
expect(result.current[0]).toEqual([
663+
{id: 'item4', category: 'B', priority: 4},
664+
{id: 'item2', category: 'B', priority: 2},
665+
]);
666+
});
667+
668+
it('should not trigger unnecessary recomputations when dependencies remain the same', async () => {
669+
await act(async () => Onyx.set(ONYXKEYS.TEST_KEY, {value: 'test'}));
670+
671+
const dependencies = ['constant'];
672+
let selectorCallCount = 0;
673+
674+
const {result, rerender} = renderHook(() =>
675+
useOnyx(
676+
ONYXKEYS.TEST_KEY,
677+
{
678+
selector: (data) => {
679+
selectorCallCount++;
680+
return `${dependencies.join(',')}:${(data as {value?: string})?.value}`;
681+
},
682+
},
683+
dependencies,
684+
),
685+
);
686+
687+
await act(async () => waitForPromisesToResolve());
688+
689+
expect(result.current[0]).toBe('constant:test');
690+
expect(selectorCallCount).toBe(1);
691+
692+
// Force rerender without changing dependencies
693+
await act(async () => {
694+
rerender(ONYXKEYS.COLLECTION.TEST_KEY);
695+
});
696+
697+
// Selector should not recompute since dependencies haven't changed
698+
expect(result.current[0]).toBe('constant:test');
699+
expect(selectorCallCount).toBe(1);
700+
701+
// Update underlying data
702+
await act(async () => Onyx.merge(ONYXKEYS.TEST_KEY, {value: 'updated'}));
703+
704+
// Selector should recompute due to data change
705+
expect(result.current[0]).toBe('constant:updated');
706+
expect(selectorCallCount).toBe(2);
707+
});
708+
709+
it('should handle dependencies with deep equality changes', async () => {
710+
await act(async () => Onyx.set(ONYXKEYS.TEST_KEY, {items: ['a', 'b', 'c']}));
711+
712+
let config = {includeItems: ['a', 'b']};
713+
let selectorCallCount = 0;
714+
715+
const {result, rerender} = renderHook(() =>
716+
useOnyx(
717+
ONYXKEYS.TEST_KEY,
718+
{
719+
selector: (data) => {
720+
selectorCallCount++;
721+
const typedData = data as {items?: string[]};
722+
if (!typedData?.items) return [];
723+
return typedData.items.filter((item: string) => config.includeItems.includes(item));
724+
},
725+
},
726+
[config],
727+
),
728+
);
729+
730+
await act(async () => waitForPromisesToResolve());
731+
732+
expect(result.current[0]).toEqual(['a', 'b']);
733+
expect(selectorCallCount).toBe(1);
734+
735+
// Change config to new object with same content
736+
await act(async () => {
737+
config = {includeItems: ['a', 'b']};
738+
rerender(ONYXKEYS.COLLECTION.TEST_KEY);
739+
});
740+
741+
// Should not recompute since deep equality shows no change
742+
expect(result.current[0]).toEqual(['a', 'b']);
743+
expect(selectorCallCount).toBe(1);
744+
745+
// Change config content
746+
await act(async () => {
747+
config = {includeItems: ['b', 'c']};
748+
rerender(ONYXKEYS.COLLECTION.TEST_KEY);
749+
});
750+
751+
// Should recompute due to content change
752+
expect(result.current[0]).toEqual(['b', 'c']);
753+
expect(selectorCallCount).toBe(2);
754+
});
539755
});
540756

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

0 commit comments

Comments
 (0)