| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842 |
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8">
- <title>Markdown Unit Tests</title>
- <link rel="icon" href="data:;base64,iVBORw0KGgo=">
- <style type="text/css">
- :root {
- font-family: sans-serif;
- --color-passed: #090;
- --color-failed: #a00;
- --color-errored: #a80;
- --color-untested: #888;
- --color-background: #fff;
- --color-text: #000;
- --color-secondary: #888;
- background-color: var(--color-background);
- color: var(--color-text);
- }
- .testclass {
- border: 1px solid var(--color-secondary);
- padding: 0.5em 1em;
- margin-bottom: 1em;
- max-width: 50em;
- }
- .testclassname {
- font-weight: bold;
- font-size: 1.25rem;
- padding-bottom: 0.25em;
- }
- .testclassstatus.passed { color: var(--color-passed); }
- .testclassstatus.failed { color: var(--color-failed); }
- .testclassstatus.errored { color: var(--color-errored); }
- .testclassstatus.untested { color: var(--color-untested); }
- .testcase {
- clear: both;
- padding: 0.2em 0;
- margin-left: 2em;
- }
- .testcase {
- border-top: 1px solid var(--color-secondary);
- }
- .testcasename {
- font-family: monospace;
- }
- .testcasestatus {
- font-weight: bold;
- font-size: 80%;
- float: left;
- }
- .testcasetiming {
- float: right;
- color: var(--color-secondary);
- font-size: 80%;
- }
- .testcasererun {
- float: right;
- padding-left: 0.5em;
- cursor: pointer;
- font-size: 150%;
- }
- .testcaseresult {
- height: 1em;
- }
- .testcasemessage {
- clear: both;
- }
- .result-untested {
- color: var(--color-untested);
- }
- .result-testing {
- color: var(--color-text);
- }
- .result-passed {
- color: var(--color-passed);
- }
- .result-failed {
- color: var(--color-failed);
- }
- .result-errored {
- color: var(--color-errored);
- }
- @media (prefers-color-scheme: dark) {
- :root {
- --color-passed: #090;
- --color-failed: #a00;
- --color-errored: #a80;
- --color-untested: #888;
- --color-background: #000;
- --color-text: #fff;
- --color-secondary: #888;
- }
- }
- </style>
- <script src="js/markdown.js"></script>
- <script src="js/spreadsheet.js"></script>
- <!-- Testing infrastructure -->
- <script>
- /**
- * @param {String} text
- * @returns {String}
- */
- function escapeHTML(text) {
- return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\n/g, '<br/>\n');
- }
-
- class ResultType {
- static untested = new ResultType('untested');
- static testing = new ResultType('testing');
- static passed = new ResultType('passed');
- static failed = new ResultType('failed');
- static errored = new ResultType('errored');
-
- #name;
-
- constructor(name) {
- this.#name = name;
- }
-
- toString() {
- return `${this.constructor.name}.${this.#name}`;
- }
- }
-
- class FailureError extends Error {
- constructor(message=null) {
- super(message);
- }
- }
-
- class BaseTest {
- setUp() {}
- tearDown() {}
- /** @var {TestCaseRunner|null} */
- currentRunner = null;
- fail(failMessage=null) {
- throw new FailureError(failMessage || 'failed');
- }
- assertTrue(test, failMessage=null) {
- if (!test) this.fail(failMessage || `expected true, got ${test}`);
- }
- assertFalse(test, failMessage=null) {
- if (test) this.fail(failMessage || `expected false, got ${test}`);
- }
- assertEqual(a, b, floatDifferenceRatio=0.000001, failMessage=null) {
- if (MDUtils.equal(a, b, floatDifferenceRatio)) return;
- const aVal = (typeof a == 'string') ? `"${a}"` : `${a}`;
- const bVal = (typeof b == 'string') ? `"${b}"` : `${b}`;
- if (aVal.length > 20 || bVal.length > 20) {
- this.fail(failMessage || `equality failed:\n${aVal}\n!=\n${bVal}`);
- } else {
- this.fail(failMessage || `equality failed: ${aVal} != ${bVal}`);
- }
- }
- expectError(e=true) {
- if (this.currentRunner) this.currentRunner.expectedError = e;
- }
- /**
- * @param {number} maxTime maximum time in seconds
- * @param {function} timedCode code to run and time
- */
- profile(maxTime, timedCode) {
- const startTime = performance.now();
- const result = timedCode();
- const endTime = performance.now();
- const seconds = (endTime - startTime) / 1000.0;
- if (seconds > maxTime) {
- this.fail(`Expected <= ${maxTime}s execution time, actual ${seconds}s.`);
- }
- return result;
- }
- }
-
- /**
- * Manages the running and results of a single test method on a test
- * class.
- */
- class TestCaseRunner {
- /** @var {number} */
- static #nextUniqueId = 1;
- /** @var {number} */
- uniqueId = 0;
- /** @var {BaseTest} */
- #objectUnderTest;
- /** @var {method} */
- #method;
- /** @var {ResultType} */
- result = ResultType.untested;
- /** @var {String|null} */
- message = null;
- expectedError = null;
- duration = null;
- /** @var {String} */
- get className() { return this.#objectUnderTest.constructor.name; }
- /** @var {String} */
- get methodName() { return this.#method.name; }
- /**
- * @param {BaseTest} objectUnderTest
- * @param {method} method
- */
- constructor(objectUnderTest, method) {
- this.#objectUnderTest = objectUnderTest;
- this.#method = method;
- this.uniqueId = TestCaseRunner.#nextUniqueId++;
- }
- run() {
- var start;
- this.expectedError = null;
- this.#objectUnderTest.currentRunner = this;
- try {
- this.#objectUnderTest.setUp();
- } catch (e) {
- console.error(`Failed to run ${this.className}.setUp() - ${e.message}`);
- this.result = ResultType.errored;
- this.message = e.message;
- return;
- }
- try {
- start = performance.now();
- this.#method.bind(this.#objectUnderTest)();
- this.duration = performance.now() - start;
- this.result = ResultType.passed;
- this.message = null;
- } catch (e) {
- this.duration = performance.now() - start;
- if (e instanceof FailureError) {
- this.result = ResultType.failed;
- this.message = e.message;
- } else if (this.#isExpectedError(e)) {
- this.result = ResultType.passed;
- this.message = null;
- } else {
- this.result = ResultType.errored;
- this.message = e.message;
- if (e.stack !== undefined) {
- this.message += "\n" + e.stack;
- }
- }
- } finally {
- this.expectedError = null;
- try {
- this.#objectUnderTest.tearDown();
- this.#objectUnderTest.currentRunner = null;
- } catch (e0) {
- console.error(`Failed to run ${this.className}.tearDown() - ${e0.message}`);
- this.result = ResultType.errored;
- this.message = e0.message;
- }
- }
- }
- get #cssId() { return `testcase${this.uniqueId}`; }
- #isExpectedError(e) {
- if (this.expectedError === null) return false;
- if (this.expectedError === true) return true;
- // TODO: Have a way to specify details about what kind of error is expected. Maybe a prototype instance and/or a testing lambda.
- return false;
- }
- toHTML() {
- var html = `<div class="testcase" id="${this.#cssId}">`;
- html += `<div class="testcasename"><span class="testcasemethod">${this.methodName}</span></div>`;
- if (this.result == ResultType.passed || this.result == ResultType.failed || this.result == ResultType.errored) {
- const rerunId = `testcasererun-${this.#cssId}`;
- const testId = this.uniqueId;
- html += `<div class="testcasererun" id="${rerunId}">🔄</div>`;
- setTimeout(() => {
- const node = document.getElementById(rerunId);
- if (!node) return;
- node.addEventListener('click', () => {
- TestClassRunner.rerun(testId);
- });
- }, 0);
- }
- html += '<div class="testcaseresult">';
- switch (this.result) {
- case ResultType.untested:
- html += '<div class="testcasestatus result-untested">Waiting to test</div>';
- break;
- case ResultType.testing:
- html += '<div class="testcasestatus result-testing">Testing...</div>';
- break;
- case ResultType.passed:
- html += '<div class="testcasestatus result-passed">Passed</div>';
- break;
- case ResultType.failed:
- html += '<div class="testcasestatus result-failed">Failed</div>';
- break;
- case ResultType.errored:
- html += '<div class="testcasestatus result-errored">Errored</div>';
- break;
- }
- if (this.duration !== null) {
- html += `<div class="testcasetiming">${Number(this.duration / 1000.0).toFixed(3)}s</div>`;
- }
- html += '</div>';
- if (this.message !== null) {
- html += `<div class="testcasemessage">${escapeHTML(this.message)}</div>`;
- }
- html += `</div>`;
- return html;
- }
- /**
- * Updates the HTML node in-place with the current status, or
- * adds it if it does not exist yet.
- */
- updateHTML() {
- let existing = document.getElementById(this.#cssId);
- if (existing) {
- existing.outerHTML = this.toHTML();
- } else {
- document.getElementById('results').innerHTML += this.toHTML();
- }
- }
-
- /**
- * @param {object} objectUnderTest
- * @returns {TestCaseRunner[]}
- */
- static findTestCases(objectUnderTest) {
- if (!(objectUnderTest instanceof BaseTest)) return [];
- var members = [];
- var obj = objectUnderTest;
- do {
- members.push(...Object.getOwnPropertyNames(obj));
- } while (obj = Object.getPrototypeOf(obj));
- return members.sort().filter((e, i, arr) => {
- if (e != arr[i + 1] && typeof objectUnderTest[e] == 'function' && e.startsWith('test')) return true;
- }).map((name) => {
- return new TestCaseRunner(objectUnderTest, objectUnderTest[name]);
- });
- }
- }
-
- class TestClassRunner {
- static #nextUniqueId = 1;
-
- #uniqueId = 0;
- #theClass;
- #instance;
- #testCases;
-
- get testCases() { return this.#testCases; }
-
- constructor(theClass) {
- this.#theClass = theClass;
- this.#instance = new theClass();
- this.#testCases = TestCaseRunner.findTestCases(this.#instance);
- this.#uniqueId = TestClassRunner.#nextUniqueId++;
- }
- get #cssId() { return `testclass${this.#uniqueId}`; }
- #summaryHTML() {
- var anyTesting = false;
- var anyFailed = false;
- var anyErrored = false;
- var anyUntested = false;
- var anyPassed = false;
- var allPassed = true;
- for (const test of this.testCases) {
- switch (test.result) {
- case ResultType.untested:
- anyUntested = true;
- allPassed = false;
- break;
- case ResultType.testing:
- anyTesting = true;
- allPassed = false;
- break;
- case ResultType.passed:
- anyPassed = true;
- break;
- case ResultType.failed:
- anyFailed = true;
- allPassed = false;
- break;
- case ResultType.errored:
- anyErrored = true;
- allPassed = false;
- break;
- }
- }
- var html = '';
- html += `<summary class="testclasssummary" id="${this.#cssId}summary">`;
- html += `<span class="testclassname">${this.#theClass.name}</span> `;
- if (anyTesting || (anyUntested && (anyPassed || anyFailed || anyErrored))) {
- html += '<span class="testclassstatus testing">Testing...</span>';
- } else if (anyErrored) {
- html += '<span class="testclassstatus errored">Errored</span>';
- } else if (anyFailed) {
- html += '<span class="testclassstatus failed">Failed</span>';
- } else if (allPassed) {
- html += '<span class="testclassstatus passed">Passed</span>';
- }
- html += '</summary>';
- return html;
- }
- toHTML() {
- var html = '';
- html += `<div class="testclass" id="${this.#cssId}">`;
- html += `<details id="${this.#cssId}details">`;
- html += this.#summaryHTML();
- for (const testCase of this.#testCases) {
- html += testCase.toHTML();
- }
- html += '</details>';
- html += '</div>';
- return html;
- }
- updateHTML() {
- var existing = document.getElementById(`${this.#cssId}summary`);
- if (!existing) {
- document.getElementById('results').innerHTML += this.toHTML();
- } else {
- existing.outerHTML = this.#summaryHTML();
- var allPassed = true;
- for (const test of this.testCases) {
- if (test.result != ResultType.passed) {
- allPassed = false;
- break;
- }
- }
- if (!TestClassRunner.#isRerunning) {
- document.getElementById(`${this.#cssId}details`).open = !allPassed;
- }
- }
- }
-
- static #idToTestCase = {}; // number -> [TestClassRunner, TestCaseRunner]
- static runAll(testClasses) {
- for (const testClass of testClasses) {
- const classRunner = new TestClassRunner(testClass);
- classRunner.updateHTML();
- const cases = classRunner.testCases.map(function(test) { return [ classRunner, test ] });
- for (const testTuple of cases) {
- this.#idToTestCase[testTuple[1].uniqueId] = testTuple;
- }
- this.#testQueue.push(...cases);
- }
- this.#runTestQueue();
- }
-
- static #testQueue = []; // tuples of [TestClassRunner, TestCaseRunner]
- static #testInterval = null;
- static #isRerunning = false;
- static #runTestQueue() {
- if (this.#testInterval) return;
- this.#testInterval = setInterval(function() {
- if (TestClassRunner.#testQueue.length == 0) {
- clearInterval(TestClassRunner.#testInterval);
- TestClassRunner.#testInterval = null;
- return;
- }
- var classRunner;
- var testCase;
- [ classRunner, testCase ] = TestClassRunner.#testQueue[0];
- if (testCase.result == ResultType.untested) {
- testCase.result = ResultType.testing;
- testCase.updateHTML();
- classRunner.updateHTML();
- } else if (testCase.result == ResultType.testing) {
- TestClassRunner.#testQueue.splice(0, 1);
- testCase.run();
- testCase.updateHTML();
- classRunner.updateHTML();
- }
- }, 1);
- }
-
- static rerun(testId) {
- const tuple = this.#idToTestCase[testId];
- if (!tuple) return;
- /** @type {TestClassRunner} */
- const testClass = tuple[0];
- /** @type {TestCaseRunner} */
- const testCase = tuple[1];
- testCase.result = ResultType.untested;
- testCase.updateHTML();
- this.#testQueue.push([testClass, testCase]);
- this.#isRerunning = true;
- this.#runTestQueue();
- }
- }
- </script>
- <!-- Tests -->
- <script>
- function onLoad() {
- let testClasses = [
- TokenTests,
- UtilsTests,
- InlineTests,
- BlockTests,
- CellValueTests,
- CellAddressRangeTests,
- ExpressionSetTests,
- SpreadsheetMarkdownIntegrationTests,
- ];
- TestClassRunner.runAll(testClasses);
- }
- document.addEventListener('DOMContentLoaded', onLoad);
-
- function normalizeWhitespace(str) {
- return str.replace(/\s+/g, ' ').replace(/(?:^\s+|\s+$)/g, '');
- }
-
- class TokenTests extends BaseTest {
- test_findFirstTokens() {
- const tokens = [
- new MDToken('Lorem', MDTokenType.Text),
- new MDToken(' ', MDTokenType.Whitespace),
- new MDToken('_', MDTokenType.Underscore),
- new MDToken('ipsum', MDTokenType.Text),
- new MDToken('_', MDTokenType.Underscore),
- new MDToken(' ', MDTokenType.Whitespace),
- new MDToken('dolor', MDTokenType.Text),
- new MDToken('_', MDTokenType.Underscore),
- new MDToken('sit', MDTokenType.Text),
- new MDToken('_', MDTokenType.Underscore),
- ];
- const pattern = [
- MDTokenType.Underscore,
- ];
- const result = MDToken.findFirstTokens(tokens, pattern);
- const expected = {
- tokens: [ tokens[2] ],
- index: 2,
- };
- this.assertEqual(result, expected);
- }
-
- test_findFirstTokens_optionalWhitespace1() {
- const tokens = [
- new MDToken('Lorem', MDTokenType.Text),
- new MDToken(' ', MDTokenType.Whitespace),
- new MDToken('[ipsum]', MDTokenType.Label, 'ipsum'),
- new MDToken('(link.html)', MDTokenType.URL, 'link.html'),
- new MDToken(' ', MDTokenType.Whitespace),
- new MDToken('dolor', MDTokenType.Text),
- ];
- const pattern = [
- MDTokenType.Label,
- MDTokenType.META_OptionalWhitespace,
- MDTokenType.URL,
- ];
- const result = MDToken.findFirstTokens(tokens, pattern);
- const expected = {
- tokens: [ tokens[2], tokens[3] ],
- index: 2,
- };
- this.assertEqual(result, expected);
- }
-
- test_findFirstTokens_optionalWhitespace2() {
- const tokens = [
- new MDToken('Lorem', MDTokenType.Text),
- new MDToken(' ', MDTokenType.Whitespace),
- new MDToken('[ipsum]', MDTokenType.Label, 'ipsum'),
- new MDToken(' ', MDTokenType.Whitespace),
- new MDToken('(link.html)', MDTokenType.URL, 'link.html'),
- new MDToken(' ', MDTokenType.Whitespace),
- new MDToken('dolor', MDTokenType.Text),
- ];
- const pattern = [
- MDTokenType.Label,
- MDTokenType.META_OptionalWhitespace,
- MDTokenType.URL,
- ];
- const result = MDToken.findFirstTokens(tokens, pattern);
- const expected = {
- tokens: [ tokens[2], tokens[3], tokens[4] ],
- index: 2,
- };
- this.assertEqual(result, expected);
- }
-
- test_findPairedTokens() {
- const tokens = [
- new MDToken('Lorem', MDTokenType.Text),
- new MDToken(' ', MDTokenType.Whitespace),
- new MDToken('_', MDTokenType.Underscore),
- new MDToken('ipsum', MDTokenType.Text),
- new MDToken('_', MDTokenType.Underscore),
- new MDToken(' ', MDTokenType.Whitespace),
- new MDToken('dolor', MDTokenType.Text),
- new MDToken('_', MDTokenType.Underscore),
- new MDToken('sit', MDTokenType.Text),
- new MDToken('_', MDTokenType.Underscore),
- ];
- const pattern = [
- MDTokenType.Underscore,
- ];
- const result = MDToken.findPairedTokens(tokens, pattern, pattern);
- const expected = {
- startTokens: [ tokens[2] ],
- contentTokens: [ tokens[3] ],
- endTokens: [ tokens[4] ],
- startIndex: 2,
- contentIndex: 3,
- endIndex: 4,
- totalLength: 3,
- }
- this.assertEqual(result, expected);
- }
- }
-
- class UtilsTests extends BaseTest {
- test_stripIndent() {
- this.assertEqual(MDUtils.stripIndent(''), '');
- this.assertEqual(MDUtils.stripIndent(' '), '');
- this.assertEqual(MDUtils.stripIndent('foo'), 'foo');
- this.assertEqual(MDUtils.stripIndent(' foo'), 'foo');
- this.assertEqual(MDUtils.stripIndent(' foo'), 'foo');
- this.assertEqual(MDUtils.stripIndent(' foo'), 'foo');
- this.assertEqual(MDUtils.stripIndent(' foo'), 'foo');
- this.assertEqual(MDUtils.stripIndent(' foo'), ' foo');
- this.assertEqual(MDUtils.stripIndent('\tfoo'), 'foo');
- this.assertEqual(MDUtils.stripIndent('\t\tfoo'), '\tfoo');
- this.assertEqual(MDUtils.stripIndent('\t\tfoo', 2), 'foo');
- this.assertEqual(MDUtils.stripIndent(' foo', 2), 'foo');
- }
-
- test_countIndents() {
- this.assertEqual(MDUtils.countIndents(''), 0);
- this.assertEqual(MDUtils.countIndents(' '), 1);
- this.assertEqual(MDUtils.countIndents(' '), 1);
- this.assertEqual(MDUtils.countIndents('foo'), 0);
- this.assertEqual(MDUtils.countIndents('foo'), 0);
- this.assertEqual(MDUtils.countIndents(' foo'), 1);
- this.assertEqual(MDUtils.countIndents(' foo'), 1);
- this.assertEqual(MDUtils.countIndents(' foo'), 1);
- this.assertEqual(MDUtils.countIndents(' foo'), 1);
- this.assertEqual(MDUtils.countIndents(' foo'), 2);
- this.assertEqual(MDUtils.countIndents('\tfoo'), 1);
- this.assertEqual(MDUtils.countIndents('\t\tfoo'), 2);
-
- this.assertEqual(MDUtils.countIndents('', true), 0);
- this.assertEqual(MDUtils.countIndents(' ', true), 0);
- this.assertEqual(MDUtils.countIndents(' ', true), 1);
- this.assertEqual(MDUtils.countIndents('foo', true), 0);
- this.assertEqual(MDUtils.countIndents(' foo', true), 0);
- this.assertEqual(MDUtils.countIndents(' foo', true), 0);
- this.assertEqual(MDUtils.countIndents(' foo', true), 0);
- this.assertEqual(MDUtils.countIndents(' foo', true), 1);
- this.assertEqual(MDUtils.countIndents(' foo', true), 1);
- this.assertEqual(MDUtils.countIndents('\tfoo', true), 1);
- this.assertEqual(MDUtils.countIndents('\t\tfoo', true), 2);
- }
-
- test_tokenizeLabel() {
- // Escapes are preserved
- this.assertEqual(MDUtils.tokenizeLabel('[foo] bar'), [ '[foo]', 'foo' ]);
- this.assertEqual(MDUtils.tokenizeLabel('[foo\\[] bar'), [ '[foo\\[]', 'foo\\[' ]);
- this.assertEqual(MDUtils.tokenizeLabel('[foo\\]] bar'), [ '[foo\\]]', 'foo\\]' ]);
- this.assertEqual(MDUtils.tokenizeLabel('[foo[]] bar'), [ '[foo[]]', 'foo[]' ]);
- this.assertEqual(MDUtils.tokenizeLabel('[foo\\(] bar'), [ '[foo\\(]', 'foo\\(' ]);
- this.assertEqual(MDUtils.tokenizeLabel('[foo\\)] bar'), [ '[foo\\)]', 'foo\\)' ]);
- this.assertEqual(MDUtils.tokenizeLabel('[foo()] bar'), [ '[foo()]', 'foo()' ]);
-
- this.assertEqual(MDUtils.tokenizeLabel('foo bar'), null);
- this.assertEqual(MDUtils.tokenizeLabel('[foo\\] bar'), null);
- this.assertEqual(MDUtils.tokenizeLabel('[foo bar'), null);
- this.assertEqual(MDUtils.tokenizeLabel('[foo[] bar'), null);
- }
-
- test_tokenizeURL() {
- this.assertEqual(MDUtils.tokenizeURL('(page.html) foo'), [ '(page.html)', 'page.html', null ]);
- this.assertEqual(MDUtils.tokenizeURL('(page.html "link title") foo'), [ '(page.html "link title")', 'page.html', 'link title' ]);
- this.assertEqual(MDUtils.tokenizeURL('(https://example.com/path/page.html?query=foo&bar=baz#fragment) foo'), [ '(https://example.com/path/page.html?query=foo&bar=baz#fragment)', 'https://example.com/path/page.html?query=foo&bar=baz#fragment', null ]);
-
- this.assertEqual(MDUtils.tokenizeURL('page.html foo'), null);
- this.assertEqual(MDUtils.tokenizeURL('(page.html foo'), null);
- this.assertEqual(MDUtils.tokenizeURL('page.html) foo'), null);
- this.assertEqual(MDUtils.tokenizeURL('(page.html "title) foo'), null);
- this.assertEqual(MDUtils.tokenizeURL('(page .html) foo'), null);
- this.assertEqual(MDUtils.tokenizeURL('(user@example.com) foo'), null);
- this.assertEqual(MDUtils.tokenizeURL('(user@example.com "title") foo'), null);
- }
-
- test_tokenizeEmail() {
- this.assertEqual(MDUtils.tokenizeEmail('(user@example.com)'), [ '(user@example.com)', 'user@example.com', null ]);
- this.assertEqual(MDUtils.tokenizeEmail('(user@example.com "link title")'), [ '(user@example.com "link title")', 'user@example.com', 'link title' ]);
-
- this.assertEqual(MDUtils.tokenizeEmail('(https://example.com) foo'), null);
- this.assertEqual(MDUtils.tokenizeEmail('(https://example.com "link title") foo'), null);
- this.assertEqual(MDUtils.tokenizeEmail('(user@example.com "link title) foo'), null);
- this.assertEqual(MDUtils.tokenizeEmail('(user@example.com foo'), null);
- this.assertEqual(MDUtils.tokenizeEmail('user@example.com) foo'), null);
- }
- }
-
- class InlineTests extends BaseTest {
- /** @type {Markdown} */
- parser;
- md(markdown) {
- return normalizeWhitespace(this.parser.toHTML(markdown));
- }
-
- setUp() {
- this.parser = Markdown.completeParser;
- }
-
- test_simpleText() {
- let markdown = 'Lorem ipsum';
- let expected = 'Lorem ipsum';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_strong() {
- let markdown = 'Lorem **ipsum** dolor **sit**';
- let expected = 'Lorem <strong>ipsum</strong> dolor <strong>sit</strong>';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_emphasis() {
- let markdown = 'Lorem _ipsum_ dolor _sit_';
- let expected = 'Lorem <em>ipsum</em> dolor <em>sit</em>';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_strongEmphasis_cleanNesting1() {
- let markdown = 'Lorem **ipsum *dolor* sit** amet';
- let expected = 'Lorem <strong>ipsum <em>dolor</em> sit</strong> amet';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_strongEmphasis_cleanNesting2() {
- let markdown = 'Lorem *ipsum **dolor** sit* amet';
- let expected = 'Lorem <em>ipsum <strong>dolor</strong> sit</em> amet';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_strongEmphasis_tightNesting() {
- let markdown = 'Lorem ***ipsum*** dolor';
- let expected1 = 'Lorem <strong><em>ipsum</em></strong> dolor';
- let expected2 = 'Lorem <em><strong>ipsum</strong></em> dolor';
- let actual = this.md(markdown);
- this.assertTrue(actual == expected1 || actual == expected2);
- }
-
- test_strongEmphasis_lopsidedNesting1() {
- let markdown = 'Lorem ***ipsum* dolor** sit';
- let expected = 'Lorem <strong><em>ipsum</em> dolor</strong> sit';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_strongEmphasis_lopsidedNesting2() {
- let markdown = 'Lorem ***ipsum** dolor* sit';
- let expected = 'Lorem <em><strong>ipsum</strong> dolor</em> sit';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_strongEmphasis_lopsidedNesting3() {
- let markdown = 'Lorem **ipsum *dolor*** sit';
- let expected = 'Lorem <strong>ipsum <em>dolor</em></strong> sit';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_strongEmphasis_lopsidedNesting4() {
- let markdown = 'Lorem *ipsum **dolor*** sit';
- let expected = 'Lorem <em>ipsum <strong>dolor</strong></em> sit';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_inlineCode() {
- let markdown = 'Lorem `ipsum` dolor';
- let expected = 'Lorem <code>ipsum</code> dolor';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_inlineCode_withInnerBacktick() {
- let markdown = 'Lorem ``ip`su`m`` dolor';
- let expected = 'Lorem <code>ip`su`m</code> dolor';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_strikethrough_double() {
- let markdown = 'Lorem ~~ipsum~~ dolor';
- let expected = 'Lorem <s>ipsum</s> dolor';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_subscript() {
- let markdown = 'H~2~O';
- let expected = 'H<sub>2</sub>O';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_superscript() {
- let markdown = 'E=mc^2^';
- let expected = 'E=mc<sup>2</sup>';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_highlight() {
- let markdown = 'Lorem ==ipsum== dolor';
- let expected = 'Lorem <mark>ipsum</mark> dolor';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_underline() {
- let markdown = 'Lorem __ipsum__ dolor';
- let expected = 'Lorem <u>ipsum</u> dolor';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_link_fullyQualified() {
- let markdown = 'Lorem [ipsum](https://example.com/path/page.html) dolor';
- let expected = 'Lorem <a href="https://example.com/path/page.html">ipsum</a> dolor';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_link_relative() {
- let markdown = 'Lorem [ipsum](page.html) dolor';
- let expected = 'Lorem <a href="page.html">ipsum</a> dolor';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_link_title() {
- let markdown = 'Lorem [ipsum](page.html "link title") dolor';
- let expected = 'Lorem <a href="page.html" title="link title">ipsum</a> dolor';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_link_literal() {
- let markdown = 'Lorem <https://example.com> dolor';
- let expected = 'Lorem <a href="https://example.com">https://example.com</a> dolor';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_link_ref() {
- let markdown = "Lorem [ipsum][ref] dolor\n\n[ref]: https://example.com";
- let expected = '<p>Lorem <a href="https://example.com">ipsum</a> dolor</p>';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_link_email() {
- let markdown = 'Lorem [ipsum](user@example.com) dolor';
- let expected = 'Lorem <a href="mailto:user@example.com">ipsum</a> dolor';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_link_email_withTitle() {
- let markdown = 'Lorem [ipsum](user@example.com "title") dolor';
- let expected = 'Lorem <a href="mailto:user@example.com" title="title">ipsum</a> dolor';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_link_literalEmail() {
- let markdown = 'Lorem <user@example.com> dolor';
- let expected = 'Lorem <a href="mailto:user@example.com">user@example.com</a> dolor';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_link_image() {
- let markdown = 'Lorem [](page.html) ipsum';
- let expected = 'Lorem <a href="page.html"><img src="image.jpg" alt="alt"></a> ipsum';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_link_image_complex() {
- let markdown = 'Lorem [![alt] (image.jpg "image title")] (page.html "link title") ipsum';
- let expected = 'Lorem <a href="page.html" title="link title"><img src="image.jpg" alt="alt" title="image title"></a> ipsum';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_image() {
- let markdown = 'Lorem  dolor';
- let expected = 'Lorem <img src="image.jpg" alt="alt text"> dolor';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_image_noAlt() {
- let markdown = 'Lorem  dolor';
- let expected = 'Lorem <img src="image.jpg"> dolor';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_image_withTitle() {
- let markdown = 'Lorem  dolor';
- let expected = 'Lorem <img src="image.jpg" alt="alt text" title="image title"> dolor';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_image_ref() {
- let markdown = 'Lorem ![alt text][ref] dolor\n\n' +
- '[ref]: image.jpg "image title"';
- let expected = '<p>Lorem <img src="image.jpg" alt="alt text" title="image title"> dolor</p>';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_htmlTags() {
- let markdown = 'Lorem <strong title=\'with " quote\' id="with \' apostrophe" lang=unquoted translate forbidden="true">ipsum</strong> dolor';
- let expected = 'Lorem <strong title="with " quote" id="with \' apostrophe" lang="unquoted" translate>ipsum</strong> dolor';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
- }
-
- class BlockTests extends BaseTest {
- /** @type {Markdown} */
- parser;
- md(markdown) {
- return normalizeWhitespace(this.parser.toHTML(markdown));
- }
-
- setUp() {
- this.parser = Markdown.completeParser;
- }
-
- test_paragraphs() {
- let markdown = "Lorem ipsum\n\nDolor sit amet";
- let expected = "<p>Lorem ipsum</p> <p>Dolor sit amet</p>";
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_paragraph_lineGrouping() {
- let markdown = "Lorem ipsum\ndolor sit amet";
- let expected = "Lorem ipsum dolor sit amet";
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_header_underlineH1() {
- let markdown = "Header 1\n===\n\nLorem ipsum";
- let expected = "<h1>Header 1</h1> <p>Lorem ipsum</p>";
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_header_underlineH2() {
- let markdown = "Header 2\n---\n\nLorem ipsum";
- let expected = "<h2>Header 2</h2> <p>Lorem ipsum</p>";
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_header_hash() {
- let markdown = "# Header 1\n## Header 2\n### Header 3\n#### Header 4\n##### Header 5\n###### Header 6\n";
- let expected = '<h1>Header 1</h1> <h2>Header 2</h2> <h3>Header 3</h3> <h4>Header 4</h4> <h5>Header 5</h5> <h6>Header 6</h6>';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_header_hash_trailing() {
- let markdown = "# Header 1 #\n## Header 2 ##\n### Header 3 ######";
- let expected = '<h1>Header 1</h1> <h2>Header 2</h2> <h3>Header 3</h3>';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_unorderedList() {
- let markdown = "* Lorem\n* Ipsum\n* Dolor";
- let expected = '<ul> <li>Lorem</li> <li>Ipsum</li> <li>Dolor</li> </ul>';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_unorderedList_nested() {
- let markdown = "* Lorem\n + Ipsum\n* Dolor";
- let expected = '<ul> <li>Lorem<ul> <li>Ipsum</li> </ul> </li> <li>Dolor</li> </ul>';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_unorderedList_hitch() {
- // This incomplete bulleted list locked up the browser at one
- // point, not forever but a REALLY long time
- this.profile(1.0, () => {
- let markdown = "Testing\n\n* ";
- let expected = '<p>Testing</p> <ul> <li></li> </ul>';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- });
- }
-
- test_orderedList() {
- let markdown = "1. Lorem\n1. Ipsum\n5. Dolor";
- let expected = '<ol> <li>Lorem</li> <li>Ipsum</li> <li>Dolor</li> </ol>';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_orderedList_numbering() {
- let markdown = "4. Lorem\n1. Ipsum\n9. Dolor";
- let expected = '<ol start="4"> <li>Lorem</li> <li>Ipsum</li> <li>Dolor</li> </ol>';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_orderedList_nested1() {
- let markdown = "1. Lorem\n 1. Ipsum\n1. Dolor";
- let expected = '<ol> <li>Lorem<ol> <li>Ipsum</li> </ol> </li> <li>Dolor</li> </ol>';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_orderedList_nested2() {
- let markdown = "1. Lorem\n 1. Ipsum\n 1. Dolor\n 1. Sit\n1. Amet";
- let expected = '<ol> <li>Lorem<ol> <li>Ipsum<ol> <li>Dolor</li> </ol> </li> <li>Sit</li> </ol> </li> <li>Amet</li> </ol>';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_blockquote() {
- let markdown = '> Lorem ipsum dolor';
- let expected = '<blockquote>Lorem ipsum dolor</blockquote>';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_blockquote_paragraphs() {
- let markdown = '> Lorem ipsum dolor\n>\n>Sit amet';
- let expected = '<blockquote> <p>Lorem ipsum dolor</p> <p>Sit amet</p> </blockquote>';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_blockquote_list() {
- let markdown = '> 1. Lorem\n> 2. Ipsum';
- let expected = '<blockquote> <ol> <li>Lorem</li> <li>Ipsum</li> </ol> </blockquote>';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_codeBlock_indented() {
- let markdown = "Code\n\n function foo() {\n return 'bar';\n }\n\nend";
- let expected = "<p>Code</p>\n\n<pre><code>function foo() {\n return 'bar';\n}</code></pre>\n\n<p>end</p>";
- let actual = this.parser.toHTML(markdown).trim(); // don't normalize whitespace
- this.assertEqual(actual.replace(/ /g, '⎵'), expected.replace(/ /g, '⎵'));
- }
-
- test_codeBlock_fenced() {
- let markdown = "Code\n\n```\nfunction foo() {\n return 'bar';\n}\n```\n\nend";
- let expected = "<p>Code</p>\n\n<pre><code>function foo() {\n return 'bar';\n}</code></pre>\n\n<p>end</p>";
- let actual = this.parser.toHTML(markdown).trim(); // don't normalize whitespace
- this.assertEqual(actual.replace(/ /g, '⎵'), expected.replace(/ /g, '⎵'));
- }
-
- test_codeBlock_fenced_language() {
- let markdown = "Code\n\n```javascript\nfunction foo() {\n return 'bar';\n}\n```\n\nend";
- let expected = "<p>Code</p>\n\n<pre><code class=\"language-javascript\">function foo() {\n return 'bar';\n}</code></pre>\n\n<p>end</p>";
- let actual = this.parser.toHTML(markdown).trim(); // don't normalize whitespace
- this.assertEqual(actual.replace(/ /g, '⎵'), expected.replace(/ /g, '⎵'));
- }
-
- test_horizontalRule() {
- let markdown = "Before\n\n---\n\n- - -\n\n***\n\n* * * * * * *\n\nafter";
- let expected = "<p>Before</p> <hr> <hr> <hr> <hr> <p>after</p>";
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_table_unfenced() {
- let markdown = "Column A | Column B | Column C\n--- | --- | ---\n1 | 2 | 3\n4 | 5 | 6";
- let expected = "<table> <thead> <tr> <th>Column A</th> <th>Column B</th> <th>Column C</th> </tr> </thead> <tbody> <tr> <td>1</td> <td>2</td> <td>3</td> </tr> <tr> <td>4</td> <td>5</td> <td>6</td> </tr> </tbody> </table>";
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_table_fenced() {
- let markdown = "| Column A | Column B | Column C |\n| --- | --- | --- |\n| 1 | 2 | 3\n4 | 5 | 6 |";
- let expected = "<table> <thead> <tr> <th>Column A</th> <th>Column B</th> <th>Column C</th> </tr> </thead> <tbody> <tr> <td>1</td> <td>2</td> <td>3</td> </tr> <tr> <td>4</td> <td>5</td> <td>6</td> </tr> </tbody> </table>";
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_table_alignment() {
- let markdown = 'Column A | Column B | Column C\n' +
- ':--- | :---: | ---:\n' +
- '1 | 2 | 3\n' +
- '4 | 5 | 6';
- let expected = '<table> ' +
- '<thead> ' +
- '<tr> ' +
- '<th style="text-align: left;">Column A</th> ' +
- '<th style="text-align: center;">Column B</th> ' +
- '<th style="text-align: right;">Column C</th> ' +
- '</tr> ' +
- '</thead> ' +
- '<tbody> ' +
- '<tr> ' +
- '<td style="text-align: left;">1</td> ' +
- '<td style="text-align: center;">2</td> ' +
- '<td style="text-align: right;">3</td> ' +
- '</tr> ' +
- '<tr> ' +
- '<td style="text-align: left;">4</td> ' +
- '<td style="text-align: center;">5</td> ' +
- '<td style="text-align: right;">6</td> ' +
- '</tr> ' +
- '</tbody> ' +
- '</table>';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_table_holes() {
- let markdown = 'Column A||Column C\n' +
- '---|---|---\n' +
- '|1|2||\n' +
- '|4||6|\n' +
- '||8|9|';
- let expected = '<table> ' +
- '<thead> ' +
- '<tr> ' +
- '<th>Column A</th> ' +
- '<th></th> ' +
- '<th>Column C</th> ' +
- '</tr> ' +
- '</thead> ' +
- '<tbody> ' +
- '<tr> ' +
- '<td>1</td> ' +
- '<td>2</td> ' +
- '<td></td> ' +
- '</tr> ' +
- '<tr> ' +
- '<td>4</td> ' +
- '<td></td> ' +
- '<td>6</td> ' +
- '</tr> ' +
- '<tr> ' +
- '<td></td> ' +
- '<td>8</td> ' +
- '<td>9</td> ' +
- '</tr> ' +
- '</tbody> ' +
- '</table>';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_definitionList() {
- let markdown = 'term\n' +
- ': definition\n' +
- 'another' +
- ' term\n' +
- ': def 1\n' +
- ' broken on next line\n' +
- ': def 2';
- let expected = '<dl> ' +
- '<dt>term</dt> ' +
- '<dd>definition</dd> ' +
- '<dt>another term</dt> ' +
- '<dd>def 1 broken on next line</dd> ' +
- '<dd>def 2</dd> ' +
- '</dl>';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_footnotes() {
- let markdown = 'Lorem ipsum[^1] dolor[^2] sit[^1] amet\n\n[^1]: A footnote\n[^2]: Another footnote';
- let expected = '<p>Lorem ipsum<sup id="footnoteref_1"><a href="#footnote_1">1</a></sup> ' +
- 'dolor<sup id="footnoteref_2"><a href="#footnote_2">2</a></sup> ' +
- 'sit<sup id="footnoteref_3"><a href="#footnote_1">1</a></sup> amet</p> ' +
- '<div class="footnotes">' +
- '<hr/>' +
- '<ol>' +
- '<li value="1" id="footnote_1">A footnote <a href="#footnoteref_1" class="footnote-backref">↩︎</a> <a href="#footnoteref_3" class="footnote-backref">↩︎</a></li> ' +
- '<li value="2" id="footnote_2">Another footnote <a href="#footnoteref_2" class="footnote-backref">↩︎</a></li> ' +
- '</ol>' +
- '</div>';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
-
- test_abbreviations() {
- let markdown = 'Lorem ipsum HTML dolor HTML sit\n' +
- '\n' +
- '*[HTML]: Hypertext Markup Language';
- let expected = '<p>Lorem ipsum <abbr title="Hypertext Markup Language">HTML</abbr> dolor <abbr title="Hypertext Markup Language">HTML</abbr> sit</p>';
- let actual = this.md(markdown);
- this.assertEqual(actual, expected);
- }
- }
-
- class CellValueTests extends BaseTest {
- test_fromCellString_blank() {
- var value;
- value = CellValue.fromCellString('');
- this.assertEqual(value.type, CellValue.TYPE_BLANK);
- this.assertEqual(value.formattedValue, '');
- this.assertEqual(value.value, null);
- value = CellValue.fromCellString(' ');
- this.assertEqual(value.type, CellValue.TYPE_BLANK);
- this.assertEqual(value.formattedValue, '');
- this.assertEqual(value.value, null);
- }
-
- test_fromCellString_number() {
- var value;
- value = CellValue.fromCellString('123');
- this.assertEqual(value.type, CellValue.TYPE_NUMBER);
- this.assertEqual(value.formattedValue, '123');
- this.assertEqual(value.value, 123);
- this.assertEqual(value.decimals, 0);
- value = CellValue.fromCellString('-0');
- this.assertEqual(value.type, CellValue.TYPE_NUMBER);
- this.assertEqual(value.formattedValue, '-0');
- this.assertEqual(value.value, 0);
- this.assertEqual(value.decimals, 0);
- value = CellValue.fromCellString('1,234');
- this.assertEqual(value.type, CellValue.TYPE_NUMBER);
- this.assertEqual(value.formattedValue, '1,234');
- this.assertEqual(value.value, 1234);
- this.assertEqual(value.decimals, 0);
- value = CellValue.fromCellString('-1,234,567.89');
- this.assertEqual(value.type, CellValue.TYPE_NUMBER);
- this.assertEqual(value.formattedValue, '-1,234,567.89');
- this.assertEqual(value.value, -1234567.89);
- this.assertEqual(value.decimals, 2);
- }
-
- test_fromCellString_percent() {
- var value;
- value = CellValue.fromCellString('123%');
- this.assertEqual(value.type, CellValue.TYPE_PERCENT);
- this.assertEqual(value.formattedValue, '123%');
- this.assertEqual(value.value, 1.23, 0.0001);
- this.assertEqual(value.decimals, 0);
- value = CellValue.fromCellString('-12.3%');
- this.assertEqual(value.type, CellValue.TYPE_PERCENT);
- this.assertEqual(value.formattedValue, '-12.3%');
- this.assertEqual(value.value, -0.123, 0.0001);
- this.assertEqual(value.decimals, 1);
- }
-
- test_fromCellString_currency() {
- var value;
- value = CellValue.fromCellString('$123');
- this.assertEqual(value.type, CellValue.TYPE_CURRENCY);
- this.assertEqual(value.formattedValue, '$123');
- this.assertEqual(value.value, 123);
- this.assertEqual(value.decimals, 0);
- value = CellValue.fromCellString('-$12.34');
- this.assertEqual(value.type, CellValue.TYPE_CURRENCY);
- this.assertEqual(value.formattedValue, '-$12.34');
- this.assertEqual(value.value, -12.34, 0.0001);
- this.assertEqual(value.decimals, 2);
- }
-
- test_fromCellString_boolean() {
- var value;
- value = CellValue.fromCellString('true');
- this.assertEqual(value.type, CellValue.TYPE_BOOLEAN);
- this.assertEqual(value.formattedValue, 'TRUE');
- this.assertEqual(value.value, true);
- value = CellValue.fromCellString('false');
- this.assertEqual(value.type, CellValue.TYPE_BOOLEAN);
- this.assertEqual(value.formattedValue, 'FALSE');
- this.assertEqual(value.value, false);
- }
-
- test_fromCellString_string() {
- var value;
- value = CellValue.fromCellString('some text');
- this.assertEqual(value.type, CellValue.TYPE_STRING);
- this.assertEqual(value.formattedValue, 'some text');
- this.assertEqual(value.value, 'some text');
- value = CellValue.fromCellString("'0123");
- this.assertEqual(value.type, CellValue.TYPE_STRING);
- this.assertEqual(value.formattedValue, '0123');
- this.assertEqual(value.value, '0123');
- value = CellValue.fromCellString("'=123");
- this.assertEqual(value.type, CellValue.TYPE_STRING);
- this.assertEqual(value.formattedValue, '=123');
- this.assertEqual(value.value, '=123');
- }
-
- test_fromCellString_formula() {
- var value;
- value = CellValue.fromCellString('=A*B');
- this.assertEqual(value.type, CellValue.TYPE_FORMULA);
- this.assertEqual(value.formattedValue, '=A*B');
- this.assertEqual(value.value, '=A*B');
- value = CellValue.fromCellString('=MAX(A, 3)');
- this.assertEqual(value.type, CellValue.TYPE_FORMULA);
- this.assertEqual(value.formattedValue, '=MAX(A, 3)');
- this.assertEqual(value.value, '=MAX(A, 3)');
- }
-
- test_fromValue_null() {
- var value;
- value = CellValue.fromValue(null);
- this.assertEqual(value.type, CellValue.TYPE_BLANK);
- }
-
- test_fromValue_number() {
- var value;
- value = CellValue.fromValue(123);
- this.assertEqual(value.type, CellValue.TYPE_NUMBER);
- this.assertEqual(value.formattedValue, '123');
- value = CellValue.fromValue(3.141592);
- this.assertEqual(value.type, CellValue.TYPE_NUMBER);
- this.assertEqual(value.formattedValue, '3.141592');
- value = CellValue.fromValue(123456789);
- this.assertEqual(value.type, CellValue.TYPE_NUMBER);
- this.assertEqual(value.formattedValue, '123,456,789');
- }
-
- test_fromValue_boolean() {
- var value;
- value = CellValue.fromValue(true);
- this.assertEqual(value.type, CellValue.TYPE_BOOLEAN);
- this.assertEqual(value.formattedValue, 'TRUE');
- value = CellValue.fromValue(false);
- this.assertEqual(value.type, CellValue.TYPE_BOOLEAN);
- this.assertEqual(value.formattedValue, 'FALSE');
- }
-
- test_fromValue_string() {
- var value;
- value = CellValue.fromValue('foo');
- this.assertEqual(value.type, CellValue.TYPE_STRING);
- this.assertEqual(value.formattedValue, 'foo');
- value = CellValue.fromValue('123');
- this.assertEqual(value.type, CellValue.TYPE_STRING);
- this.assertEqual(value.formattedValue, '123');
- }
-
- test_fromValue_formula() {
- var value;
- value = CellValue.fromValue('=A*B');
- this.assertEqual(value.type, CellValue.TYPE_FORMULA);
- this.assertEqual(value.formattedValue, '=A*B');
- }
-
- test_operation_add() {
- var a, b, result, expected;
- a = CellValue.fromValue(3);
- b = CellValue.fromValue(4);
- result = a.add(b)
- expected = new CellValue('7', 7, CellValue.TYPE_NUMBER, 0);
- this.assertEqual(result, expected);
-
- a = CellValue.fromCellString('100%');
- b = CellValue.fromCellString('50%');
- result = a.add(b);
- expected = new CellValue('150%', 1.5, CellValue.TYPE_PERCENT, 0);
- this.assertEqual(result, expected);
-
- a = CellValue.fromCellString('$123');
- b = CellValue.fromCellString('$321');
- result = a.add(b);
- expected = new CellValue('$444.00', 444, CellValue.TYPE_CURRENCY, 2);
- this.assertEqual(result, expected);
- }
-
- test_operation_subtract() {
- var a, b, result, expected;
- a = CellValue.fromValue(9);
- b = CellValue.fromValue(4);
- result = a.subtract(b)
- expected = new CellValue('5', 5, CellValue.TYPE_NUMBER, 0);
- this.assertEqual(result, expected);
-
- a = CellValue.fromCellString('100%');
- b = CellValue.fromCellString('50%');
- result = a.subtract(b);
- expected = new CellValue('50%', 0.5, CellValue.TYPE_PERCENT, 0);
- this.assertEqual(result, expected);
-
- a = CellValue.fromCellString('$321');
- b = CellValue.fromCellString('$123');
- result = a.subtract(b);
- expected = new CellValue('$198.00', 198, CellValue.TYPE_CURRENCY, 2);
- this.assertEqual(result, expected);
- }
-
- test_operation_multiply() {
- var a, b, result, expected;
- a = CellValue.fromValue(3);
- b = CellValue.fromValue(4);
- result = a.multiply(b)
- expected = new CellValue('12', 12, CellValue.TYPE_NUMBER, 0);
- this.assertEqual(result, expected);
-
- a = CellValue.fromCellString('150%');
- b = CellValue.fromCellString('50%');
- result = a.multiply(b);
- expected = new CellValue('75%', 0.75, CellValue.TYPE_PERCENT, 0);
- this.assertEqual(result, expected);
-
- a = CellValue.fromCellString('$321');
- b = CellValue.fromCellString('50%');
- result = a.multiply(b);
- expected = new CellValue('$160.50', 160.50, CellValue.TYPE_CURRENCY, 2);
- this.assertEqual(result, expected);
- }
-
- test_operation_divide() {
- var a, b, result, expected;
- a = CellValue.fromValue(12);
- b = CellValue.fromValue(4);
- result = a.divide(b)
- expected = new CellValue('3', 3, CellValue.TYPE_NUMBER, 0);
- this.assertEqual(result, expected);
-
- a = CellValue.fromCellString('150%');
- b = CellValue.fromCellString('50%');
- result = a.divide(b);
- expected = new CellValue('300%', 3.0, CellValue.TYPE_PERCENT, 0);
- this.assertEqual(result, expected);
-
- a = CellValue.fromCellString('$321');
- b = CellValue.fromCellString('200%');
- result = a.divide(b);
- expected = new CellValue('$160.50', 160.50, CellValue.TYPE_CURRENCY, 2);
- this.assertEqual(result, expected);
- }
-
- test_operation_modulo() {
- var a, b, result, expected;
- a = CellValue.fromValue(7);
- b = CellValue.fromValue(4);
- result = a.modulo(b)
- expected = new CellValue('3', 3, CellValue.TYPE_NUMBER, 0);
- this.assertEqual(result, expected);
-
- a = CellValue.fromCellString('175%');
- b = CellValue.fromCellString('50%');
- result = a.modulo(b);
- expected = new CellValue('25%', 0.25, CellValue.TYPE_PERCENT, 0);
- this.assertEqual(result, expected);
-
- a = CellValue.fromCellString('$327');
- b = CellValue.fromCellString('$20');
- result = a.modulo(b);
- expected = new CellValue('$7.00', 7.00, CellValue.TYPE_CURRENCY, 2);
- this.assertEqual(result, expected);
- }
-
- test_operation_unaryNot() {
- this.assertEqual(CellValue.fromValue(true).not(), CellValue.fromValue(false));
- this.assertEqual(CellValue.fromValue(false).not(), CellValue.fromValue(true));
- }
-
- test_operation_comparators() {
- const a = CellValue.fromValue(3);
- const b = CellValue.fromValue(4);
- const t = CellValue.fromValue(true);
- const f = CellValue.fromValue(false);
-
- this.assertEqual(a.lt(b), t);
- this.assertEqual(a.lte(b), t);
- this.assertEqual(a.gt(b), f);
- this.assertEqual(a.gte(b), f);
- this.assertEqual(a.eq(b), f);
- this.assertEqual(a.neq(b), t);
-
- this.assertEqual(b.lt(a), f);
- this.assertEqual(b.lte(a), f);
- this.assertEqual(b.gt(a), t);
- this.assertEqual(b.gte(a), t);
- this.assertEqual(b.eq(a), f);
- this.assertEqual(b.neq(a), t);
-
- this.assertEqual(a.lt(a), f);
- this.assertEqual(a.lte(a), t);
- this.assertEqual(a.gt(a), f);
- this.assertEqual(a.gte(a), t);
- this.assertEqual(a.eq(a), t);
- this.assertEqual(a.neq(a), f);
- }
-
- test_operation_concatenate() {
- const a = CellValue.fromValue('abc');
- const b = CellValue.fromValue('xyz');
- const result = a.concatenate(b);
- const expected = CellValue.fromValue('abcxyz');
- this.assertEqual(result, expected);
- }
- }
-
- class CellAddressRangeTests extends BaseTest {
- test_iterator() {
- const grid = new SpreadsheetGrid(3, 4);
- let range = new CellAddressRange(new CellAddress(0, 0), new CellAddress(2, 3));
- var visited = [];
- var sanity = 100;
- for (const address of range.cellsIn(grid)) {
- visited.push(address.name);
- if (sanity-- < 0) break;
- }
- const result = visited.join(',');
- const expected = 'A1,A2,A3,A4,B1,B2,B3,B4,C1,C2,C3,C4';
- this.assertEqual(result, expected);
- }
-
- test_iterator_column() {
- const grid = new SpreadsheetGrid(3, 4);
- let range = new CellAddressRange(new CellAddress(1, -1), new CellAddress(2, -1));
- var visited = [];
- var sanity = 100;
- for (const address of range.cellsIn(grid)) {
- visited.push(address.name);
- if (sanity-- < 0) break;
- }
- const result = visited.join(',');
- const expected = 'B1,B2,B3,B4,C1,C2,C3,C4';
- this.assertEqual(result, expected);
- }
-
- test_iterator_beyondBounds() {
- const grid = new SpreadsheetGrid(3, 4);
- let range = new CellAddressRange(new CellAddress(0, 1), new CellAddress(9, 9));
- var visited = [];
- var sanity = 100;
- for (const address of range.cellsIn(grid)) {
- visited.push(address.name);
- if (sanity-- < 0) break;
- }
- const result = visited.join(',');
- const expected = 'A2,A3,A4,B2,B3,B4,C2,C3,C4';
- this.assertEqual(result, expected);
- }
-
- test_iterator_outOfBounds() {
- const grid = new SpreadsheetGrid(3, 4);
- let range = new CellAddressRange(new CellAddress(5, 0), new CellAddress(5, 9));
- var visited = [];
- var sanity = 100;
- for (const address of range.cellsIn(grid)) {
- visited.push(address.name);
- if (sanity-- < 0) break;
- }
- const result = visited.join(',');
- const expected = '';
- this.assertEqual(result, expected);
- }
- }
-
- class ExpressionSetTests extends BaseTest {
- test_simpleMath() {
- const grid = new SpreadsheetGrid(1, 1);
- grid.cells[0][0].originalValue = CellValue.fromCellString('=7*3');
- const expressionSet = new CellExpressionSet(grid);
- expressionSet.calculateCells();
- const expected = CellValue.fromValue(21);
- this.assertEqual(grid.cells[0][0].outputValue, expected);
- }
-
- test_reference() {
- const grid = new SpreadsheetGrid(3, 1);
- grid.cells[0][0].originalValue = CellValue.fromValue(123);
- grid.cells[1][0].originalValue = CellValue.fromValue(3);
- grid.cells[2][0].originalValue = CellValue.fromCellString('=A1*B1');
- const expressionSet = new CellExpressionSet(grid);
- expressionSet.calculateCells();
- const expected = CellValue.fromValue(369);
- this.assertEqual(grid.cells[2][0].outputValue, expected);
- }
-
- test_infixPriority() {
- const grid = new SpreadsheetGrid(1, 1);
- grid.cells[0][0].originalValue = CellValue.fromCellString('=4*9/3+7-4');
- const expressionSet = new CellExpressionSet(grid);
- expressionSet.calculateCells();
- const expected = CellValue.fromValue(15);
- this.assertEqual(grid.cells[0][0].outputValue, expected);
- }
-
- test_filledFormula() {
- const grid = new SpreadsheetGrid(3, 3);
- grid.cells[0][0].originalValue = CellValue.fromValue(4);
- grid.cells[1][0].originalValue = CellValue.fromValue(5);
- grid.cells[2][0].originalValue = CellValue.fromCellString('=A1*B1 FILL');
- grid.cells[0][1].originalValue = CellValue.fromValue(6);
- grid.cells[1][1].originalValue = CellValue.fromValue(7);
- grid.cells[0][2].originalValue = CellValue.fromValue(8);
- grid.cells[1][2].originalValue = CellValue.fromValue(9);
- const expressionSet = new CellExpressionSet(grid);
- expressionSet.calculateCells();
- this.assertEqual(grid.cells[2][0].outputValue, CellValue.fromValue(20));
- this.assertEqual(grid.cells[2][1].outputValue, CellValue.fromValue(42));
- this.assertEqual(grid.cells[2][2].outputValue, CellValue.fromValue(72));
- }
-
- test_dependencies() {
- const grid = new SpreadsheetGrid(2, 4);
- grid.cells[0][0].originalValue = CellValue.fromValue(1);
- grid.cells[1][0].originalValue = CellValue.fromCellString('=A1+B2');
- grid.cells[0][1].originalValue = CellValue.fromValue(2);
- grid.cells[1][1].originalValue = CellValue.fromCellString('=A2+B3');
- grid.cells[0][2].originalValue = CellValue.fromValue(3);
- grid.cells[1][2].originalValue = CellValue.fromCellString('=A3+B4');
- grid.cells[0][3].originalValue = CellValue.fromValue(4);
- grid.cells[1][3].originalValue = CellValue.fromCellString('=A4');
- const expressionSet = new CellExpressionSet(grid);
- expressionSet.calculateCells();
- this.assertEqual(grid.cells[1][0].outputValue, CellValue.fromValue(10));
- this.assertEqual(grid.cells[1][1].outputValue, CellValue.fromValue(9));
- this.assertEqual(grid.cells[1][2].outputValue, CellValue.fromValue(7));
- this.assertEqual(grid.cells[1][3].outputValue, CellValue.fromValue(4));
- }
-
- _test_simple_formula(formula, expected) {
- const grid = new SpreadsheetGrid(1, 1);
- grid.cells[0][0].originalValue = CellValue.fromCellString(formula);
- const expressionSet = new CellExpressionSet(grid);
- expressionSet.calculateCells();
- const result = grid.cells[0][0].outputValue;
- const exp = (expected instanceof CellValue) ? expected : CellValue.fromValue(expected);
- this.assertEqual(result.type, exp.type);
- this.assertEqual(result.decimals, exp.decimals);
- this.assertEqual(result.formattedValue, exp.formattedValue);
- this.assertEqual(result.value, exp.value);
- }
-
- test_func_abs() {
- this._test_simple_formula('=ABS(-3)', 3);
- this._test_simple_formula('=ABS(4)', 4);
- }
-
- test_func_and() {
- this._test_simple_formula('=AND(FALSE, FALSE)', false);
- this._test_simple_formula('=AND(FALSE, TRUE)', false);
- this._test_simple_formula('=AND(TRUE, FALSE)', false);
- this._test_simple_formula('=AND(TRUE, TRUE)', true);
- this._test_simple_formula('=AND(TRUE, TRUE, TRUE, TRUE)', true);
- this._test_simple_formula('=AND(TRUE, TRUE, TRUE, FALSE)', false);
- }
-
- test_func_average() {
- this._test_simple_formula('=AVERAGE(4, 6, 2, 4)', 4);
- this._test_simple_formula('=AVERAGE(4, 6, 2, "foo", 4)', 4);
- }
-
- test_func_ceiling() {
- this._test_simple_formula('=CEILING(3.1)', 4);
- this._test_simple_formula('=CEILING(3)', 3);
- this._test_simple_formula('=CEILING(-3.1)', -3);
- }
-
- test_func_exp() {
- this._test_simple_formula('=EXP(1)', 2.718281828459045);
- }
-
- test_func_floor() {
- this._test_simple_formula('=FLOOR(3.1)', 3);
- this._test_simple_formula('=FLOOR(3)', 3);
- this._test_simple_formula('=FLOOR(-3.1)', -4);
- }
-
- test_func_if() {
- this._test_simple_formula('=IF(FALSE, 4, 6)', 6);
- this._test_simple_formula('=IF(TRUE, 4, 6)', 4);
- }
-
- test_func_ifs() {
- this._test_simple_formula('=IFS(TRUE, 1, FALSE, 2, FALSE, 3, FALSE, 4, 5)', 1);
- this._test_simple_formula('=IFS(FALSE, 1, TRUE, 2, FALSE, 3, FALSE, 4, 5)', 2);
- this._test_simple_formula('=IFS(FALSE, 1, FALSE, 2, TRUE, 3, FALSE, 4, 5)', 3);
- this._test_simple_formula('=IFS(FALSE, 1, FALSE, 2, FALSE, 3, TRUE, 4, 5)', 4);
- this._test_simple_formula('=IFS(FALSE, 1, FALSE, 2, FALSE, 3, FALSE, 4, 5)', 5);
- }
-
- test_func_ln() {
- this._test_simple_formula('=LN(2.718281828459045)', 1);
- }
-
- test_func_log() {
- this._test_simple_formula('=LOG(1000, 10)', 3);
- this._test_simple_formula('=LOG(64, 2)', 6);
- }
-
- test_func_lower() {
- this._test_simple_formula('=LOWER("MiXeD")', 'mixed');
- }
-
- test_func_max() {
- this._test_simple_formula('=MAX(4, 8, 5, 2)', 8);
- }
-
- test_func_min() {
- this._test_simple_formula('=MIN(4, 8, 5, 2)', 2);
- }
-
- test_func_mod() {
- this._test_simple_formula('=MOD(37, 4)', 1);
- }
-
- test_func_not() {
- this._test_simple_formula('=NOT(TRUE)', false);
- this._test_simple_formula('=NOT(FALSE)', true);
- }
-
- test_func_or() {
- this._test_simple_formula('=OR(FALSE, FALSE)', false);
- this._test_simple_formula('=OR(FALSE, TRUE)', true);
- this._test_simple_formula('=OR(TRUE, FALSE)', true);
- this._test_simple_formula('=OR(TRUE, TRUE)', true);
- this._test_simple_formula('=OR(FALSE, FALSE, FALSE, TRUE)', true);
- }
-
- test_func_power() {
- this._test_simple_formula('=POWER(2, 3)', 8);
- }
-
- test_func_round() {
- this._test_simple_formula('=ROUND(3.1)', 3);
- this._test_simple_formula('=ROUND(3.5)', 4);
- this._test_simple_formula('=ROUND(4)', 4);
- this._test_simple_formula('=ROUND(-3.1)', -3);
- this._test_simple_formula('=ROUND(-3.5)', -3);
- this._test_simple_formula('=ROUND(-3.9)', -4);
- this._test_simple_formula('=ROUND(3.1415926535, 1)', 3.1);
- this._test_simple_formula('=ROUND(3.1415926535, 2)', 3.14);
- this._test_simple_formula('=ROUND(31.415926535, -1)', 30);
- }
-
- test_func_sqrt() {
- this._test_simple_formula('=SQRT(16)', 4);
- }
-
- test_func_substitute() {
- this._test_simple_formula('=SUBSTITUTE("cat sat on the mat", "at", "ot")', 'cot sot on the mot');
- this._test_simple_formula('=SUBSTITUTE("cAt saT on the mat", "at", "ot")', 'cot sot on the mot');
- this._test_simple_formula('=SUBSTITUTE("c.*t s.*t on the m.*t", ".*t", "ot")', 'cot sot on the mot');
- }
-
- test_func_sum() {
- this._test_simple_formula('=SUM(1, 2, 3, 4, 5)', 15);
- }
-
- test_func_upper() {
- this._test_simple_formula('=UPPER("mIxEd")', 'MIXED');
- }
-
- test_func_xor() {
- this._test_simple_formula('=XOR(FALSE, FALSE)', false);
- this._test_simple_formula('=XOR(FALSE, TRUE)', true);
- this._test_simple_formula('=XOR(TRUE, FALSE)', true);
- this._test_simple_formula('=XOR(TRUE, TRUE)', false);
- this._test_simple_formula('=XOR(FALSE, FALSE, TRUE)', true);
- this._test_simple_formula('=XOR(TRUE, FALSE, TRUE)', false);
- }
-
- test_format() {
- this._test_simple_formula('=2.718281828459045 ; number 3', new CellValue('2.718', 2.718281828459045, 'number', 3));
- this._test_simple_formula('=2.718281828459045 ; percent 2', new CellValue('271.83%', 2.718281828459045, 'percent', 2));
- this._test_simple_formula('=2.718281828459045 ; currency 2', new CellValue('$2.72', 2.718281828459045, 'currency', 2));
- }
- }
-
- class SpreadsheetMarkdownIntegrationTests extends BaseTest {
- parser;
-
- setUp() {
- this.parser = new Markdown([ ...Markdown.allReaders, new MDSpreadsheetReader() ]);
- }
-
- md(markdown) {
- return normalizeWhitespace(this.parser.toHTML(markdown));
- }
-
- test_integration() {
- const markdown = '| A | B | C |\n| --- | --- | --- |\n| 3 | 4 | =A*B |';
- const expected = '<table> <thead> ' +
- '<tr> <th>A</th> <th>B</th> <th>C</th> </tr> ' +
- '</thead> <tbody> <tr> ' +
- '<td class="spreadsheet-type-number" data-numeric-value="3" data-string-value="3">3</td> ' +
- '<td class="spreadsheet-type-number" data-numeric-value="4" data-string-value="4">4</td> ' +
- '<td class="calculated spreadsheet-type-number" data-numeric-value="12" data-string-value="12">12</td> ' +
- '</tr> </tbody> </table>';
- const result = this.md(markdown);
- this.assertEqual(result, expected);
- }
-
- test_bailOut() {
- // If no formulas found table isn't modified
- const markdown = '| A | B | C |\n| --- | --- | --- |\n| 3 | 4 | A*B |';
- const expected = '<table> <thead> ' +
- '<tr> <th>A</th> <th>B</th> <th>C</th> </tr> ' +
- '</thead> <tbody> <tr> ' +
- '<td>3</td> ' +
- '<td>4</td> ' +
- '<td>A*B</td> ' +
- '</tr> </tbody> </table>';
- const result = this.md(markdown);
- this.assertEqual(result, expected);
- }
-
- test_observedError1() {
- // Saw "Uncaught Error: columnIndex must be number, got string" from this
- const markdown = '| Unit Price | Qty | Subtotal |\n| ---: | ---: | ---: |\n| $1.23 | 2 | =A*B FILL |\n| $4.99 | 6 | |\n| Total | | =SUM(C:C) |';
- const expected = '<table> <thead> <tr> ' +
- '<th style="text-align: right;">Unit Price</th> ' +
- '<th style="text-align: right;">Qty</th> ' +
- '<th>Subtotal</th> ' +
- '</tr> </thead> <tbody> <tr> ' +
- '<td class="spreadsheet-type-currency" style="text-align: right;" data-numeric-value="1.23" data-string-value="1.23">$1.23</td> ' +
- '<td class="spreadsheet-type-number" style="text-align: right;" data-numeric-value="2" data-string-value="2">2</td> ' +
- '<td class="calculated spreadsheet-type-currency" data-numeric-value="2.46" data-string-value="2.46">$2.46</td> ' +
- '</tr> <tr> ' +
- '<td class="spreadsheet-type-currency" style="text-align: right;" data-numeric-value="4.99" data-string-value="4.99">$4.99</td> ' +
- '<td class="spreadsheet-type-number" style="text-align: right;" data-numeric-value="6" data-string-value="6">6</td> ' +
- '<td class="calculated spreadsheet-type-currency" data-numeric-value="29.94" data-string-value="29.94">$29.94</td> ' +
- '</tr> <tr> ' +
- '<td class="spreadsheet-type-string" style="text-align: right;" data-string-value="Total">Total</td> ' +
- '<td class="spreadsheet-type-blank" style="text-align: right;" data-numeric-value="0" data-string-value=""></td> ' +
- '<td class="calculated spreadsheet-type-currency" data-numeric-value="32.4" data-string-value="32.4">$32.40</td> ' +
- '</tr> </tbody> </table>';
- const result = this.md(markdown);
- this.assertEqual(result, expected);
- }
- }
- </script>
- </head>
- <body>
- <div id="results"></div>
- </body>
- </html>
|