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

[v2] Web Storage #216

Merged
merged 13 commits into from
Oct 20, 2019
Merged
8 changes: 6 additions & 2 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@ const commonSettings = {
};

module.exports = {
setupFiles: ['jest-localstorage-mock'],
projects: [
{
...commonSettings,
displayName: 'core',
roots: ['<rootDir>/packages/core'],
testMatch: ['<rootDir>/packages/core/__tests__/*{.,-}test.ts'],
roots: ['<rootDir>/packages/core', '<rootDir>/packages/storage-web'],
testMatch: [
'<rootDir>/packages/core/__tests__/*{.,-}test.ts',
'<rootDir>/packages/storage-web/__tests__/*{.,-}test.ts',
],
},
],
};
4 changes: 2 additions & 2 deletions packages/core/src/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
import {FactoryOptions, LoggerAction} from '../types';

export const factoryOptions: FactoryOptions = {
logger: __DEV__,
JesseRWeigel marked this conversation as resolved.
Show resolved Hide resolved
errorHandler: __DEV__,
logger: false,
errorHandler: false,
};

export function simpleErrorHandler(e: Error | string) {
Expand Down
96 changes: 96 additions & 0 deletions packages/storage-web/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import WebStorage from '../src';
import 'jest-localstorage-mock';

describe('WebStorage', () => {
beforeEach(() => {
localStorage.clear();
});
describe('main API', () => {
const webStorage = new WebStorage();

it('gets single item from localStorage', async () => {
localStorage.setItem('key1', 'value1');
expect(await webStorage.getSingle('key1')).toBe('value1');
});

it('saves single item to localStorage', async () => {
await webStorage.setSingle('key1', 'value1');
expect(localStorage.__STORE__['key1']).toBe('value1');
});

it('gets multiple items from localStorage', async () => {
localStorage.setItem('key1', 'value1');
localStorage.setItem('key2', 'value2');
expect(await webStorage.getMany(['key1', 'key2'])).toEqual([
'value1',
'value2',
]);
});

it('saves multiple items to localStorage', async () => {
await webStorage.setMany([{key1: 'value1'}, {key2: 'value2'}]);
expect(localStorage.__STORE__).toEqual({key1: 'value1', key2: 'value2'});
});

it('removes single item from localStorage', async () => {
localStorage.setItem('key1', 'value1');
localStorage.setItem('key2', 'value2');
await webStorage.removeSingle('key1');
expect(localStorage.__STORE__).toEqual({key2: 'value2'});
});

it('removes multiple items from localStorage', async () => {
localStorage.setItem('key1', 'value1');
localStorage.setItem('key2', 'value2');
localStorage.setItem('key3', 'value3');
await webStorage.removeMany(['key1', 'key2']);
expect(localStorage.__STORE__).toEqual({key3: 'value3'});
});

it('gets keys from localStorage', async () => {
localStorage.setItem('key1', 'value1');
localStorage.setItem('key2', 'value2');
expect(await webStorage.getKeys()).toEqual(['key1', 'key2']);
});

it('removes all keys from localStorage', async () => {
localStorage.setItem('key1', 'value1');
localStorage.setItem('key2', 'value2');
await webStorage.dropStorage();
expect(localStorage.__STORE__).toEqual({});
});
});
// describe('utils', () => {
JesseRWeigel marked this conversation as resolved.
Show resolved Hide resolved
// it('uses logger when provided', async () => {
// const loggerFunc = jest.fn();

// const as = new WebStorage<any, any>(mockedStorage, {
// logger: loggerFunc,
// errorHandler: false,
// });

// await as.get('key');
// expect(loggerFunc).toBeCalledTimes(1);
// expect(loggerFunc).toBeCalledWith({action: 'read-single', key: 'key'});
// });

// it('uses error handler when provided', async () => {
// const errorHandler = jest.fn();

// const error = new Error('Fatal!');
// mockedStorage.getSingle.mockImplementationOnce(async () => {
// throw error;
// });

// const as = new WebStorage<any, any>(mockedStorage, {
// errorHandler,
// logger: false,
// });

// await as.get('key');

// expect(errorHandler).toBeCalledTimes(1);
// expect(errorHandler).toBeCalledWith(error);
// });
// });
});
6 changes: 6 additions & 0 deletions packages/storage-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@
"react-native": ">=0.58"
},
"devDependencies": {
"jest-localstorage-mock": "^2.4.0",
"react": "^16.0",
"react-native": ">=0.58"
},
"jest": {
"setupFiles": [
"jest-localstorage-mock"
]
}
}
95 changes: 95 additions & 0 deletions packages/storage-web/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,98 @@
* LICENSE file in the root directory of this source tree.
*
*/
import {
IStorageBackend,
EmptyStorageModel,
StorageOptions,
} from '@react-native-community/async-storage';

