From 49348590d1417d9ed0284d103b9ec015ab1189df Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Tue, 5 Mar 2024 15:37:46 -0700 Subject: [PATCH] Merge pull request #496 from Expensify/tgolen-revert-PR475 Revert "refactor: unify storage/providers (for further InMemory storage integration) [1/3]" --- jestSetup.js | 6 +- lib/Onyx.js | 34 ++--- lib/storage/InstanceSync/index.ts | 16 -- lib/storage/InstanceSync/index.web.ts | 65 --------- lib/storage/NativeStorage.ts | 3 + lib/storage/WebStorage.ts | 73 +++++++++ lib/storage/__mocks__/index.ts | 4 - lib/storage/index.native.ts | 3 + lib/storage/index.ts | 138 +----------------- lib/storage/platforms/index.native.ts | 3 - lib/storage/platforms/index.ts | 3 - .../{IDBKeyValProvider.ts => IDBKeyVal.ts} | 36 ++--- .../{SQLiteProvider.ts => SQLiteStorage.ts} | 27 ++-- lib/storage/providers/types.ts | 6 +- .../providers/IDBKeyvalProviderTest.js | 2 +- .../storage/providers/StorageProviderTest.js | 10 +- 16 files changed, 128 insertions(+), 301 deletions(-) delete mode 100644 lib/storage/InstanceSync/index.ts delete mode 100644 lib/storage/InstanceSync/index.web.ts create mode 100644 lib/storage/NativeStorage.ts create mode 100644 lib/storage/WebStorage.ts create mode 100644 lib/storage/index.native.ts delete mode 100644 lib/storage/platforms/index.native.ts delete mode 100644 lib/storage/platforms/index.ts rename lib/storage/providers/{IDBKeyValProvider.ts => IDBKeyVal.ts} (74%) rename lib/storage/providers/{SQLiteProvider.ts => SQLiteStorage.ts} (85%) diff --git a/jestSetup.js b/jestSetup.js index 0a5ef85fb..2288af999 100644 --- a/jestSetup.js +++ b/jestSetup.js @@ -1,7 +1,7 @@ jest.mock('./lib/storage'); -jest.mock('./lib/storage/platforms/index.native', () => require('./lib/storage/__mocks__')); -jest.mock('./lib/storage/platforms/index', () => require('./lib/storage/__mocks__')); -jest.mock('./lib/storage/providers/IDBKeyValProvider', () => require('./lib/storage/__mocks__')); +jest.mock('./lib/storage/NativeStorage', () => require('./lib/storage/__mocks__')); +jest.mock('./lib/storage/WebStorage', () => require('./lib/storage/__mocks__')); +jest.mock('./lib/storage/providers/IDBKeyVal', () => require('./lib/storage/__mocks__')); jest.mock('react-native-device-info', () => ({getFreeDiskStorage: () => {}})); jest.mock('react-native-quick-sqlite', () => ({ diff --git a/lib/Onyx.js b/lib/Onyx.js index b5409bfe4..301003170 100644 --- a/lib/Onyx.js +++ b/lib/Onyx.js @@ -1554,31 +1554,7 @@ function setMemoryOnlyKeys(keyList) { * }, * }); */ -function init({ - keys = {}, - initialKeyStates = {}, - safeEvictionKeys = [], - maxCachedKeysCount = 1000, - captureMetrics = false, - shouldSyncMultipleInstances = Boolean(global.localStorage), - debugSetState = false, -} = {}) { - Storage.init(); - - if (shouldSyncMultipleInstances) { - Storage.keepInstancesSync((key, value) => { - const prevValue = cache.getValue(key, false); - cache.set(key, value); - keyChanged(key, value, prevValue); - }); - } - - if (captureMetrics) { - // The code here is only bundled and applied when the captureMetrics is set - // eslint-disable-next-line no-use-before-define - applyDecorators(); - } - +function init({keys = {}, initialKeyStates = {}, safeEvictionKeys = [], maxCachedKeysCount = 1000, shouldSyncMultipleInstances = Boolean(global.localStorage), debugSetState = false} = {}) { if (debugSetState) { PerformanceUtils.setShouldDebugSetState(true); } @@ -1609,6 +1585,14 @@ function init({ // Initialize all of our keys with data provided then give green light to any pending connections Promise.all([addAllSafeEvictionKeysToRecentlyAccessedList(), initializeWithDefaultKeyStates()]).then(deferredInitTask.resolve); + + if (shouldSyncMultipleInstances && _.isFunction(Storage.keepInstancesSync)) { + Storage.keepInstancesSync((key, value) => { + const prevValue = cache.getValue(key, false); + cache.set(key, value); + keyChanged(key, value, prevValue); + }); + } } const Onyx = { diff --git a/lib/storage/InstanceSync/index.ts b/lib/storage/InstanceSync/index.ts deleted file mode 100644 index 327fbefdb..000000000 --- a/lib/storage/InstanceSync/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import NOOP from 'lodash/noop'; - -/** - * This is used to keep multiple browser tabs in sync, therefore only needed on web - * On native platforms, we omit this syncing logic by setting this to mock implementation. - */ -const InstanceSync = { - init: NOOP, - setItem: NOOP, - removeItem: NOOP, - removeItems: NOOP, - mergeItem: NOOP, - clear: void>(callback: T) => Promise.resolve(callback()), -}; - -export default InstanceSync; diff --git a/lib/storage/InstanceSync/index.web.ts b/lib/storage/InstanceSync/index.web.ts deleted file mode 100644 index a06fcd572..000000000 --- a/lib/storage/InstanceSync/index.web.ts +++ /dev/null @@ -1,65 +0,0 @@ -/* eslint-disable no-invalid-this */ -/** - * The InstancesSync object provides data-changed events like the ones that exist - * when using LocalStorage APIs in the browser. These events are great because multiple tabs can listen for when - * data changes and then stay up-to-date with everything happening in Onyx. - */ -import type {KeyList, Key, OnStorageKeyChanged, Value} from '../providers/types'; - -const SYNC_ONYX = 'SYNC_ONYX'; - -/** - * Raise an event through `localStorage` to let other tabs know a value changed - * @param {String} onyxKey - */ -function raiseStorageSyncEvent(onyxKey: Key) { - global.localStorage.setItem(SYNC_ONYX, onyxKey); - global.localStorage.removeItem(SYNC_ONYX); -} - -function raiseStorageSyncManyKeysEvent(onyxKeys: KeyList) { - onyxKeys.forEach((onyxKey) => { - raiseStorageSyncEvent(onyxKey); - }); -} - -const InstanceSync = { - /** - * @param {Function} onStorageKeyChanged Storage synchronization mechanism keeping all opened tabs in sync - */ - init: (onStorageKeyChanged: OnStorageKeyChanged) => { - // This listener will only be triggered by events coming from other tabs - global.addEventListener('storage', (event) => { - // Ignore events that don't originate from the SYNC_ONYX logic - if (event.key !== SYNC_ONYX || !event.newValue) { - return; - } - - const onyxKey = event.newValue; - // @ts-expect-error `this` will be substituted later in actual function call - this.getItem(onyxKey).then((value: Value) => onStorageKeyChanged(onyxKey, value)); - }); - }, - setItem: raiseStorageSyncEvent, - removeItem: raiseStorageSyncEvent, - removeItems: raiseStorageSyncManyKeysEvent, - mergeItem: raiseStorageSyncEvent, - clear: (clearImplementation: () => void) => { - let allKeys: KeyList; - - // The keys must be retrieved before storage is cleared or else the list of keys would be empty - // @ts-expect-error `this` will be substituted later in actual function call - return this.getAllKeys() - .then((keys: KeyList) => { - allKeys = keys; - }) - .then(() => clearImplementation()) - .then(() => { - // Now that storage is cleared, the storage sync event can happen which is a more atomic action - // for other browser tabs - raiseStorageSyncManyKeysEvent(allKeys); - }); - }, -}; - -export default InstanceSync; diff --git a/lib/storage/NativeStorage.ts b/lib/storage/NativeStorage.ts new file mode 100644 index 000000000..1473613fa --- /dev/null +++ b/lib/storage/NativeStorage.ts @@ -0,0 +1,3 @@ +import SQLiteStorage from './providers/SQLiteStorage'; + +export default SQLiteStorage; diff --git a/lib/storage/WebStorage.ts b/lib/storage/WebStorage.ts new file mode 100644 index 000000000..439215b90 --- /dev/null +++ b/lib/storage/WebStorage.ts @@ -0,0 +1,73 @@ +/** + * This file is here to wrap IDBKeyVal with a layer that provides data-changed events like the ones that exist + * when using LocalStorage APIs in the browser. These events are great because multiple tabs can listen for when + * data changes and then stay up-to-date with everything happening in Onyx. + */ +import Storage from './providers/IDBKeyVal'; +import type {KeyList, Key} from './providers/types'; +import type StorageProvider from './providers/types'; + +const SYNC_ONYX = 'SYNC_ONYX'; + +/** + * Raise an event thorough `localStorage` to let other tabs know a value changed + */ +function raiseStorageSyncEvent(onyxKey: Key) { + global.localStorage.setItem(SYNC_ONYX, onyxKey); + global.localStorage.removeItem(SYNC_ONYX); +} + +function raiseStorageSyncManyKeysEvent(onyxKeys: KeyList) { + onyxKeys.forEach((onyxKey) => { + raiseStorageSyncEvent(onyxKey); + }); +} + +const webStorage: StorageProvider = { + ...Storage, + /** + * @param onStorageKeyChanged Storage synchronization mechanism keeping all opened tabs in sync + */ + keepInstancesSync(onStorageKeyChanged) { + // Override set, remove and clear to raise storage events that we intercept in other tabs + this.setItem = (key, value) => Storage.setItem(key, value).then(() => raiseStorageSyncEvent(key)); + + this.removeItem = (key) => Storage.removeItem(key).then(() => raiseStorageSyncEvent(key)); + + this.removeItems = (keys) => Storage.removeItems(keys).then(() => raiseStorageSyncManyKeysEvent(keys)); + + this.mergeItem = (key, batchedChanges, modifiedData) => Storage.mergeItem(key, batchedChanges, modifiedData).then(() => raiseStorageSyncEvent(key)); + + // If we just call Storage.clear other tabs will have no idea which keys were available previously + // so that they can call keysChanged for them. That's why we iterate over every key and raise a storage sync + // event for each one + this.clear = () => { + let allKeys: KeyList; + + // The keys must be retrieved before storage is cleared or else the list of keys would be empty + return Storage.getAllKeys() + .then((keys) => { + allKeys = keys; + }) + .then(() => Storage.clear()) + .then(() => { + // Now that storage is cleared, the storage sync event can happen which is a more atomic action + // for other browser tabs + allKeys.forEach(raiseStorageSyncEvent); + }); + }; + + // This listener will only be triggered by events coming from other tabs + global.addEventListener('storage', (event) => { + // Ignore events that don't originate from the SYNC_ONYX logic + if (event.key !== SYNC_ONYX || !event.newValue) { + return; + } + + const onyxKey = event.newValue; + Storage.getItem(onyxKey).then((value) => onStorageKeyChanged(onyxKey, value)); + }); + }, +}; + +export default webStorage; diff --git a/lib/storage/__mocks__/index.ts b/lib/storage/__mocks__/index.ts index 2c8578ea2..e202c0f6d 100644 --- a/lib/storage/__mocks__/index.ts +++ b/lib/storage/__mocks__/index.ts @@ -10,7 +10,6 @@ const set = jest.fn((key, value) => { }); const idbKeyvalMock: StorageProvider = { - init: () => undefined, setItem(key, value) { return set(key, value); }, @@ -61,12 +60,10 @@ const idbKeyvalMock: StorageProvider = { }, // eslint-disable-next-line @typescript-eslint/no-empty-function setMemoryOnlyKeys() {}, - keepInstancesSync: () => undefined, }; const idbKeyvalMockSpy = { idbKeyvalSet: set, - init: jest.fn(idbKeyvalMock.init), setItem: jest.fn(idbKeyvalMock.setItem), getItem: jest.fn(idbKeyvalMock.getItem), removeItem: jest.fn(idbKeyvalMock.removeItem), @@ -83,7 +80,6 @@ const idbKeyvalMockSpy = { }), getDatabaseSize: jest.fn(idbKeyvalMock.getDatabaseSize), setMemoryOnlyKeys: jest.fn(idbKeyvalMock.setMemoryOnlyKeys), - keepInstancesSync: jest.fn(idbKeyvalMock.keepInstancesSync), }; export default idbKeyvalMockSpy; diff --git a/lib/storage/index.native.ts b/lib/storage/index.native.ts new file mode 100644 index 000000000..51b21ca5a --- /dev/null +++ b/lib/storage/index.native.ts @@ -0,0 +1,3 @@ +import NativeStorage from './NativeStorage'; + +export default NativeStorage; diff --git a/lib/storage/index.ts b/lib/storage/index.ts index a38348fc2..4ee520d20 100644 --- a/lib/storage/index.ts +++ b/lib/storage/index.ts @@ -1,137 +1,3 @@ -import PlatformStorage from './platforms'; -import InstanceSync from './InstanceSync'; -import type StorageProvider from './providers/types'; +import WebStorage from './WebStorage'; -const provider = PlatformStorage; -let shouldKeepInstancesSync = false; - -type Storage = { - getStorageProvider: () => StorageProvider; -} & StorageProvider; - -const Storage: Storage = { - /** - * Returns the storage provider currently in use - */ - getStorageProvider() { - return provider; - }, - - /** - * Initializes all providers in the list of storage providers - * and enables fallback providers if necessary - */ - init() { - provider.init(); - }, - - /** - * Get the value of a given key or return `null` if it's not available - */ - getItem: (key) => provider.getItem(key), - - /** - * Get multiple key-value pairs for the give array of keys in a batch - */ - multiGet: (keys) => provider.multiGet(keys), - - /** - * Sets the value for a given key. The only requirement is that the value should be serializable to JSON string - */ - setItem: (key, value) => { - const promise = provider.setItem(key, value); - - if (shouldKeepInstancesSync) { - return promise.then(() => InstanceSync.setItem(key)); - } - - return promise; - }, - - /** - * Stores multiple key-value pairs in a batch - */ - multiSet: (pairs) => provider.multiSet(pairs), - - /** - * Merging an existing value with a new one - */ - mergeItem: (key, changes, modifiedData) => { - const promise = provider.mergeItem(key, changes, modifiedData); - - if (shouldKeepInstancesSync) { - return promise.then(() => InstanceSync.mergeItem(key)); - } - - return promise; - }, - - /** - * Multiple merging of existing and new values in a batch - * This function also removes all nested null values from an object. - */ - multiMerge: (pairs) => provider.multiMerge(pairs), - - /** - * Removes given key and its value - */ - removeItem: (key) => { - const promise = provider.removeItem(key); - - if (shouldKeepInstancesSync) { - return promise.then(() => InstanceSync.removeItem(key)); - } - - return promise; - }, - - /** - * Remove given keys and their values - */ - removeItems: (keys) => { - const promise = provider.removeItems(keys); - - if (shouldKeepInstancesSync) { - return promise.then(() => InstanceSync.removeItems(keys)); - } - - return promise; - }, - - /** - * Clears everything - */ - clear: () => { - if (shouldKeepInstancesSync) { - return InstanceSync.clear(() => provider.clear()); - } - - return provider.clear(); - }, - - // This is a noop for now in order to keep clients from crashing see https://github.com/Expensify/Expensify/issues/312438 - setMemoryOnlyKeys: () => provider.setMemoryOnlyKeys(), - - /** - * Returns all available keys - */ - getAllKeys: () => provider.getAllKeys(), - - /** - * Gets the total bytes of the store - */ - getDatabaseSize: () => provider.getDatabaseSize(), - - /** - * @param onStorageKeyChanged - Storage synchronization mechanism keeping all opened tabs in sync (web only) - */ - keepInstancesSync(onStorageKeyChanged) { - // If InstanceSync is null, it means we're on a native platform and we don't need to keep instances in sync - if (InstanceSync == null) return; - - shouldKeepInstancesSync = true; - InstanceSync.init(onStorageKeyChanged); - }, -}; - -export default Storage; +export default WebStorage; diff --git a/lib/storage/platforms/index.native.ts b/lib/storage/platforms/index.native.ts deleted file mode 100644 index 95822c4a5..000000000 --- a/lib/storage/platforms/index.native.ts +++ /dev/null @@ -1,3 +0,0 @@ -import NativeStorage from '../providers/SQLiteProvider'; - -export default NativeStorage; diff --git a/lib/storage/platforms/index.ts b/lib/storage/platforms/index.ts deleted file mode 100644 index 0b95dc97d..000000000 --- a/lib/storage/platforms/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import WebStorage from '../providers/IDBKeyValProvider'; - -export default WebStorage; diff --git a/lib/storage/providers/IDBKeyValProvider.ts b/lib/storage/providers/IDBKeyVal.ts similarity index 74% rename from lib/storage/providers/IDBKeyValProvider.ts rename to lib/storage/providers/IDBKeyVal.ts index ea3593999..bc429bc26 100644 --- a/lib/storage/providers/IDBKeyValProvider.ts +++ b/lib/storage/providers/IDBKeyVal.ts @@ -6,23 +6,19 @@ import type {Value} from './types'; // We don't want to initialize the store while the JS bundle loads as idb-keyval will try to use global.indexedDB // which might not be available in certain environments that load the bundle (e.g. electron main process). -let idbKeyValStore: UseStore; +let customStoreInstance: UseStore; +function getCustomStore(): UseStore { + if (!customStoreInstance) { + customStoreInstance = createStore('OnyxDB', 'keyvaluepairs'); + } + return customStoreInstance; +} const provider: StorageProvider = { - /** - * Initializes the storage provider - */ - init() { - const newIdbKeyValStore = createStore('OnyxDB', 'keyvaluepairs'); - - if (newIdbKeyValStore == null) throw Error('IDBKeyVal store could not be created'); - - idbKeyValStore = newIdbKeyValStore; - }, - setItem: (key, value) => set(key, value, idbKeyValStore), - multiGet: (keysParam) => getMany(keysParam, idbKeyValStore).then((values) => values.map((value, index) => [keysParam[index], value])), + setItem: (key, value) => set(key, value, getCustomStore()), + multiGet: (keysParam) => getMany(keysParam, getCustomStore()).then((values) => values.map((value, index) => [keysParam[index], value])), multiMerge: (pairs) => - idbKeyValStore('readwrite', (store) => { + getCustomStore()('readwrite', (store) => { // Note: we are using the manual store transaction here, to fit the read and update // of the items in one transaction to achieve best performance. const getValues = Promise.all(pairs.map(([key]) => promisifyRequest(store.get(key)))); @@ -41,17 +37,17 @@ const provider: StorageProvider = { // Since Onyx also merged the existing value with the changes, we can just set the value directly return provider.setItem(key, modifiedData); }, - multiSet: (pairs) => setMany(pairs, idbKeyValStore), - clear: () => clear(idbKeyValStore), + multiSet: (pairs) => setMany(pairs, getCustomStore()), + clear: () => clear(getCustomStore()), // eslint-disable-next-line @typescript-eslint/no-empty-function setMemoryOnlyKeys: () => {}, - getAllKeys: () => keys(idbKeyValStore), + getAllKeys: () => keys(getCustomStore()), getItem: (key) => - get(key, idbKeyValStore) + get(key, getCustomStore()) // idb-keyval returns undefined for missing items, but this needs to return null so that idb-keyval does the same thing as SQLiteStorage. .then((val) => (val === undefined ? null : val)), - removeItem: (key) => del(key, idbKeyValStore), - removeItems: (keysParam) => delMany(keysParam, idbKeyValStore), + removeItem: (key) => del(key, getCustomStore()), + removeItems: (keysParam) => delMany(keysParam, getCustomStore()), getDatabaseSize() { if (!window.navigator || !window.navigator.storage) { throw new Error('StorageManager browser API unavailable'); diff --git a/lib/storage/providers/SQLiteProvider.ts b/lib/storage/providers/SQLiteStorage.ts similarity index 85% rename from lib/storage/providers/SQLiteProvider.ts rename to lib/storage/providers/SQLiteStorage.ts index 4b93e821c..6011277bc 100644 --- a/lib/storage/providers/SQLiteProvider.ts +++ b/lib/storage/providers/SQLiteStorage.ts @@ -2,7 +2,7 @@ * The SQLiteStorage provider stores everything in a key/value store by * converting the value to a JSON string */ -import type {BatchQueryResult, QuickSQLiteConnection} from 'react-native-quick-sqlite'; +import type {BatchQueryResult} from 'react-native-quick-sqlite'; import {open} from 'react-native-quick-sqlite'; import {getFreeDiskStorage} from 'react-native-device-info'; import type StorageProvider from './types'; @@ -10,23 +10,17 @@ import utils from '../../utils'; import type {KeyList, KeyValuePairList} from './types'; const DB_NAME = 'OnyxDB'; -let db: QuickSQLiteConnection; +const db = open({name: DB_NAME}); -const provider: StorageProvider = { - /** - * Initializes the storage provider - */ - init() { - db = open({name: DB_NAME}); +db.execute('CREATE TABLE IF NOT EXISTS keyvaluepairs (record_key TEXT NOT NULL PRIMARY KEY , valueJSON JSON NOT NULL) WITHOUT ROWID;'); - db.execute('CREATE TABLE IF NOT EXISTS keyvaluepairs (record_key TEXT NOT NULL PRIMARY KEY , valueJSON JSON NOT NULL) WITHOUT ROWID;'); +// All of the 3 pragmas below were suggested by SQLite team. +// You can find more info about them here: https://www.sqlite.org/pragma.html +db.execute('PRAGMA CACHE_SIZE=-20000;'); +db.execute('PRAGMA synchronous=NORMAL;'); +db.execute('PRAGMA journal_mode=WAL;'); - // All of the 3 pragmas below were suggested by SQLite team. - // You can find more info about them here: https://www.sqlite.org/pragma.html - db.execute('PRAGMA CACHE_SIZE=-20000;'); - db.execute('PRAGMA synchronous=NORMAL;'); - db.execute('PRAGMA journal_mode=WAL;'); - }, +const provider: StorageProvider = { getItem(key) { return db.executeAsync('SELECT record_key, valueJSON FROM keyvaluepairs WHERE record_key = ?;', [key]).then(({rows}) => { if (!rows || rows?.length === 0) { @@ -101,6 +95,9 @@ const provider: StorageProvider = { // eslint-disable-next-line @typescript-eslint/no-empty-function setMemoryOnlyKeys: () => {}, + + // eslint-disable-next-line @typescript-eslint/no-empty-function + keepInstancesSync: () => {}, }; export default provider; diff --git a/lib/storage/providers/types.ts b/lib/storage/providers/types.ts index 8749034d4..3f8113729 100644 --- a/lib/storage/providers/types.ts +++ b/lib/storage/providers/types.ts @@ -9,10 +9,6 @@ type KeyValuePairList = KeyValuePair[]; type OnStorageKeyChanged = (key: Key, value: Value | null) => void; type StorageProvider = { - /** - * Initializes the storage provider - */ - init: () => void; /** * Gets the value of a given key or return `null` if it's not available in storage */ @@ -82,4 +78,4 @@ type StorageProvider = { }; export default StorageProvider; -export type {Value, Key, KeyList, KeyValuePairList, OnStorageKeyChanged}; +export type {Value, Key, KeyList, KeyValuePairList}; diff --git a/tests/unit/storage/providers/IDBKeyvalProviderTest.js b/tests/unit/storage/providers/IDBKeyvalProviderTest.js index 9fa895d51..ea4f5601a 100644 --- a/tests/unit/storage/providers/IDBKeyvalProviderTest.js +++ b/tests/unit/storage/providers/IDBKeyvalProviderTest.js @@ -1,6 +1,6 @@ import _ from 'underscore'; -import IDBKeyValProviderMock from '../../../../lib/storage/providers/IDBKeyValProvider'; +import IDBKeyValProviderMock from '../../../../lib/storage/providers/IDBKeyVal'; import createDeferredTask from '../../../../lib/createDeferredTask'; import waitForPromisesToResolve from '../../../utils/waitForPromisesToResolve'; diff --git a/tests/unit/storage/providers/StorageProviderTest.js b/tests/unit/storage/providers/StorageProviderTest.js index 5ce43cfc9..82aca46b5 100644 --- a/tests/unit/storage/providers/StorageProviderTest.js +++ b/tests/unit/storage/providers/StorageProviderTest.js @@ -1,11 +1,11 @@ /* eslint-disable import/first */ -jest.unmock('../../../../lib/storage/platforms/index.native'); -jest.unmock('../../../../lib/storage/platforms/index'); -jest.unmock('../../../../lib/storage/providers/IDBKeyValProvider'); +jest.unmock('../../../../lib/storage/NativeStorage'); +jest.unmock('../../../../lib/storage/WebStorage'); +jest.unmock('../../../../lib/storage/providers/IDBKeyVal'); import _ from 'underscore'; -import NativeStorage from '../../../../lib/storage/platforms/index.native'; -import WebStorage from '../../../../lib/storage/platforms/index'; +import NativeStorage from '../../../../lib/storage/NativeStorage'; +import WebStorage from '../../../../lib/storage/WebStorage'; it('storage providers have same methods implemented', () => { const nativeMethods = _.keys(NativeStorage);