Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mergeCollection handle null values #537

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions API-INTERNAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ Otherwise removes all nested null values in objects and returns the object</p>
<dt><a href="#prepareKeyValuePairsForStorage">prepareKeyValuePairsForStorage()</a> ⇒</dt>
<dd><p>Storage expects array like: [[&quot;@MyApp_user&quot;, value_1], [&quot;@MyApp_key&quot;, value_2]]
This method transforms an object like {&#39;@MyApp_user&#39;: myUserValue, &#39;@MyApp_key&#39;: myKeyValue}
to an array of key-value pairs in the above format and removes key-value pairs that are being set to null</p>
to an array of key-value pairs in the above format</p>
</dd>
<dt><a href="#applyMerge">applyMerge(changes)</a></dt>
<dd><p>Merges an array of changes with an existing value</p>
Expand Down Expand Up @@ -376,7 +376,7 @@ Otherwise removes all nested null values in objects and returns the object
## prepareKeyValuePairsForStorage() ⇒
Storage expects array like: [["@MyApp_user", value_1], ["@MyApp_key", value_2]]
This method transforms an object like {'@MyApp_user': myUserValue, '@MyApp_key': myKeyValue}
to an array of key-value pairs in the above format and removes key-value pairs that are being set to null
to an array of key-value pairs in the above format

**Kind**: global function
**Returns**: an array of key - value pairs <[key, value]>
Expand Down
8 changes: 5 additions & 3 deletions lib/Onyx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ function set<TKey extends OnyxKey>(key: TKey, value: OnyxEntry<KeyValueMapping[T
* @param data object keyed by ONYXKEYS and the values to set
*/
function multiSet(data: Partial<NullableKeyValueMapping>): Promise<void> {
const keyValuePairs = OnyxUtils.prepareKeyValuePairsForStorage(data);
const keyValuePairs = OnyxUtils.prepareKeyValuePairsForStorage(data, true);

const updatePromises = keyValuePairs.map(([key, value]) => {
const prevValue = cache.getValue(key, false);
Expand Down Expand Up @@ -419,8 +419,10 @@ function mergeCollection<TKey extends CollectionKeyBase, TMap>(collectionKey: TK
obj[key] = mergedCollection[key];
return obj;
}, {});
const keyValuePairsForExistingCollection = OnyxUtils.prepareKeyValuePairsForStorage(existingKeyCollection);
const keyValuePairsForNewCollection = OnyxUtils.prepareKeyValuePairsForStorage(newCollection);

// We don't want to remove null values because the provider's merge method uses them to remove their respective keys
const keyValuePairsForExistingCollection = OnyxUtils.prepareKeyValuePairsForStorage(existingKeyCollection, false);
const keyValuePairsForNewCollection = OnyxUtils.prepareKeyValuePairsForStorage(newCollection, true);

const promises = [];

Expand Down
12 changes: 8 additions & 4 deletions lib/OnyxUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -982,11 +982,15 @@ function removeNullValues(key: OnyxKey, value: OnyxValue<OnyxKey>): RemoveNullVa
/**
* Storage expects array like: [["@MyApp_user", value_1], ["@MyApp_key", value_2]]
* This method transforms an object like {'@MyApp_user': myUserValue, '@MyApp_key': myKeyValue}
* to an array of key-value pairs in the above format and removes key-value pairs that are being set to null

* @return an array of key - value pairs <[key, value]>
* to an array of key-value pairs in the above format
*
* @return an array of key - value pairs <[key, value]>
*/
function prepareKeyValuePairsForStorage(data: Record<OnyxKey, OnyxValue<OnyxKey>>): Array<[OnyxKey, OnyxValue<OnyxKey>]> {
function prepareKeyValuePairsForStorage(data: Record<OnyxKey, OnyxValue<OnyxKey>>, shouldRemoveNullObjectValues: boolean): Array<[OnyxKey, OnyxValue<OnyxKey>]> {
if (!shouldRemoveNullObjectValues) {
return Object.entries(data);
}

const keyValuePairs: Array<[OnyxKey, OnyxValue<OnyxKey>]> = [];

Object.entries(data).forEach(([key, value]) => {
Expand Down
62 changes: 62 additions & 0 deletions tests/unit/onyxMultiMergeWebStorageTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,68 @@ describe('Onyx.mergeCollection() and WebStorage', () => {
});
});

it('merge with null values', () => {
// Given empty storage
expect(StorageMock.getMockStore().test_1).toBeFalsy();
expect(StorageMock.getMockStore().test_2).toBeFalsy();
expect(StorageMock.getMockStore().test_3).toBeFalsy();

// And an empty cache values for the collection keys
expect(OnyxCache.getValue('test_1')).toBeFalsy();
expect(OnyxCache.getValue('test_2')).toBeFalsy();
expect(OnyxCache.getValue('test_3')).toBeFalsy();

// When we merge additional data and wait for the change
const data = {a: 'a', b: 'b'};
Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_KEY, {
test_1: data,
test_2: data,
test_3: data,
});

return waitForPromisesToResolve()
.then(() => {
// Then the cache and storage should match
expect(OnyxCache.getValue('test_1')).toEqual(data);
expect(OnyxCache.getValue('test_2')).toEqual(data);
expect(OnyxCache.getValue('test_3')).toEqual(data);
expect(StorageMock.getMockStore().test_1).toEqual(data);
expect(StorageMock.getMockStore().test_2).toEqual(data);
expect(StorageMock.getMockStore().test_3).toEqual(data);

// When we merge additional data containing null values and wait for the change
const additionalData = {b: null, c: 'c'};
Onyx.mergeCollection(ONYX_KEYS.COLLECTION.TEST_KEY, {
test_1: additionalData,
test_2: additionalData,
test_3: additionalData,
});

return waitForPromisesToResolve();
})
.then(() => {
const finalObjectCache = {
a: 'a',
b: null,
c: 'c',
};
const finalObject = {
a: 'a',
c: 'c',
};
Comment on lines +186 to +194
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cache having null values in intended #411


// Then our new data should merge with the existing data in the cache
expect(OnyxCache.getValue('test_1')).toEqual(finalObjectCache);
expect(OnyxCache.getValue('test_2')).toEqual(finalObjectCache);
expect(OnyxCache.getValue('test_3')).toEqual(finalObjectCache);

// And the storage should reflect the same state but with nulled key-values removed
expect(StorageMock.getMockStore().test_1).toEqual(finalObject);
expect(StorageMock.getMockStore().test_2).toEqual(finalObject);
expect(StorageMock.getMockStore().test_3).toEqual(finalObject);
});
});

it('setItem() and multiMerge()', () => {
// Onyx should be empty after clear() is called
expect(StorageMock.getMockStore()).toEqual({});
Expand Down
Loading