react-native-navigation的迁移库

Commands.test.ts 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. import forEach from 'lodash/forEach'
  2. import filter from 'lodash/filter'
  3. import invoke from 'lodash/invoke'
  4. import { mock, verify, instance, deepEqual, when, anything, anyString } from 'ts-mockito';
  5. import { LayoutTreeParser } from './LayoutTreeParser';
  6. import { LayoutTreeCrawler } from './LayoutTreeCrawler';
  7. import { Store } from '../components/Store';
  8. import { Commands } from './Commands';
  9. import { CommandsObserver } from '../events/CommandsObserver';
  10. import { NativeCommandsSender } from '../adapters/NativeCommandsSender';
  11. import { OptionsProcessor } from './OptionsProcessor';
  12. import { UniqueIdProvider } from '../adapters/UniqueIdProvider';
  13. import { Options } from '../interfaces/Options';
  14. describe('Commands', () => {
  15. let uut: Commands;
  16. let mockedNativeCommandsSender: NativeCommandsSender;
  17. let mockedStore: Store;
  18. let commandsObserver: CommandsObserver;
  19. let mockedUniqueIdProvider: UniqueIdProvider;
  20. beforeEach(() => {
  21. mockedNativeCommandsSender = mock(NativeCommandsSender);
  22. mockedUniqueIdProvider = mock(UniqueIdProvider);
  23. when(mockedUniqueIdProvider.generate(anything())).thenCall((prefix) => `${prefix}+UNIQUE_ID`);
  24. const uniqueIdProvider = instance(mockedUniqueIdProvider);
  25. mockedStore = mock(Store);
  26. commandsObserver = new CommandsObserver(uniqueIdProvider);
  27. const mockedOptionsProcessor = mock(OptionsProcessor);
  28. const optionsProcessor = instance(mockedOptionsProcessor) as OptionsProcessor;
  29. uut = new Commands(
  30. mockedStore,
  31. instance(mockedNativeCommandsSender),
  32. new LayoutTreeParser(uniqueIdProvider),
  33. new LayoutTreeCrawler(instance(mockedStore), optionsProcessor),
  34. commandsObserver,
  35. uniqueIdProvider,
  36. optionsProcessor
  37. );
  38. });
  39. describe('setRoot', () => {
  40. it('sends setRoot to native after parsing into a correct layout tree', () => {
  41. uut.setRoot({
  42. root: { component: { name: 'com.example.MyScreen' } }
  43. });
  44. verify(
  45. mockedNativeCommandsSender.setRoot(
  46. 'setRoot+UNIQUE_ID',
  47. deepEqual({
  48. root: {
  49. type: 'Component',
  50. id: 'Component+UNIQUE_ID',
  51. children: [],
  52. data: { name: 'com.example.MyScreen', options: {}, passProps: undefined }
  53. },
  54. modals: [],
  55. overlays: []
  56. })
  57. )
  58. ).called();
  59. });
  60. it('returns a promise with the resolved layout', async () => {
  61. when(mockedNativeCommandsSender.setRoot(anything(), anything())).thenResolve(
  62. 'the resolved layout'
  63. );
  64. const result = await uut.setRoot({ root: { component: { name: 'com.example.MyScreen' } } });
  65. expect(result).toEqual('the resolved layout');
  66. });
  67. it('inputs modals and overlays', () => {
  68. uut.setRoot({
  69. root: { component: { name: 'com.example.MyScreen' } },
  70. modals: [{ component: { name: 'com.example.MyModal' } }],
  71. overlays: [{ component: { name: 'com.example.MyOverlay' } }]
  72. });
  73. verify(
  74. mockedNativeCommandsSender.setRoot(
  75. 'setRoot+UNIQUE_ID',
  76. deepEqual({
  77. root: {
  78. type: 'Component',
  79. id: 'Component+UNIQUE_ID',
  80. children: [],
  81. data: {
  82. name: 'com.example.MyScreen',
  83. options: {},
  84. passProps: undefined
  85. }
  86. },
  87. modals: [
  88. {
  89. type: 'Component',
  90. id: 'Component+UNIQUE_ID',
  91. children: [],
  92. data: {
  93. name: 'com.example.MyModal',
  94. options: {},
  95. passProps: undefined
  96. }
  97. }
  98. ],
  99. overlays: [
  100. {
  101. type: 'Component',
  102. id: 'Component+UNIQUE_ID',
  103. children: [],
  104. data: {
  105. name: 'com.example.MyOverlay',
  106. options: {},
  107. passProps: undefined
  108. }
  109. }
  110. ]
  111. })
  112. )
  113. ).called();
  114. });
  115. });
  116. describe('mergeOptions', () => {
  117. it('passes options for component', () => {
  118. uut.mergeOptions('theComponentId', { blurOnUnmount: true });
  119. verify(
  120. mockedNativeCommandsSender.mergeOptions(
  121. 'theComponentId',
  122. deepEqual({ blurOnUnmount: true })
  123. )
  124. ).called();
  125. });
  126. });
  127. describe('updateProps', () => {
  128. it('delegates to store', () => {
  129. uut.updateProps('theComponentId', {someProp: 'someValue'});
  130. verify(mockedStore.updateProps('theComponentId', deepEqual({someProp: 'someValue'})));
  131. });
  132. it('notifies commands observer', () => {
  133. uut.updateProps('theComponentId', {someProp: 'someValue'});
  134. verify(commandsObserver.notify('updateProps', deepEqual({componentId: 'theComponentId', props: {someProp: 'someValue'}})));
  135. });
  136. });
  137. describe('showModal', () => {
  138. it('sends command to native after parsing into a correct layout tree', () => {
  139. uut.showModal({ component: { name: 'com.example.MyScreen' } });
  140. verify(
  141. mockedNativeCommandsSender.showModal(
  142. 'showModal+UNIQUE_ID',
  143. deepEqual({
  144. type: 'Component',
  145. id: 'Component+UNIQUE_ID',
  146. data: {
  147. name: 'com.example.MyScreen',
  148. options: {},
  149. passProps: undefined
  150. },
  151. children: []
  152. })
  153. )
  154. ).called();
  155. });
  156. it('returns a promise with the resolved layout', async () => {
  157. when(mockedNativeCommandsSender.showModal(anything(), anything())).thenResolve(
  158. 'the resolved layout'
  159. );
  160. const result = await uut.showModal({ component: { name: 'com.example.MyScreen' } });
  161. expect(result).toEqual('the resolved layout');
  162. });
  163. });
  164. describe('dismissModal', () => {
  165. it('sends command to native', () => {
  166. uut.dismissModal('myUniqueId', {});
  167. verify(
  168. mockedNativeCommandsSender.dismissModal(
  169. 'dismissModal+UNIQUE_ID',
  170. 'myUniqueId',
  171. deepEqual({})
  172. )
  173. ).called();
  174. });
  175. it('returns a promise with the id', async () => {
  176. when(
  177. mockedNativeCommandsSender.dismissModal(anyString(), anything(), anything())
  178. ).thenResolve('the id');
  179. const result = await uut.dismissModal('myUniqueId');
  180. expect(result).toEqual('the id');
  181. });
  182. });
  183. describe('dismissAllModals', () => {
  184. it('sends command to native', () => {
  185. uut.dismissAllModals({});
  186. verify(
  187. mockedNativeCommandsSender.dismissAllModals('dismissAllModals+UNIQUE_ID', deepEqual({}))
  188. ).called();
  189. });
  190. it('returns a promise with the id', async () => {
  191. when(mockedNativeCommandsSender.dismissAllModals(anyString(), anything())).thenResolve(
  192. 'the id'
  193. );
  194. const result = await uut.dismissAllModals();
  195. expect(result).toEqual('the id');
  196. });
  197. });
  198. describe('push', () => {
  199. it('resolves with the parsed layout', async () => {
  200. when(mockedNativeCommandsSender.push(anyString(), anyString(), anything())).thenResolve(
  201. 'the resolved layout'
  202. );
  203. const result = await uut.push('theComponentId', {
  204. component: { name: 'com.example.MyScreen' }
  205. });
  206. expect(result).toEqual('the resolved layout');
  207. });
  208. it('parses into correct layout node and sends to native', () => {
  209. uut.push('theComponentId', { component: { name: 'com.example.MyScreen' } });
  210. verify(
  211. mockedNativeCommandsSender.push(
  212. 'push+UNIQUE_ID',
  213. 'theComponentId',
  214. deepEqual({
  215. type: 'Component',
  216. id: 'Component+UNIQUE_ID',
  217. data: {
  218. name: 'com.example.MyScreen',
  219. options: {},
  220. passProps: undefined
  221. },
  222. children: []
  223. })
  224. )
  225. ).called();
  226. });
  227. });
  228. describe('pop', () => {
  229. it('pops a component, passing componentId', () => {
  230. uut.pop('theComponentId', {});
  231. verify(
  232. mockedNativeCommandsSender.pop('pop+UNIQUE_ID', 'theComponentId', deepEqual({}))
  233. ).called();
  234. });
  235. it('pops a component, passing componentId and options', () => {
  236. const options: Options = { popGesture: true };
  237. uut.pop('theComponentId', options);
  238. verify(mockedNativeCommandsSender.pop('pop+UNIQUE_ID', 'theComponentId', options)).called();
  239. });
  240. it('pop returns a promise that resolves to componentId', async () => {
  241. when(mockedNativeCommandsSender.pop(anyString(), anyString(), anything())).thenResolve(
  242. 'theComponentId'
  243. );
  244. const result = await uut.pop('theComponentId', {});
  245. expect(result).toEqual('theComponentId');
  246. });
  247. });
  248. describe('popTo', () => {
  249. it('pops all components until the passed Id is top', () => {
  250. uut.popTo('theComponentId', {});
  251. verify(
  252. mockedNativeCommandsSender.popTo('popTo+UNIQUE_ID', 'theComponentId', deepEqual({}))
  253. ).called();
  254. });
  255. it('returns a promise that resolves to targetId', async () => {
  256. when(mockedNativeCommandsSender.popTo(anyString(), anyString(), anything())).thenResolve(
  257. 'theComponentId'
  258. );
  259. const result = await uut.popTo('theComponentId');
  260. expect(result).toEqual('theComponentId');
  261. });
  262. });
  263. describe('popToRoot', () => {
  264. it('pops all components to root', () => {
  265. uut.popToRoot('theComponentId', {});
  266. verify(
  267. mockedNativeCommandsSender.popToRoot('popToRoot+UNIQUE_ID', 'theComponentId', deepEqual({}))
  268. ).called();
  269. });
  270. it('returns a promise that resolves to targetId', async () => {
  271. when(mockedNativeCommandsSender.popToRoot(anyString(), anyString(), anything())).thenResolve(
  272. 'theComponentId'
  273. );
  274. const result = await uut.popToRoot('theComponentId');
  275. expect(result).toEqual('theComponentId');
  276. });
  277. });
  278. describe('setStackRoot', () => {
  279. it('parses into correct layout node and sends to native', () => {
  280. uut.setStackRoot('theComponentId', [{ component: { name: 'com.example.MyScreen' } }]);
  281. verify(
  282. mockedNativeCommandsSender.setStackRoot(
  283. 'setStackRoot+UNIQUE_ID',
  284. 'theComponentId',
  285. deepEqual([
  286. {
  287. type: 'Component',
  288. id: 'Component+UNIQUE_ID',
  289. data: {
  290. name: 'com.example.MyScreen',
  291. options: {},
  292. passProps: undefined
  293. },
  294. children: []
  295. }
  296. ])
  297. )
  298. ).called();
  299. });
  300. });
  301. describe('showOverlay', () => {
  302. it('sends command to native after parsing into a correct layout tree', () => {
  303. uut.showOverlay({ component: { name: 'com.example.MyScreen' } });
  304. verify(
  305. mockedNativeCommandsSender.showOverlay(
  306. 'showOverlay+UNIQUE_ID',
  307. deepEqual({
  308. type: 'Component',
  309. id: 'Component+UNIQUE_ID',
  310. data: {
  311. name: 'com.example.MyScreen',
  312. options: {},
  313. passProps: undefined
  314. },
  315. children: []
  316. })
  317. )
  318. ).called();
  319. });
  320. it('resolves with the component id', async () => {
  321. when(mockedNativeCommandsSender.showOverlay(anyString(), anything())).thenResolve(
  322. 'Component1'
  323. );
  324. const result = await uut.showOverlay({ component: { name: 'com.example.MyScreen' } });
  325. expect(result).toEqual('Component1');
  326. });
  327. });
  328. describe('dismissOverlay', () => {
  329. it('check promise returns true', async () => {
  330. when(mockedNativeCommandsSender.dismissOverlay(anyString(), anyString())).thenResolve(true);
  331. const result = await uut.dismissOverlay('Component1');
  332. verify(mockedNativeCommandsSender.dismissOverlay(anyString(), anyString())).called();
  333. expect(result).toEqual(true);
  334. });
  335. it('send command to native with componentId', () => {
  336. uut.dismissOverlay('Component1');
  337. verify(
  338. mockedNativeCommandsSender.dismissOverlay('dismissOverlay+UNIQUE_ID', 'Component1')
  339. ).called();
  340. });
  341. });
  342. describe('notifies commandsObserver', () => {
  343. let cb: any;
  344. let mockedLayoutTreeParser: LayoutTreeParser;
  345. let mockedLayoutTreeCrawler: LayoutTreeCrawler;
  346. let anotherMockedUniqueIdProvider: UniqueIdProvider;
  347. beforeEach(() => {
  348. cb = jest.fn();
  349. mockedLayoutTreeParser = mock(LayoutTreeParser);
  350. mockedLayoutTreeCrawler = mock(LayoutTreeCrawler);
  351. commandsObserver.register(cb);
  352. const mockedOptionsProcessor = mock(OptionsProcessor) as OptionsProcessor;
  353. anotherMockedUniqueIdProvider = mock(UniqueIdProvider);
  354. when(anotherMockedUniqueIdProvider.generate(anything())).thenCall(
  355. (prefix) => `${prefix}+UNIQUE_ID`
  356. );
  357. uut = new Commands(
  358. mockedStore,
  359. mockedNativeCommandsSender,
  360. instance(mockedLayoutTreeParser),
  361. instance(mockedLayoutTreeCrawler),
  362. commandsObserver,
  363. instance(anotherMockedUniqueIdProvider),
  364. instance(mockedOptionsProcessor)
  365. );
  366. });
  367. function getAllMethodsOfUut() {
  368. const uutFns = Object.getOwnPropertyNames(Commands.prototype);
  369. const methods = filter(uutFns, (fn) => fn !== 'constructor');
  370. expect(methods.length).toBeGreaterThan(1);
  371. return methods;
  372. }
  373. describe('passes correct params', () => {
  374. const argsForMethodName: Record<string, any[]> = {
  375. setRoot: [{}],
  376. setDefaultOptions: [{}],
  377. mergeOptions: ['id', {}],
  378. updateProps: ['id', {}],
  379. showModal: [{}],
  380. dismissModal: ['id', {}],
  381. dismissAllModals: [{}],
  382. push: ['id', {}],
  383. pop: ['id', {}],
  384. popTo: ['id', {}],
  385. popToRoot: ['id', {}],
  386. setStackRoot: ['id', [{}]],
  387. showOverlay: [{}],
  388. dismissOverlay: ['id'],
  389. getLaunchArgs: ['id']
  390. };
  391. const paramsForMethodName: Record<string, object> = {
  392. setRoot: {
  393. commandId: 'setRoot+UNIQUE_ID',
  394. layout: { root: null, modals: [], overlays: [] }
  395. },
  396. setDefaultOptions: { options: {} },
  397. mergeOptions: { componentId: 'id', options: {} },
  398. updateProps: { componentId: 'id', props: {} },
  399. showModal: { commandId: 'showModal+UNIQUE_ID', layout: null },
  400. dismissModal: { commandId: 'dismissModal+UNIQUE_ID', componentId: 'id', mergeOptions: {} },
  401. dismissAllModals: { commandId: 'dismissAllModals+UNIQUE_ID', mergeOptions: {} },
  402. push: { commandId: 'push+UNIQUE_ID', componentId: 'id', layout: null },
  403. pop: { commandId: 'pop+UNIQUE_ID', componentId: 'id', mergeOptions: {} },
  404. popTo: { commandId: 'popTo+UNIQUE_ID', componentId: 'id', mergeOptions: {} },
  405. popToRoot: { commandId: 'popToRoot+UNIQUE_ID', componentId: 'id', mergeOptions: {} },
  406. setStackRoot: {
  407. commandId: 'setStackRoot+UNIQUE_ID',
  408. componentId: 'id',
  409. layout: [null]
  410. },
  411. showOverlay: { commandId: 'showOverlay+UNIQUE_ID', layout: null },
  412. dismissOverlay: { commandId: 'dismissOverlay+UNIQUE_ID', componentId: 'id' },
  413. getLaunchArgs: { commandId: 'getLaunchArgs+UNIQUE_ID' }
  414. };
  415. forEach(getAllMethodsOfUut(), (m) => {
  416. it(`for ${m}`, () => {
  417. expect(argsForMethodName).toHaveProperty(m);
  418. expect(paramsForMethodName).toHaveProperty(m);
  419. invoke(uut, m, ...argsForMethodName[m]);
  420. expect(cb).toHaveBeenCalledTimes(1);
  421. expect(cb).toHaveBeenCalledWith(m, paramsForMethodName[m]);
  422. });
  423. });
  424. });
  425. });
  426. });