vis.js is a dynamic, browser-based visualization library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

638 lines
17 KiB

  1. /**
  2. * TODO - add tests for:
  3. * ====
  4. *
  5. * - !!! good test case with the tags for max width
  6. * - pathological cases of spaces (and other whitespace!)
  7. * - html unclosed or unopened tags
  8. * - html tag combinations with no font defined (e.g. bold within mono)
  9. */
  10. var assert = require('assert')
  11. var Label = require('../lib/network/modules/components/shared/Label').default;
  12. var NodesHandler = require('../lib/network/modules/NodesHandler').default;
  13. /**************************************************************
  14. * Dummy class definitions for minimal required functionality.
  15. **************************************************************/
  16. class DummyContext {
  17. measureText(text) {
  18. return {
  19. width: 12*text.length,
  20. height: 14
  21. };
  22. }
  23. }
  24. class DummyLayoutEngine {
  25. positionInitially() {}
  26. }
  27. /**************************************************************
  28. * End Dummy class definitions
  29. **************************************************************/
  30. describe('Network Label', function() {
  31. /**
  32. * Retrieve options object from a NodesHandler instance
  33. *
  34. * NOTE: these are options at the node-level
  35. */
  36. function getOptions(options = {}) {
  37. var body = {
  38. functions: {},
  39. emitter: {
  40. on: function() {}
  41. }
  42. }
  43. var nodesHandler = new NodesHandler(body, {}, options, new DummyLayoutEngine() );
  44. //console.log(JSON.stringify(nodesHandler.options, null, 2));
  45. return nodesHandler.options;
  46. }
  47. /**
  48. * Check if the returned lines and blocks are as expected.
  49. *
  50. * All width/height fields and font info are ignored.
  51. * Within blocks, only the text is compared
  52. */
  53. function checkBlocks(returned, expected) {
  54. let showBlocks = () => {
  55. return '\nreturned: ' + JSON.stringify(returned, null, 2) + '\n' +
  56. 'expected: ' + JSON.stringify(expected, null, 2);
  57. }
  58. assert.equal(expected.lines.length, returned.lines.length, 'Number of lines does not match, ' + showBlocks());
  59. for (let i = 0; i < returned.lines.length; ++i) {
  60. let retLine = returned.lines[i];
  61. let expLine = expected.lines[i];
  62. assert(retLine.blocks.length === expLine.blocks.length, 'Number of blocks does not match, ' + showBlocks());
  63. for (let j = 0; j < retLine.blocks.length; ++j) {
  64. let retBlock = retLine.blocks[j];
  65. let expBlock = expLine.blocks[j];
  66. assert(retBlock.text === expBlock.text, 'Text does not match, ' + showBlocks());
  67. assert(retBlock.mod !== undefined);
  68. if (retBlock.mod === 'normal' || retBlock.mod === '') {
  69. assert(expBlock.mod === undefined || expBlock.mod === 'normal' || expBlock === '',
  70. 'No mod field expected in returned, ' + showBlocks());
  71. } else {
  72. assert(retBlock.mod === expBlock.mod, 'Mod fields do not match, line: ' + i + ', block: ' + j +
  73. '; ret: ' + retBlock.mod + ', exp: ' + expBlock.mod + '\n' + showBlocks());
  74. }
  75. }
  76. }
  77. }
  78. function checkProcessedLabels(label, text, expected) {
  79. var ctx = new DummyContext();
  80. for (var i in text) {
  81. var ret = label._processLabelText(ctx, false, false, text[i]);
  82. //console.log(JSON.stringify(ret, null, 2));
  83. checkBlocks(ret, expected[i]);
  84. }
  85. }
  86. /**************************************************************
  87. * Test data
  88. **************************************************************/
  89. var normal_text = [
  90. "label text",
  91. "label\nwith\nnewlines",
  92. "OnereallylongwordthatshouldgooverwidthConstraint.maximumifdefined",
  93. "One really long sentence that should go over widthConstraint.maximum if defined",
  94. "Reallyoneenormouslylargelabel withtwobigwordsgoingoverwayovermax"
  95. ]
  96. var html_text = [
  97. "label <b>with</b> <code>some</code> <i>multi <b>tags</b></i>",
  98. "label <b>with</b> <code>some</code> \n <i>multi <b>tags</b></i>\n and newlines" // NB spaces around \n's
  99. ];
  100. var markdown_text = [
  101. "label *with* `some` _multi *tags*_",
  102. "label *with* `some` \n _multi *tags*_\n and newlines" // NB spaces around \n's
  103. ];
  104. /**************************************************************
  105. * Expected Results
  106. **************************************************************/
  107. var normal_expected = [{
  108. // In first item, width/height kept in for reference
  109. width: 120,
  110. height: 14,
  111. lines: [{
  112. width: 120,
  113. height: 14,
  114. blocks: [{
  115. text: "label text",
  116. width: 120,
  117. height: 14,
  118. }]
  119. }]
  120. }, {
  121. lines: [{
  122. blocks: [{text: "label"}]
  123. }, {
  124. blocks: [{text: "with"}]
  125. }, {
  126. blocks: [{text: "newlines"}]
  127. }]
  128. }, {
  129. // From here onward, changes width max width set
  130. lines: [{
  131. blocks: [{text: "OnereallylongwordthatshouldgooverwidthConstraint.maximumifdefined"}]
  132. }]
  133. }, {
  134. lines: [{
  135. blocks: [{text: "One really long sentence that should go over widthConstraint.maximum if defined"}]
  136. }]
  137. }, {
  138. lines: [{
  139. blocks: [{text: "Reallyoneenormouslylargelabel withtwobigwordsgoingoverwayovermax"}]
  140. }]
  141. }];
  142. const indexWidthConstrained = 2; // index of first item that will be different with max width set
  143. var normal_widthConstraint_expected = normal_expected.slice(0, indexWidthConstrained);
  144. Array.prototype.push.apply(normal_widthConstraint_expected, [{
  145. lines: [{
  146. blocks: [{text: "Onereallylongwordthatshoul"}]
  147. }, {
  148. blocks: [{text: "dgooverwidthConstraint.max"}]
  149. }, {
  150. blocks: [{text: "imumifdefined"}]
  151. }]
  152. }, {
  153. lines: [{
  154. blocks: [{text: "One really long"}]
  155. }, {
  156. blocks: [{text: "sentence that should"}]
  157. }, {
  158. blocks: [{text: "go over"}]
  159. }, {
  160. blocks: [{text: "widthConstraint.maximum"}]
  161. }, {
  162. blocks: [{text: "if defined"}]
  163. }]
  164. }, {
  165. lines: [{
  166. blocks: [{text: "Reallyoneenormouslylargela"}]
  167. }, {
  168. blocks: [{text: "bel"}]
  169. }, {
  170. blocks: [{text: "withtwobigwordsgoingoverwa"}]
  171. }, {
  172. blocks: [{text: "yovermax"}]
  173. }]
  174. }]);
  175. var html_unchanged_expected = [{
  176. lines: [{
  177. blocks: [{text: "label <b>with</b> <code>some</code> <i>multi <b>tags</b></i>"}]
  178. }]
  179. }, {
  180. lines: [{
  181. blocks: [{text: "label <b>with</b> <code>some</code> "}]
  182. }, {
  183. blocks: [{text: " <i>multi <b>tags</b></i>"}]
  184. }, {
  185. blocks: [{text: " and newlines"}]
  186. }]
  187. }];
  188. var html_widthConstraint_unchanged = [{
  189. lines: [{
  190. blocks: [{text: "label <b>with</b>"}]
  191. }, {
  192. blocks: [{text: "<code>some</code>"}]
  193. }, {
  194. blocks: [{text: "<i>multi"}]
  195. }, {
  196. blocks: [{text: "<b>tags</b></i>"}]
  197. }]
  198. }, {
  199. lines: [{
  200. blocks: [{text: "label <b>with</b>"}]
  201. }, {
  202. blocks: [{text: "<code>some</code> "}]
  203. }, {
  204. blocks: [{text: " <i>multi"}]
  205. }, {
  206. blocks: [{text: "<b>tags</b></i>"}]
  207. }, {
  208. blocks: [{text: " and newlines"}]
  209. }]
  210. }];
  211. var markdown_unchanged_expected = [{
  212. lines: [{
  213. blocks: [{text: "label *with* `some` _multi *tags*_"}]
  214. }]
  215. }, {
  216. lines: [{
  217. blocks: [{text: "label *with* `some` "}]
  218. }, {
  219. blocks: [{text: " _multi *tags*_"}]
  220. }, {
  221. blocks: [{text: " and newlines"}]
  222. }]
  223. }];
  224. var markdown_widthConstraint_expected= [{
  225. lines: [{
  226. blocks: [{text: "label *with* `some`"}]
  227. }, {
  228. blocks: [{text: "_multi *tags*_"}]
  229. }]
  230. }, {
  231. lines: [{
  232. blocks: [{text: "label *with* `some` "}]
  233. }, {
  234. blocks: [{text: " _multi *tags*_"}]
  235. }, {
  236. blocks: [{text: " and newlines"}]
  237. }]
  238. }];
  239. var multi_expected = [{
  240. lines: [{
  241. blocks: [
  242. {text: "label "},
  243. {text: "with" , mod: 'bold'},
  244. {text: " "},
  245. {text: "some" , mod: 'mono'},
  246. {text: " "},
  247. {text: "multi ", mod: 'ital'},
  248. {text: "tags" , mod: 'boldital'}
  249. ]
  250. }]
  251. }, {
  252. lines: [{
  253. blocks: [
  254. {text: "label "},
  255. {text: "with" , mod: 'bold'},
  256. {text: " "},
  257. {text: "some" , mod: 'mono'},
  258. {text: " "}
  259. ]
  260. }, {
  261. blocks: [
  262. {text: " "},
  263. {text: "multi ", mod: 'ital'},
  264. {text: "tags" , mod: 'boldital'}
  265. ]
  266. }, {
  267. blocks: [{text: " and newlines"}]
  268. }]
  269. }];
  270. /**************************************************************
  271. * End Expected Results
  272. **************************************************************/
  273. it('parses normal text labels', function (done) {
  274. var label = new Label({}, getOptions());
  275. checkProcessedLabels(label, normal_text , normal_expected);
  276. checkProcessedLabels(label, html_text , html_unchanged_expected); // html unchanged
  277. checkProcessedLabels(label, markdown_text, markdown_unchanged_expected); // markdown unchanged
  278. done();
  279. });
  280. it('parses html labels', function (done) {
  281. var options = getOptions(options);
  282. options.font.multi = true; // TODO: also test 'html', also test illegal value here
  283. var label = new Label({}, options);
  284. checkProcessedLabels(label, normal_text , normal_expected); // normal as usual
  285. checkProcessedLabels(label, html_text , multi_expected);
  286. checkProcessedLabels(label, markdown_text, markdown_unchanged_expected); // markdown unchanged
  287. done();
  288. });
  289. it('parses markdown labels', function (done) {
  290. var options = getOptions(options);
  291. options.font.multi = 'markdown'; // TODO: also test 'md', also test illegal value here
  292. var label = new Label({}, options);
  293. checkProcessedLabels(label, normal_text , normal_expected); // normal as usual
  294. checkProcessedLabels(label, html_text , html_unchanged_expected); // html unchanged
  295. checkProcessedLabels(label, markdown_text, multi_expected);
  296. done();
  297. });
  298. it('handles normal text with widthConstraint.maximum', function (done) {
  299. var options = getOptions(options);
  300. //
  301. // What the user would set:
  302. //
  303. // options.widthConstraint = { minimum: 100, maximum: 200};
  304. //
  305. // No sense in adding minWdt, not used when splitting labels into lines
  306. //
  307. // This comment also applies to the usage of maxWdt in the test cases below
  308. //
  309. options.font.maxWdt = 300;
  310. var label = new Label({}, options);
  311. checkProcessedLabels(label, normal_text , normal_widthConstraint_expected);
  312. checkProcessedLabels(label, html_text , html_widthConstraint_unchanged); // html unchanged
  313. // Following is an unlucky selection, because the first line broken on the final character (space)
  314. // So we cheat a bit here
  315. options.font.maxWdt = 320;
  316. label = new Label({}, options);
  317. checkProcessedLabels(label, markdown_text, markdown_widthConstraint_expected); // markdown unchanged
  318. done();
  319. });
  320. it('handles html tags with widthConstraint.maximum', function (done) {
  321. var options = getOptions(options);
  322. options.font.multi = true;
  323. options.font.maxWdt = 300;
  324. var label = new Label({}, options);
  325. checkProcessedLabels(label, normal_text , normal_widthConstraint_expected);
  326. checkProcessedLabels(label, html_text , multi_expected);
  327. // Following is an unlucky selection, because the first line broken on the final character (space)
  328. // So we cheat a bit here
  329. options.font.maxWdt = 320;
  330. label = new Label({}, options);
  331. checkProcessedLabels(label, markdown_text, markdown_widthConstraint_expected);
  332. done();
  333. });
  334. it('handles markdown tags with widthConstraint.maximum', function (done) {
  335. var options = getOptions(options);
  336. options.font.multi = 'markdown';
  337. options.font.maxWdt = 300;
  338. var label = new Label({}, options);
  339. checkProcessedLabels(label, normal_text , normal_widthConstraint_expected);
  340. checkProcessedLabels(label, html_text , html_widthConstraint_unchanged);
  341. checkProcessedLabels(label, markdown_text, multi_expected);
  342. done();
  343. });
  344. it('compresses spaces in multifont', function (done) {
  345. var options = getOptions(options);
  346. var text = [
  347. "Too many spaces here!",
  348. "one two three four five six .",
  349. "This thing:\n - could be\n - a kind\n - of list", // multifont: 2 spaces at start line reduced to 1
  350. ];
  351. //
  352. // multifont disabled: spaces are preserved
  353. //
  354. var label = new Label({}, options);
  355. var expected = [{
  356. lines: [{
  357. blocks: [{text: "Too many spaces here!"}],
  358. }]
  359. }, {
  360. lines: [{
  361. blocks: [{text: "one two three four five six ."}],
  362. }]
  363. }, {
  364. lines: [{
  365. blocks: [{text: "This thing:"}],
  366. }, {
  367. blocks: [{text: " - could be"}],
  368. }, {
  369. blocks: [{text: " - a kind"}],
  370. }, {
  371. blocks: [{text: " - of list"}],
  372. }]
  373. }];
  374. checkProcessedLabels(label, text, expected);
  375. //
  376. // multifont disabled width maxwidth: spaces are preserved
  377. //
  378. options.font.maxWdt = 300;
  379. var label = new Label({}, options);
  380. var expected_maxwidth = [{
  381. lines: [{
  382. blocks: [{text: "Too many spaces"}],
  383. }, {
  384. blocks: [{text: " here!"}],
  385. }]
  386. }, {
  387. lines: [{
  388. blocks: [{text: "one two three "}],
  389. }, {
  390. blocks: [{text: "four five six"}],
  391. }, {
  392. blocks: [{text: " ."}],
  393. }]
  394. }, {
  395. lines: [{
  396. blocks: [{text: "This thing:"}],
  397. }, {
  398. blocks: [{text: " - could be"}],
  399. }, {
  400. blocks: [{text: " - a kind"}],
  401. }, {
  402. blocks: [{text: " - of list"}],
  403. }]
  404. }];
  405. checkProcessedLabels(label, text, expected_maxwidth);
  406. //
  407. // multifont enabled: spaces are compressed
  408. //
  409. options = getOptions(options);
  410. options.font.multi = true;
  411. var label = new Label({}, options);
  412. var expected_multifont = [{
  413. lines: [{
  414. blocks: [{text: "Too many spaces here!"}],
  415. }]
  416. }, {
  417. lines: [{
  418. blocks: [{text: "one two three four five six ."}],
  419. }]
  420. }, {
  421. lines: [{
  422. blocks: [{text: "This thing:"}],
  423. }, {
  424. blocks: [{text: " - could be"}],
  425. }, {
  426. blocks: [{text: " - a kind"}],
  427. }, {
  428. blocks: [{text: " - of list"}],
  429. }]
  430. }];
  431. checkProcessedLabels(label, text, expected_multifont);
  432. //
  433. // multifont enabled with max width: spaces are compressed
  434. //
  435. options.font.maxWdt = 300;
  436. var label = new Label({}, options);
  437. var expected_multifont_maxwidth = [{
  438. lines: [{
  439. blocks: [{text: "Too many spaces"}],
  440. }, {
  441. blocks: [{text: "here!"}],
  442. }]
  443. }, {
  444. lines: [{
  445. blocks: [{text: "one two three four"}],
  446. }, {
  447. blocks: [{text: "five six ."}],
  448. }]
  449. }, {
  450. lines: [{
  451. blocks: [{text: "This thing:"}],
  452. }, {
  453. blocks: [{text: " - could be"}],
  454. }, {
  455. blocks: [{text: " - a kind"}],
  456. }, {
  457. blocks: [{text: " - of list"}],
  458. }]
  459. }];
  460. checkProcessedLabels(label, text, expected_multifont_maxwidth);
  461. done();
  462. });
  463. it('parses single huge word on line with preceding whitespace when max width set', function (done) {
  464. var options = getOptions(options);
  465. options.font.maxWdt = 300;
  466. assert.equal(options.font.multi, false);
  467. /**
  468. * Allows negative indexing, counting from back (ruby style)
  469. */
  470. /*
  471. TODO: Use when the actual bug is fixed and tests pass.
  472. let splitAt = (text, pos, getFirst) => {
  473. if (pos < 0) { pos = text.length + pos;
  474. if (getFirst) {
  475. return text.substring(0, pos));
  476. } else {
  477. return text.substring(pos));
  478. }
  479. }
  480. */
  481. var label = new Label({}, options);
  482. var longWord = "asd;lkfja;lfkdj;alkjfd;alskfj";
  483. var text = [
  484. "Mind the space!\n " + longWord,
  485. "Mind the empty line!\n\n" + longWord,
  486. "Mind the dos empty line!\r\n\r\n" + longWord
  487. ];
  488. var expected = [{
  489. lines: [{
  490. blocks: [{text: "Mind the space!"}]
  491. }, {
  492. blocks: [{text: ""}]
  493. }, {
  494. blocks: [{text: "asd;lkfja;lfkdj;alkjfd;als"}]
  495. }, {
  496. blocks: [{text: "kfj"}]
  497. }]
  498. }, {
  499. lines: [{
  500. blocks: [{text: "Mind the empty"}]
  501. }, {
  502. blocks: [{text: "line!"}]
  503. }, {
  504. blocks: [{text: ""}]
  505. }, {
  506. blocks: [{text: "asd;lkfja;lfkdj;alkjfd;als"}]
  507. }, {
  508. blocks: [{text: "kfj"}]
  509. }]
  510. }, {
  511. lines: [{
  512. blocks: [{text: "Mind the dos empty"}]
  513. }, {
  514. blocks: [{text: "line!"}]
  515. }, {
  516. blocks: [{text: ""}]
  517. }, {
  518. blocks: [{text: "asd;lkfja;lfkdj;alkjfd;als"}]
  519. }, {
  520. blocks: [{text: "kfj"}]
  521. }]
  522. }];
  523. checkProcessedLabels(label, text, expected);
  524. //
  525. // Multi font enabled. For current case, output should be identical to no multi font
  526. //
  527. options.font.multi = true;
  528. var label = new Label({}, options);
  529. checkProcessedLabels(label, text, expected);
  530. done();
  531. });
  532. });