Skip to content

Commit 9445bbf

Browse files
committed
Add cache-first regression test for Storage.multiSet rejection
Adds a parallel test inside the 'mergeCollection cache-first ordering' describe asserting that cache + subscribers reflect the merged data even when Storage.multiSet (the new-keys path) rejects. The existing test only exercises the Storage.multiMerge (existing-keys) path. Also adds an afterEach that restores StorageMock.multiMerge and StorageMock.multiSet to their originals after each test in this block, so the rejecting mocks no longer leak into later describe blocks (eviction, afterInit) whose setup relies on these storage methods working normally. Addresses review feedback on Expensify#787.
1 parent 42b796d commit 9445bbf

1 file changed

Lines changed: 52 additions & 0 deletions

File tree

tests/unit/onyxUtilsTest.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -805,6 +805,18 @@ describe('OnyxUtils', () => {
805805
});
806806

807807
describe('mergeCollection cache-first ordering', () => {
808+
// Save originals so we can restore them after each test. The tests below replace
809+
// StorageMock.multiMerge / StorageMock.multiSet with rejecting mocks; without
810+
// restoring, the mock leaks into later describe blocks (e.g. eviction tests) whose
811+
// setup relies on these storage methods working normally.
812+
const originalMultiMerge = StorageMock.multiMerge;
813+
const originalMultiSet = StorageMock.multiSet;
814+
815+
afterEach(() => {
816+
StorageMock.multiMerge = originalMultiMerge;
817+
StorageMock.multiSet = originalMultiSet;
818+
});
819+
808820
it('updates cache and notifies subscribers even when Storage.multiMerge rejects', async () => {
809821
const collectionKey = ONYXKEYS.COLLECTION.TEST_KEY;
810822
const existingMemberKey = `${collectionKey}1`;
@@ -845,6 +857,46 @@ describe('OnyxUtils', () => {
845857
expect(lastBroadcast?.[existingMemberKey]).toEqual({value: 'merged'});
846858
expect(lastBroadcast?.[newMemberKey]).toEqual({value: 'new'});
847859
});
860+
861+
it('updates cache and notifies subscribers even when Storage.multiSet rejects', async () => {
862+
const collectionKey = ONYXKEYS.COLLECTION.TEST_KEY;
863+
const newMemberKey1 = `${collectionKey}1`;
864+
const newMemberKey2 = `${collectionKey}2`;
865+
866+
// No keys are seeded, so every merged key is a "new" key. This forces the merge path
867+
// to use Storage.multiSet (existing keys would go through Storage.multiMerge).
868+
const collectionCallback = jest.fn();
869+
Onyx.connect({
870+
key: collectionKey,
871+
waitForCollectionCallback: true,
872+
callback: collectionCallback,
873+
});
874+
await waitForPromisesToResolve();
875+
collectionCallback.mockClear();
876+
877+
// Force Storage.multiSet to reject with a non-retriable IDB error so the failure
878+
// path is taken without burning the full retry budget and without rejecting the
879+
// outer Onyx.mergeCollection promise.
880+
const nonRetriableIdbError = Object.assign(new Error('Internal error opening backing store for indexedDB.open.'), {name: 'UnknownError'});
881+
StorageMock.multiSet = jest.fn().mockRejectedValue(nonRetriableIdbError);
882+
883+
await Onyx.mergeCollection(collectionKey, {
884+
[newMemberKey1]: {value: 'first'},
885+
[newMemberKey2]: {value: 'second'},
886+
} as GenericCollection);
887+
888+
// Cache must reflect the merge regardless of the multiSet rejection. This is the
889+
// cache-first / storage-second invariant that mergeCollectionWithPatches must honor.
890+
const cachedCollection = OnyxCache.getCollectionData(collectionKey);
891+
expect(cachedCollection?.[newMemberKey1]).toEqual({value: 'first'});
892+
expect(cachedCollection?.[newMemberKey2]).toEqual({value: 'second'});
893+
894+
// Subscribers must have been notified with the merged values.
895+
expect(collectionCallback).toHaveBeenCalled();
896+
const lastBroadcast = collectionCallback.mock.calls.at(-1)?.[0] as Record<string, unknown> | undefined;
897+
expect(lastBroadcast?.[newMemberKey1]).toEqual({value: 'first'});
898+
expect(lastBroadcast?.[newMemberKey2]).toEqual({value: 'second'});
899+
});
848900
});
849901

850902
describe('storage eviction', () => {

0 commit comments

Comments
 (0)