react-native-navigation的迁移库

Commands.test.ts 14KB

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