123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470 |
- import * as _ from 'lodash';
- import { LayoutTreeParser } from './LayoutTreeParser';
- import { LayoutTreeCrawler } from './LayoutTreeCrawler';
- import { Store } from '../components/Store';
- import { UniqueIdProvider } from '../adapters/UniqueIdProvider.mock';
- import { NativeCommandsSender } from '../adapters/NativeCommandsSender.mock';
- import { Commands } from './Commands';
- import { CommandsObserver } from '../events/CommandsObserver';
-
- describe('Commands', () => {
- let uut: Commands;
- let mockCommandsSender;
- let store;
- let commandsObserver: CommandsObserver;
-
- beforeEach(() => {
- mockCommandsSender = new NativeCommandsSender();
- store = new Store();
- commandsObserver = new CommandsObserver();
-
- uut = new Commands(
- mockCommandsSender,
- new LayoutTreeParser(),
- new LayoutTreeCrawler(new UniqueIdProvider(), store),
- commandsObserver,
- new UniqueIdProvider()
- );
- });
-
- describe('setRoot', () => {
- it('sends setRoot to native after parsing into a correct layout tree', () => {
- uut.setRoot({
- root: {
- component: {
- name: 'com.example.MyScreen'
- }
- }
- });
- expect(mockCommandsSender.setRoot).toHaveBeenCalledTimes(1);
- expect(mockCommandsSender.setRoot).toHaveBeenCalledWith('setRoot+UNIQUE_ID', {
- root: {
- type: 'Component',
- id: 'Component+UNIQUE_ID',
- children: [],
- data: {
- name: 'com.example.MyScreen',
- options: {}
- }
- },
- modals: [],
- overlays: []
- });
- });
-
- it('deep clones input to avoid mutation errors', () => {
- const obj = {};
- uut.setRoot({ root: { component: { name: 'bla', inner: obj } as any } });
- expect(mockCommandsSender.setRoot.mock.calls[0][1].root.data.inner).not.toBe(obj);
- });
-
- it('passProps into components', () => {
- const passProps = {
- fn: () => 'Hello'
- };
- expect(store.getPropsForId('Component+UNIQUE_ID')).toEqual({});
- uut.setRoot({ root: { component: { name: 'asd', passProps } } });
- expect(store.getPropsForId('Component+UNIQUE_ID')).toEqual(passProps);
- expect(store.getPropsForId('Component+UNIQUE_ID').fn()).toEqual('Hello');
- });
-
- it('returns a promise with the resolved layout', async () => {
- mockCommandsSender.setRoot.mockReturnValue(Promise.resolve('the resolved layout'));
- const result = await uut.setRoot({ root: { component: { name: 'com.example.MyScreen' } } });
- expect(result).toEqual('the resolved layout');
- });
-
- it('inputs modals and overlays', () => {
- uut.setRoot({
- root: {
- component: {
- name: 'com.example.MyScreen'
- }
- },
- modals: [
- {
- component: {
- name: 'com.example.MyModal'
- }
- }
- ],
- overlays: [
- {
- component: {
- name: 'com.example.MyOverlay'
- }
- }
- ]
- });
- expect(mockCommandsSender.setRoot).toHaveBeenCalledTimes(1);
- expect(mockCommandsSender.setRoot).toHaveBeenCalledWith('setRoot+UNIQUE_ID', {
- root:
- {
- type: 'Component',
- id: 'Component+UNIQUE_ID',
- children: [],
- data: {
- name: 'com.example.MyScreen',
- options: {}
- }
- },
- modals: [
- {
- type: 'Component',
- id: 'Component+UNIQUE_ID',
- children: [],
- data: {
- name: 'com.example.MyModal',
- options: {}
- }
- }
- ],
- overlays: [
- {
- type: 'Component',
- id: 'Component+UNIQUE_ID',
- children: [],
- data: {
- name: 'com.example.MyOverlay',
- options: {}
- }
- }
- ]
- });
- });
- });
-
- describe('mergeOptions', () => {
- it('deep clones input to avoid mutation errors', () => {
- const obj = { title: 'test' };
- uut.mergeOptions('theComponentId', obj as any);
- expect(mockCommandsSender.mergeOptions.mock.calls[0][1]).not.toBe(obj);
- });
-
- it('passes options for component', () => {
- uut.mergeOptions('theComponentId', { title: '1' } as any);
- expect(mockCommandsSender.mergeOptions).toHaveBeenCalledTimes(1);
- expect(mockCommandsSender.mergeOptions).toHaveBeenCalledWith('theComponentId', { title: '1' });
- });
- });
-
- describe('setDefaultOptions', () => {
- it('deep clones input to avoid mutation errors', () => {
- const obj = { title: 'test' };
- uut.setDefaultOptions(obj as any);
- expect(mockCommandsSender.setDefaultOptions.mock.calls[0][0]).not.toBe(obj);
- });
- });
-
- describe('showModal', () => {
- it('sends command to native after parsing into a correct layout tree', () => {
- uut.showModal({
- component: {
- name: 'com.example.MyScreen'
- }
- });
- expect(mockCommandsSender.showModal).toHaveBeenCalledTimes(1);
- expect(mockCommandsSender.showModal).toHaveBeenCalledWith('showModal+UNIQUE_ID', {
- type: 'Component',
- id: 'Component+UNIQUE_ID',
- data: {
- name: 'com.example.MyScreen',
- options: {}
- },
- children: []
- });
- });
-
- it('deep clones input to avoid mutation errors', () => {
- const obj = {};
- uut.showModal({ component: { name: 'name', inner: obj } as any });
- expect(mockCommandsSender.showModal.mock.calls[0][1].data.inner).not.toBe(obj);
- });
-
- it('passProps into components', () => {
- const passProps = {};
- expect(store.getPropsForId('Component+UNIQUE_ID')).toEqual({});
- uut.showModal({
- component: {
- name: 'com.example.MyScreen',
- passProps
- }
- });
- expect(store.getPropsForId('Component+UNIQUE_ID')).toEqual(passProps);
- });
-
- it('returns a promise with the resolved layout', async () => {
- mockCommandsSender.showModal.mockReturnValue(Promise.resolve('the resolved layout'));
- const result = await uut.showModal({ component: { name: 'com.example.MyScreen' } });
- expect(result).toEqual('the resolved layout');
- });
- });
-
- describe('dismissModal', () => {
- it('sends command to native', () => {
- uut.dismissModal('myUniqueId');
- expect(mockCommandsSender.dismissModal).toHaveBeenCalledTimes(1);
- expect(mockCommandsSender.dismissModal).toHaveBeenCalledWith('dismissModal+UNIQUE_ID', 'myUniqueId');
- });
-
- it('returns a promise with the id', async () => {
- mockCommandsSender.dismissModal.mockReturnValue(Promise.resolve('the id'));
- const result = await uut.dismissModal('myUniqueId');
- expect(result).toEqual('the id');
- });
- });
-
- describe('dismissAllModals', () => {
- it('sends command to native', () => {
- uut.dismissAllModals();
- expect(mockCommandsSender.dismissAllModals).toHaveBeenCalledTimes(1);
- expect(mockCommandsSender.dismissAllModals).toHaveBeenCalledWith('dismissAllModals+UNIQUE_ID');
- });
-
- it('returns a promise with the id', async () => {
- mockCommandsSender.dismissAllModals.mockReturnValue(Promise.resolve('the id'));
- const result = await uut.dismissAllModals();
- expect(result).toEqual('the id');
- });
- });
-
- describe('push', () => {
- it('deep clones input to avoid mutation errors', () => {
- const obj = {};
- uut.push('theComponentId', { component: { name: 'name', passProps: { foo: obj } } });
- expect(mockCommandsSender.push.mock.calls[0][2].data.passProps.foo).not.toBe(obj);
- });
-
- it('resolves with the parsed layout', async () => {
- mockCommandsSender.push.mockReturnValue(Promise.resolve('the resolved layout'));
- const result = await uut.push('theComponentId', { component: { name: 'com.example.MyScreen' } });
- expect(result).toEqual('the resolved layout');
- });
-
- it('parses into correct layout node and sends to native', () => {
- uut.push('theComponentId', { component: { name: 'com.example.MyScreen' } });
- expect(mockCommandsSender.push).toHaveBeenCalledTimes(1);
- expect(mockCommandsSender.push).toHaveBeenCalledWith('push+UNIQUE_ID', 'theComponentId', {
- type: 'Component',
- id: 'Component+UNIQUE_ID',
- data: {
- name: 'com.example.MyScreen',
- options: {}
- },
- children: []
- });
- });
- });
-
- describe('pop', () => {
- it('pops a component, passing componentId', () => {
- uut.pop('theComponentId', {});
- expect(mockCommandsSender.pop).toHaveBeenCalledTimes(1);
- expect(mockCommandsSender.pop).toHaveBeenCalledWith('pop+UNIQUE_ID', 'theComponentId', {});
- });
- it('pops a component, passing componentId and options', () => {
- const options = {
- customTransition: {
- animations: [
- { type: 'sharedElement', fromId: 'title2', toId: 'title1', startDelay: 0, springVelocity: 0.2, duration: 0.5 }
- ],
- duration: 0.8
- }
- };
- uut.pop('theComponentId', options as any);
- expect(mockCommandsSender.pop).toHaveBeenCalledTimes(1);
- expect(mockCommandsSender.pop).toHaveBeenCalledWith('pop+UNIQUE_ID', 'theComponentId', options);
- });
-
- it('pop returns a promise that resolves to componentId', async () => {
- mockCommandsSender.pop.mockReturnValue(Promise.resolve('theComponentId'));
- const result = await uut.pop('theComponentId', {});
- expect(result).toEqual('theComponentId');
- });
- });
-
- describe('popTo', () => {
- it('pops all components until the passed Id is top', () => {
- uut.popTo('theComponentId');
- expect(mockCommandsSender.popTo).toHaveBeenCalledTimes(1);
- expect(mockCommandsSender.popTo).toHaveBeenCalledWith('popTo+UNIQUE_ID', 'theComponentId');
- });
-
- it('returns a promise that resolves to targetId', async () => {
- mockCommandsSender.popTo.mockReturnValue(Promise.resolve('theComponentId'));
- const result = await uut.popTo('theComponentId');
- expect(result).toEqual('theComponentId');
- });
- });
-
- describe('popToRoot', () => {
- it('pops all components to root', () => {
- uut.popToRoot('theComponentId');
- expect(mockCommandsSender.popToRoot).toHaveBeenCalledTimes(1);
- expect(mockCommandsSender.popToRoot).toHaveBeenCalledWith('popToRoot+UNIQUE_ID', 'theComponentId');
- });
-
- it('returns a promise that resolves to targetId', async () => {
- mockCommandsSender.popToRoot.mockReturnValue(Promise.resolve('theComponentId'));
- const result = await uut.popToRoot('theComponentId');
- expect(result).toEqual('theComponentId');
- });
- });
-
- describe('setStackRoot', () => {
- it('parses into correct layout node and sends to native', () => {
- uut.setStackRoot('theComponentId', { component: { name: 'com.example.MyScreen' } });
- expect(mockCommandsSender.setStackRoot).toHaveBeenCalledTimes(1);
- expect(mockCommandsSender.setStackRoot).toHaveBeenCalledWith('setStackRoot+UNIQUE_ID', 'theComponentId', {
- type: 'Component',
- id: 'Component+UNIQUE_ID',
- data: {
- name: 'com.example.MyScreen',
- options: {}
- },
- children: []
- });
- });
- });
-
- describe('showOverlay', () => {
- it('sends command to native after parsing into a correct layout tree', () => {
- uut.showOverlay({
- component: {
- name: 'com.example.MyScreen'
- }
- });
- expect(mockCommandsSender.showOverlay).toHaveBeenCalledTimes(1);
- expect(mockCommandsSender.showOverlay).toHaveBeenCalledWith('showOverlay+UNIQUE_ID', {
- type: 'Component',
- id: 'Component+UNIQUE_ID',
- data: {
- name: 'com.example.MyScreen',
- options: {}
- },
- children: []
- });
- });
-
- it('deep clones input to avoid mutation errors', () => {
- const obj = {};
- uut.showOverlay({ component: { name: 'name', inner: obj } as any });
- expect(mockCommandsSender.showOverlay.mock.calls[0][1].data.inner).not.toBe(obj);
- });
-
- it('resolves with the component id', async () => {
- mockCommandsSender.showOverlay.mockReturnValue(Promise.resolve('Component1'));
- const result = await uut.showOverlay({ component: { name: 'com.example.MyScreen' } });
- expect(result).toEqual('Component1');
- });
- });
-
- describe('dismissOverlay', () => {
- it('check promise returns true', async () => {
- mockCommandsSender.dismissOverlay.mockReturnValue(Promise.resolve(true));
- const result = await uut.dismissOverlay('Component1');
- expect(mockCommandsSender.dismissOverlay).toHaveBeenCalledTimes(1);
- expect(result).toEqual(true);
- });
-
- it('send command to native with componentId', () => {
- uut.dismissOverlay('Component1');
- expect(mockCommandsSender.dismissOverlay).toHaveBeenCalledTimes(1);
- expect(mockCommandsSender.dismissOverlay).toHaveBeenCalledWith('dismissOverlay+UNIQUE_ID', 'Component1');
- });
- });
-
- describe('notifies commandsObserver', () => {
- let cb;
-
- beforeEach(() => {
- cb = jest.fn();
- const mockParser = { parse: () => 'parsed' };
- const mockCrawler = { crawl: (x) => x, processOptions: (x) => x };
- commandsObserver.register(cb);
- uut = new Commands(mockCommandsSender, mockParser, mockCrawler, commandsObserver, new UniqueIdProvider());
- });
-
- function getAllMethodsOfUut() {
- const uutFns = Object.getOwnPropertyNames(Commands.prototype);
- const methods = _.filter(uutFns, (fn) => fn !== 'constructor');
- expect(methods.length).toBeGreaterThan(1);
- return methods;
- }
-
- function getAllMethodsOfNativeCommandsSender() {
- const nativeCommandsSenderFns = _.functions(mockCommandsSender);
- expect(nativeCommandsSenderFns.length).toBeGreaterThan(1);
- return nativeCommandsSenderFns;
- }
-
- it('always call last, when nativeCommand fails, dont notify listeners', () => {
- // throw when calling any native commands sender
- _.forEach(getAllMethodsOfNativeCommandsSender(), (fn) => {
- mockCommandsSender[fn].mockImplementation(() => {
- throw new Error(`throwing from mockNativeCommandsSender`);
- });
- });
-
- expect(getAllMethodsOfUut().sort()).toEqual(getAllMethodsOfNativeCommandsSender().sort());
-
- // call all commands on uut, all should throw, no commandObservers called
- _.forEach(getAllMethodsOfUut(), (m) => {
- expect(() => uut[m]()).toThrow();
- expect(cb).not.toHaveBeenCalled();
- });
- });
-
- it('notify on all commands', () => {
- _.forEach(getAllMethodsOfUut(), (m) => {
- uut[m]({});
- });
- expect(cb).toHaveBeenCalledTimes(getAllMethodsOfUut().length);
- });
-
- describe('passes correct params', () => {
- const argsForMethodName = {
- setRoot: [{}],
- setDefaultOptions: [{}],
- mergeOptions: ['id', {}],
- showModal: [{}],
- dismissModal: ['id'],
- dismissAllModals: [],
- push: ['id', {}],
- pop: ['id', {}],
- popTo: ['id'],
- popToRoot: ['id'],
- setStackRoot: ['id', {}],
- showOverlay: [{}],
- dismissOverlay: ['id'],
- getLaunchArgs: ['id']
- };
- const paramsForMethodName = {
- setRoot: { commandId: 'setRoot+UNIQUE_ID', layout: { root: 'parsed', modals: [], overlays: [] } },
- setDefaultOptions: { options: {} },
- mergeOptions: { componentId: 'id', options: {} },
- showModal: { commandId: 'showModal+UNIQUE_ID', layout: 'parsed' },
- dismissModal: { commandId: 'dismissModal+UNIQUE_ID', componentId: 'id' },
- dismissAllModals: { commandId: 'dismissAllModals+UNIQUE_ID' },
- push: { commandId: 'push+UNIQUE_ID', componentId: 'id', layout: 'parsed' },
- pop: { commandId: 'pop+UNIQUE_ID', componentId: 'id', options: {} },
- popTo: { commandId: 'popTo+UNIQUE_ID', componentId: 'id' },
- popToRoot: { commandId: 'popToRoot+UNIQUE_ID', componentId: 'id' },
- setStackRoot: { commandId: 'setStackRoot+UNIQUE_ID', componentId: 'id', layout: 'parsed' },
- showOverlay: { commandId: 'showOverlay+UNIQUE_ID', layout: 'parsed' },
- dismissOverlay: { commandId: 'dismissOverlay+UNIQUE_ID', componentId: 'id' },
- getLaunchArgs: { commandId: 'getLaunchArgs+UNIQUE_ID' },
- };
- _.forEach(getAllMethodsOfUut(), (m) => {
- it(`for ${m}`, () => {
- expect(argsForMethodName).toHaveProperty(m);
- expect(paramsForMethodName).toHaveProperty(m);
- _.invoke(uut, m, ...argsForMethodName[m]);
- expect(cb).toHaveBeenCalledTimes(1);
- expect(cb).toHaveBeenCalledWith(m, paramsForMethodName[m]);
- });
- });
- });
- });
- });
|