Parcourir la source

More JS unit tests

main
Rocketsoup il y a 1 an
Parent
révision
f795640d63
2 fichiers modifiés avec 297 ajouts et 77 suppressions
  1. 83
    17
      js/markdown.js
  2. 214
    60
      testjs.html

+ 83
- 17
js/markdown.js Voir le fichier

113
 	static escapeHTML(str) {
113
 	static escapeHTML(str) {
114
 		return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
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
 	}
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
 class _MDLinkSpan extends _MDSpan {
251
 class _MDLinkSpan extends _MDSpan {
223
 	/** @var {String} */
252
 	/** @var {String} */
224
 	link;
253
 	link;
241
 	}
270
 	}
242
 
271
 
243
 	toHTML(state) {
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
 		if (this.target) {
280
 		if (this.target) {
246
 			html += ` target="${_MDUtils.escapeHTML(this.target)}"`;
281
 			html += ` target="${_MDUtils.escapeHTML(this.target)}"`;
247
 		}
282
 		}
1263
 	static #urlRegex = /^\((\S+?)\)/i;  // 1=URL
1298
 	static #urlRegex = /^\((\S+?)\)/i;  // 1=URL
1264
 	static #emailWithTitleRegex = new RegExp("^\\(\\s*(" + this.#baseEmailRegex.source + ")\\s+\"(.*?)\"\\s*\\)", "i");  // 1=email, 2=title
1299
 	static #emailWithTitleRegex = new RegExp("^\\(\\s*(" + this.#baseEmailRegex.source + ")\\s+\"(.*?)\"\\s*\\)", "i");  // 1=email, 2=title
1265
 	static #emailRegex = new RegExp("^\\(\\s*(" + this.#baseEmailRegex.source + ")\\s*\\)", "i");  // 1=email
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
 	 * Finds a `[label]` at the start of a string. Regex was too inefficient for this.
1305
 	 * Finds a `[label]` at the start of a string. Regex was too inefficient for this.
1367
 				endText();
1402
 				endText();
1368
 				tokens.push(new _MDToken(groups[0], _MDTokenType.Label, groups[1]));
1403
 				tokens.push(new _MDToken(groups[0], _MDTokenType.Label, groups[1]));
1369
 				p += groups[0].length - 1;
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
 			} else if (groups = this.#emailWithTitleRegex.exec(remainder)) {
1405
 			} else if (groups = this.#emailWithTitleRegex.exec(remainder)) {
1376
 				// Email address with title   (user@example.com  "Foo")
1406
 				// Email address with title   (user@example.com  "Foo")
1377
 				endText();
1407
 				endText();
1378
 				tokens.push(new _MDToken(groups[0], _MDTokenType.Email, groups[1], groups[2]));
1408
 				tokens.push(new _MDToken(groups[0], _MDTokenType.Email, groups[1], groups[2]));
1379
 				p += groups[0].length - 1;
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
 				endText();
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
 				p += groups[0].length - 1;
1414
 				p += groups[0].length - 1;
1385
 			} else if (groups = this.#emailRegex.exec(remainder)) {
1415
 			} else if (groups = this.#emailRegex.exec(remainder)) {
1386
 				// Email   (user@example.com)
1416
 				// Email   (user@example.com)
1387
 				endText();
1417
 				endText();
1388
 				tokens.push(new _MDToken(groups[0], _MDTokenType.Email, groups[1]));
1418
 				tokens.push(new _MDToken(groups[0], _MDTokenType.Email, groups[1]));
1389
 				p += groups[0].length - 1;
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
 				endText();
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
 				p += groups[0].length - 1;
1424
 				p += groups[0].length - 1;
1395
 			} else if (groups = this.#simpleEmailRegex.exec(remainder)) {
1425
 			} else if (groups = this.#simpleEmailRegex.exec(remainder)) {
1396
 				// Simple email   <user@example.com>
1426
 				// Simple email   <user@example.com>
1397
 				endText();
1427
 				endText();
1398
 				tokens.push(new _MDToken(groups[0], _MDTokenType.SimpleEmail, groups[1]));
1428
 				tokens.push(new _MDToken(groups[0], _MDTokenType.SimpleEmail, groups[1]));
1399
 				p += groups[0].length - 1;
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
 			} else if (tag = this.#htmlTag(remainder)) {
1435
 			} else if (tag = this.#htmlTag(remainder)) {
1401
 				endText();
1436
 				endText();
1402
 				tokens.push(new _MDToken(tag.fullTag, _MDTokenType.HTMLTag, tag.fullTag, null, tag));
1437
 				tokens.push(new _MDToken(tag.fullTag, _MDTokenType.HTMLTag, tag.fullTag, null, tag));
1461
 		// First pass - contiguous constructs
1496
 		// First pass - contiguous constructs
1462
 		do {
1497
 		do {
1463
 			anyChanges = false;
1498
 			anyChanges = false;
1464
-			// ![alt](image.jpg)
1499
+			// <https://example.com>
1465
 			if ((index = this.#firstTokenIndex(spans, [
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
 				_MDTokenType.Bang,
1519
 				_MDTokenType.Bang,
1467
 				_MDTokenType.Label,
1520
 				_MDTokenType.Label,
1468
 				_MDTokenType.URL,
1521
 				_MDTokenType.URL,
1486
 				anyChanges = true;
1539
 				anyChanges = true;
1487
 			}
1540
 			}
1488
 			
1541
 			
1489
-			// [text](link.html)
1542
+			// [text](link.html "title")
1490
 			else if ((index = this.#firstTokenIndex(spans, [
1543
 			else if ((index = this.#firstTokenIndex(spans, [
1491
 				_MDTokenType.Label,
1544
 				_MDTokenType.Label,
1492
 				_MDTokenType.URL,
1545
 				_MDTokenType.URL,
1493
 			])) !== null) {
1546
 			])) !== null) {
1494
 				let text = spans[index + 0].content;
1547
 				let text = spans[index + 0].content;
1495
 				let url = spans[index + 1].content;
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
 				anyChanges = true;
1563
 				anyChanges = true;
1498
 			}
1564
 			}
