Browse Source

More JS unit tests

main
Rocketsoup 1 year ago
parent
commit
f795640d63
2 changed files with 297 additions and 77 deletions
  1. 83
    17
      js/markdown.js
  2. 214
    60
      testjs.html

+ 83
- 17
js/markdown.js View File

@@ -113,6 +113,18 @@ class _MDUtils {
113 113
 	static escapeHTML(str) {
114 114
 		return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
115 115
 	}
116
+
117
+	/**
118
+	 * @param {String} email
119
+	 */
120
+	static escapeObfuscated(text) {
121
+		var html = '';
122
+		for (var p = 0; p < text.length; p++) {
123
+			const cp = text.codePointAt(p);
124
+			html += `&#${cp};`;
125
+		}
126
+		return html;
127
+	}
116 128
 }
117 129
 
118 130
 
@@ -219,6 +231,23 @@ class _MDHTMLSpan extends _MDSpan {
219 231
 	}
220 232
 }
221 233
 
234
+class _MDObfuscatedTextSpan extends _MDSpan {
235
+	/** @param {String} text */
236
+	text;
237
+
238
+	/**
239
+	 * @param {String} text
240
+	 */
241
+	constructor(text) {
242
+		super();
243
+		this.text = text;
244
+	}
245
+
246
+	toHTML(state) {
247
+		return _MDUtils.escapeObfuscated(this.text);
248
+	}
249
+}
250
+
222 251
 class _MDLinkSpan extends _MDSpan {
223 252
 	/** @var {String} */
224 253
 	link;
@@ -241,7 +270,13 @@ class _MDLinkSpan extends _MDSpan {
241 270
 	}
242 271
 
243 272
 	toHTML(state) {
244
-		var html = `<a href="${_MDUtils.escapeHTML(this.link)}"`;
273
+		var escapedLink;
274
+		if (this.link.startsWith('mailto:')) {
275
+			escapedLink = 'mailto:' + _MDUtils.escapeObfuscated(this.link.substring(7));
276
+		} else {
277
+			escapedLink = _MDUtils.escapeHTML(this.link);
278
+		}
279
+		var html = `<a href="${escapedLink}"`;
245 280
 		if (this.target) {
246 281
 			html += ` target="${_MDUtils.escapeHTML(this.target)}"`;
247 282
 		}
@@ -1263,8 +1298,8 @@ class Markdown {
1263 1298
 	static #urlRegex = /^\((\S+?)\)/i;  // 1=URL
1264 1299
 	static #emailWithTitleRegex = new RegExp("^\\(\\s*(" + this.#baseEmailRegex.source + ")\\s+\"(.*?)\"\\s*\\)", "i");  // 1=email, 2=title
1265 1300
 	static #emailRegex = new RegExp("^\\(\\s*(" + this.#baseEmailRegex.source + ")\\s*\\)", "i");  // 1=email
1266
-	static #simpleURLRegex = new RegExp("^<" + this.#baseURLRegex.source + ">", "i");  // 1=URL
1267
-	static #simpleEmailRegex = new RegExp("^<" + this.#baseEmailRegex.source + ">", "i");  // 1=email
1301
+	static #simpleURLRegex = new RegExp("^<(" + this.#baseURLRegex.source + ")>", "i");  // 1=URL
1302
+	static #simpleEmailRegex = new RegExp("^<(" + this.#baseEmailRegex.source + ")>", "i");  // 1=email
1268 1303
 
1269 1304
 	/**
1270 1305
 	 * Finds a `[label]` at the start of a string. Regex was too inefficient for this.
@@ -1367,36 +1402,36 @@ class Markdown {
1367 1402
 				endText();
1368 1403
 				tokens.push(new _MDToken(groups[0], _MDTokenType.Label, groups[1]));
1369 1404
 				p += groups[0].length - 1;
1370
-			} else if (groups = this.#urlWithTitleRegex.exec(remainder)) {
1371
-				// URL with title   (https://foo "Bar")
1372
-				endText();
1373
-				tokens.push(new _MDToken(groups[0], _MDTokenType.URL, groups[1], groups[2]));
1374
-				p += groups[0].length - 1;
1375 1405
 			} else if (groups = this.#emailWithTitleRegex.exec(remainder)) {
1376 1406
 				// Email address with title   (user@example.com  "Foo")
1377 1407
 				endText();
1378 1408
 				tokens.push(new _MDToken(groups[0], _MDTokenType.Email, groups[1], groups[2]));
1379 1409
 				p += groups[0].length - 1;
1380
-			} else if (groups = this.#urlRegex.exec(remainder)) {
1381
-				// URL   (https://example.com)
1410
+			} else if (groups = this.#urlWithTitleRegex.exec(remainder)) {
1411
+				// URL with title   (https://foo "Bar")
1382 1412
 				endText();
1383
-				tokens.push(new _MDToken(groups[0], _MDTokenType.URL, groups[1]));
1413
+				tokens.push(new _MDToken(groups[0], _MDTokenType.URL, groups[1], groups[2]));
1384 1414
 				p += groups[0].length - 1;
1385 1415
 			} else if (groups = this.#emailRegex.exec(remainder)) {
1386 1416
 				// Email   (user@example.com)
1387 1417
 				endText();
1388 1418
 				tokens.push(new _MDToken(groups[0], _MDTokenType.Email, groups[1]));
1389 1419
 				p += groups[0].length - 1;
1390
-			} else if (groups = this.#simpleURLRegex.exec(remainder)) {
1391
-				// Simple URL   <https://example.com>
1420
+			} else if (groups = this.#urlRegex.exec(remainder)) {
1421
+				// URL   (https://example.com)
1392 1422
 				endText();
1393
-				tokens.push(new _MDToken(groups[0], _MDTokenType.SimpleLink, groups[1]));
1423
+				tokens.push(new _MDToken(groups[0], _MDTokenType.URL, groups[1]));
1394 1424
 				p += groups[0].length - 1;
1395 1425
 			} else if (groups = this.#simpleEmailRegex.exec(remainder)) {
1396 1426
 				// Simple email   <user@example.com>
1397 1427
 				endText();
1398 1428
 				tokens.push(new _MDToken(groups[0], _MDTokenType.SimpleEmail, groups[1]));
1399 1429
 				p += groups[0].length - 1;
1430
+			} else if (groups = this.#simpleURLRegex.exec(remainder)) {
1431
+				// Simple URL   <https://example.com>
1432
+				endText();
1433
+				tokens.push(new _MDToken(groups[0], _MDTokenType.SimpleLink, groups[1]));
1434
+				p += groups[0].length - 1;
1400 1435
 			} else if (tag = this.#htmlTag(remainder)) {
1401 1436
 				endText();
1402 1437
 				tokens.push(new _MDToken(tag.fullTag, _MDTokenType.HTMLTag, tag.fullTag, null, tag));
@@ -1461,8 +1496,26 @@ class Markdown {
1461 1496
 		// First pass - contiguous constructs
1462 1497
 		do {
1463 1498
 			anyChanges = false;
1464
-			// ![alt](image.jpg)
1499
+			// <https://example.com>
1465 1500
 			if ((index = this.#firstTokenIndex(spans, [
1501
+				_MDTokenType.SimpleLink,
1502
+			])) !== null) {
1503
+				let url = spans[index].content;
1504
+				spans.splice(index, 1, new _MDLinkSpan(url, new _MDTextSpan(url)));
1505
+				anyChanges = true;
1506
+			}
1507
+
1508
+			// <user@example.com>
1509
+			else if ((index = this.#firstTokenIndex(spans, [
1510
+				_MDTokenType.SimpleEmail,
1511
+			])) !== null) {
1512
+				let email = spans[index].content;
1513
+				spans.splice(index, 1, new _MDLinkSpan(`mailto:${email}`, new _MDObfuscatedTextSpan(email)));
1514
+				anyChanges = true;
1515
+			}
1516
+
1517
+			// ![alt](image.jpg "title")
1518
+			else if ((index = this.#firstTokenIndex(spans, [
1466 1519
 				_MDTokenType.Bang,
1467 1520
 				_MDTokenType.Label,
1468 1521
 				_MDTokenType.URL,
@@ -1486,14 +1539,27 @@ class Markdown {
1486 1539
 				anyChanges = true;
1487 1540
 			}
1488 1541
 			
1489
-			// [text](link.html)
1542
+			// [text](link.html "title")
1490 1543
 			else if ((index = this.#firstTokenIndex(spans, [
1491 1544
 				_MDTokenType.Label,
1492 1545
 				_MDTokenType.URL,
1493 1546
 			])) !== null) {
1494 1547
 				let text = spans[index + 0].content;
1495 1548
 				let url = spans[index + 1].content;
1496
-				spans.splice(index, 2, new _MDLinkSpan(url, this.#readInline(state, text)));
1549
+				let title = spans[index + 1].extra;
1550
+				spans.splice(index, 2, new _MDLinkSpan(url, this.#readInline(state, text), title));
1551
+				anyChanges = true;
1552
+			}
1553
+
1554
+			// [text](user@example.com "title")
1555
+			else if ((index = this.#firstTokenIndex(spans, [
1556
+				_MDTokenType.Label,
1557
+				_MDTokenType.Email,
1558
+			])) !== null) {
1559
+				let text = spans[index + 0].content;
1560
+				let email = spans[index + 1].content;
1561
+				let title = spans[index + 1].extra;
1562
+				spans.splice(index, 2, new _MDLinkSpan(`mailto:${email}`, this.#readInline(state, text), title));
1497 1563
 				anyChanges = true;
1498 1564
 			}
1499 1565
 

+ 214
- 60
testjs.html View File

@@ -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:&#117;&#115;&#101;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;">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:&#117;&#115;&#101;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;" 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:&#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</p>';
438
+					let actual = normalizeWhitespace(Markdown.toHTML(markdown));
439
+					this.assertEqual(actual, expected);
440
+				}
441
+
442
+				test_image() {
443
+					let markdown = 'Lorem ![alt text](image.jpg) 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 ![](image.jpg) 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 ![alt text](image.jpg "image title") 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
 			}

Loading…
Cancel
Save