PHP and Javascript implementations of a simple markdown parser
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

testjs.html 58KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679
  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.000001, 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. CellAddressRangeTests,
  420. ExpressionSetTests,
  421. ];
  422. TestClassRunner.runAll(testClasses);
  423. }
  424. document.addEventListener('DOMContentLoaded', onLoad);
  425. function normalizeWhitespace(str) {
  426. return str.replace(/\s+/g, ' ').replace(/(?:^\s+|\s+$)/g, '');
  427. }
  428. class TokenTests extends BaseTest {
  429. test_findFirstTokens() {
  430. const tokens = [
  431. new MDToken('Lorem', MDTokenType.Text),
  432. new MDToken(' ', MDTokenType.Whitespace),
  433. new MDToken('_', MDTokenType.Underscore),
  434. new MDToken('ipsum', MDTokenType.Text),
  435. new MDToken('_', MDTokenType.Underscore),
  436. new MDToken(' ', MDTokenType.Whitespace),
  437. new MDToken('dolor', MDTokenType.Text),
  438. new MDToken('_', MDTokenType.Underscore),
  439. new MDToken('sit', MDTokenType.Text),
  440. new MDToken('_', MDTokenType.Underscore),
  441. ];
  442. const pattern = [
  443. MDTokenType.Underscore,
  444. ];
  445. const result = MDToken.findFirstTokens(tokens, pattern);
  446. const expected = {
  447. tokens: [ tokens[2] ],
  448. index: 2,
  449. };
  450. this.assertEqual(result, expected);
  451. }
  452. test_findFirstTokens_optionalWhitespace1() {
  453. const tokens = [
  454. new MDToken('Lorem', MDTokenType.Text),
  455. new MDToken(' ', MDTokenType.Whitespace),
  456. new MDToken('[ipsum]', MDTokenType.Label, 'ipsum'),
  457. new MDToken('(link.html)', MDTokenType.URL, 'link.html'),
  458. new MDToken(' ', MDTokenType.Whitespace),
  459. new MDToken('dolor', MDTokenType.Text),
  460. ];
  461. const pattern = [
  462. MDTokenType.Label,
  463. MDTokenType.META_OptionalWhitespace,
  464. MDTokenType.URL,
  465. ];
  466. const result = MDToken.findFirstTokens(tokens, pattern);
  467. const expected = {
  468. tokens: [ tokens[2], tokens[3] ],
  469. index: 2,
  470. };
  471. this.assertEqual(result, expected);
  472. }
  473. test_findFirstTokens_optionalWhitespace2() {
  474. const tokens = [
  475. new MDToken('Lorem', MDTokenType.Text),
  476. new MDToken(' ', MDTokenType.Whitespace),
  477. new MDToken('[ipsum]', MDTokenType.Label, 'ipsum'),
  478. new MDToken(' ', MDTokenType.Whitespace),
  479. new MDToken('(link.html)', MDTokenType.URL, 'link.html'),
  480. new MDToken(' ', MDTokenType.Whitespace),
  481. new MDToken('dolor', MDTokenType.Text),
  482. ];
  483. const pattern = [
  484. MDTokenType.Label,
  485. MDTokenType.META_OptionalWhitespace,
  486. MDTokenType.URL,
  487. ];
  488. const result = MDToken.findFirstTokens(tokens, pattern);
  489. const expected = {
  490. tokens: [ tokens[2], tokens[3], tokens[4] ],
  491. index: 2,
  492. };
  493. this.assertEqual(result, expected);
  494. }
  495. test_findPairedTokens() {
  496. const tokens = [
  497. new MDToken('Lorem', MDTokenType.Text),
  498. new MDToken(' ', MDTokenType.Whitespace),
  499. new MDToken('_', MDTokenType.Underscore),
  500. new MDToken('ipsum', MDTokenType.Text),
  501. new MDToken('_', MDTokenType.Underscore),
  502. new MDToken(' ', MDTokenType.Whitespace),
  503. new MDToken('dolor', MDTokenType.Text),
  504. new MDToken('_', MDTokenType.Underscore),
  505. new MDToken('sit', MDTokenType.Text),
  506. new MDToken('_', MDTokenType.Underscore),
  507. ];
  508. const pattern = [
  509. MDTokenType.Underscore,
  510. ];
  511. const result = MDToken.findPairedTokens(tokens, pattern, pattern);
  512. const expected = {
  513. startTokens: [ tokens[2] ],
  514. contentTokens: [ tokens[3] ],
  515. endTokens: [ tokens[4] ],
  516. startIndex: 2,
  517. contentIndex: 3,
  518. endIndex: 4,
  519. totalLength: 3,
  520. }
  521. this.assertEqual(result, expected);
  522. }
  523. }
  524. class UtilsTests extends BaseTest {
  525. test_stripIndent() {
  526. this.assertEqual(MDUtils.stripIndent(''), '');
  527. this.assertEqual(MDUtils.stripIndent(' '), '');
  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(' foo'), 'foo');
  533. this.assertEqual(MDUtils.stripIndent(' foo'), ' foo');
  534. this.assertEqual(MDUtils.stripIndent('\tfoo'), 'foo');
  535. this.assertEqual(MDUtils.stripIndent('\t\tfoo'), '\tfoo');
  536. this.assertEqual(MDUtils.stripIndent('\t\tfoo', 2), 'foo');
  537. this.assertEqual(MDUtils.stripIndent(' foo', 2), 'foo');
  538. }
  539. test_countIndents() {
  540. this.assertEqual(MDUtils.countIndents(''), 0);
  541. this.assertEqual(MDUtils.countIndents(' '), 1);
  542. this.assertEqual(MDUtils.countIndents(' '), 1);
  543. this.assertEqual(MDUtils.countIndents('foo'), 0);
  544. this.assertEqual(MDUtils.countIndents('foo'), 0);
  545. this.assertEqual(MDUtils.countIndents(' foo'), 1);
  546. this.assertEqual(MDUtils.countIndents(' foo'), 1);
  547. this.assertEqual(MDUtils.countIndents(' foo'), 1);
  548. this.assertEqual(MDUtils.countIndents(' foo'), 1);
  549. this.assertEqual(MDUtils.countIndents(' foo'), 2);
  550. this.assertEqual(MDUtils.countIndents('\tfoo'), 1);
  551. this.assertEqual(MDUtils.countIndents('\t\tfoo'), 2);
  552. this.assertEqual(MDUtils.countIndents('', true), 0);
  553. this.assertEqual(MDUtils.countIndents(' ', true), 0);
  554. this.assertEqual(MDUtils.countIndents(' ', true), 1);
  555. this.assertEqual(MDUtils.countIndents('foo', true), 0);
  556. this.assertEqual(MDUtils.countIndents(' foo', true), 0);
  557. this.assertEqual(MDUtils.countIndents(' foo', true), 0);
  558. this.assertEqual(MDUtils.countIndents(' foo', true), 0);
  559. this.assertEqual(MDUtils.countIndents(' foo', true), 1);
  560. this.assertEqual(MDUtils.countIndents(' foo', true), 1);
  561. this.assertEqual(MDUtils.countIndents('\tfoo', true), 1);
  562. this.assertEqual(MDUtils.countIndents('\t\tfoo', true), 2);
  563. }
  564. test_tokenizeLabel() {
  565. // Escapes are preserved
  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'), [ '[foo\\)]', 'foo\\)' ]);
  572. this.assertEqual(MDUtils.tokenizeLabel('[foo()] bar'), [ '[foo()]', 'foo()' ]);
  573. this.assertEqual(MDUtils.tokenizeLabel('foo bar'), null);
  574. this.assertEqual(MDUtils.tokenizeLabel('[foo\\] bar'), null);
  575. this.assertEqual(MDUtils.tokenizeLabel('[foo bar'), null);
  576. this.assertEqual(MDUtils.tokenizeLabel('[foo[] bar'), null);
  577. }
  578. test_tokenizeURL() {
  579. this.assertEqual(MDUtils.tokenizeURL('(page.html) foo'), [ '(page.html)', 'page.html', null ]);
  580. this.assertEqual(MDUtils.tokenizeURL('(page.html "link title") foo'), [ '(page.html "link title")', 'page.html', 'link title' ]);
  581. 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 ]);
  582. this.assertEqual(MDUtils.tokenizeURL('page.html foo'), null);
  583. this.assertEqual(MDUtils.tokenizeURL('(page.html foo'), null);
  584. this.assertEqual(MDUtils.tokenizeURL('page.html) foo'), null);
  585. this.assertEqual(MDUtils.tokenizeURL('(page.html "title) foo'), null);
  586. this.assertEqual(MDUtils.tokenizeURL('(page .html) foo'), null);
  587. this.assertEqual(MDUtils.tokenizeURL('(user@example.com) foo'), null);
  588. this.assertEqual(MDUtils.tokenizeURL('(user@example.com "title") foo'), null);
  589. }
  590. test_tokenizeEmail() {
  591. this.assertEqual(MDUtils.tokenizeEmail('(user@example.com)'), [ '(user@example.com)', 'user@example.com', null ]);
  592. this.assertEqual(MDUtils.tokenizeEmail('(user@example.com "link title")'), [ '(user@example.com "link title")', 'user@example.com', 'link title' ]);
  593. this.assertEqual(MDUtils.tokenizeEmail('(https://example.com) foo'), null);
  594. this.assertEqual(MDUtils.tokenizeEmail('(https://example.com "link title") foo'), null);
  595. this.assertEqual(MDUtils.tokenizeEmail('(user@example.com "link title) foo'), null);
  596. this.assertEqual(MDUtils.tokenizeEmail('(user@example.com foo'), null);
  597. this.assertEqual(MDUtils.tokenizeEmail('user@example.com) foo'), null);
  598. }
  599. }
  600. class InlineTests extends BaseTest {
  601. /** @type {Markdown} */
  602. parser;
  603. md(markdown) {
  604. return normalizeWhitespace(this.parser.toHTML(markdown));
  605. }
  606. setUp() {
  607. this.parser = Markdown.completeParser;
  608. }
  609. test_simpleText() {
  610. let markdown = 'Lorem ipsum';
  611. let expected = 'Lorem ipsum';
  612. let actual = this.md(markdown);
  613. this.assertEqual(actual, expected);
  614. }
  615. test_strong() {
  616. let markdown = 'Lorem **ipsum** dolor **sit**';
  617. let expected = 'Lorem <strong>ipsum</strong> dolor <strong>sit</strong>';
  618. let actual = this.md(markdown);
  619. this.assertEqual(actual, expected);
  620. }
  621. test_emphasis() {
  622. let markdown = 'Lorem _ipsum_ dolor _sit_';
  623. let expected = 'Lorem <em>ipsum</em> dolor <em>sit</em>';
  624. let actual = this.md(markdown);
  625. this.assertEqual(actual, expected);
  626. }
  627. test_strongEmphasis_cleanNesting1() {
  628. let markdown = 'Lorem **ipsum *dolor* sit** amet';
  629. let expected = 'Lorem <strong>ipsum <em>dolor</em> sit</strong> amet';
  630. let actual = this.md(markdown);
  631. this.assertEqual(actual, expected);
  632. }
  633. test_strongEmphasis_cleanNesting2() {
  634. let markdown = 'Lorem *ipsum **dolor** sit* amet';
  635. let expected = 'Lorem <em>ipsum <strong>dolor</strong> sit</em> amet';
  636. let actual = this.md(markdown);
  637. this.assertEqual(actual, expected);
  638. }
  639. test_strongEmphasis_tightNesting() {
  640. let markdown = 'Lorem ***ipsum*** dolor';
  641. let expected1 = 'Lorem <strong><em>ipsum</em></strong> dolor';
  642. let expected2 = 'Lorem <em><strong>ipsum</strong></em> dolor';
  643. let actual = this.md(markdown);
  644. this.assertTrue(actual == expected1 || actual == expected2);
  645. }
  646. test_strongEmphasis_lopsidedNesting1() {
  647. let markdown = 'Lorem ***ipsum* dolor** sit';
  648. let expected = 'Lorem <strong><em>ipsum</em> dolor</strong> sit';
  649. let actual = this.md(markdown);
  650. this.assertEqual(actual, expected);
  651. }
  652. test_strongEmphasis_lopsidedNesting2() {
  653. let markdown = 'Lorem ***ipsum** dolor* sit';
  654. let expected = 'Lorem <em><strong>ipsum</strong> dolor</em> sit';
  655. let actual = this.md(markdown);
  656. this.assertEqual(actual, expected);
  657. }
  658. test_strongEmphasis_lopsidedNesting3() {
  659. let markdown = 'Lorem **ipsum *dolor*** sit';
  660. let expected = 'Lorem <strong>ipsum <em>dolor</em></strong> sit';
  661. let actual = this.md(markdown);
  662. this.assertEqual(actual, expected);
  663. }
  664. test_strongEmphasis_lopsidedNesting4() {
  665. let markdown = 'Lorem *ipsum **dolor*** sit';
  666. let expected = 'Lorem <em>ipsum <strong>dolor</strong></em> sit';
  667. let actual = this.md(markdown);
  668. this.assertEqual(actual, expected);
  669. }
  670. test_inlineCode() {
  671. let markdown = 'Lorem `ipsum` dolor';
  672. let expected = 'Lorem <code>ipsum</code> dolor';
  673. let actual = this.md(markdown);
  674. this.assertEqual(actual, expected);
  675. }
  676. test_inlineCode_withInnerBacktick() {
  677. let markdown = 'Lorem ``ip`su`m`` dolor';
  678. let expected = 'Lorem <code>ip`su`m</code> dolor';
  679. let actual = this.md(markdown);
  680. this.assertEqual(actual, expected);
  681. }
  682. test_strikethrough_single() {
  683. let markdown = 'Lorem ~ipsum~ dolor';
  684. let expected = 'Lorem <strike>ipsum</strike> dolor';
  685. let actual = this.md(markdown);
  686. this.assertEqual(actual, expected);
  687. }
  688. test_strikethrough_double() {
  689. let markdown = 'Lorem ~~ipsum~~ dolor';
  690. let expected = 'Lorem <strike>ipsum</strike> dolor';
  691. let actual = this.md(markdown);
  692. this.assertEqual(actual, expected);
  693. }
  694. test_link_fullyQualified() {
  695. let markdown = 'Lorem [ipsum](https://example.com/path/page.html) dolor';
  696. let expected = 'Lorem <a href="https://example.com/path/page.html">ipsum</a> dolor';
  697. let actual = this.md(markdown);
  698. this.assertEqual(actual, expected);
  699. }
  700. test_link_relative() {
  701. let markdown = 'Lorem [ipsum](page.html) dolor';
  702. let expected = 'Lorem <a href="page.html">ipsum</a> dolor';
  703. let actual = this.md(markdown);
  704. this.assertEqual(actual, expected);
  705. }
  706. test_link_title() {
  707. let markdown = 'Lorem [ipsum](page.html "link title") dolor';
  708. let expected = 'Lorem <a href="page.html" title="link title">ipsum</a> dolor';
  709. let actual = this.md(markdown);
  710. this.assertEqual(actual, expected);
  711. }
  712. test_link_literal() {
  713. let markdown = 'Lorem <https://example.com> dolor';
  714. let expected = 'Lorem <a href="https://example.com">https://example.com</a> dolor';
  715. let actual = this.md(markdown);
  716. this.assertEqual(actual, expected);
  717. }
  718. test_link_ref() {
  719. let markdown = "Lorem [ipsum][ref] dolor\n\n[ref]: https://example.com";
  720. let expected = '<p>Lorem <a href="https://example.com">ipsum</a> dolor</p>';
  721. let actual = this.md(markdown);
  722. this.assertEqual(actual, expected);
  723. }
  724. test_link_email() {
  725. let markdown = 'Lorem [ipsum](user@example.com) dolor';
  726. 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';
  727. let actual = this.md(markdown);
  728. this.assertEqual(actual, expected);
  729. }
  730. test_link_email_withTitle() {
  731. let markdown = 'Lorem [ipsum](user@example.com "title") dolor';
  732. 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';
  733. let actual = this.md(markdown);
  734. this.assertEqual(actual, expected);
  735. }
  736. test_link_literalEmail() {
  737. let markdown = 'Lorem <user@example.com> dolor';
  738. 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';
  739. let actual = this.md(markdown);
  740. this.assertEqual(actual, expected);
  741. }
  742. test_link_image() {
  743. let markdown = 'Lorem [![alt](image.jpg)](page.html) ipsum';
  744. let expected = 'Lorem <a href="page.html"><img src="image.jpg" alt="alt"></a> ipsum';
  745. let actual = this.md(markdown);
  746. this.assertEqual(actual, expected);
  747. }
  748. test_link_image_complex() {
  749. let markdown = 'Lorem [![alt] (image.jpg "image title")] (page.html "link title") ipsum';
  750. let expected = 'Lorem <a href="page.html" title="link title"><img src="image.jpg" alt="alt" title="image title"></a> ipsum';
  751. let actual = this.md(markdown);
  752. this.assertEqual(actual, expected);
  753. }
  754. test_image() {
  755. let markdown = 'Lorem ![alt text](image.jpg) dolor';
  756. let expected = 'Lorem <img src="image.jpg" alt="alt text"> dolor';
  757. let actual = this.md(markdown);
  758. this.assertEqual(actual, expected);
  759. }
  760. test_image_noAlt() {
  761. let markdown = 'Lorem ![](image.jpg) dolor';
  762. let expected = 'Lorem <img src="image.jpg"> dolor';
  763. let actual = this.md(markdown);
  764. this.assertEqual(actual, expected);
  765. }
  766. test_image_withTitle() {
  767. let markdown = 'Lorem ![alt text](image.jpg "image title") dolor';
  768. let expected = 'Lorem <img src="image.jpg" alt="alt text" title="image title"> dolor';
  769. let actual = this.md(markdown);
  770. this.assertEqual(actual, expected);
  771. }
  772. test_image_ref() {
  773. let markdown = 'Lorem ![alt text][ref] dolor\n\n' +
  774. '[ref]: image.jpg "image title"';
  775. let expected = '<p>Lorem <img src="image.jpg" alt="alt text" title="image title"> dolor</p>';
  776. let actual = this.md(markdown);
  777. this.assertEqual(actual, expected);
  778. }
  779. test_htmlTags() {
  780. let markdown = 'Lorem <strong title="value" foo=\'with " quote\' bar="with \' apostrophe" attr=unquoted checked>ipsum</strong> dolor';
  781. let expected = markdown;
  782. let actual = this.md(markdown);
  783. this.assertEqual(actual, expected);
  784. }
  785. }
  786. class BlockTests extends BaseTest {
  787. /** @type {Markdown} */
  788. parser;
  789. md(markdown) {
  790. return normalizeWhitespace(this.parser.toHTML(markdown));
  791. }
  792. setUp() {
  793. this.parser = Markdown.completeParser;
  794. }
  795. test_paragraphs() {
  796. let markdown = "Lorem ipsum\n\nDolor sit amet";
  797. let expected = "<p>Lorem ipsum</p> <p>Dolor sit amet</p>";
  798. let actual = this.md(markdown);
  799. this.assertEqual(actual, expected);
  800. }
  801. test_paragraph_lineGrouping() {
  802. let markdown = "Lorem ipsum\ndolor sit amet";
  803. let expected = "Lorem ipsum dolor sit amet";
  804. let actual = this.md(markdown);
  805. this.assertEqual(actual, expected);
  806. }
  807. test_header_underlineH1() {
  808. let markdown = "Header 1\n===\n\nLorem ipsum";
  809. let expected = "<h1>Header 1</h1> <p>Lorem ipsum</p>";
  810. let actual = this.md(markdown);
  811. this.assertEqual(actual, expected);
  812. }
  813. test_header_underlineH2() {
  814. let markdown = "Header 2\n---\n\nLorem ipsum";
  815. let expected = "<h2>Header 2</h2> <p>Lorem ipsum</p>";
  816. let actual = this.md(markdown);
  817. this.assertEqual(actual, expected);
  818. }
  819. test_header_hash() {
  820. let markdown = "# Header 1\n## Header 2\n### Header 3\n#### Header 4\n##### Header 5\n###### Header 6\n";
  821. 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>';
  822. let actual = this.md(markdown);
  823. this.assertEqual(actual, expected);
  824. }
  825. test_header_hash_trailing() {
  826. let markdown = "# Header 1 #\n## Header 2 ##\n### Header 3 ######";
  827. let expected = '<h1>Header 1</h1> <h2>Header 2</h2> <h3>Header 3</h3>';
  828. let actual = this.md(markdown);
  829. this.assertEqual(actual, expected);
  830. }
  831. test_unorderedList() {
  832. let markdown = "* Lorem\n* Ipsum\n* Dolor";
  833. let expected = '<ul> <li>Lorem</li> <li>Ipsum</li> <li>Dolor</li> </ul>';
  834. let actual = this.md(markdown);
  835. this.assertEqual(actual, expected);
  836. }
  837. test_unorderedList_nested() {
  838. let markdown = "* Lorem\n + Ipsum\n* Dolor";
  839. let expected = '<ul> <li>Lorem <ul> <li>Ipsum</li> </ul></li> <li>Dolor</li> </ul>';
  840. let actual = this.md(markdown);
  841. this.assertEqual(actual, expected);
  842. }
  843. test_unorderedList_hitch() {
  844. // This incomplete bulleted list locked up the browser at one
  845. // point, not forever but a REALLY long time
  846. this.profile(1.0, () => {
  847. let markdown = "Testing\n\n* ";
  848. let expected = '<p>Testing</p> <ul> <li></li> </ul>';
  849. let actual = this.md(markdown);
  850. this.assertEqual(actual, expected);
  851. });
  852. }
  853. test_orderedList() {
  854. let markdown = "1. Lorem\n1. Ipsum\n5. Dolor";
  855. let expected = '<ol> <li>Lorem</li> <li>Ipsum</li> <li>Dolor</li> </ol>';
  856. let actual = this.md(markdown);
  857. this.assertEqual(actual, expected);
  858. }
  859. test_orderedList_numbering() {
  860. let markdown = "4. Lorem\n1. Ipsum\n9. Dolor";
  861. let expected = '<ol start="4"> <li>Lorem</li> <li>Ipsum</li> <li>Dolor</li> </ol>';
  862. let actual = this.md(markdown);
  863. this.assertEqual(actual, expected);
  864. }
  865. test_orderedList_nested1() {
  866. let markdown = "1. Lorem\n 1. Ipsum\n1. Dolor";
  867. let expected = '<ol> <li>Lorem <ol> <li>Ipsum</li> </ol></li> <li>Dolor</li> </ol>';
  868. let actual = this.md(markdown);
  869. this.assertEqual(actual, expected);
  870. }
  871. test_orderedList_nested2() {
  872. let markdown = "1. Lorem\n 1. Ipsum\n 1. Dolor\n 1. Sit\n1. Amet";
  873. let expected = '<ol> <li>Lorem <ol> <li>Ipsum <ol> <li>Dolor</li> </ol></li> <li>Sit</li> </ol></li> <li>Amet</li> </ol>';
  874. let actual = this.md(markdown);
  875. this.assertEqual(actual, expected);
  876. }
  877. test_blockquote() {
  878. let markdown = '> Lorem ipsum dolor';
  879. let expected = '<blockquote> Lorem ipsum dolor </blockquote>';
  880. let actual = this.md(markdown);
  881. this.assertEqual(actual, expected);
  882. }
  883. test_blockquote_paragraphs() {
  884. let markdown = '> Lorem ipsum dolor\n>\n>Sit amet';
  885. let expected = '<blockquote> <p>Lorem ipsum dolor</p> <p>Sit amet</p> </blockquote>';
  886. let actual = this.md(markdown);
  887. this.assertEqual(actual, expected);
  888. }
  889. test_blockquote_list() {
  890. let markdown = '> 1. Lorem\n> 2. Ipsum';
  891. let expected = '<blockquote> <ol> <li>Lorem</li> <li>Ipsum</li> </ol> </blockquote>';
  892. let actual = this.md(markdown);
  893. this.assertEqual(actual, expected);
  894. }
  895. test_codeBlock_indented() {
  896. let markdown = "Code\n\n function foo() {\n return 'bar';\n }\n\nend";
  897. let expected = "<p>Code</p>\n\n<pre><code>function foo() {\n return 'bar';\n}</code></pre>\n<p>end</p>\n";
  898. let actual = this.parser.toHTML(markdown); // don't normalize whitespace
  899. this.assertEqual(actual.replace(/ /g, '⎵'), expected.replace(/ /g, '⎵'));
  900. }
  901. test_codeBlock_fenced() {
  902. let markdown = "Code\n\n```\nfunction foo() {\n return 'bar';\n}\n```\n\nend";
  903. let expected = "<p>Code</p>\n\n<pre><code>function foo() {\n return 'bar';\n}</code></pre>\n<p>end</p>\n";
  904. let actual = this.parser.toHTML(markdown); // don't normalize whitespace
  905. this.assertEqual(actual.replace(/ /g, '⎵'), expected.replace(/ /g, '⎵'));
  906. }
  907. test_horizontalRule() {
  908. let markdown = "Before\n\n---\n\n- - -\n\n***\n\n* * * * * * *\n\nafter";
  909. let expected = "<p>Before</p> <hr> <hr> <hr> <hr> <p>after</p>";
  910. let actual = this.md(markdown);
  911. this.assertEqual(actual, expected);
  912. }
  913. test_table_unfenced() {
  914. let markdown = "Column A | Column B | Column C\n--- | --- | ---\n1 | 2 | 3\n4 | 5 | 6";
  915. 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>";
  916. let actual = this.md(markdown);
  917. this.assertEqual(actual, expected);
  918. }
  919. test_table_fenced() {
  920. let markdown = "| Column A | Column B | Column C |\n| --- | --- | --- |\n| 1 | 2 | 3\n4 | 5 | 6 |";
  921. 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>";
  922. let actual = this.md(markdown);
  923. this.assertEqual(actual, expected);
  924. }
  925. test_table_alignment() {
  926. let markdown = 'Column A | Column B | Column C\n' +
  927. ':--- | :---: | ---:\n' +
  928. '1 | 2 | 3\n' +
  929. '4 | 5 | 6';
  930. let expected = '<table> ' +
  931. '<thead> ' +
  932. '<tr> ' +
  933. '<th align="left">Column A</th> ' +
  934. '<th align="center">Column B</th> ' +
  935. '<th align="right">Column C</th> ' +
  936. '</tr> ' +
  937. '</thead> ' +
  938. '<tbody> ' +
  939. '<tr> ' +
  940. '<td align="left">1</td> ' +
  941. '<td align="center">2</td> ' +
  942. '<td align="right">3</td> ' +
  943. '</tr> ' +
  944. '<tr> ' +
  945. '<td align="left">4</td> ' +
  946. '<td align="center">5</td> ' +
  947. '<td align="right">6</td> ' +
  948. '</tr> ' +
  949. '</tbody> ' +
  950. '</table>';
  951. let actual = this.md(markdown);
  952. this.assertEqual(actual, expected);
  953. }
  954. test_table_holes() {
  955. let markdown = 'Column A||Column C\n' +
  956. '---|---|---\n' +
  957. '|1|2||\n' +
  958. '|4||6|\n' +
  959. '||8|9|';
  960. let expected = '<table> ' +
  961. '<thead> ' +
  962. '<tr> ' +
  963. '<th>Column A</th> ' +
  964. '<th></th> ' +
  965. '<th>Column C</th> ' +
  966. '</tr> ' +
  967. '</thead> ' +
  968. '<tbody> ' +
  969. '<tr> ' +
  970. '<td>1</td> ' +
  971. '<td>2</td> ' +
  972. '<td></td> ' +
  973. '</tr> ' +
  974. '<tr> ' +
  975. '<td>4</td> ' +
  976. '<td></td> ' +
  977. '<td>6</td> ' +
  978. '</tr> ' +
  979. '<tr> ' +
  980. '<td></td> ' +
  981. '<td>8</td> ' +
  982. '<td>9</td> ' +
  983. '</tr> ' +
  984. '</tbody> ' +
  985. '</table>';
  986. let actual = this.md(markdown);
  987. this.assertEqual(actual, expected);
  988. }
  989. test_definitionList() {
  990. let markdown = 'term\n' +
  991. ': definition\n' +
  992. 'another' +
  993. ' term\n' +
  994. ': def 1\n' +
  995. ' broken on next line\n' +
  996. ': def 2';
  997. let expected = '<dl> ' +
  998. '<dt>term</dt> ' +
  999. '<dd>definition</dd> ' +
  1000. '<dt>another term</dt> ' +
  1001. '<dd>def 1 broken on next line</dd> ' +
  1002. '<dd>def 2</dd> ' +
  1003. '</dl>';
  1004. let actual = this.md(markdown);
  1005. this.assertEqual(actual, expected);
  1006. }
  1007. test_footnotes() {
  1008. let markdown = 'Lorem ipsum[^1] dolor[^2] sit[^1] amet\n\n[^1]: A footnote\n[^2]: Another footnote';
  1009. let expected = '<p>Lorem ipsum<sup id="footnoteref_1"><a href="#footnote_1">1</a></sup> ' +
  1010. 'dolor<sup id="footnoteref_2"><a href="#footnote_2">2</a></sup> ' +
  1011. 'sit<sup id="footnoteref_3"><a href="#footnote_1">1</a></sup> amet</p> ' +
  1012. '<div class="footnotes">' +
  1013. '<hr/>' +
  1014. '<ol>' +
  1015. '<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> ' +
  1016. '<li value="2" id="footnote_2">Another footnote <a href="#footnoteref_2" class="footnote-backref">↩︎</a></li> ' +
  1017. '</ol>' +
  1018. '</div>';
  1019. let actual = this.md(markdown);
  1020. this.assertEqual(actual, expected);
  1021. }
  1022. test_abbreviations() {
  1023. let markdown = 'Lorem ipsum HTML dolor HTML sit\n' +
  1024. '\n' +
  1025. '*[HTML]: Hypertext Markup Language';
  1026. let expected = '<p>Lorem ipsum <abbr title="Hypertext Markup Language">HTML</abbr> dolor <abbr title="Hypertext Markup Language">HTML</abbr> sit</p>';
  1027. let actual = this.md(markdown);
  1028. this.assertEqual(actual, expected);
  1029. }
  1030. }
  1031. class CellValueTests extends BaseTest {
  1032. test_fromCellString_blank() {
  1033. var value;
  1034. value = CellValue.fromCellString('');
  1035. this.assertEqual(value.type, CellValue.TYPE_BLANK);
  1036. this.assertEqual(value.formattedValue, '');
  1037. this.assertEqual(value.value, null);
  1038. value = CellValue.fromCellString(' ');
  1039. this.assertEqual(value.type, CellValue.TYPE_BLANK);
  1040. this.assertEqual(value.formattedValue, '');
  1041. this.assertEqual(value.value, null);
  1042. }
  1043. test_fromCellString_number() {
  1044. var value;
  1045. value = CellValue.fromCellString('123');
  1046. this.assertEqual(value.type, CellValue.TYPE_NUMBER);
  1047. this.assertEqual(value.formattedValue, '123');
  1048. this.assertEqual(value.value, 123);
  1049. this.assertEqual(value.decimals, 0);
  1050. value = CellValue.fromCellString('-0');
  1051. this.assertEqual(value.type, CellValue.TYPE_NUMBER);
  1052. this.assertEqual(value.formattedValue, '-0');
  1053. this.assertEqual(value.value, 0);
  1054. this.assertEqual(value.decimals, 0);
  1055. value = CellValue.fromCellString('1,234');
  1056. this.assertEqual(value.type, CellValue.TYPE_NUMBER);
  1057. this.assertEqual(value.formattedValue, '1,234');
  1058. this.assertEqual(value.value, 1234);
  1059. this.assertEqual(value.decimals, 0);
  1060. value = CellValue.fromCellString('-1,234,567.89');
  1061. this.assertEqual(value.type, CellValue.TYPE_NUMBER);
  1062. this.assertEqual(value.formattedValue, '-1,234,567.89');
  1063. this.assertEqual(value.value, -1234567.89);
  1064. this.assertEqual(value.decimals, 2);
  1065. }
  1066. test_fromCellString_percent() {
  1067. var value;
  1068. value = CellValue.fromCellString('123%');
  1069. this.assertEqual(value.type, CellValue.TYPE_PERCENT);
  1070. this.assertEqual(value.formattedValue, '123%');
  1071. this.assertEqual(value.value, 1.23, 0.0001);
  1072. this.assertEqual(value.decimals, 0);
  1073. value = CellValue.fromCellString('-12.3%');
  1074. this.assertEqual(value.type, CellValue.TYPE_PERCENT);
  1075. this.assertEqual(value.formattedValue, '-12.3%');
  1076. this.assertEqual(value.value, -0.123, 0.0001);
  1077. this.assertEqual(value.decimals, 1);
  1078. }
  1079. test_fromCellString_currency() {
  1080. var value;
  1081. value = CellValue.fromCellString('$123');
  1082. this.assertEqual(value.type, CellValue.TYPE_CURRENCY);
  1083. this.assertEqual(value.formattedValue, '$123');
  1084. this.assertEqual(value.value, 123);
  1085. this.assertEqual(value.decimals, 0);
  1086. value = CellValue.fromCellString('-$12.34');
  1087. this.assertEqual(value.type, CellValue.TYPE_CURRENCY);
  1088. this.assertEqual(value.formattedValue, '-$12.34');
  1089. this.assertEqual(value.value, -12.34, 0.0001);
  1090. this.assertEqual(value.decimals, 2);
  1091. }
  1092. test_fromCellString_boolean() {
  1093. var value;
  1094. value = CellValue.fromCellString('true');
  1095. this.assertEqual(value.type, CellValue.TYPE_BOOLEAN);
  1096. this.assertEqual(value.formattedValue, 'TRUE');
  1097. this.assertEqual(value.value, true);
  1098. value = CellValue.fromCellString('false');
  1099. this.assertEqual(value.type, CellValue.TYPE_BOOLEAN);
  1100. this.assertEqual(value.formattedValue, 'FALSE');
  1101. this.assertEqual(value.value, false);
  1102. }
  1103. test_fromCellString_string() {
  1104. var value;
  1105. value = CellValue.fromCellString('some text');
  1106. this.assertEqual(value.type, CellValue.TYPE_STRING);
  1107. this.assertEqual(value.formattedValue, 'some text');
  1108. this.assertEqual(value.value, 'some text');
  1109. value = CellValue.fromCellString("'123");
  1110. this.assertEqual(value.type, CellValue.TYPE_STRING);
  1111. this.assertEqual(value.formattedValue, '123');
  1112. this.assertEqual(value.value, '123');
  1113. }
  1114. test_fromCellString_formula() {
  1115. var value;
  1116. value = CellValue.fromCellString('=A*B');
  1117. this.assertEqual(value.type, CellValue.TYPE_FORMULA);
  1118. this.assertEqual(value.formattedValue, '=A*B');
  1119. this.assertEqual(value.value, '=A*B');
  1120. value = CellValue.fromCellString('=MAX(A, 3)');
  1121. this.assertEqual(value.type, CellValue.TYPE_FORMULA);
  1122. this.assertEqual(value.formattedValue, '=MAX(A, 3)');
  1123. this.assertEqual(value.value, '=MAX(A, 3)');
  1124. }
  1125. test_fromValue_null() {
  1126. var value;
  1127. value = CellValue.fromValue(null);
  1128. this.assertEqual(value.type, CellValue.TYPE_BLANK);
  1129. }
  1130. test_fromValue_number() {
  1131. var value;
  1132. value = CellValue.fromValue(123);
  1133. this.assertEqual(value.type, CellValue.TYPE_NUMBER);
  1134. this.assertEqual(value.formattedValue, '123');
  1135. value = CellValue.fromValue(3.141592);
  1136. this.assertEqual(value.type, CellValue.TYPE_NUMBER);
  1137. this.assertEqual(value.formattedValue, '3.141592');
  1138. value = CellValue.fromValue(123456789);
  1139. this.assertEqual(value.type, CellValue.TYPE_NUMBER);
  1140. this.assertEqual(value.formattedValue, '123,456,789');
  1141. }
  1142. test_fromValue_boolean() {
  1143. var value;
  1144. value = CellValue.fromValue(true);
  1145. this.assertEqual(value.type, CellValue.TYPE_BOOLEAN);
  1146. this.assertEqual(value.formattedValue, 'TRUE');
  1147. value = CellValue.fromValue(false);
  1148. this.assertEqual(value.type, CellValue.TYPE_BOOLEAN);
  1149. this.assertEqual(value.formattedValue, 'FALSE');
  1150. }
  1151. test_fromValue_string() {
  1152. var value;
  1153. value = CellValue.fromValue('foo');
  1154. this.assertEqual(value.type, CellValue.TYPE_STRING);
  1155. this.assertEqual(value.formattedValue, 'foo');
  1156. value = CellValue.fromValue('123');
  1157. this.assertEqual(value.type, CellValue.TYPE_STRING);
  1158. this.assertEqual(value.formattedValue, '123');
  1159. }
  1160. test_fromValue_formula() {
  1161. var value;
  1162. value = CellValue.fromValue('=A*B');
  1163. this.assertEqual(value.type, CellValue.TYPE_FORMULA);
  1164. this.assertEqual(value.formattedValue, '=A*B');
  1165. }
  1166. test_operation_add() {
  1167. var a, b, result, expected;
  1168. a = CellValue.fromValue(3);
  1169. b = CellValue.fromValue(4);
  1170. result = a.add(b)
  1171. expected = new CellValue('7', 7, CellValue.TYPE_NUMBER, 0);
  1172. this.assertEqual(result, expected);
  1173. a = CellValue.fromCellString('100%');
  1174. b = CellValue.fromCellString('50%');
  1175. result = a.add(b);
  1176. expected = new CellValue('150%', 1.5, CellValue.TYPE_PERCENT, 0);
  1177. this.assertEqual(result, expected);
  1178. a = CellValue.fromCellString('$123');
  1179. b = CellValue.fromCellString('$321');
  1180. result = a.add(b);
  1181. expected = new CellValue('$444.00', 444, CellValue.TYPE_CURRENCY, 2);
  1182. this.assertEqual(result, expected);
  1183. }
  1184. test_operation_subtract() {
  1185. var a, b, result, expected;
  1186. a = CellValue.fromValue(9);
  1187. b = CellValue.fromValue(4);
  1188. result = a.subtract(b)
  1189. expected = new CellValue('5', 5, CellValue.TYPE_NUMBER, 0);
  1190. this.assertEqual(result, expected);
  1191. a = CellValue.fromCellString('100%');
  1192. b = CellValue.fromCellString('50%');
  1193. result = a.subtract(b);
  1194. expected = new CellValue('50%', 0.5, CellValue.TYPE_PERCENT, 0);
  1195. this.assertEqual(result, expected);
  1196. a = CellValue.fromCellString('$321');
  1197. b = CellValue.fromCellString('$123');
  1198. result = a.subtract(b);
  1199. expected = new CellValue('$198.00', 198, CellValue.TYPE_CURRENCY, 2);
  1200. this.assertEqual(result, expected);
  1201. }
  1202. test_operation_multiply() {
  1203. var a, b, result, expected;
  1204. a = CellValue.fromValue(3);
  1205. b = CellValue.fromValue(4);
  1206. result = a.multiply(b)
  1207. expected = new CellValue('12', 12, CellValue.TYPE_NUMBER, 0);
  1208. this.assertEqual(result, expected);
  1209. a = CellValue.fromCellString('150%');
  1210. b = CellValue.fromCellString('50%');
  1211. result = a.multiply(b);
  1212. expected = new CellValue('75%', 0.75, CellValue.TYPE_PERCENT, 0);
  1213. this.assertEqual(result, expected);
  1214. a = CellValue.fromCellString('$321');
  1215. b = CellValue.fromCellString('50%');
  1216. result = a.multiply(b);
  1217. expected = new CellValue('$160.50', 160.50, CellValue.TYPE_CURRENCY, 2);
  1218. this.assertEqual(result, expected);
  1219. }
  1220. test_operation_divide() {
  1221. var a, b, result, expected;
  1222. a = CellValue.fromValue(12);
  1223. b = CellValue.fromValue(4);
  1224. result = a.divide(b)
  1225. expected = new CellValue('3', 3, CellValue.TYPE_NUMBER, 0);
  1226. this.assertEqual(result, expected);
  1227. a = CellValue.fromCellString('150%');
  1228. b = CellValue.fromCellString('50%');
  1229. result = a.divide(b);
  1230. expected = new CellValue('300%', 3.0, CellValue.TYPE_PERCENT, 0);
  1231. this.assertEqual(result, expected);
  1232. a = CellValue.fromCellString('$321');
  1233. b = CellValue.fromCellString('200%');
  1234. result = a.divide(b);
  1235. expected = new CellValue('$160.50', 160.50, CellValue.TYPE_CURRENCY, 2);
  1236. this.assertEqual(result, expected);
  1237. }
  1238. test_operation_modulo() {
  1239. var a, b, result, expected;
  1240. a = CellValue.fromValue(7);
  1241. b = CellValue.fromValue(4);
  1242. result = a.modulo(b)
  1243. expected = new CellValue('3', 3, CellValue.TYPE_NUMBER, 0);
  1244. this.assertEqual(result, expected);
  1245. a = CellValue.fromCellString('175%');
  1246. b = CellValue.fromCellString('50%');
  1247. result = a.modulo(b);
  1248. expected = new CellValue('25%', 0.25, CellValue.TYPE_PERCENT, 0);
  1249. this.assertEqual(result, expected);
  1250. a = CellValue.fromCellString('$327');
  1251. b = CellValue.fromCellString('$20');
  1252. result = a.modulo(b);
  1253. expected = new CellValue('$7.00', 7.00, CellValue.TYPE_CURRENCY, 2);
  1254. this.assertEqual(result, expected);
  1255. }
  1256. test_operation_unaryNot() {
  1257. this.assertEqual(CellValue.fromValue(true).not(), CellValue.fromValue(false));
  1258. this.assertEqual(CellValue.fromValue(false).not(), CellValue.fromValue(true));
  1259. }
  1260. test_operation_comparators() {
  1261. const a = CellValue.fromValue(3);
  1262. const b = CellValue.fromValue(4);
  1263. const t = CellValue.fromValue(true);
  1264. const f = CellValue.fromValue(false);
  1265. this.assertEqual(a.lt(b), t);
  1266. this.assertEqual(a.lte(b), t);
  1267. this.assertEqual(a.gt(b), f);
  1268. this.assertEqual(a.gte(b), f);
  1269. this.assertEqual(a.eq(b), f);
  1270. this.assertEqual(a.neq(b), t);
  1271. this.assertEqual(b.lt(a), f);
  1272. this.assertEqual(b.lte(a), f);
  1273. this.assertEqual(b.gt(a), t);
  1274. this.assertEqual(b.gte(a), t);
  1275. this.assertEqual(b.eq(a), f);
  1276. this.assertEqual(b.neq(a), t);
  1277. this.assertEqual(a.lt(a), f);
  1278. this.assertEqual(a.lte(a), t);
  1279. this.assertEqual(a.gt(a), f);
  1280. this.assertEqual(a.gte(a), t);
  1281. this.assertEqual(a.eq(a), t);
  1282. this.assertEqual(a.neq(a), f);
  1283. }
  1284. test_operation_concatenate() {
  1285. const a = CellValue.fromValue('abc');
  1286. const b = CellValue.fromValue('xyz');
  1287. const result = a.concatenate(b);
  1288. const expected = CellValue.fromValue('abcxyz');
  1289. this.assertEqual(result, expected);
  1290. }
  1291. }
  1292. class CellAddressRangeTests extends BaseTest {
  1293. test_iterator() {
  1294. const grid = new SpreadsheetGrid(3, 4);
  1295. let range = new CellAddressRange(new CellAddress(0, 0), new CellAddress(2, 3));
  1296. var visited = [];
  1297. var sanity = 100;
  1298. for (const address of range.cellsIn(grid)) {
  1299. visited.push(address.name);
  1300. if (sanity-- < 0) break;
  1301. }
  1302. const result = visited.join(',');
  1303. const expected = 'A1,A2,A3,A4,B1,B2,B3,B4,C1,C2,C3,C4';
  1304. this.assertEqual(result, expected);
  1305. }
  1306. test_iterator_column() {
  1307. const grid = new SpreadsheetGrid(3, 4);
  1308. let range = new CellAddressRange(new CellAddress(1, -1), new CellAddress(2, -1));
  1309. var visited = [];
  1310. var sanity = 100;
  1311. for (const address of range.cellsIn(grid)) {
  1312. visited.push(address.name);
  1313. if (sanity-- < 0) break;
  1314. }
  1315. const result = visited.join(',');
  1316. const expected = 'B1,B2,B3,B4,C1,C2,C3,C4';
  1317. this.assertEqual(result, expected);
  1318. }
  1319. test_iterator_beyondBounds() {
  1320. const grid = new SpreadsheetGrid(3, 4);
  1321. let range = new CellAddressRange(new CellAddress(0, 1), new CellAddress(9, 9));
  1322. var visited = [];
  1323. var sanity = 100;
  1324. for (const address of range.cellsIn(grid)) {
  1325. visited.push(address.name);
  1326. if (sanity-- < 0) break;
  1327. }
  1328. const result = visited.join(',');
  1329. const expected = 'A2,A3,A4,B2,B3,B4,C2,C3,C4';
  1330. this.assertEqual(result, expected);
  1331. }
  1332. test_iterator_outOfBounds() {
  1333. const grid = new SpreadsheetGrid(3, 4);
  1334. let range = new CellAddressRange(new CellAddress(5, 0), new CellAddress(5, 9));
  1335. var visited = [];
  1336. var sanity = 100;
  1337. for (const address of range.cellsIn(grid)) {
  1338. visited.push(address.name);
  1339. if (sanity-- < 0) break;
  1340. }
  1341. const result = visited.join(',');
  1342. const expected = '';
  1343. this.assertEqual(result, expected);
  1344. }
  1345. }
  1346. class ExpressionSetTests extends BaseTest {
  1347. test_simpleMath() {
  1348. const grid = new SpreadsheetGrid(1, 1);
  1349. grid.cells[0][0].originalValue = CellValue.fromCellString('=7*3');
  1350. const expressionSet = new CellExpressionSet(grid);
  1351. expressionSet.calculateCells();
  1352. const expected = CellValue.fromValue(21);
  1353. this.assertEqual(grid.cells[0][0].outputValue, expected);
  1354. }
  1355. test_reference() {
  1356. const grid = new SpreadsheetGrid(3, 1);
  1357. grid.cells[0][0].originalValue = CellValue.fromValue(123);
  1358. grid.cells[1][0].originalValue = CellValue.fromValue(3);
  1359. grid.cells[2][0].originalValue = CellValue.fromCellString('=A1*B1');
  1360. const expressionSet = new CellExpressionSet(grid);
  1361. expressionSet.calculateCells();
  1362. const expected = CellValue.fromValue(369);
  1363. this.assertEqual(grid.cells[2][0].outputValue, expected);
  1364. }
  1365. test_infixPriority() {
  1366. const grid = new SpreadsheetGrid(1, 1);
  1367. grid.cells[0][0].originalValue = CellValue.fromCellString('=4*9/3+7-4');
  1368. const expressionSet = new CellExpressionSet(grid);
  1369. expressionSet.calculateCells();
  1370. const expected = CellValue.fromValue(15);
  1371. this.assertEqual(grid.cells[0][0].outputValue, expected);
  1372. }
  1373. test_filledFormula() {
  1374. const grid = new SpreadsheetGrid(3, 3);
  1375. grid.cells[0][0].originalValue = CellValue.fromValue(4);
  1376. grid.cells[1][0].originalValue = CellValue.fromValue(5);
  1377. grid.cells[2][0].originalValue = CellValue.fromCellString('=A1*B1 FILL');
  1378. grid.cells[0][1].originalValue = CellValue.fromValue(6);
  1379. grid.cells[1][1].originalValue = CellValue.fromValue(7);
  1380. grid.cells[0][2].originalValue = CellValue.fromValue(8);
  1381. grid.cells[1][2].originalValue = CellValue.fromValue(9);
  1382. const expressionSet = new CellExpressionSet(grid);
  1383. expressionSet.calculateCells();
  1384. this.assertEqual(grid.cells[2][0].outputValue, CellValue.fromValue(20));
  1385. this.assertEqual(grid.cells[2][1].outputValue, CellValue.fromValue(42));
  1386. this.assertEqual(grid.cells[2][2].outputValue, CellValue.fromValue(72));
  1387. }
  1388. test_dependencies() {
  1389. const grid = new SpreadsheetGrid(2, 4);
  1390. grid.cells[0][0].originalValue = CellValue.fromValue(1);
  1391. grid.cells[1][0].originalValue = CellValue.fromCellString('=A1+B2');
  1392. grid.cells[0][1].originalValue = CellValue.fromValue(2);
  1393. grid.cells[1][1].originalValue = CellValue.fromCellString('=A2+B3');
  1394. grid.cells[0][2].originalValue = CellValue.fromValue(3);
  1395. grid.cells[1][2].originalValue = CellValue.fromCellString('=A3+B4');
  1396. grid.cells[0][3].originalValue = CellValue.fromValue(4);
  1397. grid.cells[1][3].originalValue = CellValue.fromCellString('=A4');
  1398. const expressionSet = new CellExpressionSet(grid);
  1399. expressionSet.calculateCells();
  1400. this.assertEqual(grid.cells[1][0].outputValue, CellValue.fromValue(10));
  1401. this.assertEqual(grid.cells[1][1].outputValue, CellValue.fromValue(9));
  1402. this.assertEqual(grid.cells[1][2].outputValue, CellValue.fromValue(7));
  1403. this.assertEqual(grid.cells[1][3].outputValue, CellValue.fromValue(4));
  1404. }
  1405. _test_simple_formula(formula, expected) {
  1406. const grid = new SpreadsheetGrid(1, 1);
  1407. grid.cells[0][0].originalValue = CellValue.fromCellString(formula);
  1408. const expressionSet = new CellExpressionSet(grid);
  1409. expressionSet.calculateCells();
  1410. const result = grid.cells[0][0].outputValue;
  1411. const exp = (expected instanceof CellValue) ? expected : CellValue.fromValue(expected);
  1412. this.assertEqual(result.type, exp.type);
  1413. this.assertEqual(result.decimals, exp.decimals);
  1414. this.assertEqual(result.formattedValue, exp.formattedValue);
  1415. this.assertEqual(result.value, exp.value);
  1416. }
  1417. test_func_abs() {
  1418. this._test_simple_formula('=ABS(-3)', 3);
  1419. this._test_simple_formula('=ABS(4)', 4);
  1420. }
  1421. test_func_and() {
  1422. this._test_simple_formula('=AND(FALSE, FALSE)', false);
  1423. this._test_simple_formula('=AND(FALSE, TRUE)', false);
  1424. this._test_simple_formula('=AND(TRUE, FALSE)', false);
  1425. this._test_simple_formula('=AND(TRUE, TRUE)', true);
  1426. }
  1427. test_func_average() {
  1428. this._test_simple_formula('=AVERAGE(4, 6, 2, 4)', 4);
  1429. }
  1430. test_func_ceiling() {
  1431. this._test_simple_formula('=CEILING(3.1)', 4);
  1432. this._test_simple_formula('=CEILING(3)', 3);
  1433. this._test_simple_formula('=CEILING(-3.1)', -3);
  1434. }
  1435. test_func_exp() {
  1436. this._test_simple_formula('=EXP(1)', 2.718281828459045);
  1437. }
  1438. test_func_floor() {
  1439. this._test_simple_formula('=FLOOR(3.1)', 3);
  1440. this._test_simple_formula('=FLOOR(3)', 3);
  1441. this._test_simple_formula('=FLOOR(-3.1)', -4);
  1442. }
  1443. test_func_if() {
  1444. this._test_simple_formula('=IF(FALSE, 4, 6)', 6);
  1445. this._test_simple_formula('=IF(TRUE, 4, 6)', 4);
  1446. }
  1447. test_func_ifs() {
  1448. this._test_simple_formula('=IFS(TRUE, 1, FALSE, 2, FALSE, 3, FALSE, 4, 5)', 1);
  1449. this._test_simple_formula('=IFS(FALSE, 1, TRUE, 2, FALSE, 3, FALSE, 4, 5)', 2);
  1450. this._test_simple_formula('=IFS(FALSE, 1, FALSE, 2, TRUE, 3, FALSE, 4, 5)', 3);
  1451. this._test_simple_formula('=IFS(FALSE, 1, FALSE, 2, FALSE, 3, TRUE, 4, 5)', 4);
  1452. this._test_simple_formula('=IFS(FALSE, 1, FALSE, 2, FALSE, 3, FALSE, 4, 5)', 5);
  1453. }
  1454. test_func_ln() {
  1455. this._test_simple_formula('=LN(2.718281828459045)', 1);
  1456. }
  1457. test_func_log() {
  1458. this._test_simple_formula('=LOG(1000, 10)', 3);
  1459. }
  1460. test_func_lower() {
  1461. this._test_simple_formula('=LOWER("MiXeD")', 'mixed');
  1462. }
  1463. test_func_max() {
  1464. this._test_simple_formula('=MAX(4, 8, 5, 2)', 8);
  1465. }
  1466. test_func_min() {
  1467. this._test_simple_formula('=MIN(4, 8, 5, 2)', 2);
  1468. }
  1469. test_func_mod() {
  1470. this._test_simple_formula('=MOD(37, 4)', 1);
  1471. }
  1472. test_func_not() {
  1473. this._test_simple_formula('=NOT(TRUE)', false);
  1474. this._test_simple_formula('=NOT(FALSE)', true);
  1475. }
  1476. test_func_or() {
  1477. this._test_simple_formula('=OR(FALSE, FALSE)', false);
  1478. this._test_simple_formula('=OR(FALSE, TRUE)', true);
  1479. this._test_simple_formula('=OR(TRUE, FALSE)', true);
  1480. this._test_simple_formula('=OR(TRUE, TRUE)', true);
  1481. this._test_simple_formula('=OR(FALSE, FALSE, FALSE, TRUE)', true);
  1482. }
  1483. test_func_power() {
  1484. this._test_simple_formula('=POWER(2, 3)', 8);
  1485. }
  1486. test_func_round() {
  1487. this._test_simple_formula('=ROUND(3.1)', 3);
  1488. this._test_simple_formula('=ROUND(3.5)', 4);
  1489. this._test_simple_formula('=ROUND(4)', 4);
  1490. this._test_simple_formula('=ROUND(-3.1)', -3);
  1491. this._test_simple_formula('=ROUND(-3.5)', -3);
  1492. this._test_simple_formula('=ROUND(-3.9)', -4);
  1493. }
  1494. test_func_sqrt() {
  1495. this._test_simple_formula('=SQRT(16)', 4);
  1496. }
  1497. test_func_substitute() {
  1498. this._test_simple_formula('=SUBSTITUTE("cat sat on the mat", "at", "ot")', 'cot sot on the mot');
  1499. }
  1500. test_func_sum() {
  1501. this._test_simple_formula('=SUM(1, 2, 3, 4, 5)', 15);
  1502. }
  1503. test_func_upper() {
  1504. this._test_simple_formula('=UPPER("mIxEd")', 'MIXED');
  1505. }
  1506. test_func_xor() {
  1507. this._test_simple_formula('=XOR(FALSE, FALSE)', false);
  1508. this._test_simple_formula('=XOR(FALSE, TRUE)', true);
  1509. this._test_simple_formula('=XOR(TRUE, FALSE)', true);
  1510. this._test_simple_formula('=XOR(TRUE, TRUE)', false);
  1511. this._test_simple_formula('=XOR(FALSE, FALSE, TRUE)', true);
  1512. this._test_simple_formula('=XOR(TRUE, FALSE, TRUE)', false);
  1513. }
  1514. test_format() {
  1515. this._test_simple_formula('=2.718281828459045 ; number 3', new CellValue('2.718', 2.718281828459045, 'number', 3));
  1516. this._test_simple_formula('=2.718281828459045 ; percent 2', new CellValue('271.83%', 2.718281828459045, 'percent', 2));
  1517. this._test_simple_formula('=2.718281828459045 ; currency 2', new CellValue('$2.72', 2.718281828459045, 'currency', 2));
  1518. }
  1519. }
  1520. </script>
  1521. </head>
  1522. <body>
  1523. <div id="results"></div>
  1524. </body>
  1525. </html>