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

Platform specific mocks #2

Draft
wants to merge 25 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
763cc75
add multiMerge and multiSet to tab synchronization
bernhardoj Mar 26, 2024
5348f2c
prettier
bernhardoj Mar 26, 2024
3a6ed5f
Merge branch 'main' into fix/sync-multi-set-and-merge
bernhardoj Mar 27, 2024
e7cfee5
synchronize multiMerge and multiSet between tabs
bernhardoj Mar 27, 2024
ae41bd6
Add commit token
roryabraham Mar 27, 2024
b950a0e
chore: log error additionally
kirillzyusko Mar 27, 2024
f64fddb
Merge pull request #522 from Expensify/Rory-FixOnyxPublish
luacmartins Mar 27, 2024
b0cde54
2.0.23
OSBotify Mar 27, 2024
cfd00c0
Merge pull request #521 from bernhardoj/fix/sync-multi-set-and-merge
yuwenmemon Mar 27, 2024
905026a
2.0.24
OSBotify Mar 27, 2024
e2225f4
Merge pull request #524 from margelo/chore/log-additional-error
marcaaron Mar 28, 2024
667e0d2
2.0.25
OSBotify Mar 28, 2024
cd2d014
Exposes ResultMetada type
fabioh8010 Apr 4, 2024
b9f48b5
Merge pull request #527 from fabioh8010/feature/expose-result-metadat…
cristipaval Apr 5, 2024
9f6c405
2.0.26
OSBotify Apr 5, 2024
5a2e047
update storage provider types and add mock type
chrispader Apr 9, 2024
06096a3
add .yalc to .gitignore
chrispader Apr 9, 2024
91f68e2
fix: jest-haste-map warning
chrispader Apr 9, 2024
3d37ad4
update jestSetup config with new mocks
chrispader Apr 9, 2024
6e81607
update mock and test
chrispader Apr 9, 2024
af3d146
update mock
chrispader Apr 9, 2024
cfb7639
update tests
chrispader Apr 9, 2024
2db6236
remove delta
chrispader Apr 9, 2024
a257dc2
update mocks and tests
chrispader Apr 9, 2024
e585fd4
update tests
chrispader Apr 9, 2024
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
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@ dist/
.github/OSBotify-private-key.asc

# Published package
*.tgz
*.tgz

