diff --git a/API.md b/API.md index 82b7777f..3ac3a0eb 100644 --- a/API.md +++ b/API.md @@ -319,6 +319,7 @@ Initialize the store with actions and listening for storage events | [options.captureMetrics] | Boolean | | Enables Onyx benchmarking and exposes the get/print/reset functions | | [options.shouldSyncMultipleInstances] | Boolean | | Auto synchronize storage events between multiple instances of Onyx running in different tabs/windows. Defaults to true for platforms that support local storage (web/desktop) | | [options.debugSetState] | Boolean | | Enables debugging setState() calls to connected components. | +| [options.enableDevTools] | Boolean | | Enables debugging using Redux DevTools extension. | **Example** ```js diff --git a/README.md b/README.md index f1f34d1c..4ce8a2c2 100644 --- a/README.md +++ b/README.md @@ -321,8 +321,18 @@ Sample output of `Onyx.printMetrics()` # Debug mode +## Using debugSetState + It can be useful to log why Onyx is calling `setState()` on a particular React component so that we can understand which key changed, what changed about the value, and the connected component that ultimately rendered as a result. When used correctly this can help isolate problem areas and unnecessary renders in the code. To enable this feature, pass `debugSetState: true` to the config and grep JS console logs for `[Onyx-Debug]`. +## Using Redux DevTools extension + +It can be useful to check the order of writes to the storage and it's state at a specific point in time. + +First, install Redux DevTools through your favorite browser ([Edge](https://microsoftedge.microsoft.com/addons/detail/redux-devtools/nnkgneoiohoecpdiaponcejilbhhikei), [Chrome](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd), [Firefox](https://addons.mozilla.org/en-US/firefox/addon/reduxdevtools/)) + +Then, you can enable this type of debugging by passing `enableDevTools: true` to `Onyx.init({...})`. + # Development `react-native` bundles source using the `metro` bundler. `metro` does not follow symlinks, so we can't use `npm link` to diff --git a/lib/DevTools.js b/lib/DevTools.js new file mode 100644 index 00000000..2282c4aa --- /dev/null +++ b/lib/DevTools.js @@ -0,0 +1,49 @@ +import {connectViaExtension} from 'remotedev'; +import _ from 'underscore'; + +class DevTools { + /** + * @callback onStateChange + * @param {object} state + */ + /** + * Creates an instance of DevTools, with an internal state that mirrors the storage. + * + * @param {object} initialState - initial state of the storage + * @param {onStateChange} onStateChange - callback which is triggered when we timetravel to a different registered action + */ + constructor(initialState = {}) { + this.state = initialState; + this.remotedev = connectViaExtension(); + this.remotedev.init(this.state); + } + + /** + * Registers an action that updated the current state of the storage + * + * @param {string} type - name of the action + * @param {any} payload - data written to the storage + * @param {object} stateChanges - partial state that got updated after the changes + */ + registerAction(type, payload = undefined, stateChanges = {}) { + const newState = { + ...this.state, + ...stateChanges, + }; + + this.remotedev.send({type, payload}, newState); + this.state = newState; + } + + /** + * This clears the internal state of the DevTools, preserving the keys not included in `keyToBeRemoved` + * + * @param {string[]} keysToBeRemoved + */ + clearState(keysToBeRemoved = []) { + const pairs = _.map(keysToBeRemoved, key => [key, undefined]); + this.registerAction('CLEAR', undefined, _.object((pairs))); + } +} + +export default DevTools; diff --git a/lib/Onyx.js b/lib/Onyx.js index 27f49aec..600ea1e7 100644 --- a/lib/Onyx.js +++ b/lib/Onyx.js @@ -8,6 +8,7 @@ import createDeferredTask from './createDeferredTask'; import fastMerge from './fastMerge'; import * as PerformanceUtils from './metrics/PerformanceUtils'; import Storage from './storage'; +import DevTools from './DevTools'; // Method constants const METHOD = { @@ -37,6 +38,11 @@ let recentlyAccessedKeys = []; // whatever appears in this list it will NEVER be a candidate for eviction. let evictionAllowList = []; +let devTools = { + registerAction: () => {}, + clearState: () => {}, +}; + // Holds a map of keys and connectionID arrays whose keys will never be automatically evicted as // long as we have at least one subscriber that returns false for the canEvict property. const evictionBlocklist = {}; @@ -842,7 +848,10 @@ function notifyCollectionSubscribersOnNextTick(key, value) { function remove(key) { cache.drop(key); notifySubscribersOnNextTick(key, null); - return Storage.removeItem(key); + return Storage.removeItem(key).then((result) => { + devTools.registerAction(`REMOVE/${key.toUpperCase()}`, undefined, {[key]: undefined}); + return result; + }); } /** @@ -966,6 +975,7 @@ function set(key, value) { const hasChanged = cache.hasValueChanged(key, valueWithNullRemoved); // This approach prioritizes fast UI changes without waiting for data to be stored in device storage. + broadcastUpdate(key, value, 'set'); broadcastUpdate(key, valueWithNullRemoved, hasChanged, 'set'); // If the value has not changed, calling Storage.setItem() would be redundant and a waste of performance, so return early instead. @@ -974,6 +984,10 @@ function set(key, value) { } return Storage.setItem(key, valueWithNullRemoved) + .then((result) => { + devTools.registerAction(`SET/${key.toUpperCase()}`, valueWithNullRemoved, {[key]: valueWithNullRemoved}); + return result; + }) .catch(error => evictStorageAndRetry(error, set, key, valueWithNullRemoved)); } @@ -1004,6 +1018,11 @@ function multiSet(data) { // Update cache and optimistically inform subscribers on the next tick cache.set(key, val); notifySubscribersOnNextTick(key, val); + if (_.isNull(val)) { + devTools.registerAction(`REMOVE/${key.toUpperCase()}`, val, {[key]: undefined}); + } else { + devTools.registerAction(`SET/${key.toUpperCase()}`, val, {[key]: val}); + } }); return Storage.multiSet(keyValuePairs) @@ -1098,7 +1117,10 @@ function merge(key, changes) { return Promise.resolve(); } - return Storage.mergeItem(key, batchedChanges, modifiedData); + return Storage.mergeItem(key, batchedChanges, modifiedData).then((results) => { + devTools.registerAction(`MERGE/${key.toUpperCase()}`, modifiedData, {[key]: modifiedData}); + return results; + }); } catch (error) { Logger.logAlert(`An error occurred while applying merge for key: ${key}, Error: ${error}`); } @@ -1110,9 +1132,10 @@ function merge(key, changes) { /** * Merge user provided default key value pairs. * @private + * @param {boolean} enableDevTools * @returns {Promise} */ -function initializeWithDefaultKeyStates() { +function initializeWithDefaultKeyStates(enableDevTools = false) { return Storage.multiGet(_.keys(defaultKeyStates)) .then((pairs) => { const asObject = _.object(pairs); @@ -1120,6 +1143,9 @@ function initializeWithDefaultKeyStates() { const merged = fastMerge(asObject, defaultKeyStates); cache.merge(merged); _.each(merged, (val, key) => keyChanged(key, val)); + if (enableDevTools) { + devTools = new DevTools(merged); + } }); } @@ -1167,7 +1193,7 @@ function clear(keysToPreserve = []) { // since collection key subscribers need to be updated differently if (!isKeyToPreserve) { const oldValue = cache.getValue(key); - const newValue = _.get(defaultKeyStates, key, null); + const newValue = _.get(defaultKeyStates, key, undefined); if (newValue !== oldValue) { cache.set(key, newValue); const collectionKey = key.substring(0, key.indexOf('_') + 1); @@ -1198,11 +1224,15 @@ function clear(keysToPreserve = []) { notifyCollectionSubscribersOnNextTick(key, value); }); - const defaultKeyValuePairs = _.pairs(_.omit(defaultKeyStates, keysToPreserve)); + const defaultKeyValueState = _.omit(defaultKeyStates, keysToPreserve); + const defaultKeyValuePairs = _.pairs(defaultKeyValueState); // Remove only the items that we want cleared from storage, and reset others to default _.each(keysToBeClearedFromStorage, key => cache.drop(key)); - return Storage.removeItems(keysToBeClearedFromStorage).then(() => Storage.multiSet(defaultKeyValuePairs)); + return Storage.removeItems(keysToBeClearedFromStorage).then(() => { + devTools.clearState(keysToBeClearedFromStorage); + return Storage.multiSet(defaultKeyValuePairs); + }); }); } @@ -1276,6 +1306,11 @@ function mergeCollection(collectionKey, collection) { Promise.all(_.map(existingKeys, get)).then(() => { cache.merge(collection); keysChanged(collectionKey, collection); + if (_.isNull(collection)) { + devTools.registerAction(`REMOVE/${collectionKey.toUpperCase()}`, collection, {[collectionKey]: undefined}); + } else { + devTools.registerAction(`SET/${collectionKey.toUpperCase()}`, collection, {[collectionKey]: collection}); + } }); return Promise.all(promises) @@ -1377,6 +1412,7 @@ function init({ captureMetrics = false, shouldSyncMultipleInstances = Boolean(global.localStorage), debugSetState = false, + enableDevTools = false, } = {}) { if (captureMetrics) { // The code here is only bundled and applied when the captureMetrics is set @@ -1404,7 +1440,7 @@ function init({ // Initialize all of our keys with data provided then give green light to any pending connections Promise.all([ addAllSafeEvictionKeysToRecentlyAccessedList(), - initializeWithDefaultKeyStates(), + initializeWithDefaultKeyStates(enableDevTools), ]) .then(deferredInitTask.resolve); @@ -1412,6 +1448,11 @@ function init({ Storage.keepInstancesSync((key, value) => { cache.set(key, value); keyChanged(key, value); + if (_.isNull(value)) { + devTools.registerAction(`REMOVE/${key.toUpperCase()}`, value, {[key]: undefined}); + } else { + devTools.registerAction(`SET/${key.toUpperCase()}`, value, {[key]: value}); + } }); } } diff --git a/package-lock.json b/package-lock.json index ae5101be..adb134b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,6 +41,7 @@ "react-native-quick-sqlite": "^8.0.0-beta.2", "react-test-renderer": "18.1.0", "type-fest": "^3.12.0", + "remotedev": "^0.2.9", "webpack": "^5.72.1", "webpack-cli": "^4.9.2", "webpack-merge": "^5.8.0" @@ -4676,6 +4677,12 @@ "node": ">=0.10.0" } }, + "node_modules/base-64": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", + "integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==", + "dev": true + }, "node_modules/base/node_modules/define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", @@ -10564,6 +10571,12 @@ "xmlcreate": "^2.0.3" } }, + "node_modules/jsan": { + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/jsan/-/jsan-3.1.14.tgz", + "integrity": "sha512-wStfgOJqMv4QKktuH273f5fyi3D3vy2pHOiSDGPvpcS/q+wb/M7AK3vkCcaHbkZxDOlDU/lDJgccygKSG2OhtA==", + "dev": true + }, "node_modules/jsc-android": { "version": "250230.2.1", "resolved": "https://registry.npmjs.org/jsc-android/-/jsc-android-250230.2.1.tgz", @@ -11075,6 +11088,12 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "node_modules/linked-list": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/linked-list/-/linked-list-0.1.0.tgz", + "integrity": "sha512-Zr4ovrd0ODzF3ut2TWZMdHIxb8iFdJc/P3QM4iCJdlxxGHXo69c9hGIHzLo8/FtuR9E6WUZc5irKhtPUgOKMAg==", + "dev": true + }, "node_modules/linkify-it": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", @@ -13310,6 +13329,16 @@ "node": ">=6" } }, + "node_modules/querystring": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz", + "integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", @@ -14240,6 +14269,18 @@ "jsesc": "bin/jsesc" } }, + "node_modules/remotedev": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/remotedev/-/remotedev-0.2.9.tgz", + "integrity": "sha512-W8dHOv9BcFnetFEd08yNb5O9Hd+zkTFFnf9FRjNCkb4u+JgQ/U152Aw4q83AmY3m34d6KZwhK5ip/Qc331+4vA==", + "dev": true, + "dependencies": { + "jsan": "^3.1.3", + "querystring": "^0.2.0", + "rn-host-detect": "^1.0.1", + "socketcluster-client": "^13.0.0" + } + }, "node_modules/remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", @@ -14382,6 +14423,12 @@ "rimraf": "bin.js" } }, + "node_modules/rn-host-detect": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/rn-host-detect/-/rn-host-detect-1.2.0.tgz", + "integrity": "sha512-btNg5kzHcjZZ7t7mvvV/4wNJ9e3MPgrWivkRgWURzXL0JJ0pwWlU4zrbmdlz3HHzHOxhBhHB4D+/dbMFfu4/4A==", + "dev": true + }, "node_modules/rsvp": { "version": "4.8.5", "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", @@ -14626,6 +14673,33 @@ "node": ">=10" } }, + "node_modules/sc-channel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/sc-channel/-/sc-channel-1.2.0.tgz", + "integrity": "sha512-M3gdq8PlKg0zWJSisWqAsMmTVxYRTpVRqw4CWAdKBgAfVKumFcTjoCV0hYu7lgUXccCtCD8Wk9VkkE+IXCxmZA==", + "dev": true, + "dependencies": { + "component-emitter": "1.2.1" + } + }, + "node_modules/sc-channel/node_modules/component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha512-jPatnhd33viNplKjqXKRkGU345p263OIWzDL2wH3LGIGp5Kojo+uXizHmOADRvhGFFTnJqX3jBAKP6vvmSDKcA==", + "dev": true + }, + "node_modules/sc-errors": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sc-errors/-/sc-errors-1.4.1.tgz", + "integrity": "sha512-dBn92iIonpChTxYLgKkIT/PCApvmYT6EPIbRvbQKTgY6tbEbIy8XVUv4pGyKwEK4nCmvX4TKXcN0iXC6tNW6rQ==", + "dev": true + }, + "node_modules/sc-formatter": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sc-formatter/-/sc-formatter-3.0.3.tgz", + "integrity": "sha512-lYI/lTs1u1c0geKElcj+bmEUfcP/HuKg2iDeTijPSjiTNFzN3Cf8Qh6tVd65oi7Qn+2/oD7LP4s6GC13v/9NiQ==", + "dev": true + }, "node_modules/scheduler": { "version": "0.22.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.22.0.tgz", @@ -15068,6 +15142,68 @@ "node": ">=0.10.0" } }, + "node_modules/socketcluster-client": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/socketcluster-client/-/socketcluster-client-13.0.1.tgz", + "integrity": "sha512-hxiE2xz6mgaBlhXbtBa4POgWVEvIcjCoHzf5LTUVhI9IL8V2ltV3Ze8pQsi9egqTjSz4RHPfyrJ7BiETe5Kthw==", + "dev": true, + "dependencies": { + "base-64": "0.1.0", + "clone": "2.1.1", + "component-emitter": "1.2.1", + "linked-list": "0.1.0", + "querystring": "0.2.0", + "sc-channel": "^1.2.0", + "sc-errors": "^1.4.0", + "sc-formatter": "^3.0.1", + "uuid": "3.2.1", + "ws": "5.1.1" + } + }, + "node_modules/socketcluster-client/node_modules/clone": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", + "integrity": "sha512-h5FLmEMFHeuzqmpVRcDayNlVZ+k4uK1niyKQN6oUMe7ieJihv44Vc3dY/kDnnWX4PDQSwes48s965PG/D4GntQ==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/socketcluster-client/node_modules/component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha512-jPatnhd33viNplKjqXKRkGU345p263OIWzDL2wH3LGIGp5Kojo+uXizHmOADRvhGFFTnJqX3jBAKP6vvmSDKcA==", + "dev": true + }, + "node_modules/socketcluster-client/node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/socketcluster-client/node_modules/uuid": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/socketcluster-client/node_modules/ws": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.1.1.tgz", + "integrity": "sha512-bOusvpCb09TOBLbpMKszd45WKC2KPtxiyiHanv+H2DE3Az+1db5a/L7sVJZVDPUC1Br8f0SKRr1KjLpD1U/IAw==", + "dev": true, + "dependencies": { + "async-limiter": "~1.0.0" + } + }, "node_modules/sort-array": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/sort-array/-/sort-array-4.1.4.tgz", @@ -20689,6 +20825,12 @@ } } }, + "base-64": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", + "integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==", + "dev": true + }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -25205,6 +25347,12 @@ "xmlcreate": "^2.0.3" } }, + "jsan": { + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/jsan/-/jsan-3.1.14.tgz", + "integrity": "sha512-wStfgOJqMv4QKktuH273f5fyi3D3vy2pHOiSDGPvpcS/q+wb/M7AK3vkCcaHbkZxDOlDU/lDJgccygKSG2OhtA==", + "dev": true + }, "jsc-android": { "version": "250230.2.1", "resolved": "https://registry.npmjs.org/jsc-android/-/jsc-android-250230.2.1.tgz", @@ -25621,6 +25769,12 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "linked-list": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/linked-list/-/linked-list-0.1.0.tgz", + "integrity": "sha512-Zr4ovrd0ODzF3ut2TWZMdHIxb8iFdJc/P3QM4iCJdlxxGHXo69c9hGIHzLo8/FtuR9E6WUZc5irKhtPUgOKMAg==", + "dev": true + }, "linkify-it": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", @@ -27420,6 +27574,12 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, + "querystring": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz", + "integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==", + "dev": true + }, "querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", @@ -28185,6 +28345,18 @@ } } }, + "remotedev": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/remotedev/-/remotedev-0.2.9.tgz", + "integrity": "sha512-W8dHOv9BcFnetFEd08yNb5O9Hd+zkTFFnf9FRjNCkb4u+JgQ/U152Aw4q83AmY3m34d6KZwhK5ip/Qc331+4vA==", + "dev": true, + "requires": { + "jsan": "^3.1.3", + "querystring": "^0.2.0", + "rn-host-detect": "^1.0.1", + "socketcluster-client": "^13.0.0" + } + }, "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", @@ -28295,6 +28467,12 @@ "glob": "^7.1.3" } }, + "rn-host-detect": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/rn-host-detect/-/rn-host-detect-1.2.0.tgz", + "integrity": "sha512-btNg5kzHcjZZ7t7mvvV/4wNJ9e3MPgrWivkRgWURzXL0JJ0pwWlU4zrbmdlz3HHzHOxhBhHB4D+/dbMFfu4/4A==", + "dev": true + }, "rsvp": { "version": "4.8.5", "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", @@ -28498,6 +28676,35 @@ "xmlchars": "^2.2.0" } }, + "sc-channel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/sc-channel/-/sc-channel-1.2.0.tgz", + "integrity": "sha512-M3gdq8PlKg0zWJSisWqAsMmTVxYRTpVRqw4CWAdKBgAfVKumFcTjoCV0hYu7lgUXccCtCD8Wk9VkkE+IXCxmZA==", + "dev": true, + "requires": { + "component-emitter": "1.2.1" + }, + "dependencies": { + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha512-jPatnhd33viNplKjqXKRkGU345p263OIWzDL2wH3LGIGp5Kojo+uXizHmOADRvhGFFTnJqX3jBAKP6vvmSDKcA==", + "dev": true + } + } + }, + "sc-errors": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sc-errors/-/sc-errors-1.4.1.tgz", + "integrity": "sha512-dBn92iIonpChTxYLgKkIT/PCApvmYT6EPIbRvbQKTgY6tbEbIy8XVUv4pGyKwEK4nCmvX4TKXcN0iXC6tNW6rQ==", + "dev": true + }, + "sc-formatter": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sc-formatter/-/sc-formatter-3.0.3.tgz", + "integrity": "sha512-lYI/lTs1u1c0geKElcj+bmEUfcP/HuKg2iDeTijPSjiTNFzN3Cf8Qh6tVd65oi7Qn+2/oD7LP4s6GC13v/9NiQ==", + "dev": true + }, "scheduler": { "version": "0.22.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.22.0.tgz", @@ -28858,6 +29065,59 @@ } } }, + "socketcluster-client": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/socketcluster-client/-/socketcluster-client-13.0.1.tgz", + "integrity": "sha512-hxiE2xz6mgaBlhXbtBa4POgWVEvIcjCoHzf5LTUVhI9IL8V2ltV3Ze8pQsi9egqTjSz4RHPfyrJ7BiETe5Kthw==", + "dev": true, + "requires": { + "base-64": "0.1.0", + "clone": "2.1.1", + "component-emitter": "1.2.1", + "linked-list": "0.1.0", + "querystring": "0.2.0", + "sc-channel": "^1.2.0", + "sc-errors": "^1.4.0", + "sc-formatter": "^3.0.1", + "uuid": "3.2.1", + "ws": "5.1.1" + }, + "dependencies": { + "clone": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", + "integrity": "sha512-h5FLmEMFHeuzqmpVRcDayNlVZ+k4uK1niyKQN6oUMe7ieJihv44Vc3dY/kDnnWX4PDQSwes48s965PG/D4GntQ==", + "dev": true + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha512-jPatnhd33viNplKjqXKRkGU345p263OIWzDL2wH3LGIGp5Kojo+uXizHmOADRvhGFFTnJqX3jBAKP6vvmSDKcA==", + "dev": true + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", + "dev": true + }, + "uuid": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==", + "dev": true + }, + "ws": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.1.1.tgz", + "integrity": "sha512-bOusvpCb09TOBLbpMKszd45WKC2KPtxiyiHanv+H2DE3Az+1db5a/L7sVJZVDPUC1Br8f0SKRr1KjLpD1U/IAw==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" + } + } + } + }, "sort-array": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/sort-array/-/sort-array-4.1.4.tgz", diff --git a/package.json b/package.json index 1fc8f2c3..aa0afdac 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "react-native-performance": "^2.0.0", "react-native-quick-sqlite": "^8.0.0-beta.2", "react-test-renderer": "18.1.0", + "remotedev": "^0.2.9", "type-fest": "^3.12.0", "webpack": "^5.72.1", "webpack-cli": "^4.9.2", diff --git a/tests/unit/onyxMetricsDecorationTest.js b/tests/unit/onyxMetricsDecorationTest.js index 151835c0..56c943d1 100644 --- a/tests/unit/onyxMetricsDecorationTest.js +++ b/tests/unit/onyxMetricsDecorationTest.js @@ -63,15 +63,36 @@ describe('Onyx', () => { }); // When calling decorated methods through Onyx[methodName] - const methods = ['set', 'multiSet', 'clear', 'merge', 'mergeCollection']; - methods.forEach(name => Onyx[name]('mockKey', {mockKey: {mockValue: 'mockValue'}})); + const methods = [ + { + name: 'set', + params: ['mockKey', {mockValue: 'mockValue'}], + }, + { + name: 'multiSet', + params: [{mockKey: {mockValue: 'mockValue'}}], + }, + { + name: 'clear', + params: [[]], + }, + { + name: 'merge', + params: ['mockKey', {mockKey: {mockValue: 'mockValue'}}], + }, + { + name: 'mergeCollection', + params: ['mockKey', {mockKey: {mockValue: 'mockValue'}}], + }, + ]; + methods.forEach(({name, params}) => Onyx[name](...params)); return waitForPromisesToResolve() .then(() => { // Then metrics should have captured data for each method const summaries = Onyx.getMetrics().summaries; - methods.forEach((name) => { + methods.forEach(({name}) => { expect(summaries[`Onyx:${name}`].total).toBeGreaterThan(0); }); }); diff --git a/tests/unit/onyxTest.js b/tests/unit/onyxTest.js index 5b10e258..fb076103 100644 --- a/tests/unit/onyxTest.js +++ b/tests/unit/onyxTest.js @@ -118,7 +118,7 @@ describe('Onyx', () => { }) .then(() => { // Test key should be cleared - expect(testKeyValue).toBeNull(); + expect(testKeyValue).toBeUndefined(); // Other test key should be returned to its default state expect(otherTestValue).toBe(42);