Skip to content
Merged
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
11 changes: 11 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,14 @@ jobs:
name: Type check
command: yarn test:types

"Test: unit":
Copy link

Choose a reason for hiding this comment

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

FYI future notice: use the community orb for easier circle management: https://github.com/react-native-community/react-native-circleci-orb

<<: *js_defaults
steps:
- *addWorkspace
- run:
name: Unit tests run
command: yarn test:unit

"Test: iOS e2e":
<<: *macos_defaults
steps:
Expand Down Expand Up @@ -291,3 +299,6 @@ workflows:
- "Test: types":
requires:
- "Setup environment"
- "Test: unit":
requires:
- "Setup environment"
2 changes: 1 addition & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
"trailingComma": "all",
"bracketSpacing": false,
"jsxBracketSameLine": true,
"parser": "flow"
"parser": "typescript"
}
12 changes: 11 additions & 1 deletion babel.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
module.exports = {
presets: ['@babel/preset-typescript'],
presets: [
'@babel/preset-typescript',
[
'@babel/preset-env',
{
targets: {
node: 8,
},
},
],
],
plugins: [
'@babel/proposal-class-properties',
'@babel/proposal-object-rest-spread',
Expand Down
160 changes: 160 additions & 0 deletions core/__tests__/AsyncStorage.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import AsyncStorage from '../src/AsyncStorage';

class StorageMock implements IStorageBackend<any> {
getSingle = jest.fn();
setSingle = jest.fn();
getMany = jest.fn();
setMany = jest.fn();
removeSingle = jest.fn();
removeMany = jest.fn();
getKeys = jest.fn();
dropStorage = jest.fn();
}

describe('AsyncStorage', () => {
const mockedStorage = new StorageMock();

beforeEach(() => {
jest.resetAllMocks();
});

describe('main API', () => {
it.each(['get', 'set', 'remove'])(
'handles single %s api call',
async (methodName: string) => {
const as = new AsyncStorage(mockedStorage, {
logger: false,
errorHandler: false,
});

const key = 'myKey';
const value = {
name: 'Jerry',
};

switch (methodName) {
case 'get': {
Copy link

Choose a reason for hiding this comment

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

Yikes, that's not what I meant, this code is almost no different. You can use each like this:

it.each([
  ['get', 'myKey'], 
  ['set', 'myKey', {name: 'Jerry'}], 
  ['remove', 'myKey']
])('handles single %s api call', async (methodName: 'get' | 'set' | 'remove', ...args) => {
  await as[methodName](...args);
  expect(mockedStorage.getSingle).toBeCalledWith(...args.concat(null));
}

Copy link
Member Author

@krizzu krizzu Jul 2, 2019

Choose a reason for hiding this comment

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

yh, shame, this looks better. Will cover this in next PR.

await as.get(key);
expect(mockedStorage.getSingle).toBeCalledWith(key, null);
break;
}

case 'set': {
await as.set(key, value);
expect(mockedStorage.setSingle).toBeCalledWith(key, value, null);
break;
}

case 'remove': {
await as.remove(key);
expect(mockedStorage.removeSingle).toBeCalledWith(key, null);
break;
}
}
},
);

it.each(['set', 'read', 'remove'])(
'handles basic multi %s api call',
async (methodName: string) => {
const keys = ['key1', 'key2', 'key3'];
const keyValues = [
{key1: 'value1'},
{key2: 'value2'},
{key3: 'value3'},
];

const as = new AsyncStorage(mockedStorage, {
logger: false,
errorHandler: false,
});

switch (methodName) {
case 'get': {
await as.getMultiple(keys);
expect(mockedStorage.getMany).toBeCalledWith(keys, null);
break;
}

case 'set': {
await as.setMultiple(keyValues);
expect(mockedStorage.setMany).toBeCalledWith(keyValues, null);
break;
}

case 'remove': {
await as.removeMultiple(keys);
expect(mockedStorage.removeMany).toBeCalledWith(keys, null);
break;
}
}
},
);

it.each(['instance', 'getKeys', 'clearStorage'])(
'handles %s api call',
async (methodName: string) => {
const asyncStorage = new AsyncStorage(mockedStorage, {
logger: false,
errorHandler: false,
});

switch (methodName) {
case 'instance': {
expect(asyncStorage.instance()).toBe(mockedStorage);
break;
}

case 'getKeys': {
mockedStorage.getKeys.mockImplementationOnce(() => [
'key1',
'key2',
]);
const keys = await asyncStorage.getKeys();
expect(keys).toEqual(['key1', 'key2']);
break;
}

case 'dropStorage': {
await asyncStorage.clearStorage();
expect(mockedStorage.dropStorage).toBeCalledTimes(1);
break;
}
}
},
);
});
describe('utils', () => {
it('uses logger when provided', async () => {
const loggerFunc = jest.fn();

const as = new AsyncStorage(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 AsyncStorage(mockedStorage, {
errorHandler,
logger: false,
});

await as.get('key');

expect(errorHandler).toBeCalledTimes(1);
expect(errorHandler).toBeCalledWith(error);
});
});
});
86 changes: 86 additions & 0 deletions core/__tests__/core.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import Factory from '../src/';
import {simpleLogger, simpleErrorHandler} from '../src/defaults';

describe('AsyncStorageFactory', () => {
it('Throws when tried to instantiate', () => {
expect(() => new Factory()).toThrow(
"[AsyncStorage] AsyncStorageFactory must not be instantiated.\nInstead, use static functions, like 'create' to get AsyncStorage instance.",
);
});
});

describe('SimpleLogger', () => {
beforeAll(() => {
jest.spyOn(console, 'log').mockImplementation();
});

beforeEach(() => {
console.log.mockReset();
});

afterAll(() => {
console.log.mockRestore();
});

it('logs basic info about action', () => {
const actionInfo: LoggerAction = {
action: 'save-single',
key: 'MyKey',
value: 'MyValue',
};

simpleLogger(actionInfo);

expect(console.log).toBeCalledTimes(1);

const callArgs = console.log.mock.calls[0][0];
expect(callArgs).toContain('[AsyncStorage]');
expect(callArgs).toContain(actionInfo.key);
expect(callArgs).toContain(actionInfo.value);
});

it('handles unknown action by logging it', () => {
const actionInfo: LoggerAction = {
// @ts-ignore need to handle unknown
action: 'my-action',
};

simpleLogger(actionInfo);

expect(console.log).toBeCalledTimes(1);
expect(console.log).toBeCalledWith(
'[AsyncStorage] Unknown action: my-action',
);
});
});

describe('SimpleErrorHandler', () => {
beforeAll(() => {
jest.spyOn(console, 'error').mockImplementation();
});

beforeEach(() => {
console.error.mockReset();
});

afterAll(() => {
console.error.mockRestore();
});
it('logs error when it is a string', () => {
const errorMessage = 'Fatal!';

simpleErrorHandler(errorMessage);

expect(console.error).toBeCalledTimes(1);
expect(console.error).toBeCalledWith(errorMessage);
});

it('logs error when it is an Error', () => {
const error = new Error('Fatal!');

simpleErrorHandler(error);

expect(console.error).toBeCalledTimes(1);
expect(console.error).toBeCalledWith('Fatal!');
});
});
Loading