neovis.tests.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. import Neo4j, * as Neo4jMock from 'neo4j-driver';
  2. import Neovis from '../src/neovis';
  3. import { NEOVIS_DEFAULT_CONFIG } from '../src/neovis';
  4. import { CompletionEvent } from '../src/events';
  5. import * as testUtils from './testUtils';
  6. jest.mock('neo4j-driver');
  7. describe('Neovis', () => {
  8. const container_id = 'randomId';
  9. const initial_cypher = 'test query';
  10. const label1 = 'label1';
  11. const label2 = 'label2';
  12. const relationshipType = 'TEST';
  13. let neovis;
  14. beforeEach(() => Neo4jMock.clearAllMocks());
  15. beforeEach(() => {
  16. testUtils.clearIdCounter();
  17. document.body.innerHTML = `<div id="${container_id}"></div>`;
  18. });
  19. describe('NeoVis config defaults behavior', () => {
  20. let config = {};
  21. beforeEach(() => {
  22. config = {};
  23. });
  24. it('should merge default symbol for each label config', () => {
  25. config.labels = {
  26. a: {
  27. caption: 'name'
  28. },
  29. [NEOVIS_DEFAULT_CONFIG]: {
  30. test: 'test'
  31. }
  32. };
  33. const neovis = new Neovis(config);
  34. expect(neovis._config.labels.a).toMatchObject({ caption: 'name', test: 'test' });
  35. });
  36. it('should not change the config sent', () => {
  37. config = {
  38. labels: {
  39. a: {
  40. caption: 'name'
  41. },
  42. [NEOVIS_DEFAULT_CONFIG]: {
  43. test: 'test'
  44. }
  45. },
  46. relationships: {
  47. a: {
  48. thickness: 0.1
  49. },
  50. [NEOVIS_DEFAULT_CONFIG]: {
  51. test: 'test'
  52. }
  53. }
  54. };
  55. const configTemp = { ...config };
  56. new Neovis(config);
  57. expect(config).toMatchObject(configTemp);
  58. });
  59. it('should override default config if specific label have one', () => {
  60. config.relationships = {
  61. a: {
  62. caption: 'name',
  63. overrideThis: 'overridden'
  64. },
  65. [NEOVIS_DEFAULT_CONFIG]: {
  66. test: 'test',
  67. overrideThis: 'override'
  68. }
  69. };
  70. const neovis = new Neovis(config);
  71. expect(neovis._config.relationships.a).toMatchObject({
  72. caption: 'name', test: 'test', overrideThis: 'overridden'
  73. });
  74. });
  75. it('should merge default symbol for each relationship config', () => {
  76. config.labels = {
  77. a: {
  78. caption: 'name'
  79. },
  80. [NEOVIS_DEFAULT_CONFIG]: {
  81. test: 'test'
  82. }
  83. };
  84. const neovis = new Neovis(config);
  85. expect(neovis._config.labels.a).toMatchObject({ caption: 'name', test: 'test' });
  86. });
  87. it('should override default config if specific relationship have one', () => {
  88. config.relationships = {
  89. a: {
  90. caption: 'name',
  91. overrideThis: 'overridden'
  92. },
  93. [NEOVIS_DEFAULT_CONFIG]: {
  94. test: 'test',
  95. overrideThis: 'override'
  96. }
  97. };
  98. const neovis = new Neovis(config);
  99. expect(neovis._config.relationships.a).toMatchObject({
  100. caption: 'name', test: 'test', overrideThis: 'overridden'
  101. });
  102. });
  103. });
  104. describe('Neovis default behavior', () => {
  105. beforeEach(() => {
  106. neovis = new Neovis({ initial_cypher, container_id });
  107. });
  108. it('should call run with query', () => {
  109. neovis.render();
  110. expect(Neo4jMock.mockSessionRun).toHaveBeenCalledWith(initial_cypher, { limit: 30 });
  111. });
  112. it('should call completed when complete', () => new Promise(done => {
  113. testUtils.mockNormalRunSubscribe();
  114. neovis.render();
  115. neovis.registerOnEvent(CompletionEvent, () => {
  116. expect(true).toBe(true);
  117. done();
  118. });
  119. }));
  120. it('should save records to dataset', async () => {
  121. testUtils.mockNormalRunSubscribe([
  122. testUtils.makeRecord([testUtils.makeNode([label1])]),
  123. ]);
  124. neovis.render();
  125. await testUtils.neovisRenderDonePromise(neovis);
  126. expect(neovis._data.nodes.get(1)).toBeDefined();
  127. });
  128. it('should save paths to dataset', async () => {
  129. testUtils.mockNormalRunSubscribe([
  130. testUtils.makeRecord([testUtils.makePathFromNodes([
  131. testUtils.makeNode([label1]),
  132. testUtils.makeNode([label1])
  133. ], relationshipType)]),
  134. ]);
  135. neovis.render();
  136. await testUtils.neovisRenderDonePromise(neovis);
  137. expect(neovis._data.nodes.length).toBe(2);
  138. expect(neovis._data.edges.length).toBe(1);
  139. });
  140. it('should save record with multiple parameters', async () => {
  141. const firstNode = testUtils.makeNode([label1]);
  142. const secondNode = testUtils.makeNode([label1]);
  143. const relationship = testUtils.makeRelationship(relationshipType, firstNode, secondNode);
  144. testUtils.mockNormalRunSubscribe([
  145. testUtils.makeRecord([firstNode, secondNode, relationship])
  146. ]);
  147. neovis.render();
  148. await testUtils.neovisRenderDonePromise(neovis);
  149. expect(neovis._data.nodes.length).toBe(2);
  150. expect(neovis.nodes.length).toBe(2);
  151. expect(neovis._data.edges.length).toBe(1);
  152. expect(neovis.edges.length).toBe(1);
  153. });
  154. it('should save multiple records from different types', async () => {
  155. const firstNode = testUtils.makeNode([label1]);
  156. const secondNode = testUtils.makeNode([label1]);
  157. const relationship = testUtils.makeRelationship(relationshipType, firstNode, secondNode);
  158. testUtils.mockNormalRunSubscribe([
  159. testUtils.makeRecord([testUtils.makePathFromNodes([
  160. testUtils.makeNode([label1]),
  161. testUtils.makeNode([label1])
  162. ], relationshipType)]),
  163. testUtils.makeRecord([testUtils.makeNode([label1])]),
  164. testUtils.makeRecord([firstNode, secondNode, relationship])
  165. ]);
  166. neovis.render();
  167. await testUtils.neovisRenderDonePromise(neovis);
  168. expect(neovis._data.nodes.length).toBe(5);
  169. expect(neovis.nodes.length).toBe(5);
  170. expect(neovis._data.edges.length).toBe(2);
  171. expect(neovis.edges.length).toBe(2);
  172. });
  173. it('should save raw records', async () => {
  174. const firstNode = testUtils.makeNode([label1]);
  175. const secondNode = testUtils.makeNode([label1]);
  176. const relationship = testUtils.makeRelationship(relationshipType, firstNode, secondNode);
  177. testUtils.mockNormalRunSubscribe([
  178. testUtils.makeRecord([firstNode, secondNode, relationship])
  179. ]);
  180. neovis.render();
  181. await testUtils.neovisRenderDonePromise(neovis);
  182. expect(neovis._data.nodes.get(1).raw).toBeDefined();
  183. expect(neovis._data.nodes.get(2).raw).toBeDefined();
  184. expect(neovis._data.edges.get(3).raw).toBeDefined();
  185. });
  186. });
  187. describe('neovis with sizeCypher', () => {
  188. const sizeCypher = 'sizeCypher';
  189. const neovisConfig = {
  190. initial_cypher,
  191. container_id,
  192. labels: {
  193. [label1]: {
  194. sizeCypher: sizeCypher
  195. }
  196. }
  197. };
  198. beforeEach(() => {
  199. neovis = new Neovis(neovisConfig);
  200. });
  201. // TODO: session.readTransaction needs to be properly mocked
  202. // skipping this test until mock is added
  203. it.skip('should call sizeCypher and save return value to data set value', async () => {
  204. const node = testUtils.makeNode([label1]);
  205. testUtils.mockFullRunSubscribe({
  206. [initial_cypher]: {
  207. default: [testUtils.makeRecord([node])]
  208. },
  209. [sizeCypher]: {
  210. [node.identity.toInt()]: [testUtils.makeRecord([Neo4j.int(1)])]
  211. }
  212. });
  213. neovis.render();
  214. await testUtils.neovisRenderDonePromise(neovis);
  215. expect(Neo4jMock.mockSessionRun).toHaveBeenCalledTimes(1 + 1); // once for initial cypher and once for the sizeCypher
  216. expect(neovis._data.nodes.get(1)).toHaveProperty('value', 1);
  217. });
  218. });
  219. describe('neovis with update cypher', () => {
  220. const updateWithCypher = 'updateCypher';
  221. beforeEach(() => {
  222. neovis = new Neovis({ initial_cypher, container_id });
  223. });
  224. it('should call updateWithCypher and add the new node to visualization', async () => {
  225. const node1 = testUtils.makeNode([label1]);
  226. const node2 = testUtils.makeNode([label1]);
  227. testUtils.mockFullRunSubscribe({
  228. [initial_cypher]: {
  229. default: [testUtils.makeRecord([node1])]
  230. },
  231. [updateWithCypher]: {
  232. default: [testUtils.makeRecord([node2])]
  233. }
  234. });
  235. neovis.render();
  236. await testUtils.neovisRenderDonePromise(neovis);
  237. expect(Neo4jMock.mockSessionRun).toHaveBeenCalledTimes(1);
  238. expect(neovis._data.nodes.length).toBe(1); // 1 node before update with cypher
  239. neovis.updateWithCypher(updateWithCypher); // do the update
  240. await testUtils.neovisRenderDonePromise(neovis);
  241. expect(Neo4jMock.mockSessionRun).toHaveBeenCalledTimes(1 + 1); // once for initial cypher and once for the update
  242. expect(neovis._data.nodes.length).toBe(2); // 2 node after update with cypher
  243. });
  244. it('call updateWithCypher with same init query should not create duplicate nodes', async () => {
  245. const node1 = testUtils.makeNode([label1]);
  246. testUtils.mockFullRunSubscribe({
  247. [initial_cypher]: {
  248. default: [testUtils.makeRecord([node1])]
  249. }
  250. });
  251. neovis.render();
  252. await testUtils.neovisRenderDonePromise(neovis);
  253. expect(Neo4jMock.mockSessionRun).toHaveBeenCalledTimes(1);
  254. expect(neovis._data.nodes.length).toBe(1); // 1 node before update with cypher
  255. neovis.updateWithCypher(initial_cypher); // do the update
  256. await testUtils.neovisRenderDonePromise(neovis);
  257. expect(Neo4jMock.mockSessionRun).toHaveBeenCalledTimes(1 + 1); // once for initial cypher and once for the update
  258. expect(neovis._data.nodes.length).toBe(1); // 1 node after update with cypher
  259. });
  260. });
  261. describe('neovis config test', () => {
  262. const imageUrl = 'https://visjs.org/images/visjs_logo.png';
  263. const fontSize = 28;
  264. const fontColor = '#00FF00';
  265. let config = {
  266. container_id: container_id,
  267. labels: {
  268. [label1]: {
  269. image: imageUrl,
  270. font: {
  271. 'size': fontSize,
  272. 'color': fontColor,
  273. }
  274. }
  275. },
  276. initial_cypher: initial_cypher
  277. };
  278. beforeEach(() => {
  279. neovis = new Neovis(config);
  280. });
  281. it('image field in config should reflect in node data', async () => {
  282. const node1 = testUtils.makeNode([label1]);
  283. testUtils.mockFullRunSubscribe({
  284. [initial_cypher]: {
  285. default: [testUtils.makeRecord([node1])]
  286. }
  287. });
  288. neovis.render();
  289. await testUtils.neovisRenderDonePromise(neovis);
  290. expect(neovis._data.nodes.get(1)).toHaveProperty('image', imageUrl);
  291. });
  292. it('image field for type not specified in config should not reflect in node data', async () => {
  293. const node1 = testUtils.makeNode([label2]);
  294. testUtils.mockFullRunSubscribe({
  295. [initial_cypher]: {
  296. default: [testUtils.makeRecord([node1])]
  297. }
  298. });
  299. neovis.render();
  300. await testUtils.neovisRenderDonePromise(neovis);
  301. expect(neovis._data.nodes.get(1)).toHaveProperty('image', undefined);
  302. });
  303. it('font field in config should reflect in node data', async () => {
  304. const node1 = testUtils.makeNode([label1]);
  305. testUtils.mockFullRunSubscribe({
  306. [initial_cypher]: {
  307. default: [testUtils.makeRecord([node1])]
  308. }
  309. });
  310. neovis.render();
  311. await testUtils.neovisRenderDonePromise(neovis);
  312. expect(neovis._data.nodes.get(1).font).toBeDefined();
  313. expect(neovis._data.nodes.get(1).font.size).toBe(fontSize);
  314. expect(neovis._data.nodes.get(1).font.color).toBe(fontColor);
  315. });
  316. it('font field for type not specified in config should not reflect in node data', async () => {
  317. const node1 = testUtils.makeNode([label2]);
  318. testUtils.mockFullRunSubscribe({
  319. [initial_cypher]: {
  320. default: [testUtils.makeRecord([node1])]
  321. }
  322. });
  323. neovis.render();
  324. await testUtils.neovisRenderDonePromise(neovis);
  325. expect(neovis._data.nodes.get(1)).toHaveProperty('font', undefined);
  326. });
  327. });
  328. describe('neovis type casting test', () => {
  329. const intProperity = 'intProperity';
  330. const intProperityValue = 40;
  331. const expectedIntProperityCaption = '40';
  332. const floatProperity = 'floatProperity';
  333. const floatProperityValue = 40.5;
  334. const expectedFloatProperityCaption = '40.5';
  335. it('should cast int type properity as caption', async () => {
  336. let config = {
  337. container_id: container_id,
  338. labels: {
  339. [label1]: {
  340. 'caption': intProperity
  341. }
  342. },
  343. initial_cypher: initial_cypher
  344. };
  345. neovis = new Neovis(config);
  346. const node1 = testUtils.makeNode([label1], { [intProperity]: intProperityValue });
  347. testUtils.mockFullRunSubscribe({
  348. [initial_cypher]: {
  349. default: [testUtils.makeRecord([node1])]
  350. }
  351. });
  352. neovis.render();
  353. await testUtils.neovisRenderDonePromise(neovis);
  354. expect(Neo4jMock.mockSessionRun).toHaveBeenCalledTimes(1);
  355. expect(neovis._data.nodes.get(1)).toHaveProperty('label', expectedIntProperityCaption); // 1 node before update with cypher
  356. });
  357. it('should cast float type properity as caption', async () => {
  358. let config = {
  359. container_id: container_id,
  360. labels: {
  361. [label1]: {
  362. 'caption': floatProperity
  363. }
  364. },
  365. initial_cypher: initial_cypher
  366. };
  367. neovis = new Neovis(config);
  368. const node1 = testUtils.makeNode([label1], { [floatProperity]: floatProperityValue });
  369. testUtils.mockFullRunSubscribe({
  370. [initial_cypher]: {
  371. default: [testUtils.makeRecord([node1])]
  372. }
  373. });
  374. neovis.render();
  375. await testUtils.neovisRenderDonePromise(neovis);
  376. expect(Neo4jMock.mockSessionRun).toHaveBeenCalledTimes(1);
  377. expect(neovis._data.nodes.get(1)).toHaveProperty('label', expectedFloatProperityCaption); // 1 node before update with cypher
  378. });
  379. });
  380. });