class WebStorage<T extends EmptyStorageModel = EmptyStorageModel>
implements IStorageBackend<T> {
storage: any;
constructor(sessionStorage: boolean = false) {
this.storage = sessionStorage ? window.sessionStorage : window.localStorage;
}

async getSingle<K extends keyof T>(
key: K,
opts?: StorageOptions,
): Promise<T[K] | null> {
if (opts) {
// noop
}
return this.storage.getItem(key);
}

async setSingle<K extends keyof T>(
key: K,
value: T[K],
opts?: StorageOptions,
): Promise<void> {
if (opts && !opts.replaceCurrent) {
JesseRWeigel marked this conversation as resolved.
Show resolved Hide resolved
const current = this.storage.getItem(key);
if (!current) {
this.storage.setItem(key, value);
}
return;
}
return this.storage.setItem(key, value);
}

async getMany<K extends keyof T>(
keys: Array<K>,
opts?: StorageOptions,
): Promise<{[k in K]: T[k] | null}> {
if (opts) {
// noop
}
return Promise.all(keys.map(k => this.storage.getItem(k)));
krizzu marked this conversation as resolved.
Show resolved Hide resolved
JesseRWeigel marked this conversation as resolved.
Show resolved Hide resolved
}

async setMany<K extends keyof T>(
values: Array<{[k in K]: T[k]}>,
JesseRWeigel marked this conversation as resolved.
Show resolved Hide resolved
opts?: StorageOptions,
): Promise<void> {
if (opts) {
// noop
}
for (let keyValue of values) {
const key = Object.getOwnPropertyNames(keyValue)[0];
JesseRWeigel marked this conversation as resolved.
Show resolved Hide resolved
if (!key) {
continue;
}
this.storage.setItem(key, keyValue[key]);
}
}

async removeSingle(key: keyof T, opts?: StorageOptions): Promise<void> {
if (opts) {
// noop
}
return this.storage.removeItem(key);
}

async removeMany(keys: Array<keyof T>, opts?: StorageOptions): Promise<void> {
if (opts) {
// noop
}
return Promise.all(keys.map(k => this.storage.removeItem(k)));
}

async getKeys(opts?: StorageOptions): Promise<Array<keyof T>> {
if (opts) {
// noop
}
return Object.keys(this.storage);
}

async dropStorage(opts?: StorageOptions): Promise<void> {
if (opts) {
// noop
}
const keys = await this.getKeys();
await this.removeMany(keys);
}
}

export default WebStorage;
39 changes: 39 additions & 0 deletions packages/storage-web/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,42 @@
* LICENSE file in the root directory of this source tree.
*
*/

import {
EmptyStorageModel,
IStorageBackend,
StorageOptions,
} from '@react-native-community/async-storage';
export default class WebStorage<T = EmptyStorageModel>
implements IStorageBackend<T> {
private readonly _asyncStorageNativeModule;
storage: Function;
getSingle<K extends keyof T>(
key: K,
opts?: StorageOptions,
): Promise<T[K] | null>;

setSingle<K extends keyof T>(
key: K,
value: T[K],
opts?: StorageOptions,
): Promise<void>;

getMany<K extends keyof T>(
keys: Array<K>,
opts?: StorageOptions,
): Promise<{[k in K]: T[k] | null}>;

setMany<K extends keyof T>(
values: Array<{[k in K]: T[k]}>,
opts?: StorageOptions,
): Promise<void>;

removeSingle(key: keyof T, opts?: StorageOptions): Promise<void>;

removeMany(keys: Array<keyof T>, opts?: StorageOptions): Promise<void>;

getKeys(opts?: StorageOptions): Promise<Array<keyof T>>;

dropStorage(opts?: StorageOptions): Promise<void>;
}
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"compilerOptions": {
"target": "ES2017",
"module": "commonjs",
"lib": ["es2017"],
"lib": ["es2017", "dom"],
krizzu marked this conversation as resolved.
Show resolved Hide resolved

// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
"noEmit": true,
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5329,6 +5329,11 @@ jest-leak-detector@^24.8.0:
dependencies:
pretty-format "^24.8.0"

jest-localstorage-mock@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/jest-localstorage-mock/-/jest-localstorage-mock-2.4.0.tgz#c6073810735dd3af74020ea6c3885ec1cc6d0d13"
integrity sha512-/mC1JxnMeuIlAaQBsDMilskC/x/BicsQ/BXQxEOw+5b1aGZkkOAqAF3nu8yq449CpzGtp5jJ5wCmDNxLgA2m6A==

jest-matcher-utils@^24.8.0:
version "24.8.0"
resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.8.0.tgz#2bce42204c9af12bde46f83dc839efe8be832495"
Expand Down