/** * TODO - add tests for: * ==== * * - !!! good test case with the tags for max width * - pathological cases of spaces (and other whitespace!) * - html unclosed or unopened tags * - html tag combinations with no font defined (e.g. bold within mono) */ var assert = require('assert') var Label = require('../lib/network/modules/components/shared/Label').default; var NodesHandler = require('../lib/network/modules/NodesHandler').default; /************************************************************** * Dummy class definitions for minimal required functionality. **************************************************************/ class DummyContext { measureText(text) { return { width: 12*text.length, height: 14 }; } } class DummyLayoutEngine { positionInitially() {} } /************************************************************** * End Dummy class definitions **************************************************************/ describe('Network Label', function() { /** * Retrieve options object from a NodesHandler instance * * NOTE: these are options at the node-level */ function getOptions(options = {}) { var body = { functions: {}, emitter: { on: function() {} } } var nodesHandler = new NodesHandler(body, {}, options, new DummyLayoutEngine() ); //console.log(JSON.stringify(nodesHandler.options, null, 2)); return nodesHandler.options; } /** * Check if the returned lines and blocks are as expected. * * All width/height fields and font info are ignored. * Within blocks, only the text is compared */ function checkBlocks(returned, expected) { let showBlocks = () => { return '\nreturned: ' + JSON.stringify(returned, null, 2) + '\n' + 'expected: ' + JSON.stringify(expected, null, 2); } assert.equal(expected.lines.length, returned.lines.length, 'Number of lines does not match, ' + showBlocks()); for (let i = 0; i < returned.lines.length; ++i) { let retLine = returned.lines[i]; let expLine = expected.lines[i]; assert(retLine.blocks.length === expLine.blocks.length, 'Number of blocks does not match, ' + showBlocks()); for (let j = 0; j < retLine.blocks.length; ++j) { let retBlock = retLine.blocks[j]; let expBlock = expLine.blocks[j]; assert(retBlock.text === expBlock.text, 'Text does not match, ' + showBlocks()); assert(retBlock.mod !== undefined); if (retBlock.mod === 'normal' || retBlock.mod === '') { assert(expBlock.mod === undefined || expBlock.mod === 'normal' || expBlock === '', 'No mod field expected in returned, ' + showBlocks()); } else { assert(retBlock.mod === expBlock.mod, 'Mod fields do not match, line: ' + i + ', block: ' + j + '; ret: ' + retBlock.mod + ', exp: ' + expBlock.mod + '\n' + showBlocks()); } } } } function checkProcessedLabels(label, text, expected) { var ctx = new DummyContext(); for (var i in text) { var ret = label._processLabelText(ctx, false, false, text[i]); //console.log(JSON.stringify(ret, null, 2)); checkBlocks(ret, expected[i]); } } /************************************************************** * Test data **************************************************************/ var normal_text = [ "label text", "label\nwith\nnewlines", "OnereallylongwordthatshouldgooverwidthConstraint.maximumifdefined", "One really long sentence that should go over widthConstraint.maximum if defined", "Reallyoneenormouslylargelabel withtwobigwordsgoingoverwayovermax" ] var html_text = [ "label with some multi tags", "label with some \n multi tags\n and newlines" // NB spaces around \n's ]; var markdown_text = [ "label *with* `some` _multi *tags*_", "label *with* `some` \n _multi *tags*_\n and newlines" // NB spaces around \n's ]; /************************************************************** * Expected Results **************************************************************/ var normal_expected = [{ // In first item, width/height kept in for reference width: 120, height: 14, lines: [{ width: 120, height: 14, blocks: [{ text: "label text", width: 120, height: 14, }] }] }, { lines: [{ blocks: [{text: "label"}] }, { blocks: [{text: "with"}] }, { blocks: [{text: "newlines"}] }] }, { // From here onward, changes width max width set lines: [{ blocks: [{text: "OnereallylongwordthatshouldgooverwidthConstraint.maximumifdefined"}] }] }, { lines: [{ blocks: [{text: "One really long sentence that should go over widthConstraint.maximum if defined"}] }] }, { lines: [{ blocks: [{text: "Reallyoneenormouslylargelabel withtwobigwordsgoingoverwayovermax"}] }] }]; const indexWidthConstrained = 2; // index of first item that will be different with max width set var normal_widthConstraint_expected = normal_expected.slice(0, indexWidthConstrained); Array.prototype.push.apply(normal_widthConstraint_expected, [{ lines: [{ blocks: [{text: "Onereallylongwordthatshoul"}] }, { blocks: [{text: "dgooverwidthConstraint.max"}] }, { blocks: [{text: "imumifdefined"}] }] }, { lines: [{ blocks: [{text: "One really long"}] }, { blocks: [{text: "sentence that should"}] }, { blocks: [{text: "go over"}] }, { blocks: [{text: "widthConstraint.maximum"}] }, { blocks: [{text: "if defined"}] }] }, { lines: [{ blocks: [{text: "Reallyoneenormouslylargela"}] }, { blocks: [{text: "bel"}] }, { blocks: [{text: "withtwobigwordsgoingoverwa"}] }, { blocks: [{text: "yovermax"}] }] }]); var html_unchanged_expected = [{ lines: [{ blocks: [{text: "label with some multi tags"}] }] }, { lines: [{ blocks: [{text: "label with some "}] }, { blocks: [{text: " multi tags"}] }, { blocks: [{text: " and newlines"}] }] }]; var html_widthConstraint_unchanged = [{ lines: [{ blocks: [{text: "label with"}] }, { blocks: [{text: "some"}] }, { blocks: [{text: "multi"}] }, { blocks: [{text: "tags"}] }] }, { lines: [{ blocks: [{text: "label with"}] }, { blocks: [{text: "some "}] }, { blocks: [{text: " multi"}] }, { blocks: [{text: "tags"}] }, { blocks: [{text: " and newlines"}] }] }]; var markdown_unchanged_expected = [{ lines: [{ blocks: [{text: "label *with* `some` _multi *tags*_"}] }] }, { lines: [{ blocks: [{text: "label *with* `some` "}] }, { blocks: [{text: " _multi *tags*_"}] }, { blocks: [{text: " and newlines"}] }] }]; var markdown_widthConstraint_expected= [{ lines: [{ blocks: [{text: "label *with* `some`"}] }, { blocks: [{text: "_multi *tags*_"}] }] }, { lines: [{ blocks: [{text: "label *with* `some` "}] }, { blocks: [{text: " _multi *tags*_"}] }, { blocks: [{text: " and newlines"}] }] }]; var multi_expected = [{ lines: [{ blocks: [ {text: "label "}, {text: "with" , mod: 'bold'}, {text: " "}, {text: "some" , mod: 'mono'}, {text: " "}, {text: "multi ", mod: 'ital'}, {text: "tags" , mod: 'boldital'} ] }] }, { lines: [{ blocks: [ {text: "label "}, {text: "with" , mod: 'bold'}, {text: " "}, {text: "some" , mod: 'mono'}, {text: " "} ] }, { blocks: [ {text: " "}, {text: "multi ", mod: 'ital'}, {text: "tags" , mod: 'boldital'} ] }, { blocks: [{text: " and newlines"}] }] }]; /************************************************************** * End Expected Results **************************************************************/ it('parses normal text labels', function (done) { var label = new Label({}, getOptions()); checkProcessedLabels(label, normal_text , normal_expected); checkProcessedLabels(label, html_text , html_unchanged_expected); // html unchanged checkProcessedLabels(label, markdown_text, markdown_unchanged_expected); // markdown unchanged done(); }); it('parses html labels', function (done) { var options = getOptions(options); options.font.multi = true; // TODO: also test 'html', also test illegal value here var label = new Label({}, options); checkProcessedLabels(label, normal_text , normal_expected); // normal as usual checkProcessedLabels(label, html_text , multi_expected); checkProcessedLabels(label, markdown_text, markdown_unchanged_expected); // markdown unchanged done(); }); it('parses markdown labels', function (done) { var options = getOptions(options); options.font.multi = 'markdown'; // TODO: also test 'md', also test illegal value here var label = new Label({}, options); checkProcessedLabels(label, normal_text , normal_expected); // normal as usual checkProcessedLabels(label, html_text , html_unchanged_expected); // html unchanged checkProcessedLabels(label, markdown_text, multi_expected); done(); }); it('handles normal text with widthConstraint.maximum', function (done) { var options = getOptions(options); // // What the user would set: // // options.widthConstraint = { minimum: 100, maximum: 200}; // // No sense in adding minWdt, not used when splitting labels into lines // // This comment also applies to the usage of maxWdt in the test cases below // options.font.maxWdt = 300; var label = new Label({}, options); checkProcessedLabels(label, normal_text , normal_widthConstraint_expected); checkProcessedLabels(label, html_text , html_widthConstraint_unchanged); // html unchanged // Following is an unlucky selection, because the first line broken on the final character (space) // So we cheat a bit here options.font.maxWdt = 320; label = new Label({}, options); checkProcessedLabels(label, markdown_text, markdown_widthConstraint_expected); // markdown unchanged done(); }); it('handles html tags with widthConstraint.maximum', function (done) { var options = getOptions(options); options.font.multi = true; options.font.maxWdt = 300; var label = new Label({}, options); checkProcessedLabels(label, normal_text , normal_widthConstraint_expected); checkProcessedLabels(label, html_text , multi_expected); // Following is an unlucky selection, because the first line broken on the final character (space) // So we cheat a bit here options.font.maxWdt = 320; label = new Label({}, options); checkProcessedLabels(label, markdown_text, markdown_widthConstraint_expected); done(); }); it('handles markdown tags with widthConstraint.maximum', function (done) { var options = getOptions(options); options.font.multi = 'markdown'; options.font.maxWdt = 300; var label = new Label({}, options); checkProcessedLabels(label, normal_text , normal_widthConstraint_expected); checkProcessedLabels(label, html_text , html_widthConstraint_unchanged); checkProcessedLabels(label, markdown_text, multi_expected); done(); }); it('compresses spaces in multifont', function (done) { var options = getOptions(options); var text = [ "Too many spaces here!", "one two three four five six .", "This thing:\n - could be\n - a kind\n - of list", // multifont: 2 spaces at start line reduced to 1 ]; // // multifont disabled: spaces are preserved // var label = new Label({}, options); var expected = [{ lines: [{ blocks: [{text: "Too many spaces here!"}], }] }, { lines: [{ blocks: [{text: "one two three four five six ."}], }] }, { lines: [{ blocks: [{text: "This thing:"}], }, { blocks: [{text: " - could be"}], }, { blocks: [{text: " - a kind"}], }, { blocks: [{text: " - of list"}], }] }]; checkProcessedLabels(label, text, expected); // // multifont disabled width maxwidth: spaces are preserved // options.font.maxWdt = 300; var label = new Label({}, options); var expected_maxwidth = [{ lines: [{ blocks: [{text: "Too many spaces"}], }, { blocks: [{text: " here!"}], }] }, { lines: [{ blocks: [{text: "one two three "}], }, { blocks: [{text: "four five six"}], }, { blocks: [{text: " ."}], }] }, { lines: [{ blocks: [{text: "This thing:"}], }, { blocks: [{text: " - could be"}], }, { blocks: [{text: " - a kind"}], }, { blocks: [{text: " - of list"}], }] }]; checkProcessedLabels(label, text, expected_maxwidth); // // multifont enabled: spaces are compressed // options = getOptions(options); options.font.multi = true; var label = new Label({}, options); var expected_multifont = [{ lines: [{ blocks: [{text: "Too many spaces here!"}], }] }, { lines: [{ blocks: [{text: "one two three four five six ."}], }] }, { lines: [{ blocks: [{text: "This thing:"}], }, { blocks: [{text: " - could be"}], }, { blocks: [{text: " - a kind"}], }, { blocks: [{text: " - of list"}], }] }]; checkProcessedLabels(label, text, expected_multifont); // // multifont enabled with max width: spaces are compressed // options.font.maxWdt = 300; var label = new Label({}, options); var expected_multifont_maxwidth = [{ lines: [{ blocks: [{text: "Too many spaces"}], }, { blocks: [{text: "here!"}], }] }, { lines: [{ blocks: [{text: "one two three four"}], }, { blocks: [{text: "five six ."}], }] }, { lines: [{ blocks: [{text: "This thing:"}], }, { blocks: [{text: " - could be"}], }, { blocks: [{text: " - a kind"}], }, { blocks: [{text: " - of list"}], }] }]; checkProcessedLabels(label, text, expected_multifont_maxwidth); done(); }); it('parses single huge word on line with preceding whitespace when max width set', function (done) { var options = getOptions(options); options.font.maxWdt = 300; assert.equal(options.font.multi, false); /** * Allows negative indexing, counting from back (ruby style) */ /* TODO: Use when the actual bug is fixed and tests pass. let splitAt = (text, pos, getFirst) => { if (pos < 0) { pos = text.length + pos; if (getFirst) { return text.substring(0, pos)); } else { return text.substring(pos)); } } */ var label = new Label({}, options); var longWord = "asd;lkfja;lfkdj;alkjfd;alskfj"; var text = [ "Mind the space!\n " + longWord, "Mind the empty line!\n\n" + longWord, "Mind the dos empty line!\r\n\r\n" + longWord ]; var expected = [{ lines: [{ blocks: [{text: "Mind the space!"}] }, { blocks: [{text: ""}] }, { blocks: [{text: "asd;lkfja;lfkdj;alkjfd;als"}] }, { blocks: [{text: "kfj"}] }] }, { lines: [{ blocks: [{text: "Mind the empty"}] }, { blocks: [{text: "line!"}] }, { blocks: [{text: ""}] }, { blocks: [{text: "asd;lkfja;lfkdj;alkjfd;als"}] }, { blocks: [{text: "kfj"}] }] }, { lines: [{ blocks: [{text: "Mind the dos empty"}] }, { blocks: [{text: "line!"}] }, { blocks: [{text: ""}] }, { blocks: [{text: "asd;lkfja;lfkdj;alkjfd;als"}] }, { blocks: [{text: "kfj"}] }] }]; checkProcessedLabels(label, text, expected); // // Multi font enabled. For current case, output should be identical to no multi font // options.font.multi = true; var label = new Label({}, options); checkProcessedLabels(label, text, expected); done(); }); });