root-test.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738
  1. import { is, fromJS } from 'immutable';
  2. import { TABLE_VIEW_MODE } from '../../constants/naming';
  3. import { constructEdgeId } from '../../utils/layouter-utils';
  4. import { highlightedEdgeIdsSelector } from '../../selectors/graph-view/decorators';
  5. // var apiRoot = process.env.API_ROOT;
  6. var apiRoot = 'http://observe-front.cestong.com.cn'
  7. // Root reducer test suite using Jasmine matchers
  8. describe('RootReducer', () => {
  9. const ActionTypes = require('../../constants/action-types').default;
  10. const reducer = require('../root').default;
  11. const { initialState } = require('../root');
  12. const topologyUtils = require('../../utils/topology-utils');
  13. const topologySelectors = require('../../selectors/topology');
  14. // TODO maybe extract those to topology-utils tests?
  15. const { activeTopologyOptionsSelector } = topologySelectors;
  16. const { getAdjacentNodes, isNodesDisplayEmpty, isTopologyNodeCountZero } = topologyUtils;
  17. const { getUrlState } = require('../../utils/router-utils');
  18. // fixtures
  19. const NODE_SET = {
  20. n1: {
  21. adjacency: ['n1', 'n2'],
  22. filtered: false,
  23. id: 'n1',
  24. },
  25. n2: {
  26. filtered: false,
  27. id: 'n2',
  28. }
  29. };
  30. const topologies = [
  31. {
  32. fullName: 'Processes',
  33. hide_if_empty: true,
  34. id: 'processes',
  35. name: 'Processes',
  36. options: [
  37. {
  38. defaultValue: 'hide',
  39. id: 'unconnected',
  40. options: [
  41. {
  42. label: 'Unconnected nodes hidden',
  43. value: 'hide'
  44. }
  45. ],
  46. selectType: 'one'
  47. }
  48. ],
  49. rank: 1,
  50. stats: {
  51. edge_count: 379,
  52. filtered_nodes: 214,
  53. node_count: 320,
  54. nonpseudo_node_count: 320
  55. },
  56. sub_topologies: [],
  57. url: '/api/topology/processes' //本地用
  58. // url:`${apiRoot}/api/topology/processes` //线上用
  59. },
  60. {
  61. hide_if_empty: true,
  62. name: 'Pods',
  63. options: [
  64. {
  65. defaultValue: 'default',
  66. id: 'namespace',
  67. options: [
  68. {
  69. label: 'monitoring',
  70. value: 'monitoring'
  71. },
  72. {
  73. label: 'scope',
  74. value: 'scope'
  75. },
  76. {
  77. label: 'All Namespaces',
  78. value: 'all'
  79. }
  80. ],
  81. selectType: 'many'
  82. },
  83. {
  84. defaultValue: 'hide',
  85. id: 'pseudo',
  86. options: [
  87. {
  88. label: 'Show Unmanaged',
  89. value: 'show'
  90. },
  91. {
  92. label: 'Hide Unmanaged',
  93. value: 'hide'
  94. }
  95. ]
  96. }
  97. ],
  98. rank: 3,
  99. stats: {
  100. edge_count: 15,
  101. filtered_nodes: 16,
  102. node_count: 32,
  103. nonpseudo_node_count: 27
  104. },
  105. sub_topologies: [
  106. {
  107. hide_if_empty: true,
  108. name: 'services',
  109. options: [
  110. {
  111. defaultValue: 'default',
  112. id: 'namespace',
  113. options: [
  114. {
  115. label: 'monitoring',
  116. value: 'monitoring'
  117. },
  118. {
  119. label: 'scope',
  120. value: 'scope'
  121. },
  122. {
  123. label: 'All Namespaces',
  124. value: 'all'
  125. }
  126. ],
  127. selectType: 'many'
  128. }
  129. ],
  130. rank: 0,
  131. stats: {
  132. edge_count: 14,
  133. filtered_nodes: 16,
  134. node_count: 159,
  135. nonpseudo_node_count: 154
  136. },
  137. url: '/api/topology/services'
  138. // url:`${apiRoot}/api/topology/services`
  139. }
  140. ],
  141. url: '/api/topology/pods'
  142. // url:`${apiRoot}/api/topology/pods`
  143. }
  144. ];
  145. // actions
  146. const ChangeTopologyOptionAction = {
  147. option: 'option1',
  148. topologyId: 'topo1',
  149. type: ActionTypes.CHANGE_TOPOLOGY_OPTION,
  150. value: ['on']
  151. };
  152. const ChangeTopologyOptionAction2 = {
  153. option: 'option1',
  154. topologyId: 'topo1',
  155. type: ActionTypes.CHANGE_TOPOLOGY_OPTION,
  156. value: ['off']
  157. };
  158. const ClickNodeAction = {
  159. nodeId: 'n1',
  160. type: ActionTypes.CLICK_NODE
  161. };
  162. const ClickNode2Action = {
  163. nodeId: 'n2',
  164. type: ActionTypes.CLICK_NODE
  165. };
  166. const ClickRelativeAction = {
  167. nodeId: 'rel1',
  168. type: ActionTypes.CLICK_RELATIVE
  169. };
  170. const ClickShowTopologyForNodeAction = {
  171. nodeId: 'rel1',
  172. topologyId: 'topo2',
  173. type: ActionTypes.CLICK_SHOW_TOPOLOGY_FOR_NODE
  174. };
  175. const ClickSubTopologyAction = {
  176. topologyId: 'topo1-grouped',
  177. type: ActionTypes.CLICK_TOPOLOGY
  178. };
  179. const ClickTopologyAction = {
  180. topologyId: 'topo1',
  181. type: ActionTypes.CLICK_TOPOLOGY
  182. };
  183. const ClickTopology2Action = {
  184. topologyId: 'topo2',
  185. type: ActionTypes.CLICK_TOPOLOGY
  186. };
  187. const CloseWebsocketAction = {
  188. type: ActionTypes.CLOSE_WEBSOCKET
  189. };
  190. const deSelectNode = {
  191. type: ActionTypes.DESELECT_NODE
  192. };
  193. const OpenWebsocketAction = {
  194. type: ActionTypes.OPEN_WEBSOCKET
  195. };
  196. const ReceiveNodesDeltaAction = {
  197. delta: {
  198. add: [{
  199. adjacency: ['n1', 'n2'],
  200. id: 'n1'
  201. }, {
  202. id: 'n2'
  203. }]
  204. },
  205. type: ActionTypes.RECEIVE_NODES_DELTA
  206. };
  207. const ReceiveNodesDeltaUpdateAction = {
  208. delta: {
  209. remove: ['n2'],
  210. update: [{
  211. adjacency: ['n1'],
  212. id: 'n1'
  213. }]
  214. },
  215. type: ActionTypes.RECEIVE_NODES_DELTA
  216. };
  217. const ReceiveTopologiesAction = {
  218. topologies: [{
  219. name: 'Topo1',
  220. options: [{
  221. defaultValue: 'off',
  222. id: 'option1',
  223. options: [
  224. {value: 'on'},
  225. {value: 'off'}
  226. ]
  227. }],
  228. stats: {
  229. node_count: 1
  230. },
  231. sub_topologies: [{
  232. name: 'topo 1 grouped',
  233. url: '/topo1-grouped'
  234. }],
  235. url: '/topo1'
  236. }, {
  237. name: 'Topo2',
  238. stats: {
  239. node_count: 0
  240. },
  241. sub_topologies: [{
  242. name: 'topo 2 sub',
  243. url: '/topo2-sub'
  244. }],
  245. url: '/topo2'
  246. }],
  247. type: ActionTypes.RECEIVE_TOPOLOGIES
  248. };
  249. const ReceiveTopologiesHiddenAction = {
  250. topologies: [{
  251. name: 'Topo1',
  252. stats: {
  253. node_count: 1
  254. },
  255. url: '/topo1'
  256. }, {
  257. hide_if_empty: true,
  258. name: 'Topo2',
  259. stats: { filtered_nodes: 0, node_count: 0 },
  260. sub_topologies: [{
  261. hide_if_empty: true,
  262. name: 'topo 2 sub',
  263. stats: { filtered_nodes: 0, node_count: 0 },
  264. url: '/topo2-sub',
  265. }],
  266. url: '/topo2'
  267. }],
  268. type: ActionTypes.RECEIVE_TOPOLOGIES
  269. };
  270. const RouteAction = {
  271. state: {},
  272. type: ActionTypes.ROUTE_TOPOLOGY
  273. };
  274. const ChangeInstanceAction = {
  275. type: ActionTypes.CHANGE_INSTANCE
  276. };
  277. // Basic tests
  278. it('returns initial state', () => {
  279. const nextState = reducer(undefined, {});
  280. expect(is(nextState, initialState)).toBeTruthy();
  281. });
  282. // topology tests
  283. it('init with no topologies', () => {
  284. const nextState = reducer(undefined, {});
  285. expect(nextState.get('topologies').size).toBe(0);
  286. expect(nextState.get('currentTopology')).toBeFalsy();
  287. });
  288. it('get current topology', () => {
  289. let nextState = initialState;
  290. nextState = reducer(nextState, ReceiveTopologiesAction);
  291. nextState = reducer(nextState, ClickTopologyAction);
  292. expect(nextState.get('topologies').size).toBe(2);
  293. expect(nextState.get('currentTopology').get('name')).toBe('Topo1');
  294. expect(nextState.get('currentTopology').get('url')).toBe('/topo1');
  295. expect(nextState.get('currentTopology').get('options').first().get('id')).toEqual('option1');
  296. expect(nextState.getIn(['currentTopology', 'options']).toJS()).toEqual([{
  297. defaultValue: 'off',
  298. id: 'option1',
  299. options: [
  300. { value: 'on'},
  301. { value: 'off'}
  302. ],
  303. selectType: 'one'
  304. }]);
  305. });
  306. it('get sub-topology', () => {
  307. let nextState = initialState;
  308. nextState = reducer(nextState, ReceiveTopologiesAction);
  309. nextState = reducer(nextState, ClickSubTopologyAction);
  310. expect(nextState.get('topologies').size).toBe(2);
  311. expect(nextState.get('currentTopology').get('name')).toBe('topo 1 grouped');
  312. expect(nextState.get('currentTopology').get('url')).toBe('/topo1-grouped');
  313. expect(nextState.get('currentTopology').get('options')).toBeUndefined();
  314. });
  315. // topology options
  316. it('changes topology option', () => {
  317. let nextState = initialState;
  318. nextState = reducer(nextState, ReceiveTopologiesAction);
  319. nextState = reducer(nextState, ClickTopologyAction);
  320. // default options
  321. expect(activeTopologyOptionsSelector(nextState).has('option1')).toBeTruthy();
  322. expect(activeTopologyOptionsSelector(nextState).get('option1')).toBeInstanceOf(Array);
  323. expect(activeTopologyOptionsSelector(nextState).get('option1')).toEqual(['off']);
  324. expect(getUrlState(nextState).topologyOptions).toBeUndefined();
  325. // turn on
  326. nextState = reducer(nextState, ChangeTopologyOptionAction);
  327. expect(activeTopologyOptionsSelector(nextState).get('option1')).toEqual(['on']);
  328. expect(getUrlState(nextState).topologyOptions.topo1.option1).toEqual(['on']);
  329. // turn off
  330. nextState = reducer(nextState, ChangeTopologyOptionAction2);
  331. expect(activeTopologyOptionsSelector(nextState).get('option1')).toEqual(['off']);
  332. expect(getUrlState(nextState).topologyOptions).toBeUndefined();
  333. // sub-topology should retain main topo options
  334. nextState = reducer(nextState, ClickSubTopologyAction);
  335. expect(activeTopologyOptionsSelector(nextState).get('option1')).toEqual(['off']);
  336. expect(getUrlState(nextState).topologyOptions).toBeUndefined();
  337. // other topology w/o options dont return options, but keep in app state
  338. nextState = reducer(nextState, ClickTopology2Action);
  339. expect(activeTopologyOptionsSelector(nextState).size).toEqual(0);
  340. expect(getUrlState(nextState).topologyOptions).toBeUndefined();
  341. });
  342. it('adds/removes a topology option', () => {
  343. const addAction = {
  344. option: 'namespace',
  345. topologyId: 'services',
  346. type: ActionTypes.CHANGE_TOPOLOGY_OPTION,
  347. value: ['default', 'scope'],
  348. };
  349. const removeAction = {
  350. option: 'namespace',
  351. topologyId: 'services',
  352. type: ActionTypes.CHANGE_TOPOLOGY_OPTION,
  353. value: ['default']
  354. };
  355. let nextState = initialState;
  356. nextState = reducer(nextState, { topologies, type: ActionTypes.RECEIVE_TOPOLOGIES});
  357. nextState = reducer(nextState, { topologyId: 'services', type: ActionTypes.CLICK_TOPOLOGY });
  358. nextState = reducer(nextState, addAction);
  359. expect(activeTopologyOptionsSelector(nextState).toJS()).toEqual({
  360. namespace: ['default', 'scope'],
  361. pseudo: ['hide']
  362. });
  363. nextState = reducer(nextState, removeAction);
  364. expect(activeTopologyOptionsSelector(nextState).toJS()).toEqual({
  365. namespace: ['default'],
  366. pseudo: ['hide']
  367. });
  368. });
  369. it('sets topology options from route', () => {
  370. RouteAction.state = {
  371. selectedNodeId: null,
  372. topologyId: 'topo1',
  373. topologyOptions: {topo1: {option1: 'on'}}
  374. };
  375. let nextState = initialState;
  376. nextState = reducer(nextState, RouteAction);
  377. expect(activeTopologyOptionsSelector(nextState).get('option1')).toBe('on');
  378. expect(getUrlState(nextState).topologyOptions.topo1.option1).toBe('on');
  379. // stay same after topos have been received
  380. nextState = reducer(nextState, ReceiveTopologiesAction);
  381. nextState = reducer(nextState, ClickTopologyAction);
  382. expect(activeTopologyOptionsSelector(nextState).get('option1')).toBe('on');
  383. expect(getUrlState(nextState).topologyOptions.topo1.option1).toBe('on');
  384. });
  385. it('uses default topology options from route', () => {
  386. RouteAction.state = {
  387. selectedNodeId: null,
  388. topologyId: 'topo1',
  389. topologyOptions: null
  390. };
  391. let nextState = initialState;
  392. nextState = reducer(nextState, RouteAction);
  393. nextState = reducer(nextState, ReceiveTopologiesAction);
  394. nextState = reducer(nextState, ClickTopologyAction);
  395. expect(activeTopologyOptionsSelector(nextState).get('option1')).toEqual(['off']);
  396. expect(getUrlState(nextState).topologyOptions).toBeUndefined();
  397. });
  398. // nodes delta
  399. it('replaces adjacency on update', () => {
  400. let nextState = initialState;
  401. nextState = reducer(nextState, ReceiveNodesDeltaAction);
  402. expect(nextState.get('nodes').toJS().n1.adjacency).toEqual(['n1', 'n2']);
  403. nextState = reducer(nextState, ReceiveNodesDeltaUpdateAction);
  404. expect(nextState.get('nodes').toJS().n1.adjacency).toEqual(['n1']);
  405. });
  406. // browsing
  407. it('shows nodes that were received', () => {
  408. let nextState = initialState;
  409. nextState = reducer(nextState, ReceiveNodesDeltaAction);
  410. expect(nextState.get('nodes').toJS()).toEqual(NODE_SET);
  411. });
  412. it('knows a route was set', () => {
  413. let nextState = initialState;
  414. expect(nextState.get('routeSet')).toBeFalsy();
  415. nextState = reducer(nextState, RouteAction);
  416. expect(nextState.get('routeSet')).toBeTruthy();
  417. });
  418. it('gets selected node after click', () => {
  419. let nextState = initialState;
  420. nextState = reducer(nextState, ReceiveNodesDeltaAction);
  421. nextState = reducer(nextState, ClickNodeAction);
  422. expect(nextState.get('selectedNodeId')).toBe('n1');
  423. expect(nextState.get('nodes').toJS()).toEqual(NODE_SET);
  424. nextState = reducer(nextState, deSelectNode);
  425. expect(nextState.get('selectedNodeId')).toBe(null);
  426. expect(nextState.get('nodes').toJS()).toEqual(NODE_SET);
  427. });
  428. it('keeps showing nodes on navigating back after node click', () => {
  429. let nextState = initialState;
  430. nextState = reducer(nextState, ReceiveTopologiesAction);
  431. nextState = reducer(nextState, ClickTopologyAction);
  432. nextState = reducer(nextState, ReceiveNodesDeltaAction);
  433. expect(getUrlState(nextState).selectedNodeId).toBeUndefined();
  434. nextState = reducer(nextState, ClickNodeAction);
  435. expect(getUrlState(nextState).selectedNodeId).toEqual('n1');
  436. // go back in browsing
  437. RouteAction.state = {selectedNodeId: null, topologyId: 'topo1'};
  438. nextState = reducer(nextState, RouteAction);
  439. expect(nextState.get('selectedNodeId')).toBeNull();
  440. expect(nextState.get('nodes').toJS()).toEqual(NODE_SET);
  441. });
  442. it('closes details when changing topologies', () => {
  443. let nextState = initialState;
  444. nextState = reducer(nextState, ReceiveTopologiesAction);
  445. nextState = reducer(nextState, ClickTopologyAction);
  446. nextState = reducer(nextState, ReceiveNodesDeltaAction);
  447. expect(getUrlState(nextState).selectedNodeId).toBeUndefined();
  448. expect(getUrlState(nextState).topologyId).toEqual('topo1');
  449. nextState = reducer(nextState, ClickNodeAction);
  450. expect(getUrlState(nextState).selectedNodeId).toEqual('n1');
  451. expect(getUrlState(nextState).topologyId).toEqual('topo1');
  452. nextState = reducer(nextState, ClickSubTopologyAction);
  453. expect(getUrlState(nextState).selectedNodeId).toBeUndefined();
  454. expect(getUrlState(nextState).topologyId).toEqual('topo1-grouped');
  455. });
  456. // connection errors
  457. it('resets topology on websocket reconnect', () => {
  458. let nextState = initialState;
  459. nextState = reducer(nextState, ReceiveNodesDeltaAction);
  460. expect(nextState.get('nodes').toJS()).toEqual(NODE_SET);
  461. nextState = reducer(nextState, CloseWebsocketAction);
  462. expect(nextState.get('websocketClosed')).toBeTruthy();
  463. // keep showing old nodes
  464. expect(nextState.get('nodes').toJS()).toEqual(NODE_SET);
  465. nextState = reducer(nextState, OpenWebsocketAction);
  466. expect(nextState.get('websocketClosed')).toBeFalsy();
  467. });
  468. // adjacency test
  469. it('returns the correct adjacency set for a node', () => {
  470. let nextState = initialState;
  471. nextState = reducer(nextState, ReceiveNodesDeltaAction);
  472. expect(getAdjacentNodes(nextState).size).toEqual(0);
  473. nextState = reducer(nextState, ClickNodeAction);
  474. expect(getAdjacentNodes(nextState, 'n1').size).toEqual(2);
  475. expect(getAdjacentNodes(nextState, 'n1').has('n1')).toBeTruthy();
  476. expect(getAdjacentNodes(nextState, 'n1').has('n2')).toBeTruthy();
  477. nextState = reducer(nextState, deSelectNode);
  478. expect(getAdjacentNodes(nextState).size).toEqual(0);
  479. });
  480. // empty topology
  481. it('detects that the nodes display is empty', () => {
  482. let nextState = initialState;
  483. nextState = reducer(nextState, ReceiveTopologiesAction);
  484. nextState = reducer(nextState, ClickTopologyAction);
  485. expect(isNodesDisplayEmpty(nextState)).toBeTruthy();
  486. nextState = reducer(nextState, ReceiveNodesDeltaAction);
  487. expect(isNodesDisplayEmpty(nextState)).toBeFalsy();
  488. nextState = reducer(nextState, ClickTopology2Action);
  489. expect(isNodesDisplayEmpty(nextState)).toBeTruthy();
  490. nextState = reducer(nextState, ReceiveNodesDeltaAction);
  491. expect(isNodesDisplayEmpty(nextState)).toBeFalsy();
  492. });
  493. it('detects that the topo stats are empty', () => {
  494. let nextState = initialState;
  495. nextState = reducer(nextState, ReceiveTopologiesAction);
  496. nextState = reducer(nextState, ClickTopologyAction);
  497. expect(isTopologyNodeCountZero(nextState)).toBeFalsy();
  498. nextState = reducer(nextState, ReceiveNodesDeltaAction);
  499. expect(isTopologyNodeCountZero(nextState)).toBeFalsy();
  500. nextState = reducer(nextState, ClickTopology2Action);
  501. expect(isTopologyNodeCountZero(nextState)).toBeTruthy();
  502. nextState = reducer(nextState, ClickTopologyAction);
  503. expect(isTopologyNodeCountZero(nextState)).toBeFalsy();
  504. });
  505. it('keeps hidden topology visible if selected', () => {
  506. let nextState = initialState;
  507. nextState = reducer(nextState, ReceiveTopologiesAction);
  508. nextState = reducer(nextState, ClickTopology2Action);
  509. nextState = reducer(nextState, ReceiveTopologiesHiddenAction);
  510. expect(nextState.get('currentTopologyId')).toEqual('topo2');
  511. expect(nextState.get('topologies').toJS().length).toEqual(2);
  512. });
  513. it('keeps hidden topology visible if sub_topology selected', () => {
  514. let nextState = initialState;
  515. nextState = reducer(nextState, ReceiveTopologiesAction);
  516. nextState = reducer(nextState, { topologyId: 'topo2-sub', type: ActionTypes.CLICK_TOPOLOGY });
  517. nextState = reducer(nextState, ReceiveTopologiesHiddenAction);
  518. expect(nextState.get('currentTopologyId')).toEqual('topo2-sub');
  519. expect(nextState.get('topologies').toJS().length).toEqual(2);
  520. });
  521. it('hides hidden topology if not selected', () => {
  522. let nextState = initialState;
  523. nextState = reducer(nextState, ClickTopologyAction);
  524. nextState = reducer(nextState, ReceiveTopologiesHiddenAction);
  525. expect(nextState.get('topologies').toJS().length).toEqual(1);
  526. });
  527. // selection of relatives
  528. it('keeps relatives as a stack', () => {
  529. let nextState = initialState;
  530. nextState = reducer(nextState, ClickNodeAction);
  531. expect(nextState.get('selectedNodeId')).toBe('n1');
  532. expect(nextState.get('nodeDetails').size).toEqual(1);
  533. expect(nextState.get('nodeDetails').has('n1')).toBeTruthy();
  534. expect(nextState.get('nodeDetails').keySeq().last()).toEqual('n1');
  535. nextState = reducer(nextState, ClickRelativeAction);
  536. // stack relative, first node stays main node
  537. expect(nextState.get('selectedNodeId')).toBe('n1');
  538. expect(nextState.get('nodeDetails').keySeq().last()).toEqual('rel1');
  539. expect(nextState.get('nodeDetails').size).toEqual(2);
  540. expect(nextState.get('nodeDetails').has('rel1')).toBeTruthy();
  541. // click on first node should clear the stack
  542. nextState = reducer(nextState, ClickNodeAction);
  543. expect(nextState.get('selectedNodeId')).toBe('n1');
  544. expect(nextState.get('nodeDetails').keySeq().last()).toEqual('n1');
  545. expect(nextState.get('nodeDetails').size).toEqual(1);
  546. expect(nextState.get('nodeDetails').has('rel1')).toBeFalsy();
  547. });
  548. it('keeps clears stack when sibling is clicked', () => {
  549. let nextState = initialState;
  550. nextState = reducer(nextState, ClickNodeAction);
  551. expect(nextState.get('selectedNodeId')).toBe('n1');
  552. expect(nextState.get('nodeDetails').size).toEqual(1);
  553. expect(nextState.get('nodeDetails').has('n1')).toBeTruthy();
  554. expect(nextState.get('nodeDetails').keySeq().last()).toEqual('n1');
  555. nextState = reducer(nextState, ClickRelativeAction);
  556. // stack relative, first node stays main node
  557. expect(nextState.get('selectedNodeId')).toBe('n1');
  558. expect(nextState.get('nodeDetails').keySeq().last()).toEqual('rel1');
  559. expect(nextState.get('nodeDetails').size).toEqual(2);
  560. expect(nextState.get('nodeDetails').has('rel1')).toBeTruthy();
  561. // click on sibling node should clear the stack
  562. nextState = reducer(nextState, ClickNode2Action);
  563. expect(nextState.get('selectedNodeId')).toBe('n2');
  564. expect(nextState.get('nodeDetails').keySeq().last()).toEqual('n2');
  565. expect(nextState.get('nodeDetails').size).toEqual(1);
  566. expect(nextState.get('nodeDetails').has('n1')).toBeFalsy();
  567. expect(nextState.get('nodeDetails').has('rel1')).toBeFalsy();
  568. });
  569. it('selectes relatives topology while keeping node selected', () => {
  570. let nextState = initialState;
  571. nextState = reducer(nextState, ClickTopologyAction);
  572. nextState = reducer(nextState, ReceiveTopologiesAction);
  573. expect(nextState.get('currentTopology').get('name')).toBe('Topo1');
  574. nextState = reducer(nextState, ClickNodeAction);
  575. expect(nextState.get('selectedNodeId')).toBe('n1');
  576. expect(nextState.get('nodeDetails').size).toEqual(1);
  577. expect(nextState.get('nodeDetails').has('n1')).toBeTruthy();
  578. expect(nextState.get('nodeDetails').keySeq().last()).toEqual('n1');
  579. nextState = reducer(nextState, ClickRelativeAction);
  580. // stack relative, first node stays main node
  581. expect(nextState.get('selectedNodeId')).toBe('n1');
  582. expect(nextState.get('nodeDetails').keySeq().last()).toEqual('rel1');
  583. expect(nextState.get('nodeDetails').size).toEqual(2);
  584. expect(nextState.get('nodeDetails').has('rel1')).toBeTruthy();
  585. // click switches over to relative's topology and selectes relative
  586. nextState = reducer(nextState, ClickShowTopologyForNodeAction);
  587. expect(nextState.get('selectedNodeId')).toBe('rel1');
  588. expect(nextState.get('nodeDetails').keySeq().last()).toEqual('rel1');
  589. expect(nextState.get('nodeDetails').size).toEqual(1);
  590. expect(nextState.get('currentTopology').get('name')).toBe('Topo2');
  591. });
  592. it('closes the help dialog if the canvas is clicked', () => {
  593. let nextState = initialState.set('showingHelp', true);
  594. nextState = reducer(nextState, { type: ActionTypes.CLICK_BACKGROUND });
  595. expect(nextState.get('showingHelp')).toBe(false);
  596. });
  597. it('switches to table view when complexity is high', () => {
  598. let nextState = initialState.set('currentTopology', fromJS(topologies[0]));
  599. nextState = reducer(nextState, {type: ActionTypes.SET_RECEIVED_NODES_DELTA});
  600. expect(nextState.get('topologyViewMode')).toEqual(TABLE_VIEW_MODE);
  601. expect(nextState.get('initialNodesLoaded')).toBe(true);
  602. });
  603. it('cleans up old adjacencies', () => {
  604. // Add some nodes
  605. const action1 = {
  606. delta: { add: [{ id: 'n1' }, { id: 'n2' }] },
  607. type: ActionTypes.RECEIVE_NODES_DELTA
  608. };
  609. // Show nodes as connected
  610. const action2 = {
  611. delta: {
  612. update: [{ adjacency: ['n2'], id: 'n1' }]
  613. },
  614. type: ActionTypes.RECEIVE_NODES_DELTA
  615. };
  616. // Remove the connection
  617. const action3 = {
  618. delta: {
  619. update: [{ id: 'n1' }]
  620. },
  621. type: ActionTypes.RECEIVE_NODES_DELTA
  622. };
  623. let nextState = reducer(initialState, action1);
  624. nextState = reducer(nextState, action2);
  625. nextState = reducer(nextState, action3);
  626. expect(nextState.getIn(['nodes', 'n1', 'adjacency'])).toBeFalsy();
  627. });
  628. it('removes non-transferrable state values when changing instances', () => {
  629. let nextState = initialState;
  630. nextState = reducer(nextState, ClickNodeAction);
  631. expect(nextState.get('selectedNodeId')).toEqual('n1');
  632. expect(nextState.getIn(['nodeDetails', 'n1'])).toBeTruthy();
  633. nextState = reducer(nextState, ChangeInstanceAction);
  634. expect(nextState.get('selectedNodeId')).toBeFalsy();
  635. expect(nextState.getIn(['nodeDetails', 'n1'])).toBeFalsy();
  636. });
  637. it('highlights bidirectional edges', () => {
  638. const action = {
  639. edgeId: constructEdgeId('abc123', 'def456'),
  640. type: ActionTypes.ENTER_EDGE
  641. };
  642. const nextState = reducer(initialState, action);
  643. expect(highlightedEdgeIdsSelector(nextState).toJS()).toEqual([
  644. constructEdgeId('abc123', 'def456'),
  645. constructEdgeId('def456', 'abc123')
  646. ]);
  647. });
  648. });