"use strict";

import forEach from 'lodash/forEach';
import filter from 'lodash/filter';
import invoke from 'lodash/invoke';
import { mock, verify, instance, deepEqual, when, anything, anyString, capture } from 'ts-mockito';
import { LayoutTreeParser } from "./LayoutTreeParser.js";
import { LayoutTreeCrawler } from "./LayoutTreeCrawler.js";
import { Store } from "../components/Store.js";
import { Commands } from "./Commands.js";
import { CommandsObserver } from "../events/CommandsObserver.js";
import { NativeCommandsSender } from "../adapters/NativeCommandsSender.js";
import { OptionsProcessor } from "./OptionsProcessor.js";
import { UniqueIdProvider } from "../adapters/UniqueIdProvider.js";
import { LayoutProcessor } from "../processors/LayoutProcessor.js";
import { LayoutProcessorsStore } from "../processors/LayoutProcessorsStore.js";
import { CommandName } from "../interfaces/CommandName.js";
import { OptionsCrawler } from "./OptionsCrawler.js";
import React from 'react';
describe('Commands', () => {
  let uut;
  let mockedNativeCommandsSender;
  let mockedOptionsProcessor;
  let mockedStore;
  let commandsObserver;
  let mockedUniqueIdProvider;
  let layoutProcessor;
  beforeEach(() => {
    mockedNativeCommandsSender = mock(NativeCommandsSender);
    mockedUniqueIdProvider = mock(UniqueIdProvider);
    when(mockedUniqueIdProvider.generate(anything())).thenCall(prefix => `${prefix}+UNIQUE_ID`);
    const uniqueIdProvider = instance(mockedUniqueIdProvider);
    mockedStore = mock(Store);
    commandsObserver = new CommandsObserver(uniqueIdProvider);
    const layoutProcessorsStore = new LayoutProcessorsStore();
    mockedOptionsProcessor = mock(OptionsProcessor);
    const optionsProcessor = instance(mockedOptionsProcessor);
    layoutProcessor = new LayoutProcessor(layoutProcessorsStore);
    jest.spyOn(layoutProcessor, 'process');
    uut = new Commands(instance(mockedStore), instance(mockedNativeCommandsSender), new LayoutTreeParser(uniqueIdProvider), new LayoutTreeCrawler(instance(mockedStore), optionsProcessor), commandsObserver, uniqueIdProvider, optionsProcessor, layoutProcessor, new OptionsCrawler(instance(mockedStore), uniqueIdProvider));
  });
  describe('setRoot', () => {
    it('sends setRoot to native after parsing into a correct layout tree', () => {
      uut.setRoot({
        root: {
          component: {
            name: 'com.example.MyScreen'
          }
        }
      });
      verify(mockedNativeCommandsSender.setRoot('setRoot+UNIQUE_ID', deepEqual({
        root: {
          type: 'Component',
          id: 'Component+UNIQUE_ID',
          children: [],
          data: {
            name: 'com.example.MyScreen',
            options: {},
            passProps: undefined
          }
        },
        modals: [],
        overlays: []
      }))).called();
    });
    it('returns a promise with the resolved layout', async () => {
      when(mockedNativeCommandsSender.setRoot(anything(), anything())).thenResolve('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'
          }
        }]
      });
      verify(mockedNativeCommandsSender.setRoot('setRoot+UNIQUE_ID', deepEqual({
        root: {
          type: 'Component',
          id: 'Component+UNIQUE_ID',
          children: [],
          data: {
            name: 'com.example.MyScreen',
            options: {},
            passProps: undefined
          }
        },
        modals: [{
          type: 'Component',
          id: 'Component+UNIQUE_ID',
          children: [],
          data: {
            name: 'com.example.MyModal',
            options: {},
            passProps: undefined
          }
        }],
        overlays: [{
          type: 'Component',
          id: 'Component+UNIQUE_ID',
          children: [],
          data: {
            name: 'com.example.MyOverlay',
            options: {},
            passProps: undefined
          }
        }]
      }))).called();
    });
    it('process layout with layoutProcessor', () => {
      uut.setRoot({
        root: {
          component: {
            name: 'com.example.MyScreen'
          }
        }
      });
      expect(layoutProcessor.process).toBeCalledWith({
        component: {
          name: 'com.example.MyScreen',
          options: {},
          id: 'Component+UNIQUE_ID'
        }
      }, CommandName.SetRoot);
    });
    it('pass component static options to layoutProcessor', () => {
      when(mockedStore.getComponentClassForName('com.example.MyScreen')).thenReturn(() => class extends React.Component {
        static options() {
          return {
            topBar: {
              visible: false
            }
          };
        }
      });
      uut.setRoot({
        root: {
          component: {
            name: 'com.example.MyScreen'
          }
        }
      });
      expect(layoutProcessor.process).toBeCalledWith({
        component: {
          id: 'Component+UNIQUE_ID',
          name: 'com.example.MyScreen',
          options: {
            topBar: {
              visible: false
            }
          }
        }
      }, CommandName.SetRoot);
    });
    it('retains passProps properties identity', () => {
      const obj = {
        some: 'content'
      };
      uut.setRoot({
        root: {
          component: {
            name: 'com.example.MyScreen',
            passProps: {
              obj
            }
          }
        }
      });
      const args = capture(mockedStore.setPendingProps).last();
      expect(args[1].obj).toBe(obj);
    });
  });
  describe('mergeOptions', () => {
    it('passes options for component', () => {
      uut.mergeOptions('theComponentId', {
        blurOnUnmount: true
      });
      verify(mockedNativeCommandsSender.mergeOptions('theComponentId', deepEqual({
        blurOnUnmount: true
      }))).called();
    });
    it('show warning when invoking before componentDidMount', () => {
      jest.spyOn(console, 'warn');
      when(mockedStore.getComponentInstance('component1')).thenReturn({});
      const componentId = 'component1';
      uut.mergeOptions(componentId, {
        blurOnUnmount: true
      });
      expect(console.warn).toBeCalledWith(`Navigation.mergeOptions was invoked on component with id: ${componentId} before it is mounted, this can cause UI issues and should be avoided.\n Use static options instead.`);
    });
    it('should not show warning for mounted component', () => {
      jest.spyOn(console, 'warn');
      const componentId = 'component1';
      when(mockedStore.getComponentInstance('component1')).thenReturn({
        isMounted: true
      });
      uut.mergeOptions('component1', {
        blurOnUnmount: true
      });
      expect(console.warn).not.toBeCalledWith(`Navigation.mergeOptions was invoked on component with id: ${componentId} before it is mounted, this can cause UI issues and should be avoided.\n Use static options instead.`);
    });
    it('should not show warning for component id that does not exist', () => {
      jest.spyOn(console, 'warn');
      const componentId = 'component1';
      when(mockedStore.getComponentInstance('stackId')).thenReturn(undefined);
      uut.mergeOptions('stackId', {
        blurOnUnmount: true
      });
      expect(console.warn).not.toBeCalledWith(`Navigation.mergeOptions was invoked on component with id: ${componentId} before it is mounted, this can cause UI issues and should be avoided.\n Use static options instead.`);
    });
    it('processes mergeOptions', async () => {
      const options = {
        animations: {
          dismissModal: {
            enabled: false
          }
        }
      };
      uut.mergeOptions('myUniqueId', options);
      verify(mockedOptionsProcessor.processOptions(CommandName.MergeOptions, deepEqual(options), undefined)).called();
    });
    it('processing mergeOptions should pass component props', async () => {
      const options = {
        animations: {
          dismissModal: {
            enabled: false
          }
        }
      };
      const passProps = {
        prop: '1'
      };
      when(mockedStore.getPropsForId('myUniqueId')).thenReturn(passProps);
      uut.mergeOptions('myUniqueId', options);
      verify(mockedOptionsProcessor.processOptions(CommandName.MergeOptions, deepEqual(options), passProps)).called();
    });
  });
  describe('updateProps', () => {
    it('delegates to store', () => {
      uut.updateProps('theComponentId', {
        someProp: 'someValue'
      });
      verify(mockedStore.updateProps('theComponentId', deepEqual({
        someProp: 'someValue'
      })));
    });
    it('notifies commands observer', () => {
      uut.updateProps('theComponentId', {
        someProp: 'someValue'
      });
      verify(commandsObserver.notify('updateProps', deepEqual({
        componentId: 'theComponentId',
        props: {
          someProp: 'someValue'
        }
      })));
    });
    it('update props with callback', () => {
      const callback = jest.fn();
      uut.updateProps('theComponentId', {
        someProp: 'someValue'
      }, callback);
      const args = capture(mockedStore.updateProps).last();
      expect(args[0]).toEqual('theComponentId');
      expect(args[1]).toEqual({
        someProp: 'someValue'
      });
      expect(args[2]).toEqual(callback);
    });
  });
  describe('showModal', () => {
    it('sends command to native after parsing into a correct layout tree', () => {
      uut.showModal({
        component: {
          name: 'com.example.MyScreen'
        }
      });
      verify(mockedNativeCommandsSender.showModal('showModal+UNIQUE_ID', deepEqual({
        type: 'Component',
        id: 'Component+UNIQUE_ID',
        data: {
          name: 'com.example.MyScreen',
          options: {},
          passProps: undefined
        },
        children: []
      }))).called();
    });
    it('returns a promise with the resolved layout', async () => {
      when(mockedNativeCommandsSender.showModal(anything(), anything())).thenResolve('the resolved layout');
      const result = await uut.showModal({
        component: {
          name: 'com.example.MyScreen'
        }
      });
      expect(result).toEqual('the resolved layout');
    });
    it('process layout with layoutProcessor', () => {
      uut.showModal({
        component: {
          name: 'com.example.MyScreen'
        }
      });
      expect(layoutProcessor.process).toBeCalledWith({
        component: {
          id: 'Component+UNIQUE_ID',
          name: 'com.example.MyScreen',
          options: {}
        }
      }, CommandName.ShowModal);
    });
    it('retains passProps properties identity', () => {
      const obj = {
        some: 'content'
      };
      uut.showModal({
        component: {
          name: 'com.example.MyScreen',
          passProps: {
            obj
          }
        }
      });
      const args = capture(mockedStore.setPendingProps).last();
      expect(args[1].obj).toBe(obj);
    });
  });
  describe('dismissModal', () => {
    it('sends command to native', () => {
      uut.dismissModal('myUniqueId', {});
      verify(mockedNativeCommandsSender.dismissModal('dismissModal+UNIQUE_ID', 'myUniqueId', deepEqual({}))).called();
    });
    it('returns a promise with the id', async () => {
      when(mockedNativeCommandsSender.dismissModal(anyString(), anything(), anything())).thenResolve('the id');
      const result = await uut.dismissModal('myUniqueId');
      expect(result).toEqual('the id');
    });
    it('processes mergeOptions', async () => {
      const options = {
        animations: {
          dismissModal: {
            enabled: false
          }
        }
      };
      uut.dismissModal('myUniqueId', options);
      verify(mockedOptionsProcessor.processOptions(CommandName.DismissModal, options)).called();
    });
  });
  describe('dismissAllModals', () => {
    it('sends command to native', () => {
      uut.dismissAllModals({});
      verify(mockedNativeCommandsSender.dismissAllModals('dismissAllModals+UNIQUE_ID', deepEqual({}))).called();
    });
    it('returns a promise with the id', async () => {
      when(mockedNativeCommandsSender.dismissAllModals(anyString(), anything())).thenResolve('the id');
      const result = await uut.dismissAllModals();
      expect(result).toEqual('the id');
    });
    it('processes mergeOptions', async () => {
      const options = {
        animations: {
          dismissModal: {
            enabled: false
          }
        }
      };
      uut.dismissAllModals(options);
      verify(mockedOptionsProcessor.processOptions(CommandName.DismissAllModals, options)).called();
    });
  });
  describe('push', () => {
    it('resolves with the parsed layout', async () => {
      when(mockedNativeCommandsSender.push(anyString(), anyString(), anything())).thenResolve('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'
        }
      });
      verify(mockedNativeCommandsSender.push('push+UNIQUE_ID', 'theComponentId', deepEqual({
        type: 'Component',
        id: 'Component+UNIQUE_ID',
        data: {
          name: 'com.example.MyScreen',
          options: {},
          passProps: undefined
        },
        children: []
      }))).called();
    });
    it('process layout with layoutProcessor', () => {
      uut.push('theComponentId', {
        component: {
          name: 'com.example.MyScreen'
        }
      });
      expect(layoutProcessor.process).toBeCalledWith({
        component: {
          id: 'Component+UNIQUE_ID',
          name: 'com.example.MyScreen',
          options: {}
        }
      }, CommandName.Push);
    });
    it('retains passProps properties identity', () => {
      const obj = {
        some: 'content'
      };
      uut.push('theComponentId', {
        component: {
          name: 'com.example.MyScreen',
          passProps: {
            obj
          }
        }
      });
      const args = capture(mockedStore.setPendingProps).last();
      expect(args[1].obj).toBe(obj);
    });
  });
  describe('pop', () => {
    it('pops a component, passing componentId', () => {
      uut.pop('theComponentId', {});
      verify(mockedNativeCommandsSender.pop('pop+UNIQUE_ID', 'theComponentId', deepEqual({}))).called();
    });
    it('pops a component, passing componentId and options', () => {
      const options = {
        popGesture: true
      };
      uut.pop('theComponentId', options);
      verify(mockedNativeCommandsSender.pop('pop+UNIQUE_ID', 'theComponentId', options)).called();
    });
    it('pop returns a promise that resolves to componentId', async () => {
      when(mockedNativeCommandsSender.pop(anyString(), anyString(), anything())).thenResolve('theComponentId');
      const result = await uut.pop('theComponentId', {});
      expect(result).toEqual('theComponentId');
    });
    it('processes mergeOptions', async () => {
      const options = {
        animations: {
          pop: {
            enabled: false
          }
        }
      };
      uut.pop('theComponentId', options);
      verify(mockedOptionsProcessor.processOptions(CommandName.Pop, options)).called();
    });
  });
  describe('popTo', () => {
    it('pops all components until the passed Id is top', () => {
      uut.popTo('theComponentId', {});
      verify(mockedNativeCommandsSender.popTo('popTo+UNIQUE_ID', 'theComponentId', deepEqual({}))).called();
    });
    it('returns a promise that resolves to targetId', async () => {
      when(mockedNativeCommandsSender.popTo(anyString(), anyString(), anything())).thenResolve('theComponentId');
      const result = await uut.popTo('theComponentId');
      expect(result).toEqual('theComponentId');
    });
    it('processes mergeOptions', async () => {
      const options = {
        animations: {
          pop: {
            enabled: false
          }
        }
      };
      uut.popTo('theComponentId', options);
      verify(mockedOptionsProcessor.processOptions(CommandName.PopTo, options)).called();
    });
  });
  describe('popToRoot', () => {
    it('pops all components to root', () => {
      uut.popToRoot('theComponentId', {});
      verify(mockedNativeCommandsSender.popToRoot('popToRoot+UNIQUE_ID', 'theComponentId', deepEqual({}))).called();
    });
    it('returns a promise that resolves to targetId', async () => {
      when(mockedNativeCommandsSender.popToRoot(anyString(), anyString(), anything())).thenResolve('theComponentId');
      const result = await uut.popToRoot('theComponentId');
      expect(result).toEqual('theComponentId');
    });
    it('processes mergeOptions', async () => {
      const options = {
        animations: {
          pop: {
            enabled: false
          }
        }
      };
      uut.popToRoot('theComponentId', options);
      verify(mockedOptionsProcessor.processOptions(CommandName.PopToRoot, options)).called();
    });
  });
  describe('setStackRoot', () => {
    it('parses into correct layout node and sends to native', () => {
      uut.setStackRoot('theComponentId', [{
        component: {
          name: 'com.example.MyScreen'
        }
      }]);
      verify(mockedNativeCommandsSender.setStackRoot('setStackRoot+UNIQUE_ID', 'theComponentId', deepEqual([{
        type: 'Component',
        id: 'Component+UNIQUE_ID',
        data: {
          name: 'com.example.MyScreen',
          options: {},
          passProps: undefined
        },
        children: []
      }]))).called();
    });
    it('process layout with layoutProcessor', () => {
      uut.setStackRoot('theComponentId', [{
        component: {
          name: 'com.example.MyScreen'
        }
      }]);
      expect(layoutProcessor.process).toBeCalledWith({
        component: {
          id: 'Component+UNIQUE_ID',
          name: 'com.example.MyScreen',
          options: {}
        }
      }, CommandName.SetStackRoot);
    });
    it('retains passProps properties identity', () => {
      const obj = {
        some: 'content'
      };
      uut.setStackRoot('theComponentId', [{
        component: {
          name: 'com.example.MyScreen',
          passProps: {
            obj
          }
        }
      }]);
      const args = capture(mockedStore.setPendingProps).last();
      expect(args[1].obj).toBe(obj);
    });
  });
  describe('showOverlay', () => {
    it('sends command to native after parsing into a correct layout tree', () => {
      uut.showOverlay({
        component: {
          name: 'com.example.MyScreen'
        }
      });
      verify(mockedNativeCommandsSender.showOverlay('showOverlay+UNIQUE_ID', deepEqual({
        type: 'Component',
        id: 'Component+UNIQUE_ID',
        data: {
          name: 'com.example.MyScreen',
          options: {},
          passProps: undefined
        },
        children: []
      }))).called();
    });
    it('resolves with the component id', async () => {
      when(mockedNativeCommandsSender.showOverlay(anyString(), anything())).thenResolve('Component1');
      const result = await uut.showOverlay({
        component: {
          name: 'com.example.MyScreen'
        }
      });
      expect(result).toEqual('Component1');
    });
    it('process layout with layoutProcessor', () => {
      uut.showOverlay({
        component: {
          name: 'com.example.MyScreen'
        }
      });
      expect(layoutProcessor.process).toBeCalledWith({
        component: {
          id: 'Component+UNIQUE_ID',
          name: 'com.example.MyScreen',
          options: {}
        }
      }, CommandName.ShowOverlay);
    });
    it('retains passProps properties identity', () => {
      const obj = {
        some: 'content'
      };
      uut.showOverlay({
        component: {
          name: 'com.example.MyScreen',
          passProps: {
            obj
          }
        }
      });
      const args = capture(mockedStore.setPendingProps).last();
      expect(args[1].obj).toBe(obj);
    });
  });
  describe('dismissOverlay', () => {
    it('check promise returns true', async () => {
      when(mockedNativeCommandsSender.dismissOverlay(anyString(), anyString())).thenResolve('true');
      const result = await uut.dismissOverlay('Component1');
      verify(mockedNativeCommandsSender.dismissOverlay(anyString(), anyString())).called();
      expect(result).toEqual('true');
    });
    it('send command to native with componentId', () => {
      uut.dismissOverlay('Component1');
      verify(mockedNativeCommandsSender.dismissOverlay('dismissOverlay+UNIQUE_ID', 'Component1')).called();
    });
  });
  describe('notifies commandsObserver', () => {
    let cb;
    let mockedLayoutTreeParser;
    let mockedLayoutTreeCrawler;
    beforeEach(() => {
      cb = jest.fn();
      mockedLayoutTreeParser = mock(LayoutTreeParser);
      mockedLayoutTreeCrawler = mock(LayoutTreeCrawler);
      commandsObserver.register(cb);
      const mockedOptionsProcessor = mock(OptionsProcessor);
      uut = new Commands(mockedStore, mockedNativeCommandsSender, instance(mockedLayoutTreeParser), instance(mockedLayoutTreeCrawler), commandsObserver, instance(mockedUniqueIdProvider), instance(mockedOptionsProcessor), new LayoutProcessor(new LayoutProcessorsStore()), new OptionsCrawler(instance(mockedStore), mockedUniqueIdProvider));
    });
    function getAllMethodsOfUut() {
      const uutFns = Object.getOwnPropertyNames(Commands.prototype);
      const methods = filter(uutFns, fn => fn !== 'constructor');
      expect(methods.length).toBeGreaterThan(1);
      return methods;
    }
    describe('passes correct params', () => {
      const argsForMethodName = {
        setRoot: [{}],
        setDefaultOptions: [{}],
        mergeOptions: ['id', {}],
        updateProps: ['id', {}],
        showModal: [{}],
        dismissModal: ['id', {}],
        dismissAllModals: [{}],
        push: ['id', {}],
        pop: ['id', {}],
        popTo: ['id', {}],
        popToRoot: ['id', {}],
        setStackRoot: ['id', [{}]],
        showOverlay: [{}],
        dismissOverlay: ['id'],
        dismissAllOverlays: [{}],
        getLaunchArgs: ['id']
      };
      const paramsForMethodName = {
        setRoot: {
          commandId: 'setRoot+UNIQUE_ID',
          layout: {
            root: null,
            modals: [],
            overlays: []
          }
        },
        setDefaultOptions: {
          options: {}
        },
        mergeOptions: {
          componentId: 'id',
          options: {}
        },
        updateProps: {
          componentId: 'id',
          props: {}
        },
        showModal: {
          commandId: 'showModal+UNIQUE_ID',
          layout: null
        },
        dismissModal: {
          commandId: 'dismissModal+UNIQUE_ID',
          componentId: 'id',
          mergeOptions: {}
        },
        dismissAllModals: {
          commandId: 'dismissAllModals+UNIQUE_ID',
          mergeOptions: {}
        },
        push: {
          commandId: 'push+UNIQUE_ID',
          componentId: 'id',
          layout: null
        },
        pop: {
          commandId: 'pop+UNIQUE_ID',
          componentId: 'id',
          mergeOptions: {}
        },
        popTo: {
          commandId: 'popTo+UNIQUE_ID',
          componentId: 'id',
          mergeOptions: {}
        },
        popToRoot: {
          commandId: 'popToRoot+UNIQUE_ID',
          componentId: 'id',
          mergeOptions: {}
        },
        setStackRoot: {
          commandId: 'setStackRoot+UNIQUE_ID',
          componentId: 'id',
          layout: [null]
        },
        showOverlay: {
          commandId: 'showOverlay+UNIQUE_ID',
          layout: null
        },
        dismissOverlay: {
          commandId: 'dismissOverlay+UNIQUE_ID',
          componentId: 'id'
        },
        dismissAllOverlays: {
          commandId: 'dismissAllOverlays+UNIQUE_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]);
        });
      });
    });
  });
});
//# sourceMappingURL=Commands.test.js.map