react-native-navigation的迁移库

Commands.test.ts 15KB

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