@v4fire/core
Version:
V4Fire core library
443 lines (350 loc) • 13.4 kB
JavaScript
/* eslint-disable max-lines-per-function, @typescript-eslint/strict-boolean-expressions */
/*!
* V4Fire Core
* https://github.com/V4Fire/Core
*
* Released under the MIT license
* https://github.com/V4Fire/Core/blob/master/LICENSE
*/
import express from 'express';
import Async from 'core/async';
import Provider, { provider } from 'core/data';
import EventEmitter from 'events';
describe('core/async/modules/wrappers', () => {
let server;
beforeAll(() => {
server = createServer();
});
afterAll(async () => {
await server.close();
});
@provider
class ProviderExample extends Provider {
baseURL = 'http://localhost:3000/ok';
}
describe('`wrapDataProvider`', () => {
it('should call methods from the original instance', () => {
const
$a = new Async(),
provider = new ProviderExample(),
wrappedProvider = $a.wrapDataProvider(provider);
jest.spyOn(provider, 'name');
wrappedProvider.name();
expect(provider.name).toHaveBeenCalled();
jest.spyOn(provider, 'base');
wrappedProvider.base('bar');
expect(provider.base).toHaveBeenCalledWith('bar');
});
it('should call replaced methods from the original instance', () => {
const
$a = new Async(),
provider = new ProviderExample(),
wrappedProvider = $a.wrapDataProvider(provider);
jest.spyOn(provider, 'get');
wrappedProvider.get();
expect(provider.get).toHaveBeenCalled();
jest.spyOn(provider, 'upd');
wrappedProvider.upd();
expect(provider.upd).toHaveBeenCalled();
});
it('if a group is not provided should use a class name from the provider', () => {
const
$a = new Async(),
provider = new ProviderExample(),
wrappedProvider = $a.wrapDataProvider(provider);
jest.spyOn($a, 'request');
wrappedProvider.get({id: 1});
expect($a.request.mock.lastCall[1]).toEqual({group: 'ProviderExample'});
});
it('should concatenate a global group and local group', async () => {
const
$a = new Async(),
provider = new ProviderExample(),
wrappedProvider = $a.wrapDataProvider(provider, {group: 'example'});
jest.spyOn($a, 'request');
await wrappedProvider.get({id: 1});
expect($a.request.mock.lastCall[1]).toEqual({group: 'example'});
await wrappedProvider.upd({id: 1}, {group: 'foo'});
expect($a.request.mock.lastCall[1]).toEqual({group: 'example:foo'});
});
it('should provide a group into a nested event emitter wrapper and replace the original emitter with a wrapper', () => {
const
$a = new Async(),
provider = new ProviderExample(),
fakeWrapper = {info: 'Is a wrapped event emitter'};
jest.spyOn($a, 'wrapEventEmitter').mockReturnValue(fakeWrapper);
const
wrappedProvider = $a.wrapDataProvider(provider, {group: 'example'});
expect($a.wrapEventEmitter.mock.lastCall).toEqual([provider.emitter, {group: 'example'}]);
expect(wrappedProvider.emitter).toEqual(fakeWrapper);
});
});
describe('`wrapEventEmitter`', () => {
it('should have access to event emitter properties and methods', () => {
const
$a = new Async();
const fakeEventEmitter = {
foo: () => null,
bar: 'bar'
};
const emitter = $a.wrapEventEmitter(fakeEventEmitter);
expect(emitter.foo).toEqual(fakeEventEmitter.foo);
expect(emitter.bar).toEqual(fakeEventEmitter.bar);
});
it('`addEventListener` and `addListener` should be aliases for `on`', () => {
const
$a = new Async(),
emitter = $a.wrapEventEmitter({});
expect(emitter.addEventListener).toEqual(emitter.on);
expect(emitter.addListener).toEqual(emitter.on);
});
it('should throw an error if the second parameter at `on` is not a function', () => {
const
$a = new Async(),
emitter = $a.wrapEventEmitter({on: () => null});
expect(emitter.on.bind(null, 'bar', {handleEvent: () => ({})})).toThrowError();
});
it('should concatenate a global group and local group', () => {
const
$a = new Async(),
emitterWithGroup = $a.wrapEventEmitter({on: () => null}, {group: 'example'});
jest.spyOn($a, 'on');
emitterWithGroup.on('foo', () => null, {
group: 'example2'
});
expect($a.on.mock.lastCall[3]).toEqual({group: 'example:example2'});
emitterWithGroup.on('bar', () => null);
expect($a.on.mock.lastCall[3]).toEqual({group: 'example'});
const emitterWithoutGroup = $a.wrapEventEmitter({
on: () => null
});
emitterWithoutGroup.on('foo', () => null, {
group: 'example3'
});
expect($a.on.mock.lastCall[3]).toEqual({group: 'example3'});
emitterWithoutGroup.on('bar', () => null);
expect($a.on.mock.lastCall[3]).toEqual({});
});
it('normalizes of input parameters', () => {
const
$a = new Async(),
emitter = $a.wrapEventEmitter({on: () => null});
jest.spyOn($a, 'on');
// [] => [{}]
emitter.on('foo', () => null);
expect($a.on.mock.lastCall.slice(3)).toEqual([{}]);
// [true] => [{}, true]
emitter.on('foo', () => null, true);
expect($a.on.mock.lastCall.slice(3)).toEqual([{}, true]);
/*
* [{foo: 'foo', group: 'group', label: 'label'}, null, 5]
* =>
* [{group: 'group', label: 'label'}, {foo: 'foo'}, null, 5]
*/
emitter.on('foo', () => null, {foo: 'foo', group: 'group', label: 'label'}, null, 5);
expect($a.on.mock.lastCall.slice(3)).toEqual([{group: 'group', label: 'label'}, {foo: 'foo'}, null, 5]);
});
it('`off`, `once`, `promisifyOnce` should call async wrappers', () => {
const
$a = new Async(),
emitter = $a.wrapEventEmitter(new EventEmitter());
jest.spyOn($a, 'once');
jest.spyOn($a, 'promisifyOnce');
jest.spyOn($a, 'off');
emitter.once('foo', () => null);
emitter.promisifyOnce('bar', () => null);
emitter.off({});
expect($a.once).toHaveBeenCalled();
expect($a.promisifyOnce).toHaveBeenCalled();
expect($a.off).toHaveBeenCalled();
});
it('`off` alias should return control to the original function if wrong parameters are provided', () => {
const originalMethods = {
off: false,
removeEventListener: false,
removeListener: false
};
const
$a = new Async();
const emitter = $a.wrapEventEmitter({
on: () => null,
off: () => originalMethods.off = true,
removeEventListener: () => originalMethods.removeEventListener = true,
removeListener: () => originalMethods.removeListener = true
});
emitter.off(null);
emitter.removeEventListener('foo');
emitter.removeListener({}, () => null, 'bar', 1);
expect(originalMethods).toEqual({
off: true,
removeEventListener: true,
removeListener: true
});
});
it('`emit` should call all emit like events', () => {
const results = {
emit: null,
fire: null,
dispatch: null,
dispatchEvent: null
};
const
$a = new Async(),
fakeEventEmitter = {};
const
events = Object.keys(results),
emitter = $a.wrapEventEmitter(fakeEventEmitter);
for (let i = 0; i < events.length; i += 1) {
delete fakeEventEmitter[events[i - 1]];
fakeEventEmitter[events[i]] = (event) => results[events[i]] = event;
emitter.emit(events[i]);
}
expect(results).toEqual({
emit: 'emit',
fire: 'fire',
dispatch: 'dispatch',
dispatchEvent: 'dispatchEvent'
});
});
});
describe('`wrapStorage`', () => {
const
mainMethods = ['has', 'get', 'set', 'remove', 'clear'],
methodsWithNamespace = [...mainMethods, 'namespace'];
let
$a,
mockedStorage,
methodArgs,
expectedResult;
beforeEach(() => {
$a = new Async();
methodArgs = [Symbol('firstArg'), Symbol('secondArg'), Symbol('thirdArg')];
expectedResult = Symbol('result');
mockedStorage = methodsWithNamespace.reduce((cur, name) => {
cur[name] = jest.fn().mockResolvedValue(expectedResult);
return cur;
}, {});
});
mainMethods.forEach(testMethod);
function testMethod(methodName) {
describe(`\`${methodName}\``, () => {
it('should call the original method and return its result', async () => {
const
wrappedStorage = $a.wrapStorage(mockedStorage),
result = await wrappedStorage[methodName](...methodArgs);
expect(mockedStorage[methodName]).toHaveBeenCalledWith(...methodArgs);
expect(result).toBe(expectedResult);
});
it('should mark a storage by the global group', async () => {
const
wrappedStorage = $a.wrapStorage(mockedStorage, {group: 'bla'}),
spyPromise = jest.fn().mockResolvedValue(expectedResult);
const promise = wrappedStorage[methodName](...methodArgs).then(
() => spyPromise('resolved'),
(err) => spyPromise(Object.select(err, ['type', 'reason']))
);
await $a.clearAll({group: 'bla'});
await promise;
expect(spyPromise).toHaveBeenLastCalledWith({type: 'clearAsync', reason: 'group'});
expect(spyPromise).toHaveBeenCalledTimes(1);
});
it('should consider the `group` parameter', async () => {
const
wrappedStorage = $a.wrapStorage(mockedStorage),
spyPromise = jest.fn().mockResolvedValue(expectedResult);
const promise = wrappedStorage[methodName](...methodArgs, {group: 'bla'}).then(
() => spyPromise('resolved'),
(err) => spyPromise(Object.select(err, ['type', 'reason']))
);
await $a.clearAll({group: 'bla'});
await promise;
expect(spyPromise).toHaveBeenLastCalledWith({type: 'clearAsync', reason: 'group'});
expect(spyPromise).toHaveBeenCalledTimes(1);
});
it('should consider the `label` parameter', async () => {
const
wrappedStorage = $a.wrapStorage(mockedStorage),
spyPromise = jest.fn().mockResolvedValue(expectedResult);
const promise = wrappedStorage[methodName](...methodArgs, {label: 'qoo'}).then(
() => spyPromise('resolved'),
(err) => spyPromise(Object.select(err, ['type', 'reason']))
);
await $a.clearAll({label: 'qoo'});
await promise;
expect(spyPromise).toHaveBeenLastCalledWith({type: 'clearAsync', reason: 'label'});
expect(spyPromise).toHaveBeenCalledTimes(1);
});
it('should consider the `join` parameter', async () => {
const
wrappedStorage = $a.wrapStorage(mockedStorage),
spyPromise = jest.fn().mockResolvedValue(expectedResult),
label = Symbol('label'),
promise1 = wrappedStorage[methodName](...methodArgs, {label, join: true}).then(() => spyPromise()),
promise2 = wrappedStorage[methodName](...methodArgs, {label, join: true}).then(() => spyPromise());
await Promise.all([promise1, promise2]);
expect(spyPromise).toHaveBeenCalledTimes(2);
});
it('should concatenate global and local groups', async () => {
const
wrappedStorage = $a.wrapStorage(mockedStorage, {group: 'bla'}),
spyWithoutLocal = jest.fn().mockResolvedValue(expectedResult),
spyWithLocal = jest.fn().mockResolvedValue(expectedResult);
const promiseWithoutLocal = wrappedStorage[methodName](...methodArgs).then(
() => spyWithoutLocal('resolved'),
(err) => spyWithoutLocal(Object.select(err, ['type', 'reason']))
);
const promiseWithLocal = wrappedStorage[methodName](...methodArgs, {group: 'foo'}).then(
() => spyWithLocal('resolved'),
(err) => spyWithLocal(Object.select(err, ['type', 'reason']))
);
await $a.clearAll({group: 'bla:foo'});
await Promise.all([promiseWithoutLocal, promiseWithLocal]);
expect(spyWithoutLocal).toHaveBeenLastCalledWith('resolved');
expect(spyWithoutLocal).toHaveBeenCalledTimes(1);
expect(spyWithLocal).toHaveBeenLastCalledWith({type: 'clearAsync', reason: 'group'});
expect(spyWithLocal).toHaveBeenCalledTimes(1);
});
it('should separate async options from additional parameters', () => {
const wrappedStorage = $a.wrapStorage(mockedStorage);
wrappedStorage[methodName]('firstArg', 'secondArg', {additionalArg: true}, {
group: 'bla',
label: 'foo',
join: true,
notAsyncOption: true
});
expect(mockedStorage[methodName].mock.lastCall[2]).toEqual({additionalArg: true});
expect(mockedStorage[methodName].mock.lastCall[3]).toEqual({notAsyncOption: true});
});
});
}
describe('`namespace`', () => {
it('should call the original method and return the wrapped result', () => {
const
parentStorage = $a.wrapStorage(mockedStorage),
name = Symbol('name'),
storageNamespace = Symbol('storageNamespace'),
wrappedStorageNamespace = Symbol('wrappedStorageNamespace');
jest.spyOn($a, 'wrapStorage');
mockedStorage.namespace.mockReturnValue(storageNamespace);
$a.wrapStorage.mockReturnValue(wrappedStorageNamespace);
const returnedStorage = parentStorage.namespace(name);
expect(mockedStorage.namespace).toHaveBeenCalledWith(name);
expect($a.wrapStorage.mock.lastCall[0]).toBe(storageNamespace);
expect(returnedStorage).toBe(wrappedStorageNamespace);
});
it('should concatenate global and local groups', () => {
const parentStorage = $a.wrapStorage(mockedStorage, {group: 'bla'});
jest.spyOn($a, 'wrapStorage');
parentStorage.namespace('someName', {group: 'foo'});
expect($a.wrapStorage.mock.lastCall[1]).toEqual({group: 'bla:foo'});
});
});
});
});
function createServer() {
const
serverApp = express();
serverApp.use(express.json());
serverApp.use('/ok', (req, res) => res.status(200).json({ok: true}));
return serverApp.listen(3000);
}