# Yalc
.yalc
yalc.lock
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module.exports = {
transform: {
'\\.[jt]sx?$': 'babel-jest',
},
modulePathIgnorePatterns: ['<rootDir>/dist/'],
testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/tests/unit/mocks/', '<rootDir>/tests/e2e/'],
testMatch: ['**/tests/unit/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'],
globals: {
Expand Down
23 changes: 21 additions & 2 deletions jestSetup.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,26 @@
jest.mock('idb-keyval', () => {
const idbKeyValMockBase = require('./node_modules/idb-keyval/dist/mock');

Check failure on line 2 in jestSetup.js

View workflow job for this annotation

GitHub Actions / lint

Missing file extension for "./node_modules/idb-keyval/dist/mock"

return {
clear: jest.fn(idbKeyValMockBase.clear),
createStore: jest.fn(idbKeyValMockBase.createStore),
del: jest.fn(idbKeyValMockBase.del),
delMany: jest.fn(idbKeyValMockBase.delMany),
entries: jest.fn(idbKeyValMockBase.entries),
get: jest.fn(idbKeyValMockBase.get),
getMany: jest.fn(idbKeyValMockBase.getMany),
keys: jest.fn(idbKeyValMockBase.keys),
promisifyRequest: jest.fn(idbKeyValMockBase.promisifyRequest),
set: jest.fn(idbKeyValMockBase.set),
setMany: jest.fn(idbKeyValMockBase.setMany),
update: jest.fn(idbKeyValMockBase.update),
values: jest.fn(idbKeyValMockBase.values),
};
});

jest.mock('./lib/storage');
jest.mock('./lib/storage/providers/IDBKeyValProvider', () => require('./lib/storage/providers/__mocks__/IDBKeyValProvider'));
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('react-native-device-info', () => ({getFreeDiskStorage: () => {}}));
jest.mock('react-native-quick-sqlite', () => ({
Expand Down
11 changes: 9 additions & 2 deletions lib/Logger.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
type LogData = {
message: string;
level: 'alert' | 'info';
level: 'alert' | 'info' | 'hmmm';
};
type LoggerCallback = (data: LogData) => void;

Expand Down Expand Up @@ -28,4 +28,11 @@ function logInfo(message: string) {
logger({message: `[Onyx] ${message}`, level: 'info'});
}

export {registerLogger, logInfo, logAlert};
/**
* Send an hmmm message to the logger
*/
function logHmmm(message: string) {
logger({message: `[Onyx] ${message}`, level: 'hmmm'});
}

export {registerLogger, logInfo, logAlert, logHmmm};
3 changes: 2 additions & 1 deletion lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Onyx from './Onyx';
import type {OnyxUpdate, ConnectOptions} from './Onyx';
import type {CustomTypeOptions, OnyxCollection, OnyxEntry, NullishDeep, KeyValueMapping, OnyxKey, Selector, WithOnyxInstanceState, OnyxValue} from './types';
import type {UseOnyxResult, FetchStatus} from './useOnyx';
import type {UseOnyxResult, FetchStatus, ResultMetadata} from './useOnyx';
import useOnyx from './useOnyx';
import withOnyx from './withOnyx';

Expand All @@ -21,4 +21,5 @@ export type {
UseOnyxResult,
OnyxValue,
FetchStatus,
ResultMetadata,
};
2 changes: 2 additions & 0 deletions lib/storage/InstanceSync/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const InstanceSync = {
setItem: NOOP,
removeItem: NOOP,
removeItems: NOOP,
multiMerge: NOOP,
multiSet: NOOP,
mergeItem: NOOP,
clear: <T extends () => void>(callback: T) => Promise.resolve(callback()),
};
Expand Down
2 changes: 2 additions & 0 deletions lib/storage/InstanceSync/index.web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import type {OnyxKey} from '../../types';
import NoopProvider from '../providers/NoopProvider';
import type {KeyList, OnStorageKeyChanged} from '../providers/types';
import type StorageProvider from '../providers/types';

Check failure on line 9 in lib/storage/InstanceSync/index.web.ts

View workflow job for this annotation

GitHub Actions / lint

Module '"/home/runner/work/react-native-onyx/react-native-onyx/lib/storage/providers/types"' has no default export. Did you mean to use 'import { StorageProvider } from "/home/runner/work/react-native-onyx/react-native-onyx/lib/storage/providers/types"' instead?

Check failure on line 9 in lib/storage/InstanceSync/index.web.ts

View workflow job for this annotation

GitHub Actions / lint

Using exported name 'StorageProvider' as identifier for default export

const SYNC_ONYX = 'SYNC_ONYX';

Expand Down Expand Up @@ -50,6 +50,8 @@
setItem: raiseStorageSyncEvent,
removeItem: raiseStorageSyncEvent,
removeItems: raiseStorageSyncManyKeysEvent,
multiMerge: raiseStorageSyncManyKeysEvent,
multiSet: raiseStorageSyncManyKeysEvent,
mergeItem: raiseStorageSyncEvent,
clear: (clearImplementation: () => void) => {
let allKeys: KeyList;
Expand Down
24 changes: 21 additions & 3 deletions lib/storage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import PlatformStorage from './platforms';
import InstanceSync from './InstanceSync';
import MemoryOnlyProvider from './providers/MemoryOnlyProvider';
import type StorageProvider from './providers/types';

Check failure on line 6 in lib/storage/index.ts

View workflow job for this annotation

GitHub Actions / lint

Module '"/home/runner/work/react-native-onyx/react-native-onyx/lib/storage/providers/types"' has no default export. Did you mean to use 'import { StorageProvider } from "/home/runner/work/react-native-onyx/react-native-onyx/lib/storage/providers/types"' instead?

Check failure on line 6 in lib/storage/index.ts

View workflow job for this annotation

GitHub Actions / lint

Using exported name 'StorageProvider' as identifier for default export

let provider = PlatformStorage;
let shouldKeepInstancesSync = false;
Expand All @@ -20,7 +20,7 @@
* Degrade performance by removing the storage provider and only using cache
*/
function degradePerformance(error: Error) {
Logger.logAlert(`Error while using ${provider.name}. Falling back to only using cache and dropping storage.`);
Logger.logHmmm(`Error while using ${provider.name}. Falling back to only using cache and dropping storage.\n Error: ${error.message}\n Stack: ${error.stack}\n Cause: ${error.cause}`);
console.error(error);
provider = MemoryOnlyProvider;
}
Expand Down Expand Up @@ -76,17 +76,17 @@
/**
* Get the value of a given key or return `null` if it's not available
*/
getItem: (key) => tryOrDegradePerformance(() => provider.getItem(key)),

Check failure on line 79 in lib/storage/index.ts

View workflow job for this annotation

GitHub Actions / lint

Parameter 'key' implicitly has an 'any' type.

/**
* Get multiple key-value pairs for the give array of keys in a batch
*/
multiGet: (keys) => tryOrDegradePerformance(() => provider.multiGet(keys)),

Check failure on line 84 in lib/storage/index.ts

View workflow job for this annotation

GitHub Actions / lint

Parameter 'keys' implicitly has an 'any' type.

/**
* Sets the value for a given key. The only requirement is that the value should be serializable to JSON string
*/
setItem: (key, value) =>

Check failure on line 89 in lib/storage/index.ts

View workflow job for this annotation

GitHub Actions / lint

Parameter 'key' implicitly has an 'any' type.

Check failure on line 89 in lib/storage/index.ts

View workflow job for this annotation

GitHub Actions / lint

Parameter 'value' implicitly has an 'any' type.
tryOrDegradePerformance(() => {
const promise = provider.setItem(key, value);

Expand All @@ -100,7 +100,16 @@
/**
* Stores multiple key-value pairs in a batch
*/
multiSet: (pairs) => tryOrDegradePerformance(() => provider.multiSet(pairs)),
multiSet: (pairs) =>
tryOrDegradePerformance(() => {
const promise = provider.multiSet(pairs);

if (shouldKeepInstancesSync) {
return promise.then(() => InstanceSync.multiSet(pairs.map((pair) => pair[0])));
}

return promise;
}),

/**
* Merging an existing value with a new one
Expand All @@ -120,7 +129,16 @@
* Multiple merging of existing and new values in a batch
* This function also removes all nested null values from an object.
*/
multiMerge: (pairs) => tryOrDegradePerformance(() => provider.multiMerge(pairs)),
multiMerge: (pairs) =>
tryOrDegradePerformance(() => {
const promise = provider.multiMerge(pairs);

if (shouldKeepInstancesSync) {
return promise.then(() => InstanceSync.multiMerge(pairs.map((pair) => pair[0])));
}

return promise;
}),

/**
* Removes given key and its value
Expand Down
4 changes: 3 additions & 1 deletion lib/storage/providers/IDBKeyValProvider.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type {UseStore} from 'idb-keyval';
import {set, keys, getMany, setMany, get, clear, del, delMany, createStore, promisifyRequest} from 'idb-keyval';
import utils from '../../utils';
import type StorageProvider from './types';
import type {StorageProvider} from './types';
import type {OnyxKey, OnyxValue} 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;

Check failure on line 9 in lib/storage/providers/IDBKeyValProvider.ts

View workflow job for this annotation

GitHub Actions / lint

Exporting mutable 'let' binding, use 'const' instead

const provider: StorageProvider = {
/**
Expand Down Expand Up @@ -71,3 +71,5 @@
};

export default provider;

export {idbKeyValStore};
3 changes: 1 addition & 2 deletions lib/storage/providers/MemoryOnlyProvider.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import _ from 'underscore';
import utils from '../../utils';
import type StorageProvider from './types';
import type {KeyValuePair} from './types';
import type {KeyValuePair, StorageProvider} from './types';
import type {OnyxKey, OnyxValue} from '../../types';

type Store = Record<OnyxKey, OnyxValue<OnyxKey>>;
Expand Down
2 changes: 1 addition & 1 deletion lib/storage/providers/NoopProvider.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type StorageProvider from './types';
import type {StorageProvider} from './types';

const provider: StorageProvider = {
/**
Expand Down
3 changes: 1 addition & 2 deletions lib/storage/providers/SQLiteProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
import type {BatchQueryResult, QuickSQLiteConnection} from 'react-native-quick-sqlite';
import {open} from 'react-native-quick-sqlite';
import {getFreeDiskStorage} from 'react-native-device-info';
import type StorageProvider from './types';
import utils from '../../utils';
import type {KeyList, KeyValuePairList} from './types';
import type {StorageProvider, KeyList, KeyValuePairList} from './types';

const DB_NAME = 'OnyxDB';
let db: QuickSQLiteConnection;
Expand Down
26 changes: 26 additions & 0 deletions lib/storage/providers/__mocks__/IDBKeyValProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import _ from 'underscore';
import type {UseStore} from 'idb-keyval';
import idbKeyVal from 'idb-keyval';
import type {StorageProvider, MockStorageProvider} from '../types';

const IDBKeyValProvider = jest.requireActual('../IDBKeyValProvider');
const idbKeyValStore: UseStore = IDBKeyValProvider.idbKeyValStore;

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const {name: _name, ...IDBKeyValProviderFunctions} = IDBKeyValProvider.default as StorageProvider;
const IDBKeyValProviderMockBase = _.reduce<typeof IDBKeyValProviderFunctions, Partial<MockStorageProvider>>(
IDBKeyValProviderFunctions,
(mockAcc, fn, fnName) => ({
...mockAcc,
[fnName]: jest.fn(fn),
}),
{name: IDBKeyValProvider.name},
) as MockStorageProvider;

const IDBKeyValProviderMock = {
...IDBKeyValProviderMockBase,
idbKeyval: idbKeyVal,
idbKeyValStore,
};

export default IDBKeyValProviderMock;
28 changes: 25 additions & 3 deletions lib/storage/providers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ type StorageProvider = {
/**
* Multiple merging of existing and new values in a batch
*/
multiMerge: (pairs: KeyValuePairList) => Promise<BatchQueryResult | IDBValidKey[]>;
multiMerge: (pairs: KeyValuePairList) => Promise<BatchQueryResult | IDBValidKey[] | void>;

/**
* Merges an existing value with a new one by leveraging JSON_PATCH
Expand Down Expand Up @@ -79,5 +79,27 @@ type StorageProvider = {
keepInstancesSync?: (onStorageKeyChanged: OnStorageKeyChanged) => void;
};

export default StorageProvider;
export type {KeyList, KeyValuePair, KeyValuePairList, OnStorageKeyChanged};
type MethodsOnly<T, Excluded extends keyof T = never> = Pick<
T,
{
// eslint-disable-next-line @typescript-eslint/ban-types
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T] &
Exclude<keyof T, Excluded>
>;

interface MockStorageProviderGenerics {
getItem: jest.Mock<Promise<OnyxValue<OnyxKey> | null>, [OnyxKey]>;
setItem: jest.Mock<Promise<QueryResult | void>, [OnyxKey, OnyxValue<OnyxKey>]>;
}

type MockStorageProviderMethods = {
[K in keyof MethodsOnly<StorageProvider, 'getItem' | 'setItem'>]: jest.Mock<ReturnType<StorageProvider[K]>, Parameters<StorageProvider[K]>>;
};

type MockStorageProvider<AdditionalProps = Record<string, unknown>> = MockStorageProviderGenerics &
MockStorageProviderMethods & {
name: string;
} & AdditionalProps;

export type {StorageProvider, KeyList, KeyValuePair, KeyValuePairList, OnStorageKeyChanged, MockStorageProvider};
2 changes: 1 addition & 1 deletion lib/useOnyx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,4 +188,4 @@ function useOnyx<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>>(key: TKey

export default useOnyx;

export type {UseOnyxResult, FetchStatus};
export type {UseOnyxResult, ResultMetadata, FetchStatus};
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-native-onyx",
"version": "2.0.22",
"version": "2.0.26",
"author": "Expensify, Inc.",
"homepage": "https://expensify.com",
"description": "State management for React Native",
Expand Down
Loading
Loading