1499
 
1565
 

+ 214
- 60
testjs.html Voir le fichier

39
 		</style>
39
 		</style>
40
 		<script src="js/markdown.js"></script>
40
 		<script src="js/markdown.js"></script>
41
 		<script>
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
 			function onLoad() {
42
 			function onLoad() {
45
 				let testClasses = [
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
 			class BaseTest {
82
 			class BaseTest {
117
 				setUp() {}
83
 				setUp() {}
118
 				tearDown() {}
84
 				tearDown() {}
119
-				/** @var {TestCaseRun|null} */
85
+				/** @var {TestCaseRunner|null} */
120
 				currentRunner = null;
86
 				currentRunner = null;
121
 				fail(failMessage=null) {
87
 				fail(failMessage=null) {
122
 					throw new FailureError(failMessage || 'failed');
88
 					throw new FailureError(failMessage || 'failed');
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
 				static #nextUniqueId = 1;
124
 				static #nextUniqueId = 1;
125
+				/** @var {number} */
126
+				uniqueId = 0;
154
 				/** @var {BaseTest} */
127
 				/** @var {BaseTest} */
155
 				#objectUnderTest;
128
 				#objectUnderTest;
156
 				/** @var {method} */
129
 				/** @var {method} */
157
 				#method;
130
 				#method;
158
-				uniqueId = 0;
159
 				/** @var {ResultType} */
131
 				/** @var {ResultType} */
160
 				result = ResultType.untested;
132
 				result = ResultType.untested;
161
 				/** @var {String|null} */
133
 				/** @var {String|null} */
172
 				constructor(objectUnderTest, method) {
144
 				constructor(objectUnderTest, method) {
173
 					this.#objectUnderTest = objectUnderTest;
145
 					this.#objectUnderTest = objectUnderTest;
174
 					this.#method = method;
146
 					this.#method = method;
175
-					this.uniqueId = TestCaseRun.#nextUniqueId++;
147
+					this.uniqueId = TestCaseRunner.#nextUniqueId++;
176
 				}
148
 				}
177
 				run() {
149
 				run() {
178
 					try {
150
 					try {
205
 						}
177
 						}
206
 					}
178
 					}
207
 				}
179
 				}
180
+				get #cssId() { return `testcase${this.uniqueId}`; }
208
 				#isExpectedError(e) {
181
 				#isExpectedError(e) {
209
 					if (this.expectedError === null) return false;
182
 					if (this.expectedError === null) return false;
210
 					if (this.expectedError === true) return true;
183
 					if (this.expectedError === true) return true;
212
 					return false;
185
 					return false;
213
 				}
186
 				}
214
 				toHTML() {
187
 				toHTML() {
215
-					var html = `<div class="testcase" id="testcase${this.uniqueId}">`;
188
+					var html = `<div class="testcase" id="${this.#cssId}">`;
216
 					html += `<div class="testcasename"><span class="testcaseclass">${this.className}</span>.<span class="testcasemethod">${this.methodName}</span></div>`;
189
 					html += `<div class="testcasename"><span class="testcaseclass">${this.className}</span>.<span class="testcasemethod">${this.methodName}</span></div>`;
217
 					switch (this.result) {
190
 					switch (this.result) {
218
 						case ResultType.untested:
191
 						case ResultType.untested:
236
 					html += `</div>`;
209
 					html += `</div>`;
237
 					return html;
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
 				updateHTML() {
216
 				updateHTML() {
240
-					let existing = document.getElementById(`testcase${this.uniqueId}`);
217
+					let existing = document.getElementById(this.#cssId);
241
 					if (existing) {
218
 					if (existing) {
242
 						existing.outerHTML = this.toHTML();
219
 						existing.outerHTML = this.toHTML();
243
 					} else {
220
 					} else {
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
 					var members = [];
231
 					var members = [];
255
-					var obj = instance;
232
+					var obj = objectUnderTest;
256
 					do {
233
 					do {
257
 						members.push(...Object.getOwnPropertyNames(obj));
234
 						members.push(...Object.getOwnPropertyNames(obj));
258
 					} while (obj = Object.getPrototypeOf(obj));
235
 					} while (obj = Object.getPrototypeOf(obj));
259
 					return members.sort().filter((e, i, arr) => {
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
 					}).map((name) => {
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
 				test_simpleSingleParagraph() {
315
 				test_simpleSingleParagraph() {
269
 					let markdown = 'Lorem ipsum';
316
 					let markdown = 'Lorem ipsum';
270
 					let expected = '<p>Lorem ipsum</p>';
317
 					let expected = '<p>Lorem ipsum</p>';
271
-					let actual = Markdown.toHTML(markdown).trim();
318
+					let actual = normalizeWhitespace(Markdown.toHTML(markdown));
272
 					this.assertEqual(actual, expected);
319
 					this.assertEqual(actual, expected);
273
 				}
320
 				}
274
 
321
 
275
 				test_strong() {
322
 				test_strong() {
276
 					let markdown = 'Lorem **ipsum** dolor **sit**';
323
 					let markdown = 'Lorem **ipsum** dolor **sit**';
277
 					let expected = '<p>Lorem <strong>ipsum</strong> dolor <strong>sit</strong></p>';
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
 					this.assertEqual(actual, expected);
326
 					this.assertEqual(actual, expected);
280
 				}
327
 				}
281
 
328
 
282
 				test_emphasis() {
329
 				test_emphasis() {
283
 					let markdown = 'Lorem _ipsum_ dolor _sit_';
330
 					let markdown = 'Lorem _ipsum_ dolor _sit_';
284
 					let expected = '<p>Lorem <em>ipsum</em> dolor <em>sit</em></p>';
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
 					this.assertEqual(actual, expected);
333
 					this.assertEqual(actual, expected);
287
 				}
334
 				}
288
 
335
 
289
 				test_strongEmphasis_easy() {
336
 				test_strongEmphasis_easy() {
290
 					let markdown = 'Lorem **ipsum *dolor* sit** amet';
337
 					let markdown = 'Lorem **ipsum *dolor* sit** amet';
291
 					let expected = '<p>Lorem <strong>ipsum <em>dolor</em> sit</strong> amet</p>';
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
 					this.assertEqual(actual, expected);
340
 					this.assertEqual(actual, expected);
294
 				}
341
 				}
295
 
342
 
297
 					let markdown = 'Lorem ***ipsum*** dolor';
344
 					let markdown = 'Lorem ***ipsum*** dolor';
298
 					let expected1 = '<p>Lorem <strong><em>ipsum</em></strong> dolor</p>';
345
 					let expected1 = '<p>Lorem <strong><em>ipsum</em></strong> dolor</p>';
299
 					let expected2 = '<p>Lorem <em><strong>ipsum</strong></em> dolor</p>';
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
 					this.assertTrue(actual == expected1 || actual == expected2);
348
 					this.assertTrue(actual == expected1 || actual == expected2);
302
 				}
349
 				}
303
 
350
 
304
 				test_strongEmphasis_hard1() {
351
 				test_strongEmphasis_hard1() {
305
 					let markdown = 'Lorem ***ipsum* dolor** sit';
352
 					let markdown = 'Lorem ***ipsum* dolor** sit';
306
 					let expected = '<p>Lorem <strong><em>ipsum</em> dolor</strong> sit</p>';
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
 					this.assertEqual(actual, expected);
355
 					this.assertEqual(actual, expected);
309
 				}
356
 				}
310
 
357
 
311
 				test_strongEmphasis_hard2() {
358
 				test_strongEmphasis_hard2() {
312
 					let markdown = 'Lorem ***ipsum** dolor* sit';
359
 					let markdown = 'Lorem ***ipsum** dolor* sit';
313
 					let expected = '<p>Lorem <em><strong>ipsum</strong> dolor</em> sit</p>';
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
 					this.assertEqual(actual, expected);
362
 					this.assertEqual(actual, expected);
316
 				}
363
 				}
317
 
364
 
318
 				test_inlineCode() {
365
 				test_inlineCode() {
319
 					let markdown = 'Lorem `ipsum` dolor';
366
 					let markdown = 'Lorem `ipsum` dolor';
320
 					let expected = '<p>Lorem <code>ipsum</code> dolor</p>';
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
 					this.assertEqual(actual, expected);
369
 					this.assertEqual(actual, expected);
323
 				}
370
 				}
324
 
371
 
325
 				test_inlineCode_withInnerBacktick() {
372
 				test_inlineCode_withInnerBacktick() {
326
 					let markdown = 'Lorem ``ip`su`m`` dolor';
373
 					let markdown = 'Lorem ``ip`su`m`` dolor';
327
 					let expected = '<p>Lorem <code>ip`su`m</code> dolor</p>';
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
 					this.assertEqual(actual, expected);
483
 					this.assertEqual(actual, expected);
330
 				}
484
 				}
331
 			}
485
 			}

Chargement…
Annuler
Enregistrer