|
|
@@ -39,46 +39,12 @@
|
|
39
|
39
|
</style>
|
|
40
|
40
|
<script src="js/markdown.js"></script>
|
|
41
|
41
|
<script>
|
|
42
|
|
- // TODO: Either run tests on a worker or at least on a setTimeout so
|
|
43
|
|
- // UI can update and not block the thread for the whole duration.
|
|
44
|
42
|
function onLoad() {
|
|
45
|
43
|
let testClasses = [
|
|
46
|
|
- MarkdownTest,
|
|
|
44
|
+ InlineTests,
|
|
|
45
|
+ BlockTests,
|
|
47
|
46
|
];
|
|
48
|
|
- var tests = [];
|
|
49
|
|
- for (const testClass of testClasses) {
|
|
50
|
|
- const instance = new testClass();
|
|
51
|
|
- if (!(instance instanceof BaseTest)) {
|
|
52
|
|
- console.error(`${testClass.name} does not extend BaseTest`);
|
|
53
|
|
- continue;
|
|
54
|
|
- }
|
|
55
|
|
- const testCases = TestCaseRun.findTestCases(instance);
|
|
56
|
|
- tests.push(...testCases);
|
|
57
|
|
- var classHTML = '<div class="testclass">';
|
|
58
|
|
- classHTML += `<div class="testclassname">${testClass.name}</div>`;
|
|
59
|
|
- for (const testCase of testCases) {
|
|
60
|
|
- classHTML += testCase.toHTML();
|
|
61
|
|
- }
|
|
62
|
|
- classHTML += '</div>';
|
|
63
|
|
- document.getElementById('results').innerHTML += classHTML;
|
|
64
|
|
- }
|
|
65
|
|
- var testInterval = setInterval(function() {
|
|
66
|
|
- if (tests.length == 0) {
|
|
67
|
|
- clearInterval(testInterval);
|
|
68
|
|
- testInterval = null;
|
|
69
|
|
- return;
|
|
70
|
|
- }
|
|
71
|
|
- let testCase = tests[0];
|
|
72
|
|
- if (testCase.result == ResultType.testing) {
|
|
73
|
|
- tests.splice(0, 1);
|
|
74
|
|
- testCase.updateHTML();
|
|
75
|
|
- testCase.run();
|
|
76
|
|
- testCase.updateHTML();
|
|
77
|
|
- } else {
|
|
78
|
|
- testCase.result = ResultType.testing;
|
|
79
|
|
- testCase.updateHTML();
|
|
80
|
|
- }
|
|
81
|
|
- }, 1);
|
|
|
47
|
+ TestClassRunner.runAll(testClasses);
|
|
82
|
48
|
}
|
|
83
|
49
|
|
|
84
|
50
|
/**
|
|
|
@@ -116,7 +82,7 @@
|
|
116
|
82
|
class BaseTest {
|
|
117
|
83
|
setUp() {}
|
|
118
|
84
|
tearDown() {}
|
|
119
|
|
- /** @var {TestCaseRun|null} */
|
|
|
85
|
+ /** @var {TestCaseRunner|null} */
|
|
120
|
86
|
currentRunner = null;
|
|
121
|
87
|
fail(failMessage=null) {
|
|
122
|
88
|
throw new FailureError(failMessage || 'failed');
|
|
|
@@ -149,13 +115,19 @@
|
|
149
|
115
|
}
|
|
150
|
116
|
}
|
|
151
|
117
|
|
|
152
|
|
- class TestCaseRun {
|
|
|
118
|
+ /**
|
|
|
119
|
+ * Manages the running and results of a single test method on a test
|
|
|
120
|
+ * class.
|
|
|
121
|
+ */
|
|
|
122
|
+ class TestCaseRunner {
|
|
|
123
|
+ /** @var {number} */
|
|
153
|
124
|
static #nextUniqueId = 1;
|
|
|
125
|
+ /** @var {number} */
|
|
|
126
|
+ uniqueId = 0;
|
|
154
|
127
|
/** @var {BaseTest} */
|
|
155
|
128
|
#objectUnderTest;
|
|
156
|
129
|
/** @var {method} */
|
|
157
|
130
|
#method;
|
|
158
|
|
- uniqueId = 0;
|
|
159
|
131
|
/** @var {ResultType} */
|
|
160
|
132
|
result = ResultType.untested;
|
|
161
|
133
|
/** @var {String|null} */
|
|
|
@@ -172,7 +144,7 @@
|
|
172
|
144
|
constructor(objectUnderTest, method) {
|
|
173
|
145
|
this.#objectUnderTest = objectUnderTest;
|
|
174
|
146
|
this.#method = method;
|
|
175
|
|
- this.uniqueId = TestCaseRun.#nextUniqueId++;
|
|
|
147
|
+ this.uniqueId = TestCaseRunner.#nextUniqueId++;
|
|
176
|
148
|
}
|
|
177
|
149
|
run() {
|
|
178
|
150
|
try {
|
|
|
@@ -205,6 +177,7 @@
|
|
205
|
177
|
}
|
|
206
|
178
|
}
|
|
207
|
179
|
}
|
|
|
180
|
+ get #cssId() { return `testcase${this.uniqueId}`; }
|
|
208
|
181
|
#isExpectedError(e) {
|
|
209
|
182
|
if (this.expectedError === null) return false;
|
|
210
|
183
|
if (this.expectedError === true) return true;
|
|
|
@@ -212,7 +185,7 @@
|
|
212
|
185
|
return false;
|
|
213
|
186
|
}
|
|
214
|
187
|
toHTML() {
|
|
215
|
|
- var html = `<div class="testcase" id="testcase${this.uniqueId}">`;
|
|
|
188
|
+ var html = `<div class="testcase" id="${this.#cssId}">`;
|
|
216
|
189
|
html += `<div class="testcasename"><span class="testcaseclass">${this.className}</span>.<span class="testcasemethod">${this.methodName}</span></div>`;
|
|
217
|
190
|
switch (this.result) {
|
|
218
|
191
|
case ResultType.untested:
|
|
|
@@ -236,8 +209,12 @@
|
|
236
|
209
|
html += `</div>`;
|
|
237
|
210
|
return html;
|
|
238
|
211
|
}
|
|
|
212
|
+ /**
|
|
|
213
|
+ * Updates the HTML node in-place with the current status, or
|
|
|
214
|
+ * adds it if it does not exist yet.
|
|
|
215
|
+ */
|
|
239
|
216
|
updateHTML() {
|
|
240
|
|
- let existing = document.getElementById(`testcase${this.uniqueId}`);
|
|
|
217
|
+ let existing = document.getElementById(this.#cssId);
|
|
241
|
218
|
if (existing) {
|
|
242
|
219
|
existing.outerHTML = this.toHTML();
|
|
243
|
220
|
} else {
|
|
|
@@ -246,50 +223,120 @@
|
|
246
|
223
|
}
|
|
247
|
224
|
|
|
248
|
225
|
/**
|
|
249
|
|
- * @param {object} instance
|
|
250
|
|
- * @returns {TestCaseRun[]}
|
|
|
226
|
+ * @param {object} objectUnderTest
|
|
|
227
|
+ * @returns {TestCaseRunner[]}
|
|
251
|
228
|
*/
|
|
252
|
|
- static findTestCases(instance) {
|
|
253
|
|
- if (!(instance instanceof BaseTest)) return [];
|
|
|
229
|
+ static findTestCases(objectUnderTest) {
|
|
|
230
|
+ if (!(objectUnderTest instanceof BaseTest)) return [];
|
|
254
|
231
|
var members = [];
|
|
255
|
|
- var obj = instance;
|
|
|
232
|
+ var obj = objectUnderTest;
|
|
256
|
233
|
do {
|
|
257
|
234
|
members.push(...Object.getOwnPropertyNames(obj));
|
|
258
|
235
|
} while (obj = Object.getPrototypeOf(obj));
|
|
259
|
236
|
return members.sort().filter((e, i, arr) => {
|
|
260
|
|
- if (e != arr[i + 1] && typeof instance[e] == 'function' && e.startsWith('test')) return true;
|
|
|
237
|
+ if (e != arr[i + 1] && typeof objectUnderTest[e] == 'function' && e.startsWith('test')) return true;
|
|
261
|
238
|
}).map((name) => {
|
|
262
|
|
- return new TestCaseRun(instance, instance[name]);
|
|
|
239
|
+ return new TestCaseRunner(objectUnderTest, objectUnderTest[name]);
|
|
263
|
240
|
});
|
|
264
|
241
|
}
|
|
265
|
242
|
}
|
|
266
|
243
|
|
|
267
|
|
- class MarkdownTest extends BaseTest {
|
|
|
244
|
+ class TestClassRunner {
|
|
|
245
|
+ static #nextUniqueId = 1;
|
|
|
246
|
+
|
|
|
247
|
+ #uniqueId = 0;
|
|
|
248
|
+ #theClass;
|
|
|
249
|
+ #instance;
|
|
|
250
|
+ #testCases;
|
|
|
251
|
+
|
|
|
252
|
+ get testCases() { return this.#testCases; }
|
|
|
253
|
+
|
|
|
254
|
+ constructor(theClass) {
|
|
|
255
|
+ this.#theClass = theClass;
|
|
|
256
|
+ this.#instance = new theClass();
|
|
|
257
|
+ this.#testCases = TestCaseRunner.findTestCases(this.#instance);
|
|
|
258
|
+ this.#uniqueId = TestClassRunner.#nextUniqueId++;
|
|
|
259
|
+ }
|
|
|
260
|
+ get #cssId() { return `testclass${this.#uniqueId}`; }
|
|
|
261
|
+ toHTML() {
|
|
|
262
|
+ var html = '';
|
|
|
263
|
+ html += `<div class="testclass" id="${this.#cssId}">`;
|
|
|
264
|
+ html += `<div class="testclassname">${this.#theClass.name}</div>`;
|
|
|
265
|
+ for (const testCase of this.#testCases) {
|
|
|
266
|
+ html += testCase.toHTML();
|
|
|
267
|
+ }
|
|
|
268
|
+ html += '</div>';
|
|
|
269
|
+ return html;
|
|
|
270
|
+ }
|
|
|
271
|
+ updateHTML() {
|
|
|
272
|
+ var existing = document.getElementById(this.#cssId);
|
|
|
273
|
+ if (!existing) {
|
|
|
274
|
+ document.getElementById('results').innerHTML += this.toHTML();
|
|
|
275
|
+ }
|
|
|
276
|
+ }
|
|
|
277
|
+
|
|
|
278
|
+ static runAll(testClasses) {
|
|
|
279
|
+ var tests = []; // tuples of TestClassRunner and TestCaseRunner
|
|
|
280
|
+ for (const testClass of testClasses) {
|
|
|
281
|
+ const classRunner = new TestClassRunner(testClass);
|
|
|
282
|
+ classRunner.updateHTML();
|
|
|
283
|
+ tests.push(...classRunner.testCases.map(function(test) { return [ classRunner, test ] }));
|
|
|
284
|
+ }
|
|
|
285
|
+ var testInterval = setInterval(function() {
|
|
|
286
|
+ if (tests.length == 0) {
|
|
|
287
|
+ clearInterval(testInterval);
|
|
|
288
|
+ testInterval = null;
|
|
|
289
|
+ return;
|
|
|
290
|
+ }
|
|
|
291
|
+ var classRunner;
|
|
|
292
|
+ var testCase;
|
|
|
293
|
+ [ classRunner, testCase ] = tests[0];
|
|
|
294
|
+ if (testCase.result == ResultType.untested) {
|
|
|
295
|
+ testCase.result = ResultType.testing;
|
|
|
296
|
+ testCase.updateHTML();
|
|
|
297
|
+ classRunner.updateHTML();
|
|
|
298
|
+ } else if (testCase.result == ResultType.testing) {
|
|
|
299
|
+ tests.splice(0, 1);
|
|
|
300
|
+ testCase.run();
|
|
|
301
|
+ testCase.updateHTML();
|
|
|
302
|
+ classRunner.updateHTML();
|
|
|
303
|
+ }
|
|
|
304
|
+ }, 1);
|
|
|
305
|
+ }
|
|
|
306
|
+ }
|
|
|
307
|
+
|
|
|
308
|
+ // ---------------------------------------------------------------
|
|
|
309
|
+
|
|
|
310
|
+ function normalizeWhitespace(str) {
|
|
|
311
|
+ return str.replace(/\s+/g, ' ').replace(/(?:^\s+|\s+$)/g, '');
|
|
|
312
|
+ }
|
|
|
313
|
+
|
|
|
314
|
+ class InlineTests extends BaseTest {
|
|
268
|
315
|
test_simpleSingleParagraph() {
|
|
269
|
316
|
let markdown = 'Lorem ipsum';
|
|
270
|
317
|
let expected = '<p>Lorem ipsum</p>';
|
|
271
|
|
- let actual = Markdown.toHTML(markdown).trim();
|
|
|
318
|
+ let actual = normalizeWhitespace(Markdown.toHTML(markdown));
|
|
272
|
319
|
this.assertEqual(actual, expected);
|
|
273
|
320
|
}
|
|
274
|
321
|
|
|
275
|
322
|
test_strong() {
|
|
276
|
323
|
let markdown = 'Lorem **ipsum** dolor **sit**';
|
|
277
|
324
|
let expected = '<p>Lorem <strong>ipsum</strong> dolor <strong>sit</strong></p>';
|
|
278
|
|
- let actual = Markdown.toHTML(markdown).trim();
|
|
|
325
|
+ let actual = normalizeWhitespace(Markdown.toHTML(markdown));
|
|
279
|
326
|
this.assertEqual(actual, expected);
|
|
280
|
327
|
}
|
|
281
|
328
|
|
|
282
|
329
|
test_emphasis() {
|
|
283
|
330
|
let markdown = 'Lorem _ipsum_ dolor _sit_';
|
|
284
|
331
|
let expected = '<p>Lorem <em>ipsum</em> dolor <em>sit</em></p>';
|
|
285
|
|
- let actual = Markdown.toHTML(markdown).trim();
|
|
|
332
|
+ let actual = normalizeWhitespace(Markdown.toHTML(markdown));
|
|
286
|
333
|
this.assertEqual(actual, expected);
|
|
287
|
334
|
}
|
|
288
|
335
|
|
|
289
|
336
|
test_strongEmphasis_easy() {
|
|
290
|
337
|
let markdown = 'Lorem **ipsum *dolor* sit** amet';
|
|
291
|
338
|
let expected = '<p>Lorem <strong>ipsum <em>dolor</em> sit</strong> amet</p>';
|
|
292
|
|
- let actual = Markdown.toHTML(markdown).trim();
|
|
|
339
|
+ let actual = normalizeWhitespace(Markdown.toHTML(markdown));
|
|
293
|
340
|
this.assertEqual(actual, expected);
|
|
294
|
341
|
}
|
|
295
|
342
|
|
|
|
@@ -297,35 +344,142 @@
|
|
297
|
344
|
let markdown = 'Lorem ***ipsum*** dolor';
|
|
298
|
345
|
let expected1 = '<p>Lorem <strong><em>ipsum</em></strong> dolor</p>';
|
|
299
|
346
|
let expected2 = '<p>Lorem <em><strong>ipsum</strong></em> dolor</p>';
|
|
300
|
|
- let actual = Markdown.toHTML(markdown).trim();
|
|
|
347
|
+ let actual = normalizeWhitespace(Markdown.toHTML(markdown));
|
|
301
|
348
|
this.assertTrue(actual == expected1 || actual == expected2);
|
|
302
|
349
|
}
|
|
303
|
350
|
|
|
304
|
351
|
test_strongEmphasis_hard1() {
|
|
305
|
352
|
let markdown = 'Lorem ***ipsum* dolor** sit';
|
|
306
|
353
|
let expected = '<p>Lorem <strong><em>ipsum</em> dolor</strong> sit</p>';
|
|
307
|
|
- let actual = Markdown.toHTML(markdown).trim();
|
|
|
354
|
+ let actual = normalizeWhitespace(Markdown.toHTML(markdown));
|
|
308
|
355
|
this.assertEqual(actual, expected);
|
|
309
|
356
|
}
|
|
310
|
357
|
|
|
311
|
358
|
test_strongEmphasis_hard2() {
|
|
312
|
359
|
let markdown = 'Lorem ***ipsum** dolor* sit';
|
|
313
|
360
|
let expected = '<p>Lorem <em><strong>ipsum</strong> dolor</em> sit</p>';
|
|
314
|
|
- let actual = Markdown.toHTML(markdown).trim();
|
|
|
361
|
+ let actual = normalizeWhitespace(Markdown.toHTML(markdown));
|
|
315
|
362
|
this.assertEqual(actual, expected);
|
|
316
|
363
|
}
|
|
317
|
364
|
|
|
318
|
365
|
test_inlineCode() {
|
|
319
|
366
|
let markdown = 'Lorem `ipsum` dolor';
|
|
320
|
367
|
let expected = '<p>Lorem <code>ipsum</code> dolor</p>';
|
|
321
|
|
- let actual = Markdown.toHTML(markdown).trim();
|
|
|
368
|
+ let actual = normalizeWhitespace(Markdown.toHTML(markdown));
|
|
322
|
369
|
this.assertEqual(actual, expected);
|
|
323
|
370
|
}
|
|
324
|
371
|
|
|
325
|
372
|
test_inlineCode_withInnerBacktick() {
|
|
326
|
373
|
let markdown = 'Lorem ``ip`su`m`` dolor';
|
|
327
|
374
|
let expected = '<p>Lorem <code>ip`su`m</code> dolor</p>';
|
|
328
|
|
- let actual = Markdown.toHTML(markdown).trim();
|
|
|
375
|
+ let actual = normalizeWhitespace(Markdown.toHTML(markdown));
|
|
|
376
|
+ this.assertEqual(actual, expected);
|
|
|
377
|
+ }
|
|
|
378
|
+
|
|
|
379
|
+ test_strikethrough_single() {
|
|
|
380
|
+ let markdown = 'Lorem ~ipsum~ dolor';
|
|
|
381
|
+ let expected = '<p>Lorem <strike>ipsum</strike> dolor</p>';
|
|
|
382
|
+ let actual = normalizeWhitespace(Markdown.toHTML(markdown));
|
|
|
383
|
+ this.assertEqual(actual, expected);
|
|
|
384
|
+ }
|
|
|
385
|
+
|
|
|
386
|
+ test_strikethrough_double() {
|
|
|
387
|
+ let markdown = 'Lorem ~~ipsum~~ dolor';
|
|
|
388
|
+ let expected = '<p>Lorem <strike>ipsum</strike> dolor</p>';
|
|
|
389
|
+ let actual = normalizeWhitespace(Markdown.toHTML(markdown));
|
|
|
390
|
+ this.assertEqual(actual, expected);
|
|
|
391
|
+ }
|
|
|
392
|
+
|
|
|
393
|
+ test_link_fullyQualified() {
|
|
|
394
|
+ let markdown = 'Lorem [ipsum](https://example.com/path/page.html) dolor';
|
|
|
395
|
+ let expected = '<p>Lorem <a href="https://example.com/path/page.html">ipsum</a> dolor</p>';
|
|
|
396
|
+ let actual = normalizeWhitespace(Markdown.toHTML(markdown));
|
|
|
397
|
+ this.assertEqual(actual, expected);
|
|
|
398
|
+ }
|
|
|
399
|
+
|
|
|
400
|
+ test_link_relative() {
|
|
|
401
|
+ let markdown = 'Lorem [ipsum](page.html) dolor';
|
|
|
402
|
+ let expected = '<p>Lorem <a href="page.html">ipsum</a> dolor</p>';
|
|
|
403
|
+ let actual = normalizeWhitespace(Markdown.toHTML(markdown));
|
|
|
404
|
+ this.assertEqual(actual, expected);
|
|
|
405
|
+ }
|
|
|
406
|
+
|
|
|
407
|
+ test_link_title() {
|
|
|
408
|
+ let markdown = 'Lorem [ipsum](page.html "link title") dolor';
|
|
|
409
|
+ let expected = '<p>Lorem <a href="page.html" title="link title">ipsum</a> dolor</p>';
|
|
|
410
|
+ let actual = normalizeWhitespace(Markdown.toHTML(markdown));
|
|
|
411
|
+ this.assertEqual(actual, expected);
|
|
|
412
|
+ }
|
|
|
413
|
+
|
|
|
414
|
+ test_link_literal() {
|
|
|
415
|
+ let markdown = 'Lorem <https://example.com> dolor';
|
|
|
416
|
+ let expected = '<p>Lorem <a href="https://example.com">https://example.com</a> dolor</p>';
|
|
|
417
|
+ let actual = normalizeWhitespace(Markdown.toHTML(markdown));
|
|
|
418
|
+ this.assertEqual(actual, expected);
|
|
|
419
|
+ }
|
|
|
420
|
+
|
|
|
421
|
+ test_link_email() {
|
|
|
422
|
+ let markdown = 'Lorem [ipsum](user@example.com) dolor';
|
|
|
423
|
+ let expected = '<p>Lorem <a href="mailto:user@example.com">ipsum</a> dolor</p>';
|
|
|
424
|
+ let actual = normalizeWhitespace(Markdown.toHTML(markdown));
|
|
|
425
|
+ this.assertEqual(actual, expected);
|
|
|
426
|
+ }
|
|
|
427
|
+
|
|
|
428
|
+ test_link_email_withTitle() {
|
|
|
429
|
+ let markdown = 'Lorem [ipsum](user@example.com "title") dolor';
|
|
|
430
|
+ let expected = '<p>Lorem <a href="mailto:user@example.com" title="title">ipsum</a> dolor</p>';
|
|
|
431
|
+ let actual = normalizeWhitespace(Markdown.toHTML(markdown));
|
|
|
432
|
+ this.assertEqual(actual, expected);
|
|
|
433
|
+ }
|
|
|
434
|
+
|
|
|
435
|
+ test_link_literalEmail() {
|
|
|
436
|
+ let markdown = 'Lorem <user@example.com> dolor';
|
|
|
437
|
+ let expected = '<p>Lorem <a href="mailto:user@example.com">user@example.com</a> dolor</p>';
|
|
|
438
|
+ let actual = normalizeWhitespace(Markdown.toHTML(markdown));
|
|
|
439
|
+ this.assertEqual(actual, expected);
|
|
|
440
|
+ }
|
|
|
441
|
+
|
|
|
442
|
+ test_image() {
|
|
|
443
|
+ let markdown = 'Lorem  dolor';
|
|
|
444
|
+ let expected = '<p>Lorem <img src="image.jpg" alt="alt text"> dolor</p>';
|
|
|
445
|
+ let actual = normalizeWhitespace(Markdown.toHTML(markdown));
|
|
|
446
|
+ this.assertEqual(actual, expected);
|
|
|
447
|
+ }
|
|
|
448
|
+
|
|
|
449
|
+ test_image_noAlt() {
|
|
|
450
|
+ let markdown = 'Lorem  dolor';
|
|
|
451
|
+ let expected = '<p>Lorem <img src="image.jpg"> dolor</p>';
|
|
|
452
|
+ let actual = normalizeWhitespace(Markdown.toHTML(markdown));
|
|
|
453
|
+ this.assertEqual(actual, expected);
|
|
|
454
|
+ }
|
|
|
455
|
+
|
|
|
456
|
+ test_image_withTitle() {
|
|
|
457
|
+ let markdown = 'Lorem  dolor';
|
|
|
458
|
+ let expected = '<p>Lorem <img src="image.jpg" alt="alt text" title="image title"> dolor</p>';
|
|
|
459
|
+ let actual = normalizeWhitespace(Markdown.toHTML(markdown));
|
|
|
460
|
+ this.assertEqual(actual, expected);
|
|
|
461
|
+ }
|
|
|
462
|
+ }
|
|
|
463
|
+
|
|
|
464
|
+ class BlockTests extends BaseTest {
|
|
|
465
|
+ test_paragraphs() {
|
|
|
466
|
+ let markdown = "Lorem ipsum\n\nDolor sit amet";
|
|
|
467
|
+ let expected = "<p>Lorem ipsum</p> <p>Dolor sit amet</p>";
|
|
|
468
|
+ let actual = normalizeWhitespace(Markdown.toHTML(markdown));
|
|
|
469
|
+ this.assertEqual(actual, expected);
|
|
|
470
|
+ }
|
|
|
471
|
+
|
|
|
472
|
+ test_paragraph_lineGrouping() {
|
|
|
473
|
+ let markdown = "Lorem ipsum\ndolor sit amet";
|
|
|
474
|
+ let expected = "<p>Lorem ipsum dolor sit amet</p>";
|
|
|
475
|
+ let actual = normalizeWhitespace(Markdown.toHTML(markdown));
|
|
|
476
|
+ this.assertEqual(actual, expected);
|
|
|
477
|
+ }
|
|
|
478
|
+
|
|
|
479
|
+ test_unorderedList() {
|
|
|
480
|
+ let markdown = "* Lorem\n* Ipsum\n* Dolor";
|
|
|
481
|
+ let expected = '<ul> <li>Lorem</li> <li>Ipsum</li> <li>Dolor</li> </ul>';
|
|
|
482
|
+ let actual = normalizeWhitespace(Markdown.toHTML(markdown));
|
|
329
|
483
|
this.assertEqual(actual, expected);
|
|
330
|
484
|
}
|
|
331
|
485
|
}
|