PHP and Javascript implementations of a simple markdown parser
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

testjs.html 48KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <title>Markdown Unit Tests</title>
  6. <link rel="icon" href="data:;base64,iVBORw0KGgo=">
  7. <style type="text/css">
  8. :root {
  9. font-family: sans-serif;
  10. --color-passed: #090;
  11. --color-failed: #a00;
  12. --color-errored: #a80;
  13. --color-untested: #888;
  14. }
  15. .testclass {
  16. border: 1px solid black;
  17. padding: 0.5em 1em;
  18. margin-bottom: 1em;
  19. max-width: 50em;
  20. }
  21. .testclassname {
  22. font-weight: bold;
  23. font-size: 1.25rem;
  24. padding-bottom: 0.25em;
  25. }
  26. .testclassstatus {
  27. }
  28. .testclassstatus.passed { color: var(--color-passed); }
  29. .testclassstatus.failed { color: var(--color-failed); }
  30. .testclassstatus.errored { color: var(--color-errored); }
  31. .testclassstatus.untested { color: var(--color-untested); }
  32. .testcase {
  33. clear: both;
  34. padding: 0.2em 0;
  35. margin-left: 2em;
  36. }
  37. .testcase {
  38. border-top: 1px solid #888;
  39. }
  40. .testcasename {
  41. font-family: monospace;
  42. }
  43. .testcasestatus {
  44. font-weight: bold;
  45. font-size: 80%;
  46. float: left;
  47. }
  48. .testcasetiming {
  49. float: right;
  50. color: #888;
  51. font-size: 80%;
  52. }
  53. .testcaseresult {
  54. height: 1em;
  55. }
  56. .testcasemessage {
  57. clear: both;
  58. }
  59. .result-untested {
  60. color: #888;
  61. }
  62. .result-testing {
  63. color: black;
  64. }
  65. .result-passed {
  66. color: #090;
  67. }
  68. .result-failed {
  69. color: #a00;
  70. }
  71. .result-errored {
  72. color: #a80;
  73. }
  74. </style>
  75. <script src="js/markdown.js"></script>
  76. <script src="js/spreadsheet.js"></script>
  77. <!-- Testing infrastructure -->
  78. <script>
  79. /**
  80. * @param {String} text
  81. * @returns {String}
  82. */
  83. function escapeHTML(text) {
  84. return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\n/g, '<br/>\n');
  85. }
  86. class ResultType {
  87. static untested = new ResultType('untested');
  88. static testing = new ResultType('testing');
  89. static passed = new ResultType('passed');
  90. static failed = new ResultType('failed');
  91. static errored = new ResultType('errored');
  92. #name;
  93. constructor(name) {
  94. this.#name = name;
  95. }
  96. toString() {
  97. return `${this.constructor.name}.${this.#name}`;
  98. }
  99. }
  100. class FailureError extends Error {
  101. constructor(message=null) {
  102. super(message);
  103. }
  104. }
  105. class BaseTest {
  106. setUp() {}
  107. tearDown() {}
  108. /** @var {TestCaseRunner|null} */
  109. currentRunner = null;
  110. fail(failMessage=null) {
  111. throw new FailureError(failMessage || 'failed');
  112. }
  113. assertTrue(test, failMessage=null) {
  114. if (!test) this.fail(failMessage || `expected true, got ${test}`);
  115. }
  116. assertFalse(test, failMessage=null) {
  117. if (test) this.fail(failMessage || `expected false, got ${test}`);
  118. }
  119. assertEqual(a, b, floatDifferenceRatio=0.0, failMessage=null) {
  120. if (MDUtils.equal(a, b, floatDifferenceRatio)) return;
  121. const aVal = (typeof a == 'string') ? `"${a}"` : `${a}`;
  122. const bVal = (typeof b == 'string') ? `"${b}"` : `${b}`;
  123. if (aVal.length > 20 || bVal.length > 20) {
  124. this.fail(failMessage || `equality failed:\n${aVal}\n!=\n${bVal}`);
  125. } else {
  126. this.fail(failMessage || `equality failed: ${aVal} != ${bVal}`);
  127. }
  128. }
  129. expectError(e=true) {
  130. if (this.currentRunner) this.currentRunner.expectedError = e;
  131. }
  132. /**
  133. * @param {number} maxTime maximum time in seconds
  134. * @param {function} timedCode code to run and time
  135. */
  136. profile(maxTime, timedCode) {
  137. const startTime = performance.now();
  138. const result = timedCode();
  139. const endTime = performance.now();
  140. const seconds = (endTime - startTime) / 1000.0;
  141. if (seconds > maxTime) {
  142. this.fail(`Expected <= ${maxTime}s execution time, actual ${seconds}s.`);
  143. }
  144. return result;
  145. }
  146. }
  147. /**
  148. * Manages the running and results of a single test method on a test
  149. * class.
  150. */
  151. class TestCaseRunner {
  152. /** @var {number} */
  153. static #nextUniqueId = 1;
  154. /** @var {number} */
  155. uniqueId = 0;
  156. /** @var {BaseTest} */
  157. #objectUnderTest;
  158. /** @var {method} */
  159. #method;
  160. /** @var {ResultType} */
  161. result = ResultType.untested;
  162. /** @var {String|null} */
  163. message = null;
  164. expectedError = null;
  165. duration = null;
  166. /** @var {String} */
  167. get className() { return this.#objectUnderTest.constructor.name; }
  168. /** @var {String} */
  169. get methodName() { return this.#method.name; }
  170. /**
  171. * @param {BaseTest} objectUnderTest
  172. * @param {method} method
  173. */
  174. constructor(objectUnderTest, method) {
  175. this.#objectUnderTest = objectUnderTest;
  176. this.#method = method;
  177. this.uniqueId = TestCaseRunner.#nextUniqueId++;
  178. }
  179. run() {
  180. var start;
  181. this.expectedError = null;
  182. this.#objectUnderTest.currentRunner = this;
  183. try {
  184. this.#objectUnderTest.setUp();
  185. } catch (e) {
  186. console.error(`Failed to run ${this.className}.setUp() - ${e.message}`);
  187. this.result = ResultType.errored;
  188. this.message = e.message;
  189. return;
  190. }
  191. try {
  192. start = performance.now();
  193. this.#method.bind(this.#objectUnderTest)();
  194. this.duration = performance.now() - start;
  195. this.result = ResultType.passed;
  196. this.message = null;
  197. } catch (e) {
  198. this.duration = performance.now() - start;
  199. if (e instanceof FailureError) {
  200. this.result = ResultType.failed;
  201. this.message = e.message;
  202. } else if (this.#isExpectedError(e)) {
  203. this.result = ResultType.passed;
  204. this.message = null;
  205. } else {
  206. this.result = ResultType.errored;
  207. this.message = e.message;
  208. if (e.stack !== undefined) {
  209. this.message += "\n" + e.stack;
  210. }
  211. }
  212. } finally {
  213. this.expectedError = null;
  214. try {
  215. this.#objectUnderTest.tearDown();
  216. this.#objectUnderTest.currentRunner = null;
  217. } catch (e0) {
  218. console.error(`Failed to run ${this.className}.tearDown() - ${e0.message}`);
  219. this.result = ResultType.errored;
  220. this.message = e0.message;
  221. }
  222. }
  223. }
  224. get #cssId() { return `testcase${this.uniqueId}`; }
  225. #isExpectedError(e) {
  226. if (this.expectedError === null) return false;
  227. if (this.expectedError === true) return true;
  228. // TODO: Have a way to specify details about what kind of error is expected. Maybe a prototype instance and/or a testing lambda.
  229. return false;
  230. }
  231. toHTML() {
  232. var html = `<div class="testcase" id="${this.#cssId}">`;
  233. html += `<div class="testcasename"><span class="testcasemethod">${this.methodName}</span></div>`;
  234. html += '<div class="testcaseresult">';
  235. switch (this.result) {
  236. case ResultType.untested:
  237. html += '<div class="testcasestatus result-untested">Waiting to test</div>';
  238. break;
  239. case ResultType.testing:
  240. html += '<div class="testcasestatus result-testing">Testing...</div>';
  241. break;
  242. case ResultType.passed:
  243. html += '<div class="testcasestatus result-passed">Passed</div>';
  244. break;
  245. case ResultType.failed:
  246. html += '<div class="testcasestatus result-failed">Failed</div>';
  247. break;
  248. case ResultType.errored:
  249. html += '<div class="testcasestatus result-errored">Errored</div>';
  250. break;
  251. }
  252. if (this.duration !== null) {
  253. html += `<div class="testcasetiming">${Number(this.duration / 1000.0).toFixed(3)}s</div>`;
  254. }
  255. html += '</div>';
  256. if (this.message !== null) {
  257. html += `<div class="testcasemessage">${escapeHTML(this.message)}</div>`;
  258. }
  259. html += `</div>`;
  260. return html;
  261. }
  262. /**
  263. * Updates the HTML node in-place with the current status, or
  264. * adds it if it does not exist yet.
  265. */
  266. updateHTML() {
  267. let existing = document.getElementById(this.#cssId);
  268. if (existing) {
  269. existing.outerHTML = this.toHTML();
  270. } else {
  271. document.getElementById('results').innerHTML += this.toHTML();
  272. }
  273. }
  274. /**
  275. * @param {object} objectUnderTest
  276. * @returns {TestCaseRunner[]}
  277. */
  278. static findTestCases(objectUnderTest) {
  279. if (!(objectUnderTest instanceof BaseTest)) return [];
  280. var members = [];
  281. var obj = objectUnderTest;
  282. do {
  283. members.push(...Object.getOwnPropertyNames(obj));
  284. } while (obj = Object.getPrototypeOf(obj));
  285. return members.sort().filter((e, i, arr) => {
  286. if (e != arr[i + 1] && typeof objectUnderTest[e] == 'function' && e.startsWith('test')) return true;
  287. }).map((name) => {
  288. return new TestCaseRunner(objectUnderTest, objectUnderTest[name]);
  289. });
  290. }
  291. }
  292. class TestClassRunner {
  293. static #nextUniqueId = 1;
  294. #uniqueId = 0;
  295. #theClass;
  296. #instance;
  297. #testCases;
  298. get testCases() { return this.#testCases; }
  299. constructor(theClass) {
  300. this.#theClass = theClass;
  301. this.#instance = new theClass();
  302. this.#testCases = TestCaseRunner.findTestCases(this.#instance);
  303. this.#uniqueId = TestClassRunner.#nextUniqueId++;
  304. }
  305. get #cssId() { return `testclass${this.#uniqueId}`; }
  306. #summaryHTML() {
  307. var anyTesting = false;
  308. var anyFailed = false;
  309. var anyErrored = false;
  310. var anyUntested = false;
  311. var anyPassed = false;
  312. var allPassed = true;
  313. for (const test of this.testCases) {
  314. switch (test.result) {
  315. case ResultType.untested:
  316. anyUntested = true;
  317. allPassed = false;
  318. break;
  319. case ResultType.testing:
  320. anyTesting = true;
  321. allPassed = false;
  322. break;
  323. case ResultType.passed:
  324. anyPassed = true;
  325. break;
  326. case ResultType.failed:
  327. anyFailed = true;
  328. allPassed = false;
  329. break;
  330. case ResultType.errored:
  331. anyErrored = true;
  332. allPassed = false;
  333. break;
  334. }
  335. }
  336. var html = '';
  337. html += `<summary class="testclasssummary" id="${this.#cssId}summary">`;
  338. html += `<span class="testclassname">${this.#theClass.name}</span> `;
  339. if (anyTesting || (anyUntested && (anyPassed || anyFailed || anyErrored))) {
  340. html += '<span class="testclassstatus testing">Testing...</span>';
  341. } else if (anyErrored) {
  342. html += '<span class="testclassstatus errored">Errored</span>';
  343. } else if (anyFailed) {
  344. html += '<span class="testclassstatus failed">Failed</span>';
  345. } else if (allPassed) {
  346. html += '<span class="testclassstatus passed">Passed</span>';
  347. }
  348. html += '</summary>';
  349. return html;
  350. }
  351. toHTML() {
  352. var html = '';
  353. html += `<div class="testclass" id="${this.#cssId}">`;
  354. html += `<details id="${this.#cssId}details">`;
  355. html += this.#summaryHTML();
  356. for (const testCase of this.#testCases) {
  357. html += testCase.toHTML();
  358. }
  359. html += '</details>';
  360. html += '</div>';
  361. return html;
  362. }
  363. updateHTML() {
  364. var existing = document.getElementById(`${this.#cssId}summary`);
  365. if (!existing) {
  366. document.getElementById('results').innerHTML += this.toHTML();
  367. } else {
  368. existing.outerHTML = this.#summaryHTML();
  369. var allPassed = true;
  370. for (const test of this.testCases) {
  371. if (test.result != ResultType.passed) {
  372. allPassed = false;
  373. break;
  374. }
  375. }
  376. document.getElementById(`${this.#cssId}details`).open = !allPassed;
  377. }
  378. }
  379. static runAll(testClasses) {
  380. var tests = []; // tuples of TestClassRunner and TestCaseRunner
  381. for (const testClass of testClasses) {
  382. const classRunner = new TestClassRunner(testClass);
  383. classRunner.updateHTML();
  384. tests.push(...classRunner.testCases.map(function(test) { return [ classRunner, test ] }));
  385. }
  386. var testInterval = setInterval(function() {
  387. if (tests.length == 0) {
  388. clearInterval(testInterval);
  389. testInterval = null;
  390. return;
  391. }
  392. var classRunner;
  393. var testCase;
  394. [ classRunner, testCase ] = tests[0];
  395. if (testCase.result == ResultType.untested) {
  396. testCase.result = ResultType.testing;
  397. testCase.updateHTML();
  398. classRunner.updateHTML();
  399. } else if (testCase.result == ResultType.testing) {
  400. tests.splice(0, 1);
  401. testCase.run();
  402. testCase.updateHTML();
  403. classRunner.updateHTML();
  404. }
  405. }, 1);
  406. }
  407. }
  408. </script>
  409. <!-- Tests -->
  410. <script>
  411. function onLoad() {
  412. let testClasses = [
  413. // FIXME: Reenable these! Disabled temporarily to test spreadsheets faster.
  414. // TokenTests,
  415. // UtilsTests,
  416. // InlineTests,
  417. // BlockTests,
  418. CellValueTests,
  419. ];
  420. TestClassRunner.runAll(testClasses);
  421. }
  422. document.addEventListener('DOMContentLoaded', onLoad);
  423. function normalizeWhitespace(str) {
  424. return str.replace(/\s+/g, ' ').replace(/(?:^\s+|\s+$)/g, '');
  425. }
  426. class TokenTests extends BaseTest {
  427. test_findFirstTokens() {
  428. const tokens = [
  429. new MDToken('Lorem', MDTokenType.Text),
  430. new MDToken(' ', MDTokenType.Whitespace),
  431. new MDToken('_', MDTokenType.Underscore),
  432. new MDToken('ipsum', MDTokenType.Text),
  433. new MDToken('_', MDTokenType.Underscore),
  434. new MDToken(' ', MDTokenType.Whitespace),
  435. new MDToken('dolor', MDTokenType.Text),
  436. new MDToken('_', MDTokenType.Underscore),
  437. new MDToken('sit', MDTokenType.Text),
  438. new MDToken('_', MDTokenType.Underscore),
  439. ];
  440. const pattern = [
  441. MDTokenType.Underscore,
  442. ];
  443. const result = MDToken.findFirstTokens(tokens, pattern);
  444. const expected = {
  445. tokens: [ tokens[2] ],
  446. index: 2,
  447. };
  448. this.assertEqual(result, expected);
  449. }
  450. test_findFirstTokens_optionalWhitespace1() {
  451. const tokens = [
  452. new MDToken('Lorem', MDTokenType.Text),
  453. new MDToken(' ', MDTokenType.Whitespace),
  454. new MDToken('[ipsum]', MDTokenType.Label, 'ipsum'),
  455. new MDToken('(link.html)', MDTokenType.URL, 'link.html'),
  456. new MDToken(' ', MDTokenType.Whitespace),
  457. new MDToken('dolor', MDTokenType.Text),
  458. ];
  459. const pattern = [
  460. MDTokenType.Label,
  461. MDTokenType.META_OptionalWhitespace,
  462. MDTokenType.URL,
  463. ];
  464. const result = MDToken.findFirstTokens(tokens, pattern);
  465. const expected = {
  466. tokens: [ tokens[2], tokens[3] ],
  467. index: 2,
  468. };
  469. this.assertEqual(result, expected);
  470. }
  471. test_findFirstTokens_optionalWhitespace2() {
  472. const tokens = [
  473. new MDToken('Lorem', MDTokenType.Text),
  474. new MDToken(' ', MDTokenType.Whitespace),
  475. new MDToken('[ipsum]', MDTokenType.Label, 'ipsum'),
  476. new MDToken(' ', MDTokenType.Whitespace),
  477. new MDToken('(link.html)', MDTokenType.URL, 'link.html'),
  478. new MDToken(' ', MDTokenType.Whitespace),
  479. new MDToken('dolor', MDTokenType.Text),
  480. ];
  481. const pattern = [
  482. MDTokenType.Label,
  483. MDTokenType.META_OptionalWhitespace,
  484. MDTokenType.URL,
  485. ];
  486. const result = MDToken.findFirstTokens(tokens, pattern);
  487. const expected = {
  488. tokens: [ tokens[2], tokens[3], tokens[4] ],
  489. index: 2,
  490. };
  491. this.assertEqual(result, expected);
  492. }
  493. test_findPairedTokens() {
  494. const tokens = [
  495. new MDToken('Lorem', MDTokenType.Text),
  496. new MDToken(' ', MDTokenType.Whitespace),
  497. new MDToken('_', MDTokenType.Underscore),
  498. new MDToken('ipsum', MDTokenType.Text),
  499. new MDToken('_', MDTokenType.Underscore),
  500. new MDToken(' ', MDTokenType.Whitespace),
  501. new MDToken('dolor', MDTokenType.Text),
  502. new MDToken('_', MDTokenType.Underscore),
  503. new MDToken('sit', MDTokenType.Text),
  504. new MDToken('_', MDTokenType.Underscore),
  505. ];
  506. const pattern = [
  507. MDTokenType.Underscore,
  508. ];
  509. const result = MDToken.findPairedTokens(tokens, pattern, pattern);
  510. const expected = {
  511. startTokens: [ tokens[2] ],
  512. contentTokens: [ tokens[3] ],
  513. endTokens: [ tokens[4] ],
  514. startIndex: 2,
  515. contentIndex: 3,
  516. endIndex: 4,
  517. totalLength: 3,
  518. }
  519. this.assertEqual(result, expected);
  520. }
  521. }
  522. class UtilsTests extends BaseTest {
  523. test_stripIndent() {
  524. this.assertEqual(MDUtils.stripIndent(''), '');
  525. this.assertEqual(MDUtils.stripIndent(' '), '');
  526. this.assertEqual(MDUtils.stripIndent('foo'), 'foo');
  527. this.assertEqual(MDUtils.stripIndent(' foo'), 'foo');
  528. this.assertEqual(MDUtils.stripIndent(' foo'), 'foo');
  529. this.assertEqual(MDUtils.stripIndent(' foo'), 'foo');
  530. this.assertEqual(MDUtils.stripIndent(' foo'), 'foo');
  531. this.assertEqual(MDUtils.stripIndent(' foo'), ' foo');
  532. this.assertEqual(MDUtils.stripIndent('\tfoo'), 'foo');
  533. this.assertEqual(MDUtils.stripIndent('\t\tfoo'), '\tfoo');
  534. this.assertEqual(MDUtils.stripIndent('\t\tfoo', 2), 'foo');
  535. this.assertEqual(MDUtils.stripIndent(' foo', 2), 'foo');
  536. }
  537. test_countIndents() {
  538. this.assertEqual(MDUtils.countIndents(''), 0);
  539. this.assertEqual(MDUtils.countIndents(' '), 1);
  540. this.assertEqual(MDUtils.countIndents(' '), 1);
  541. this.assertEqual(MDUtils.countIndents('foo'), 0);
  542. this.assertEqual(MDUtils.countIndents('foo'), 0);
  543. this.assertEqual(MDUtils.countIndents(' foo'), 1);
  544. this.assertEqual(MDUtils.countIndents(' foo'), 1);
  545. this.assertEqual(MDUtils.countIndents(' foo'), 1);
  546. this.assertEqual(MDUtils.countIndents(' foo'), 1);
  547. this.assertEqual(MDUtils.countIndents(' foo'), 2);
  548. this.assertEqual(MDUtils.countIndents('\tfoo'), 1);
  549. this.assertEqual(MDUtils.countIndents('\t\tfoo'), 2);
  550. this.assertEqual(MDUtils.countIndents('', true), 0);
  551. this.assertEqual(MDUtils.countIndents(' ', true), 0);
  552. this.assertEqual(MDUtils.countIndents(' ', true), 1);
  553. this.assertEqual(MDUtils.countIndents('foo', true), 0);
  554. this.assertEqual(MDUtils.countIndents(' foo', true), 0);
  555. this.assertEqual(MDUtils.countIndents(' foo', true), 0);
  556. this.assertEqual(MDUtils.countIndents(' foo', true), 0);
  557. this.assertEqual(MDUtils.countIndents(' foo', true), 1);
  558. this.assertEqual(MDUtils.countIndents(' foo', true), 1);
  559. this.assertEqual(MDUtils.countIndents('\tfoo', true), 1);
  560. this.assertEqual(MDUtils.countIndents('\t\tfoo', true), 2);
  561. }
  562. test_tokenizeLabel() {
  563. // Escapes are preserved
  564. this.assertEqual(MDUtils.tokenizeLabel('[foo] bar'), [ '[foo]', 'foo' ]);
  565. this.assertEqual(MDUtils.tokenizeLabel('[foo\\[] bar'), [ '[foo\\[]', 'foo\\[' ]);
  566. this.assertEqual(MDUtils.tokenizeLabel('[foo\\]] bar'), [ '[foo\\]]', 'foo\\]' ]);
  567. this.assertEqual(MDUtils.tokenizeLabel('[foo[]] bar'), [ '[foo[]]', 'foo[]' ]);
  568. this.assertEqual(MDUtils.tokenizeLabel('[foo\\(] bar'), [ '[foo\\(]', 'foo\\(' ]);
  569. this.assertEqual(MDUtils.tokenizeLabel('[foo\\)] bar'), [ '[foo\\)]', 'foo\\)' ]);
  570. this.assertEqual(MDUtils.tokenizeLabel('[foo()] bar'), [ '[foo()]', 'foo()' ]);
  571. this.assertEqual(MDUtils.tokenizeLabel('foo bar'), null);
  572. this.assertEqual(MDUtils.tokenizeLabel('[foo\\] bar'), null);
  573. this.assertEqual(MDUtils.tokenizeLabel('[foo bar'), null);
  574. this.assertEqual(MDUtils.tokenizeLabel('[foo[] bar'), null);
  575. }
  576. test_tokenizeURL() {
  577. this.assertEqual(MDUtils.tokenizeURL('(page.html) foo'), [ '(page.html)', 'page.html', null ]);
  578. this.assertEqual(MDUtils.tokenizeURL('(page.html "link title") foo'), [ '(page.html "link title")', 'page.html', 'link title' ]);
  579. 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 ]);
  580. this.assertEqual(MDUtils.tokenizeURL('page.html foo'), null);
  581. this.assertEqual(MDUtils.tokenizeURL('(page.html foo'), null);
  582. this.assertEqual(MDUtils.tokenizeURL('page.html) foo'), null);
  583. this.assertEqual(MDUtils.tokenizeURL('(page.html "title) foo'), null);
  584. this.assertEqual(MDUtils.tokenizeURL('(page .html) foo'), null);
  585. this.assertEqual(MDUtils.tokenizeURL('(user@example.com) foo'), null);
  586. this.assertEqual(MDUtils.tokenizeURL('(user@example.com "title") foo'), null);
  587. }
  588. test_tokenizeEmail() {
  589. this.assertEqual(MDUtils.tokenizeEmail('(user@example.com)'), [ '(user@example.com)', 'user@example.com', null ]);
  590. this.assertEqual(MDUtils.tokenizeEmail('(user@example.com "link title")'), [ '(user@example.com "link title")', 'user@example.com', 'link title' ]);
  591. this.assertEqual(MDUtils.tokenizeEmail('(https://example.com) foo'), null);
  592. this.assertEqual(MDUtils.tokenizeEmail('(https://example.com "link title") foo'), null);
  593. this.assertEqual(MDUtils.tokenizeEmail('(user@example.com "link title) foo'), null);
  594. this.assertEqual(MDUtils.tokenizeEmail('(user@example.com foo'), null);
  595. this.assertEqual(MDUtils.tokenizeEmail('user@example.com) foo'), null);
  596. }
  597. }
  598. class InlineTests extends BaseTest {
  599. /** @type {Markdown} */
  600. parser;
  601. md(markdown) {
  602. return normalizeWhitespace(this.parser.toHTML(markdown));
  603. }
  604. setUp() {
  605. this.parser = Markdown.completeParser;
  606. }
  607. test_simpleText() {
  608. let markdown = 'Lorem ipsum';
  609. let expected = 'Lorem ipsum';
  610. let actual = this.md(markdown);
  611. this.assertEqual(actual, expected);
  612. }
  613. test_strong() {
  614. let markdown = 'Lorem **ipsum** dolor **sit**';
  615. let expected = 'Lorem <strong>ipsum</strong> dolor <strong>sit</strong>';
  616. let actual = this.md(markdown);
  617. this.assertEqual(actual, expected);
  618. }
  619. test_emphasis() {
  620. let markdown = 'Lorem _ipsum_ dolor _sit_';
  621. let expected = 'Lorem <em>ipsum</em> dolor <em>sit</em>';
  622. let actual = this.md(markdown);
  623. this.assertEqual(actual, expected);
  624. }
  625. test_strongEmphasis_cleanNesting1() {
  626. let markdown = 'Lorem **ipsum *dolor* sit** amet';
  627. let expected = 'Lorem <strong>ipsum <em>dolor</em> sit</strong> amet';
  628. let actual = this.md(markdown);
  629. this.assertEqual(actual, expected);
  630. }
  631. test_strongEmphasis_cleanNesting2() {
  632. let markdown = 'Lorem *ipsum **dolor** sit* amet';
  633. let expected = 'Lorem <em>ipsum <strong>dolor</strong> sit</em> amet';
  634. let actual = this.md(markdown);
  635. this.assertEqual(actual, expected);
  636. }
  637. test_strongEmphasis_tightNesting() {
  638. let markdown = 'Lorem ***ipsum*** dolor';
  639. let expected1 = 'Lorem <strong><em>ipsum</em></strong> dolor';
  640. let expected2 = 'Lorem <em><strong>ipsum</strong></em> dolor';
  641. let actual = this.md(markdown);
  642. this.assertTrue(actual == expected1 || actual == expected2);
  643. }
  644. test_strongEmphasis_lopsidedNesting1() {
  645. let markdown = 'Lorem ***ipsum* dolor** sit';
  646. let expected = 'Lorem <strong><em>ipsum</em> dolor</strong> sit';
  647. let actual = this.md(markdown);
  648. this.assertEqual(actual, expected);
  649. }
  650. test_strongEmphasis_lopsidedNesting2() {
  651. let markdown = 'Lorem ***ipsum** dolor* sit';
  652. let expected = 'Lorem <em><strong>ipsum</strong> dolor</em> sit';
  653. let actual = this.md(markdown);
  654. this.assertEqual(actual, expected);
  655. }
  656. test_strongEmphasis_lopsidedNesting3() {
  657. let markdown = 'Lorem **ipsum *dolor*** sit';
  658. let expected = 'Lorem <strong>ipsum <em>dolor</em></strong> sit';
  659. let actual = this.md(markdown);
  660. this.assertEqual(actual, expected);
  661. }
  662. test_strongEmphasis_lopsidedNesting4() {
  663. let markdown = 'Lorem *ipsum **dolor*** sit';
  664. let expected = 'Lorem <em>ipsum <strong>dolor</strong></em> sit';
  665. let actual = this.md(markdown);
  666. this.assertEqual(actual, expected);
  667. }
  668. test_inlineCode() {
  669. let markdown = 'Lorem `ipsum` dolor';
  670. let expected = 'Lorem <code>ipsum</code> dolor';
  671. let actual = this.md(markdown);
  672. this.assertEqual(actual, expected);
  673. }
  674. test_inlineCode_withInnerBacktick() {
  675. let markdown = 'Lorem ``ip`su`m`` dolor';
  676. let expected = 'Lorem <code>ip`su`m</code> dolor';
  677. let actual = this.md(markdown);
  678. this.assertEqual(actual, expected);
  679. }
  680. test_strikethrough_single() {
  681. let markdown = 'Lorem ~ipsum~ dolor';
  682. let expected = 'Lorem <strike>ipsum</strike> dolor';
  683. let actual = this.md(markdown);
  684. this.assertEqual(actual, expected);
  685. }
  686. test_strikethrough_double() {
  687. let markdown = 'Lorem ~~ipsum~~ dolor';
  688. let expected = 'Lorem <strike>ipsum</strike> dolor';
  689. let actual = this.md(markdown);
  690. this.assertEqual(actual, expected);
  691. }
  692. test_link_fullyQualified() {
  693. let markdown = 'Lorem [ipsum](https://example.com/path/page.html) dolor';
  694. let expected = 'Lorem <a href="https://example.com/path/page.html">ipsum</a> dolor';
  695. let actual = this.md(markdown);
  696. this.assertEqual(actual, expected);
  697. }
  698. test_link_relative() {
  699. let markdown = 'Lorem [ipsum](page.html) dolor';
  700. let expected = 'Lorem <a href="page.html">ipsum</a> dolor';
  701. let actual = this.md(markdown);
  702. this.assertEqual(actual, expected);
  703. }
  704. test_link_title() {
  705. let markdown = 'Lorem [ipsum](page.html "link title") dolor';
  706. let expected = 'Lorem <a href="page.html" title="link title">ipsum</a> dolor';
  707. let actual = this.md(markdown);
  708. this.assertEqual(actual, expected);
  709. }
  710. test_link_literal() {
  711. let markdown = 'Lorem <https://example.com> dolor';
  712. let expected = 'Lorem <a href="https://example.com">https://example.com</a> dolor';
  713. let actual = this.md(markdown);
  714. this.assertEqual(actual, expected);
  715. }
  716. test_link_ref() {
  717. let markdown = "Lorem [ipsum][ref] dolor\n\n[ref]: https://example.com";
  718. let expected = '<p>Lorem <a href="https://example.com">ipsum</a> dolor</p>';
  719. let actual = this.md(markdown);
  720. this.assertEqual(actual, expected);
  721. }
  722. test_link_email() {
  723. let markdown = 'Lorem [ipsum](user@example.com) dolor';
  724. let expected = 'Lorem <a href="mailto:&#117;&#115;&#101;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;">ipsum</a> dolor';
  725. let actual = this.md(markdown);
  726. this.assertEqual(actual, expected);
  727. }
  728. test_link_email_withTitle() {
  729. let markdown = 'Lorem [ipsum](user@example.com "title") dolor';
  730. let expected = 'Lorem <a href="mailto:&#117;&#115;&#101;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;" title="title">ipsum</a> dolor';
  731. let actual = this.md(markdown);
  732. this.assertEqual(actual, expected);
  733. }
  734. test_link_literalEmail() {
  735. let markdown = 'Lorem <user@example.com> dolor';
  736. let expected = 'Lorem <a href="mailto:&#117;&#115;&#101;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;">&#117;&#115;&#101;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;</a> dolor';
  737. let actual = this.md(markdown);
  738. this.assertEqual(actual, expected);
  739. }
  740. test_link_image() {
  741. let markdown = 'Lorem [![alt](image.jpg)](page.html) ipsum';
  742. let expected = 'Lorem <a href="page.html"><img src="image.jpg" alt="alt"></a> ipsum';
  743. let actual = this.md(markdown);
  744. this.assertEqual(actual, expected);
  745. }
  746. test_link_image_complex() {
  747. let markdown = 'Lorem [![alt] (image.jpg "image title")] (page.html "link title") ipsum';
  748. let expected = 'Lorem <a href="page.html" title="link title"><img src="image.jpg" alt="alt" title="image title"></a> ipsum';
  749. let actual = this.md(markdown);
  750. this.assertEqual(actual, expected);
  751. }
  752. test_image() {
  753. let markdown = 'Lorem ![alt text](image.jpg) dolor';
  754. let expected = 'Lorem <img src="image.jpg" alt="alt text"> dolor';
  755. let actual = this.md(markdown);
  756. this.assertEqual(actual, expected);
  757. }
  758. test_image_noAlt() {
  759. let markdown = 'Lorem ![](image.jpg) dolor';
  760. let expected = 'Lorem <img src="image.jpg"> dolor';
  761. let actual = this.md(markdown);
  762. this.assertEqual(actual, expected);
  763. }
  764. test_image_withTitle() {
  765. let markdown = 'Lorem ![alt text](image.jpg "image title") dolor';
  766. let expected = 'Lorem <img src="image.jpg" alt="alt text" title="image title"> dolor';
  767. let actual = this.md(markdown);
  768. this.assertEqual(actual, expected);
  769. }
  770. test_image_ref() {
  771. let markdown = 'Lorem ![alt text][ref] dolor\n\n' +
  772. '[ref]: image.jpg "image title"';
  773. let expected = '<p>Lorem <img src="image.jpg" alt="alt text" title="image title"> dolor</p>';
  774. let actual = this.md(markdown);
  775. this.assertEqual(actual, expected);
  776. }
  777. test_htmlTags() {
  778. let markdown = 'Lorem <strong title="value" foo=\'with " quote\' bar="with \' apostrophe" attr=unquoted checked>ipsum</strong> dolor';
  779. let expected = markdown;
  780. let actual = this.md(markdown);
  781. this.assertEqual(actual, expected);
  782. }
  783. }
  784. class BlockTests extends BaseTest {
  785. /** @type {Markdown} */
  786. parser;
  787. md(markdown) {
  788. return normalizeWhitespace(this.parser.toHTML(markdown));
  789. }
  790. setUp() {
  791. this.parser = Markdown.completeParser;
  792. }
  793. test_paragraphs() {
  794. let markdown = "Lorem ipsum\n\nDolor sit amet";
  795. let expected = "<p>Lorem ipsum</p> <p>Dolor sit amet</p>";
  796. let actual = this.md(markdown);
  797. this.assertEqual(actual, expected);
  798. }
  799. test_paragraph_lineGrouping() {
  800. let markdown = "Lorem ipsum\ndolor sit amet";
  801. let expected = "Lorem ipsum dolor sit amet";
  802. let actual = this.md(markdown);
  803. this.assertEqual(actual, expected);
  804. }
  805. test_header_underlineH1() {
  806. let markdown = "Header 1\n===\n\nLorem ipsum";
  807. let expected = "<h1>Header 1</h1> <p>Lorem ipsum</p>";
  808. let actual = this.md(markdown);
  809. this.assertEqual(actual, expected);
  810. }
  811. test_header_underlineH2() {
  812. let markdown = "Header 2\n---\n\nLorem ipsum";
  813. let expected = "<h2>Header 2</h2> <p>Lorem ipsum</p>";
  814. let actual = this.md(markdown);
  815. this.assertEqual(actual, expected);
  816. }
  817. test_header_hash() {
  818. let markdown = "# Header 1\n## Header 2\n### Header 3\n#### Header 4\n##### Header 5\n###### Header 6\n";
  819. 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>';
  820. let actual = this.md(markdown);
  821. this.assertEqual(actual, expected);
  822. }
  823. test_header_hash_trailing() {
  824. let markdown = "# Header 1 #\n## Header 2 ##\n### Header 3 ######";
  825. let expected = '<h1>Header 1</h1> <h2>Header 2</h2> <h3>Header 3</h3>';
  826. let actual = this.md(markdown);
  827. this.assertEqual(actual, expected);
  828. }
  829. test_unorderedList() {
  830. let markdown = "* Lorem\n* Ipsum\n* Dolor";
  831. let expected = '<ul> <li>Lorem</li> <li>Ipsum</li> <li>Dolor</li> </ul>';
  832. let actual = this.md(markdown);
  833. this.assertEqual(actual, expected);
  834. }
  835. test_unorderedList_nested() {
  836. let markdown = "* Lorem\n + Ipsum\n* Dolor";
  837. let expected = '<ul> <li>Lorem <ul> <li>Ipsum</li> </ul></li> <li>Dolor</li> </ul>';
  838. let actual = this.md(markdown);
  839. this.assertEqual(actual, expected);
  840. }
  841. test_unorderedList_hitch() {
  842. // This incomplete bulleted list locked up the browser at one
  843. // point, not forever but a REALLY long time
  844. this.profile(1.0, () => {
  845. let markdown = "Testing\n\n* ";
  846. let expected = '<p>Testing</p> <ul> <li></li> </ul>';
  847. let actual = this.md(markdown);
  848. this.assertEqual(actual, expected);
  849. });
  850. }
  851. test_orderedList() {
  852. let markdown = "1. Lorem\n1. Ipsum\n5. Dolor";
  853. let expected = '<ol> <li>Lorem</li> <li>Ipsum</li> <li>Dolor</li> </ol>';
  854. let actual = this.md(markdown);
  855. this.assertEqual(actual, expected);
  856. }
  857. test_orderedList_numbering() {
  858. let markdown = "4. Lorem\n1. Ipsum\n9. Dolor";
  859. let expected = '<ol start="4"> <li>Lorem</li> <li>Ipsum</li> <li>Dolor</li> </ol>';
  860. let actual = this.md(markdown);
  861. this.assertEqual(actual, expected);
  862. }
  863. test_orderedList_nested1() {
  864. let markdown = "1. Lorem\n 1. Ipsum\n1. Dolor";
  865. let expected = '<ol> <li>Lorem <ol> <li>Ipsum</li> </ol></li> <li>Dolor</li> </ol>';
  866. let actual = this.md(markdown);
  867. this.assertEqual(actual, expected);
  868. }
  869. test_orderedList_nested2() {
  870. let markdown = "1. Lorem\n 1. Ipsum\n 1. Dolor\n 1. Sit\n1. Amet";
  871. let expected = '<ol> <li>Lorem <ol> <li>Ipsum <ol> <li>Dolor</li> </ol></li> <li>Sit</li> </ol></li> <li>Amet</li> </ol>';
  872. let actual = this.md(markdown);
  873. this.assertEqual(actual, expected);
  874. }
  875. test_blockquote() {
  876. let markdown = '> Lorem ipsum dolor';
  877. let expected = '<blockquote> Lorem ipsum dolor </blockquote>';
  878. let actual = this.md(markdown);
  879. this.assertEqual(actual, expected);
  880. }
  881. test_blockquote_paragraphs() {
  882. let markdown = '> Lorem ipsum dolor\n>\n>Sit amet';
  883. let expected = '<blockquote> <p>Lorem ipsum dolor</p> <p>Sit amet</p> </blockquote>';
  884. let actual = this.md(markdown);
  885. this.assertEqual(actual, expected);
  886. }
  887. test_blockquote_list() {
  888. let markdown = '> 1. Lorem\n> 2. Ipsum';
  889. let expected = '<blockquote> <ol> <li>Lorem</li> <li>Ipsum</li> </ol> </blockquote>';
  890. let actual = this.md(markdown);
  891. this.assertEqual(actual, expected);
  892. }
  893. test_codeBlock_indented() {
  894. let markdown = "Code\n\n function foo() {\n return 'bar';\n }\n\nend";
  895. let expected = "<p>Code</p>\n\n<pre><code>function foo() {\n return 'bar';\n}</code></pre>\n<p>end</p>\n";
  896. let actual = this.parser.toHTML(markdown); // don't normalize whitespace
  897. this.assertEqual(actual.replace(/ /g, '⎵'), expected.replace(/ /g, '⎵'));
  898. }
  899. test_codeBlock_fenced() {
  900. let markdown = "Code\n\n```\nfunction foo() {\n return 'bar';\n}\n```\n\nend";
  901. let expected = "<p>Code</p>\n\n<pre><code>function foo() {\n return 'bar';\n}</code></pre>\n<p>end</p>\n";
  902. let actual = this.parser.toHTML(markdown); // don't normalize whitespace
  903. this.assertEqual(actual.replace(/ /g, '⎵'), expected.replace(/ /g, '⎵'));
  904. }
  905. test_horizontalRule() {
  906. let markdown = "Before\n\n---\n\n- - -\n\n***\n\n* * * * * * *\n\nafter";
  907. let expected = "<p>Before</p> <hr> <hr> <hr> <hr> <p>after</p>";
  908. let actual = this.md(markdown);
  909. this.assertEqual(actual, expected);
  910. }
  911. test_table_unfenced() {
  912. let markdown = "Column A | Column B | Column C\n--- | --- | ---\n1 | 2 | 3\n4 | 5 | 6";
  913. 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>";
  914. let actual = this.md(markdown);
  915. this.assertEqual(actual, expected);
  916. }
  917. test_table_fenced() {
  918. let markdown = "| Column A | Column B | Column C |\n| --- | --- | --- |\n| 1 | 2 | 3\n4 | 5 | 6 |";
  919. 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>";
  920. let actual = this.md(markdown);
  921. this.assertEqual(actual, expected);
  922. }
  923. test_table_alignment() {
  924. let markdown = 'Column A | Column B | Column C\n' +
  925. ':--- | :---: | ---:\n' +
  926. '1 | 2 | 3\n' +
  927. '4 | 5 | 6';
  928. let expected = '<table> ' +
  929. '<thead> ' +
  930. '<tr> ' +
  931. '<th align="left">Column A</th> ' +
  932. '<th align="center">Column B</th> ' +
  933. '<th align="right">Column C</th> ' +
  934. '</tr> ' +
  935. '</thead> ' +
  936. '<tbody> ' +
  937. '<tr> ' +
  938. '<td align="left">1</td> ' +
  939. '<td align="center">2</td> ' +
  940. '<td align="right">3</td> ' +
  941. '</tr> ' +
  942. '<tr> ' +
  943. '<td align="left">4</td> ' +
  944. '<td align="center">5</td> ' +
  945. '<td align="right">6</td> ' +
  946. '</tr> ' +
  947. '</tbody> ' +
  948. '</table>';
  949. let actual = this.md(markdown);
  950. this.assertEqual(actual, expected);
  951. }
  952. test_table_holes() {
  953. let markdown = 'Column A||Column C\n' +
  954. '---|---|---\n' +
  955. '|1|2||\n' +
  956. '|4||6|\n' +
  957. '||8|9|';
  958. let expected = '<table> ' +
  959. '<thead> ' +
  960. '<tr> ' +
  961. '<th>Column A</th> ' +
  962. '<th></th> ' +
  963. '<th>Column C</th> ' +
  964. '</tr> ' +
  965. '</thead> ' +
  966. '<tbody> ' +
  967. '<tr> ' +
  968. '<td>1</td> ' +
  969. '<td>2</td> ' +
  970. '<td></td> ' +
  971. '</tr> ' +
  972. '<tr> ' +
  973. '<td>4</td> ' +
  974. '<td></td> ' +
  975. '<td>6</td> ' +
  976. '</tr> ' +
  977. '<tr> ' +
  978. '<td></td> ' +
  979. '<td>8</td> ' +
  980. '<td>9</td> ' +
  981. '</tr> ' +
  982. '</tbody> ' +
  983. '</table>';
  984. let actual = this.md(markdown);
  985. this.assertEqual(actual, expected);
  986. }
  987. test_definitionList() {
  988. let markdown = 'term\n' +
  989. ': definition\n' +
  990. 'another' +
  991. ' term\n' +
  992. ': def 1\n' +
  993. ' broken on next line\n' +
  994. ': def 2';
  995. let expected = '<dl> ' +
  996. '<dt>term</dt> ' +
  997. '<dd>definition</dd> ' +
  998. '<dt>another term</dt> ' +
  999. '<dd>def 1 broken on next line</dd> ' +
  1000. '<dd>def 2</dd> ' +
  1001. '</dl>';
  1002. let actual = this.md(markdown);
  1003. this.assertEqual(actual, expected);
  1004. }
  1005. test_footnotes() {
  1006. let markdown = 'Lorem ipsum[^1] dolor[^2] sit[^1] amet\n\n[^1]: A footnote\n[^2]: Another footnote';
  1007. let expected = '<p>Lorem ipsum<sup id="footnoteref_1"><a href="#footnote_1">1</a></sup> ' +
  1008. 'dolor<sup id="footnoteref_2"><a href="#footnote_2">2</a></sup> ' +
  1009. 'sit<sup id="footnoteref_3"><a href="#footnote_1">1</a></sup> amet</p> ' +
  1010. '<div class="footnotes">' +
  1011. '<hr/>' +
  1012. '<ol>' +
  1013. '<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> ' +
  1014. '<li value="2" id="footnote_2">Another footnote <a href="#footnoteref_2" class="footnote-backref">↩︎</a></li> ' +
  1015. '</ol>' +
  1016. '</div>';
  1017. let actual = this.md(markdown);
  1018. this.assertEqual(actual, expected);
  1019. }
  1020. test_abbreviations() {
  1021. let markdown = 'Lorem ipsum HTML dolor HTML sit\n' +
  1022. '\n' +
  1023. '*[HTML]: Hypertext Markup Language';
  1024. let expected = '<p>Lorem ipsum <abbr title="Hypertext Markup Language">HTML</abbr> dolor <abbr title="Hypertext Markup Language">HTML</abbr> sit</p>';
  1025. let actual = this.md(markdown);
  1026. this.assertEqual(actual, expected);
  1027. }
  1028. }
  1029. class CellValueTests extends BaseTest {
  1030. test_fromCellString_blank() {
  1031. var value;
  1032. value = CellValue.fromCellString('');
  1033. this.assertEqual(value.type, CellValue.TYPE_BLANK);
  1034. this.assertEqual(value.formattedValue, '');
  1035. this.assertEqual(value.value, null);
  1036. value = CellValue.fromCellString(' ');
  1037. this.assertEqual(value.type, CellValue.TYPE_BLANK);
  1038. this.assertEqual(value.formattedValue, '');
  1039. this.assertEqual(value.value, null);
  1040. }
  1041. test_fromCellString_number() {
  1042. var value;
  1043. value = CellValue.fromCellString('123');
  1044. this.assertEqual(value.type, CellValue.TYPE_NUMBER);
  1045. this.assertEqual(value.formattedValue, '123');
  1046. this.assertEqual(value.value, 123);
  1047. this.assertEqual(value.decimals, 0);
  1048. value = CellValue.fromCellString('-0');
  1049. this.assertEqual(value.type, CellValue.TYPE_NUMBER);
  1050. this.assertEqual(value.formattedValue, '-0');
  1051. this.assertEqual(value.value, 0);
  1052. this.assertEqual(value.decimals, 0);
  1053. value = CellValue.fromCellString('1,234');
  1054. this.assertEqual(value.type, CellValue.TYPE_NUMBER);
  1055. this.assertEqual(value.formattedValue, '1,234');
  1056. this.assertEqual(value.value, 1234);
  1057. this.assertEqual(value.decimals, 0);
  1058. value = CellValue.fromCellString('-1,234,567.89');
  1059. this.assertEqual(value.type, CellValue.TYPE_NUMBER);
  1060. this.assertEqual(value.formattedValue, '-1,234,567.89');
  1061. this.assertEqual(value.value, -1234567.89);
  1062. this.assertEqual(value.decimals, 2);
  1063. }
  1064. test_fromCellString_percent() {
  1065. var value;
  1066. value = CellValue.fromCellString('123%');
  1067. this.assertEqual(value.type, CellValue.TYPE_PERCENT);
  1068. this.assertEqual(value.formattedValue, '123%');
  1069. this.assertEqual(value.value, 1.23, 0.0001);
  1070. this.assertEqual(value.decimals, 0);
  1071. value = CellValue.fromCellString('-12.3%');
  1072. this.assertEqual(value.type, CellValue.TYPE_PERCENT);
  1073. this.assertEqual(value.formattedValue, '-12.3%');
  1074. this.assertEqual(value.value, -0.123, 0.0001);
  1075. this.assertEqual(value.decimals, 1);
  1076. }
  1077. test_fromCellString_currency() {
  1078. var value;
  1079. value = CellValue.fromCellString('$123');
  1080. this.assertEqual(value.type, CellValue.TYPE_CURRENCY);
  1081. this.assertEqual(value.formattedValue, '$123');
  1082. this.assertEqual(value.value, 123);
  1083. this.assertEqual(value.decimals, 0);
  1084. value = CellValue.fromCellString('-$12.34');
  1085. this.assertEqual(value.type, CellValue.TYPE_CURRENCY);
  1086. this.assertEqual(value.formattedValue, '-$12.34');
  1087. this.assertEqual(value.value, -12.34, 0.0001);
  1088. this.assertEqual(value.decimals, 2);
  1089. }
  1090. test_fromCellString_boolean() {
  1091. var value;
  1092. value = CellValue.fromCellString('true');
  1093. this.assertEqual(value.type, CellValue.TYPE_BOOLEAN);
  1094. this.assertEqual(value.formattedValue, 'TRUE');
  1095. this.assertEqual(value.value, true);
  1096. value = CellValue.fromCellString('false');
  1097. this.assertEqual(value.type, CellValue.TYPE_BOOLEAN);
  1098. this.assertEqual(value.formattedValue, 'FALSE');
  1099. this.assertEqual(value.value, false);
  1100. }
  1101. test_fromCellString_string() {
  1102. var value;
  1103. value = CellValue.fromCellString('some text');
  1104. this.assertEqual(value.type, CellValue.TYPE_STRING);
  1105. this.assertEqual(value.formattedValue, 'some text');
  1106. this.assertEqual(value.value, 'some text');
  1107. value = CellValue.fromCellString("'123");
  1108. this.assertEqual(value.type, CellValue.TYPE_STRING);
  1109. this.assertEqual(value.formattedValue, '123');
  1110. this.assertEqual(value.value, '123');
  1111. }
  1112. test_fromCellString_formula() {
  1113. var value;
  1114. value = CellValue.fromCellString('=A*B');
  1115. this.assertEqual(value.type, CellValue.TYPE_FORMULA);
  1116. this.assertEqual(value.formattedValue, '=A*B');
  1117. this.assertEqual(value.value, '=A*B');
  1118. value = CellValue.fromCellString('=MAX(A, 3)');
  1119. this.assertEqual(value.type, CellValue.TYPE_FORMULA);
  1120. this.assertEqual(value.formattedValue, '=MAX(A, 3)');
  1121. this.assertEqual(value.value, '=MAX(A, 3)');
  1122. }
  1123. test_fromValue_null() {
  1124. var value;
  1125. value = CellValue.fromValue(null);
  1126. this.assertEqual(value.type, CellValue.TYPE_BLANK);
  1127. }
  1128. test_fromValue_number() {
  1129. var value;
  1130. value = CellValue.fromValue(123);
  1131. this.assertEqual(value.type, CellValue.TYPE_NUMBER);
  1132. this.assertEqual(value.formattedValue, '123');
  1133. value = CellValue.fromValue(3.141592);
  1134. this.assertEqual(value.type, CellValue.TYPE_NUMBER);
  1135. this.assertEqual(value.formattedValue, '3.141592');
  1136. value = CellValue.fromValue(123456789);
  1137. this.assertEqual(value.type, CellValue.TYPE_NUMBER);
  1138. this.assertEqual(value.formattedValue, '123,456,789');
  1139. }
  1140. test_fromValue_boolean() {
  1141. var value;
  1142. value = CellValue.fromValue(true);
  1143. this.assertEqual(value.type, CellValue.TYPE_BOOLEAN);
  1144. this.assertEqual(value.formattedValue, 'TRUE');
  1145. value = CellValue.fromValue(false);
  1146. this.assertEqual(value.type, CellValue.TYPE_BOOLEAN);
  1147. this.assertEqual(value.formattedValue, 'FALSE');
  1148. }
  1149. test_fromValue_string() {
  1150. var value;
  1151. value = CellValue.fromValue('foo');
  1152. this.assertEqual(value.type, CellValue.TYPE_STRING);
  1153. this.assertEqual(value.formattedValue, 'foo');
  1154. value = CellValue.fromValue('123');
  1155. this.assertEqual(value.type, CellValue.TYPE_STRING);
  1156. this.assertEqual(value.formattedValue, '123');
  1157. }
  1158. test_fromValue_formula() {
  1159. var value;
  1160. value = CellValue.fromValue('=A*B');
  1161. this.assertEqual(value.type, CellValue.TYPE_FORMULA);
  1162. this.assertEqual(value.formattedValue, '=A*B');
  1163. }
  1164. test_operation_add() {
  1165. var a, b, result, expected;
  1166. a = CellValue.fromValue(3);
  1167. b = CellValue.fromValue(4);
  1168. result = a.add(b)
  1169. expected = new CellValue('7', 7, CellValue.TYPE_NUMBER, 0);
  1170. this.assertEqual(result, expected);
  1171. a = CellValue.fromCellString('100%');
  1172. b = CellValue.fromCellString('50%');
  1173. result = a.add(b);
  1174. expected = new CellValue('150%', 1.5, CellValue.TYPE_PERCENT, 0);
  1175. this.assertEqual(result, expected);
  1176. a = CellValue.fromCellString('$123');
  1177. b = CellValue.fromCellString('$321');
  1178. result = a.add(b);
  1179. expected = new CellValue('$444.00', 444, CellValue.TYPE_CURRENCY, 2);
  1180. this.assertEqual(result, expected);
  1181. }
  1182. test_operation_subtract() {
  1183. var a, b, result, expected;
  1184. a = CellValue.fromValue(9);
  1185. b = CellValue.fromValue(4);
  1186. result = a.subtract(b)
  1187. expected = new CellValue('5', 5, CellValue.TYPE_NUMBER, 0);
  1188. this.assertEqual(result, expected);
  1189. a = CellValue.fromCellString('100%');
  1190. b = CellValue.fromCellString('50%');
  1191. result = a.subtract(b);
  1192. expected = new CellValue('50%', 0.5, CellValue.TYPE_PERCENT, 0);
  1193. this.assertEqual(result, expected);
  1194. a = CellValue.fromCellString('$321');
  1195. b = CellValue.fromCellString('$123');
  1196. result = a.subtract(b);
  1197. expected = new CellValue('$198.00', 198, CellValue.TYPE_CURRENCY, 2);
  1198. this.assertEqual(result, expected);
  1199. }
  1200. test_operation_multiply() {
  1201. var a, b, result, expected;
  1202. a = CellValue.fromValue(3);
  1203. b = CellValue.fromValue(4);
  1204. result = a.multiply(b)
  1205. expected = new CellValue('12', 12, CellValue.TYPE_NUMBER, 0);
  1206. this.assertEqual(result, expected);
  1207. a = CellValue.fromCellString('150%');
  1208. b = CellValue.fromCellString('50%');
  1209. result = a.multiply(b);
  1210. expected = new CellValue('75%', 0.75, CellValue.TYPE_PERCENT, 0);
  1211. this.assertEqual(result, expected);
  1212. a = CellValue.fromCellString('$321');
  1213. b = CellValue.fromCellString('50%');
  1214. result = a.multiply(b);
  1215. expected = new CellValue('$160.50', 160.50, CellValue.TYPE_CURRENCY, 2);
  1216. this.assertEqual(result, expected);
  1217. }
  1218. test_operation_divide() {
  1219. var a, b, result, expected;
  1220. a = CellValue.fromValue(12);
  1221. b = CellValue.fromValue(4);
  1222. result = a.divide(b)
  1223. expected = new CellValue('3', 3, CellValue.TYPE_NUMBER, 0);
  1224. this.assertEqual(result, expected);
  1225. a = CellValue.fromCellString('150%');
  1226. b = CellValue.fromCellString('50%');
  1227. result = a.divide(b);
  1228. expected = new CellValue('300%', 3.0, CellValue.TYPE_PERCENT, 0);
  1229. this.assertEqual(result, expected);
  1230. a = CellValue.fromCellString('$321');
  1231. b = CellValue.fromCellString('200%');
  1232. result = a.divide(b);
  1233. expected = new CellValue('$160.50', 160.50, CellValue.TYPE_CURRENCY, 2);
  1234. this.assertEqual(result, expected);
  1235. }
  1236. test_operation_modulo() {
  1237. var a, b, result, expected;
  1238. a = CellValue.fromValue(7);
  1239. b = CellValue.fromValue(4);
  1240. result = a.modulo(b)
  1241. expected = new CellValue('3', 3, CellValue.TYPE_NUMBER, 0);
  1242. this.assertEqual(result, expected);
  1243. a = CellValue.fromCellString('175%');
  1244. b = CellValue.fromCellString('50%');
  1245. result = a.modulo(b);
  1246. expected = new CellValue('25%', 0.25, CellValue.TYPE_PERCENT, 0);
  1247. this.assertEqual(result, expected);
  1248. a = CellValue.fromCellString('$327');
  1249. b = CellValue.fromCellString('$20');
  1250. result = a.modulo(b);
  1251. expected = new CellValue('$7.00', 7.00, CellValue.TYPE_CURRENCY, 2);
  1252. this.assertEqual(result, expected);
  1253. }
  1254. test_operation_comparators() {
  1255. const a = CellValue.fromValue(3);
  1256. const b = CellValue.fromValue(4);
  1257. const t = CellValue.fromValue(true);
  1258. const f = CellValue.fromValue(false);
  1259. this.assertEqual(a.lt(b), t);
  1260. this.assertEqual(a.lte(b), t);
  1261. this.assertEqual(a.gt(b), f);
  1262. this.assertEqual(a.gte(b), f);
  1263. this.assertEqual(a.eq(b), f);
  1264. this.assertEqual(a.neq(b), t);
  1265. this.assertEqual(b.lt(a), f);
  1266. this.assertEqual(b.lte(a), f);
  1267. this.assertEqual(b.gt(a), t);
  1268. this.assertEqual(b.gte(a), t);
  1269. this.assertEqual(b.eq(a), f);
  1270. this.assertEqual(b.neq(a), t);
  1271. this.assertEqual(a.lt(a), f);
  1272. this.assertEqual(a.lte(a), t);
  1273. this.assertEqual(a.gt(a), f);
  1274. this.assertEqual(a.gte(a), t);
  1275. this.assertEqual(a.eq(a), t);
  1276. this.assertEqual(a.neq(a), f);
  1277. }
  1278. test_operation_concatenate() {
  1279. const a = CellValue.fromValue('abc');
  1280. const b = CellValue.fromValue('xyz');
  1281. const result = a.concatenate(b);
  1282. const expected = CellValue.fromValue('abcxyz');
  1283. this.assertEqual(result, expected);
  1284. }
  1285. }
  1286. </script>
  1287. </head>
  1288. <body>
  1289. <div id="results"></div>
  1290. </body>
  1291. </html>