Skip to content

Commit 5514916

Browse files
authored
Merge pull request #759 from Expensify/allow-preserving-collections
Allow preserving collection keys
2 parents 2ae39de + 8c59af3 commit 5514916

4 files changed

Lines changed: 92 additions & 3 deletions

File tree

lib/DevTools/RealDevTools.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type {IDevTools, DevtoolsOptions, DevtoolsConnection, ReduxDevtools} from './types';
2+
import OnyxUtils from '../OnyxUtils';
23

34
const ERROR_LABEL = 'Onyx DevTools - Error: ';
45

@@ -76,7 +77,7 @@ class RealDevTools implements IDevTools {
7677
clearState(keysToPreserve: string[] = []): void {
7778
const newState = Object.entries(this.state).reduce((obj: Record<string, unknown>, [key, value]) => {
7879
// eslint-disable-next-line no-param-reassign
79-
obj[key] = keysToPreserve.includes(key) ? value : this.defaultState[key];
80+
obj[key] = keysToPreserve.some((preserveKey) => OnyxUtils.isKeyMatch(preserveKey, key)) ? value : this.defaultState[key];
8081
return obj;
8182
}, {});
8283

lib/Onyx.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ function clear(keysToPreserve: OnyxKey[] = []): Promise<void> {
345345
// to null would cause unknown behavior)
346346
// 2.1 However, if a default key was explicitly set to null, we need to reset it to the default value
347347
for (const key of allKeys) {
348-
const isKeyToPreserve = keysToPreserve.includes(key);
348+
const isKeyToPreserve = keysToPreserve.some((preserveKey) => OnyxUtils.isKeyMatch(preserveKey, key));
349349
const isDefaultKey = key in defaultKeyStates;
350350

351351
// If the key is being removed or reset to default:
@@ -393,7 +393,7 @@ function clear(keysToPreserve: OnyxKey[] = []): Promise<void> {
393393
// Exclude RAM-only keys to prevent them from being saved to storage
394394
const defaultKeyValuePairs = Object.entries(
395395
Object.keys(defaultKeyStates)
396-
.filter((key) => !keysToPreserve.includes(key) && !OnyxUtils.isRamOnlyKey(key))
396+
.filter((key) => !keysToPreserve.some((preserveKey) => OnyxUtils.isKeyMatch(preserveKey, key)) && !OnyxUtils.isRamOnlyKey(key))
397397
.reduce((obj: KeyValueMapping, key) => {
398398
// eslint-disable-next-line no-param-reassign
399399
obj[key] = defaultKeyStates[key];

tests/unit/DevToolsTest.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,5 +137,12 @@ describe('DevTools', () => {
137137
const devToolsInstance = getDevToolsInstance() as RealDevToolsType;
138138
expect(devToolsInstance['state']).toEqual({...initialKeyStates, [ONYX_KEYS.NUM_KEY]: 2});
139139
});
140+
141+
it('Preserves collection member keys when a collection key is passed to keysToPreserve', async () => {
142+
await Onyx.mergeCollection(ONYX_KEYS.COLLECTION.NUM_KEY, exampleCollection);
143+
await Onyx.clear([ONYX_KEYS.COLLECTION.NUM_KEY]);
144+
const devToolsInstance = getDevToolsInstance() as RealDevToolsType;
145+
expect(devToolsInstance['state']).toEqual({...initialKeyStates, ...exampleCollection});
146+
});
140147
});
141148
});

tests/unit/onyxClearWebStorageTest.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,87 @@ describe('Set data while storage is clearing', () => {
127127
});
128128
});
129129

130+
it('should preserve all collection members when a collection key is passed to keysToPreserve', () => {
131+
expect.assertions(6);
132+
133+
const collectionItemKey1 = `${ONYX_KEYS.COLLECTION.TEST}1`;
134+
const collectionItemKey2 = `${ONYX_KEYS.COLLECTION.TEST}2`;
135+
136+
// Given that Onyx has a collection with two items
137+
return (
138+
Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST, {
139+
[collectionItemKey1]: {id: 1, name: 'first'},
140+
[collectionItemKey2]: {id: 2, name: 'second'},
141+
} as GenericCollection)
142+
// When clear is called with the collection prefix as a key to preserve
143+
.then(() => Onyx.clear([ONYX_KEYS.COLLECTION.TEST]))
144+
.then(() => waitForPromisesToResolve())
145+
.then(() => {
146+
// Then both collection members are preserved in the cache and storage
147+
expect(cache.get(collectionItemKey1)).toEqual({id: 1, name: 'first'});
148+
expect(cache.get(collectionItemKey2)).toEqual({id: 2, name: 'second'});
149+
150+
return Promise.all([StorageMock.getItem(collectionItemKey1), StorageMock.getItem(collectionItemKey2)]);
151+
})
152+
.then(([storedValue1, storedValue2]) => {
153+
expect(storedValue1).toEqual({id: 1, name: 'first'});
154+
expect(storedValue2).toEqual({id: 2, name: 'second'});
155+
156+
// And non-collection keys are still cleared (default key reset to default)
157+
expect(cache.get(ONYX_KEYS.DEFAULT_KEY)).toBe(DEFAULT_VALUE);
158+
return expect(StorageMock.getItem(ONYX_KEYS.DEFAULT_KEY)).resolves.toBe(DEFAULT_VALUE);
159+
})
160+
);
161+
});
162+
163+
it('should preserve collection members and still clear regular keys not in keysToPreserve', () => {
164+
expect.assertions(4);
165+
166+
const collectionItemKey1 = `${ONYX_KEYS.COLLECTION.TEST}1`;
167+
168+
// Given that Onyx has both a collection item and a regular key set
169+
return (
170+
Promise.all([Onyx.set(ONYX_KEYS.REGULAR_KEY, SET_VALUE), Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST, {[collectionItemKey1]: 'value'} as GenericCollection)])
171+
// When clear is called preserving only the collection
172+
.then(() => Onyx.clear([ONYX_KEYS.COLLECTION.TEST]))
173+
.then(() => waitForPromisesToResolve())
174+
.then(() => {
175+
// Then the collection member is preserved
176+
expect(cache.get(collectionItemKey1)).toBe('value');
177+
return expect(StorageMock.getItem(collectionItemKey1)).resolves.toBe('value');
178+
})
179+
.then(() => {
180+
// And the regular key is cleared
181+
expect(cache.get(ONYX_KEYS.REGULAR_KEY)).toBeUndefined();
182+
return expect(StorageMock.getItem(ONYX_KEYS.REGULAR_KEY)).resolves.toBeNull();
183+
})
184+
);
185+
});
186+
187+
it('should preserve both collection keys and individual keys when both are passed to keysToPreserve', () => {
188+
expect.assertions(4);
189+
190+
const collectionItemKey1 = `${ONYX_KEYS.COLLECTION.TEST}1`;
191+
192+
// Given that Onyx has a collection item and a regular key set
193+
return (
194+
Promise.all([Onyx.set(ONYX_KEYS.REGULAR_KEY, SET_VALUE), Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST, {[collectionItemKey1]: 'value'} as GenericCollection)])
195+
// When clear is called preserving both the collection and the regular key
196+
.then(() => Onyx.clear([ONYX_KEYS.COLLECTION.TEST, ONYX_KEYS.REGULAR_KEY]))
197+
.then(() => waitForPromisesToResolve())
198+
.then(() => {
199+
// Then both the collection member and the regular key are preserved
200+
expect(cache.get(collectionItemKey1)).toBe('value');
201+
expect(cache.get(ONYX_KEYS.REGULAR_KEY)).toBe(SET_VALUE);
202+
return Promise.all([StorageMock.getItem(collectionItemKey1), StorageMock.getItem(ONYX_KEYS.REGULAR_KEY)]);
203+
})
204+
.then(([storedCollectionValue, storedRegularValue]) => {
205+
expect(storedCollectionValue).toBe('value');
206+
expect(storedRegularValue).toBe(SET_VALUE);
207+
})
208+
);
209+
});
210+
130211
it('should only trigger the connection callback once when using wait for collection callback', () => {
131212
expect.assertions(4);
132213

0 commit comments

Comments
 (0)