Преглед изворни кода

Numerous bug fixes. Unit tests working again.

main
Rocketsoup пре 1 година
родитељ
комит
02d0d1c518
2 измењених фајлова са 273 додато и 103 уклоњено
  1. 120
    57
      js/markdown.js
  2. 153
    46
      testjs.html

+ 120
- 57
js/markdown.js Прегледај датотеку

@@ -165,23 +165,28 @@ class MDToken {
165 165
 		for (var s = startIndex; s < tokensToSearch.length; s++) {
166 166
 			var startMatch = this.findFirstTokens(tokensToSearch, startPattern, s);
167 167
 			if (startMatch === null) return null;
168
-			var endMatch = this.findFirstTokens(tokensToSearch, endPattern, startMatch.index + startMatch.tokens.length);
169
-			if (endMatch === null) return null;
170
-			var contents = tokensToSearch.slice(startMatch.index + startMatch.tokens.length, endMatch.index);
171
-			if (contents.length > 0 && (contentValidator === null || contentValidator(contents))) {
172
-				return {
173
-					'startTokens': startMatch.tokens,
174
-					'contentTokens': contents,
175
-					'endTokens': endMatch.tokens,
176
-					'startIndex': startMatch.index,
177
-					'contentIndex': startMatch.index + startMatch.tokens.length,
178
-					'endIndex': endMatch.index,
179
-					'totalLength': endMatch.index + endMatch.tokens.length - startMatch.index,
180
-				};
181
-			} else {
182
-				// No match. Try again right after this start index.
183
-				s = startMatch.index;
168
+			var endStart = startMatch.index + startMatch.tokens.length;
169
+			while (endStart < tokensToSearch.length) {
170
+				var endMatch = this.findFirstTokens(tokensToSearch, endPattern, endStart);
171
+				if (endMatch === null) break;
172
+				var contents = tokensToSearch.slice(startMatch.index + startMatch.tokens.length, endMatch.index);
173
+				if (contents.length > 0 && (contentValidator === null || contentValidator(contents))) {
174
+					return {
175
+						'startTokens': startMatch.tokens,
176
+						'contentTokens': contents,
177
+						'endTokens': endMatch.tokens,
178
+						'startIndex': startMatch.index,
179
+						'contentIndex': startMatch.index + startMatch.tokens.length,
180
+						'endIndex': endMatch.index,
181
+						'totalLength': endMatch.index + endMatch.tokens.length - startMatch.index,
182
+					};
183
+				} else {
184
+					// Contents rejected. Try next end match.
185
+					endStart = endMatch.index + 1;
186
+				}
184 187
 			}
188
+			// No end matches. Increment start match.
189
+			s = startMatch.index;
185 190
 		}
186 191
 		return null;
187 192
 	}
@@ -310,9 +315,11 @@ class MDUtils {
310 315
 	static tokenizeURL(line) {
311 316
 		var groups;
312 317
 		if (groups = this.#urlWithTitleRegex.exec(line)) {
318
+			if (this.tokenizeEmail(line)) return null; // make sure it's not better described as an email address
313 319
 			return groups;
314 320
 		}
315 321
 		if (groups = this.#urlRegex.exec(line)) {
322
+			if (this.tokenizeEmail(line)) return null;
316 323
 			return [...groups, null];
317 324
 		}
318 325
 		return null;
@@ -340,6 +347,20 @@ class MDUtils {
340 347
 		}
341 348
 		return null;
342 349
 	}
350
+
351
+	/**
352
+	 * Describes the type of a variable for debugging.
353
+	 *
354
+	 * @param {any} value - value
355
+	 * @returns {String} description of type
356
+	 */
357
+	static typename(value) {
358
+		if (value === null) return 'null';
359
+		if (value instanceof Object) {
360
+			return value.constructor.name;
361
+		}
362
+		return typeof value;
363
+	}
343 364
 }
344 365
 
345 366
 
@@ -862,8 +883,7 @@ class MDTableBlockReader extends MDBlockReader {
862 883
 		line = line.trim();
863 884
 		if (line.startsWith('|')) line = line.substring(1);
864 885
 		if (line.endsWith('|')) line = line.substring(0, line.length - 1);
865
-		return line.split('|').map(function(token) {
866
-			token = token.trim();
886
+		return line.split(/\s*\|\s*/).map(function(token) {
867 887
 			if (token.startsWith(':')) {
868 888
 				if (token.endsWith(':')) {
869 889
 					return MDTableCellBlock.AlignCenter;
@@ -876,14 +896,13 @@ class MDTableBlockReader extends MDBlockReader {
876 896
 		});
877 897
 	}
878 898
 
879
-	static #tableDividerRegex = /^\s*[|]?(?:\s*[:]?-+[:]?\s*\|)(?:\s*[:]?-+[:]?\s*)[|]?\s*$/;
899
+	static #tableDividerRegex = /^\s*[|]?\s*(?:[:]?-+[:]?)(?:\s*\|\s*[:]?-+[:]?)*\s*[|]?\s*$/;
880 900
 
881 901
 	readBlock(state) {
882 902
 		if (!state.hasLines(2)) return null;
883 903
 		let startP = state.p;
884 904
 		let firstLine = state.lines[startP];
885
-		var ignore, modifier;
886
-		[ignore, modifier] = MDTagModifier.fromLine(firstLine);
905
+		var modifier = MDTagModifier.fromLine(firstLine)[1];
887 906
 		let headerRow = this.#readTableRow(state, true);
888 907
 		if (headerRow === null) {
889 908
 			state.p = startP;
@@ -1114,7 +1133,7 @@ class MDParagraphBlockReader extends MDBlockReader {
1114 1133
 		if (paragraphLines.length > 0) {
1115 1134
 			state.p = p;
1116 1135
 			let content = paragraphLines.join("\n");
1117
-			return new MDParagraphBlock(state.inlineMarkdownToSpan(content));
1136
+			return new MDParagraphBlock(new MDInlineBlock(state.inlineMarkdownToSpans(content)));
1118 1137
 		}
1119 1138
 		return null;
1120 1139
 	}
@@ -1232,19 +1251,28 @@ class MDSimplePairInlineReader extends MDInlineReader {
1232 1251
 	 * @param {number} count - how many times the token is repeated to form the delimiter
1233 1252
 	 * @returns {boolean} `true` if substitution performed, `false` if not
1234 1253
 	 */
1235
-	attemptPair(state, priority, tokens, spanClass, delimiter, count=1) {
1254
+	attemptPair(state, priority, tokens, spanClass, delimiter, count=1, plaintext=false) {
1236 1255
 		let delimiters = Array(count).fill(delimiter);
1237 1256
 		let firstPassPriority = (this.substitutePriority instanceof Array) ? this.substitutePriority[0] : null;
1238 1257
 		let match = MDToken.findPairedTokens(tokens, delimiters, delimiters, function(content) {
1258
+			const firstType = content[0] instanceof MDToken ? content[0].type : null;
1259
+			const lastType = content[content.length - 1] instanceof MDToken ? content[content.length - 1].type : null;
1260
+			if (firstType == MDTokenType.Whitespace) return false;
1261
+			if (lastType == MDTokenType.Whitespace) return false;
1239 1262
 			if (priority == firstPassPriority) {
1263
+				var innerCount = 0;
1240 1264
 				for (let token of content) {
1241
-					if (token instanceof MDToken && token.type == delimiter) return false;
1265
+					if (token instanceof MDToken && token.type == delimiter) innerCount++;
1242 1266
 				}
1267
+				if ((innerCount % 2) != 0) return false;
1243 1268
 			}
1244 1269
 			return true;
1245 1270
 		});
1246 1271
 		if (match === null) return false;
1247
-		tokens.splice(match.startIndex, match.totalLength, new spanClass(state.tokensToSpans(match.contentTokens)));
1272
+		let content = (plaintext)
1273
+			? match.contentTokens.map((token) => token.original).join('')
1274
+			: state.tokensToSpans(match.contentTokens);
1275
+		tokens.splice(match.startIndex, match.totalLength, new spanClass(content));
1248 1276
 		return true;
1249 1277
 	}
1250 1278
 }
@@ -1286,7 +1314,7 @@ class MDEmphasisInlineReader extends MDSimplePairInlineReader {
1286 1314
 }
1287 1315
 
1288 1316
 class MDCodeInlineReader extends MDSimplePairInlineReader {
1289
-	constructor(tokenizePriority=0.0, substitutePriority=[0.0, 50.0]) {
1317
+	constructor(tokenizePriority=0.0, substitutePriority=0.0) {
1290 1318
 		super(tokenizePriority, substitutePriority);
1291 1319
 	}
1292 1320
 
@@ -1296,8 +1324,9 @@ class MDCodeInlineReader extends MDSimplePairInlineReader {
1296 1324
 	}
1297 1325
 
1298 1326
 	substituteTokens(state, priority, tokens) {
1299
-		if (this.attemptPair(state, priority, tokens, MDCodeSpan, MDTokenType.Backtick, 2)) return true;
1300
-		if (this.attemptPair(state, priority, tokens, MDCodeSpan, MDTokenType.Backtick)) return true;
1327
+		// ignore priority
1328
+		if (this.attemptPair(state, -1, tokens, MDCodeSpan, MDTokenType.Backtick, 2, true)) return true;
1329
+		if (this.attemptPair(state, -1, tokens, MDCodeSpan, MDTokenType.Backtick, 1, true)) return true;
1301 1330
 		return false;
1302 1331
 	}
1303 1332
 }
@@ -1452,8 +1481,8 @@ class MDLinkInlineReader extends MDInlineReader {
1452 1481
 }
1453 1482
 
1454 1483
 class MDSimpleLinkInlineReader extends MDInlineReader {
1455
-	static #simpleURLRegex = new RegExp("^<(" + MDUtils.baseURLRegex.source + ")>", "i");  // 1=URL
1456 1484
 	static #simpleEmailRegex = new RegExp("^<(" + MDUtils.baseEmailRegex.source + ")>", "i");  // 1=email
1485
+	static #simpleURLRegex = new RegExp("^<(" + MDUtils.baseURLRegex.source + ")>", "i");  // 1=URL
1457 1486
 
1458 1487
 	constructor(tokenizePriority=0.0, substitutePriority=0.0) {
1459 1488
 		super(tokenizePriority, substitutePriority);
@@ -1470,7 +1499,18 @@ class MDSimpleLinkInlineReader extends MDInlineReader {
1470 1499
 		return null;
1471 1500
 	}
1472 1501
 
1473
-	substituteTokens(state, priority, tokens) {
1502
+	#substituteEmail(state, tokens) {
1503
+		const result = MDToken.findFirstTokens(tokens, [ MDTokenType.SimpleEmail ]);
1504
+		if (result === null) return false;
1505
+		/** @type {MDToken} */
1506
+		const token = result.tokens[0];
1507
+		const link = `mailto:${token.content}`;
1508
+		const span = new MDLinkSpan(link, new MDObfuscatedTextSpan(token.content));
1509
+		tokens.splice(result.index, 1, span);
1510
+		return true;
1511
+	}
1512
+
1513
+	#substituteURL(state, tokens) {
1474 1514
 		const result = MDToken.findFirstTokens(tokens, [ MDTokenType.SimpleLink ]);
1475 1515
 		if (result === null) return false;
1476 1516
 		/** @type {MDToken} */
@@ -1480,6 +1520,12 @@ class MDSimpleLinkInlineReader extends MDInlineReader {
1480 1520
 		tokens.splice(result.index, 1, span);
1481 1521
 		return true;
1482 1522
 	}
1523
+
1524
+	substituteTokens(state, priority, tokens) {
1525
+		if (this.#substituteEmail(state, tokens)) return true;
1526
+		if (this.#substituteURL(state, tokens)) return true;
1527
+		return false;
1528
+	}
1483 1529
 }
1484 1530
 
1485 1531
 class MDHTMLTagInlineReader extends MDInlineReader {
@@ -1584,7 +1630,11 @@ class MDMultiBlock extends MDBlock {
1584 1630
 	 */
1585 1631
 	constructor(blocks) {
1586 1632
 		super();
1587
-		this.#blocks = blocks;
1633
+		if (blocks instanceof Array) {
1634
+			this.#blocks = blocks;
1635
+		} else {
1636
+			throw new Error(`${MDUtils.typename(this)} expects MDBlock[], got ${MDUtils.typename(blocks)}`);
1637
+		}
1588 1638
 	}
1589 1639
 
1590 1640
 	toHTML(state) {
@@ -1600,33 +1650,41 @@ class MDMultiBlock extends MDBlock {
1600 1650
 }
1601 1651
 
1602 1652
 class MDParagraphBlock extends MDBlock {
1603
-	/** @type {MDBlock} */
1604
-	content;
1653
+	/** @type {MDBlock[]} */
1654
+	#content;
1605 1655
 
1606 1656
 	/**
1607
-	 * @param {MDBlock} content
1657
+	 * @param {MDBlock|MDBlock[]} content
1608 1658
 	 */
1609 1659
 	constructor(content) {
1610 1660
 		super();
1611
-		this.content = content;
1661
+		if (content instanceof Array) {
1662
+			this.#content = content;
1663
+		} else if (content instanceof MDBlock) {
1664
+			this.#content = [ content ];
1665
+		} else {
1666
+			throw new Error(`${MDUtils.typename(this)} expects MDBlock[] or MDBlock, got ${MDUtils.typename(content)}`);
1667
+		}
1612 1668
 	}
1613 1669
 
1614 1670
 	toHTML(state) {
1615
-		let contentHTML = this.content.toHTML(state);
1671
+		const contentHTML = MDBlock.toHTML(this.#content, state);
1616 1672
 		return `<p${this.htmlAttributes()}>${contentHTML}</p>\n`;
1617 1673
 	}
1618 1674
 
1619 1675
 	visitChildren(fn) {
1620
-		fn(this.content);
1621
-		this.content.visitChildren(fn);
1676
+		for (const child of this.#content) {
1677
+			fn(child);
1678
+			child.visitChildren(fn);
1679
+		}
1622 1680
 	}
1623 1681
 }
1624 1682
 
1625 1683
 class MDHeaderBlock extends MDBlock {
1626 1684
 	/** @type {number} */
1627
-	level;
1685
+	#level;
1628 1686
 	/** @type {MDBlock} */
1629
-	content;
1687
+	#content;
1630 1688
 
1631 1689
 	/**
1632 1690
 	 * @param {number} level
@@ -1634,13 +1692,13 @@ class MDHeaderBlock extends MDBlock {
1634 1692
 	 */
1635 1693
 	constructor(level, content) {
1636 1694
 		super();
1637
-		this.level = level;
1638
-		this.content = content;
1695
+		this.#level = level;
1696
+		this.#content = content;
1639 1697
 	}
1640 1698
 
1641 1699
 	toHTML(state) {
1642
-		let contentHTML = this.content.toHTML(state);
1643
-		return `<h${this.level}${this.htmlAttributes()}>${contentHTML}</h${this.level}>\n`;
1700
+		let contentHTML = this.#content.toHTML(state);
1701
+		return `<h${this.#level}${this.htmlAttributes()}>${contentHTML}</h${this.level}>\n`;
1644 1702
 	}
1645 1703
 
1646 1704
 	visitChildren(fn) {
@@ -2318,15 +2376,19 @@ class MDStrikethroughSpan extends MDSpan {
2318 2376
 }
2319 2377
 
2320 2378
 class MDCodeSpan extends MDSpan {
2321
-	/** @type {string} content */
2379
+	/** @type {String} content */
2322 2380
 	#content;
2323 2381
 
2324 2382
 	/**
2325
-	 * @param {string} content
2383
+	 * @param {String} content
2326 2384
 	 */
2327 2385
 	constructor(content) {
2328 2386
 		super();
2329
-		this.#content = content;
2387
+		if (typeof content == 'string') {
2388
+			this.#content = content;
2389
+		} else {
2390
+			throw new Error(`${this.constructor.name} content must be String, got ${typeof content}`);
2391
+		}
2330 2392
 	}
2331 2393
 
2332 2394
 	toHTML(state) {
@@ -2977,6 +3039,7 @@ class MDState {
2977 3039
 				const changed = reader.substituteTokens(this, priority, spans);
2978 3040
 				if (!changed) continue;
2979 3041
 				anyChanges = true;
3042
+				break;
2980 3043
 			}
2981 3044
 		} while (anyChanges);
2982 3045
 
@@ -3126,13 +3189,13 @@ class Markdown {
3126 3189
 	 * @type {MDInlineReader[]}
3127 3190
 	 */
3128 3191
 	static standardInlineReaders = [
3129
-		new MDStrongInlineReader(10.0, [ 0.0, 50.0 ]),
3130
-		new MDEmphasisInlineReader(15.0, [ 0.0, 50.0 ]),
3131
-		new MDCodeInlineReader(20.0, [ 0.0, 50.0 ]),
3132
-		new MDImageInlineReader(25.0, 10.0),
3133
-		new MDLinkInlineReader(30.0, 15.0),
3134
-		new MDSimpleLinkInlineReader(35.0, 20.0),
3135
-		new MDHTMLTagInlineReader(80.0, 25.0),
3192
+		new MDStrongInlineReader(10.0, [ 0.0, 2.0 ]),
3193
+		new MDEmphasisInlineReader(15.0, [ 5.0, 55.0 ]),
3194
+		new MDCodeInlineReader(20.0, [ 10.0, 60.0 ]),
3195
+		new MDImageInlineReader(25.0, 15.0),
3196
+		new MDLinkInlineReader(30.0, 20.0),
3197
+		new MDSimpleLinkInlineReader(35.0, 25.0),
3198
+		new MDHTMLTagInlineReader(80.0, 30.0),
3136 3199
 	];
3137 3200
 
3138 3201
 	/**
@@ -3141,9 +3204,9 @@ class Markdown {
3141 3204
 	 */
3142 3205
 	static allInlineReaders = [
3143 3206
 		...this.standardInlineReaders,
3144
-		new MDStrikethroughInlineReader(21.0, [ 0.0, 50.0 ]),
3145
-		new MDFootnoteInlineReader(5.0, 30.0),
3146
-		new MDModifierInlineReader(90.0, 35.0),
3207
+		new MDStrikethroughInlineReader(21.0, [ 12.0, 50.0 ]),
3208
+		new MDFootnoteInlineReader(5.0, 40.0),
3209
+		new MDModifierInlineReader(90.0, 45.0),
3147 3210
 	];
3148 3211
 
3149 3212
 	/**

+ 153
- 46
testjs.html Прегледај датотеку

@@ -50,21 +50,14 @@
50 50
 			}
51 51
 		</style>
52 52
 		<script src="js/markdown.js"></script>
53
+		<!-- Testing infrastructure -->
53 54
 		<script>
54
-			function onLoad() {
55
-				let testClasses = [
56
-					InlineTests,
57
-					BlockTests,
58
-				];
59
-				TestClassRunner.runAll(testClasses);
60
-			}
61
-
62 55
 			/**
63 56
 			 * @param {String} text
64 57
 			 * @returns {String}
65 58
 			 */
66 59
 			function escapeHTML(text) {
67
-				return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
60
+				return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\n/g, '<br/>\n');
68 61
 			}
69 62
 
70 63
 			class ResultType {
@@ -106,7 +99,14 @@
106 99
 					if (test) this.fail(failMessage || `expected false, got ${test}`);
107 100
 				}
108 101
 				assertEqual(a, b, failMessage=null) {
109
-					if (a != b) this.fail(failMessage || `equality failed: ${a} != ${b}`);
102
+					if (a == b) return;
103
+					const aVal = `${a}`;
104
+					const bVal = `${b}`;
105
+					if (aVal.length > 20 || bVal.length > 20) {
106
+						this.fail(failMessage || `equality failed:\n${aVal}\n!=\n${bVal}`);
107
+					} else {
108
+						this.fail(failMessage || `equality failed: ${aVal} != ${bVal}`);
109
+					}
110 110
 				}
111 111
 				expectError(e=true) {
112 112
 					if (this.currentRunner) this.currentRunner.expectedError = e;
@@ -176,6 +176,9 @@
176 176
 						} else {
177 177
 							this.result = ResultType.errored;
178 178
 							this.message = e.message;
179
+							if (e.stack !== undefined) {
180
+								this.message += "\n" + e.stack;
181
+							}
179 182
 						}
180 183
 					} finally {
181 184
 						this.expectedError = null;
@@ -316,215 +319,319 @@
316 319
 					}, 1);
317 320
 				}
318 321
 			}
319
-
320
-			// ---------------------------------------------------------------
322
+		</script>
323
+		<!-- Tests -->
324
+		<script>
325
+			function onLoad() {
326
+				let testClasses = [
327
+					TokenTests,
328
+					InlineTests,
329
+					BlockTests,
330
+				];
331
+				TestClassRunner.runAll(testClasses);
332
+			}
333
+			document.addEventListener('DOMContentLoaded', onLoad);
321 334
 
322 335
 			function normalizeWhitespace(str) {
323 336
 				return str.replace(/\s+/g, ' ').replace(/(?:^\s+|\s+$)/g, '');
324 337
 			}
325 338
 
339
+			class TokenTests extends BaseTest {
340
+				test_findFirstTokens() {
341
+					const tokens = [
342
+						new MDToken('Lorem', MDTokenType.Text),
343
+						new MDToken(' ', MDTokenType.Whitespace),
344
+						new MDToken('_', MDTokenType.Underscore),
345
+						new MDToken('ipsum', MDTokenType.Text),
346
+						new MDToken('_', MDTokenType.Underscore),
347
+						new MDToken(' ', MDTokenType.Whitespace),
348
+						new MDToken('dolor', MDTokenType.Text),
349
+						new MDToken('_', MDTokenType.Underscore),
350
+						new MDToken('sit', MDTokenType.Text),
351
+						new MDToken('_', MDTokenType.Underscore),
352
+					];
353
+					const pattern = [
354
+						MDTokenType.Underscore,
355
+					];
356
+					const result = MDToken.findFirstTokens(tokens, pattern);
357
+					const expected = {
358
+						tokens: [ tokens[2] ],
359
+						index: 2,
360
+					};
361
+					this.assertEqual(JSON.stringify(result), JSON.stringify(expected));
362
+				}
363
+
364
+				test_findPairedTokens() {
365
+					const tokens = [
366
+						new MDToken('Lorem', MDTokenType.Text),
367
+						new MDToken(' ', MDTokenType.Whitespace),
368
+						new MDToken('_', MDTokenType.Underscore),
369
+						new MDToken('ipsum', MDTokenType.Text),
370
+						new MDToken('_', MDTokenType.Underscore),
371
+						new MDToken(' ', MDTokenType.Whitespace),
372
+						new MDToken('dolor', MDTokenType.Text),
373
+						new MDToken('_', MDTokenType.Underscore),
374
+						new MDToken('sit', MDTokenType.Text),
375
+						new MDToken('_', MDTokenType.Underscore),
376
+					];
377
+					const pattern = [
378
+						MDTokenType.Underscore,
379
+					];
380
+					const result = MDToken.findPairedTokens(tokens, pattern, pattern);
381
+					const expected = {
382
+						startTokens: [ tokens[2] ],
383
+						contentTokens: [ tokens[3] ],
384
+						endTokens: [ tokens[4] ],
385
+						startIndex: 2,
386
+						contentIndex: 3,
387
+						endIndex: 4,
388
+						totalLength: 3,
389
+					}
390
+					this.assertEqual(JSON.stringify(result), JSON.stringify(expected));
391
+				}
392
+			}
393
+
326 394
 			class InlineTests extends BaseTest {
395
+				/** @type {Markdown} */
396
+				parser;
397
+				md(markdown) {
398
+					return normalizeWhitespace(this.parser.toHTML(markdown));
399
+				}
400
+			
401
+				setUp() {
402
+					this.parser = Markdown.completeParser;
403
+				}
404
+
327 405
 				test_simpleSingleParagraph() {
328 406
 					let markdown = 'Lorem ipsum';
329 407
 					let expected = '<p>Lorem ipsum</p>';
330
-					let actual = normalizeWhitespace(Markdown.toHTML(markdown));
408
+					let actual = this.md(markdown);
331 409
 					this.assertEqual(actual, expected);
332 410
 				}
333 411
 
334 412
 				test_strong() {
335 413
 					let markdown = 'Lorem **ipsum** dolor **sit**';
336 414
 					let expected = '<p>Lorem <strong>ipsum</strong> dolor <strong>sit</strong></p>';
337
-					let actual = normalizeWhitespace(Markdown.toHTML(markdown));
415
+					let actual = this.md(markdown);
338 416
 					this.assertEqual(actual, expected);
339 417
 				}
340 418
 
341 419
 				test_emphasis() {
342 420
 					let markdown = 'Lorem _ipsum_ dolor _sit_';
343 421
 					let expected = '<p>Lorem <em>ipsum</em> dolor <em>sit</em></p>';
344
-					let actual = normalizeWhitespace(Markdown.toHTML(markdown));
422
+					let actual = this.md(markdown);
345 423
 					this.assertEqual(actual, expected);
346 424
 				}
347 425
 
348
-				test_strongEmphasis_easy() {
426
+				test_strongEmphasis_cleanNesting1() {
349 427
 					let markdown = 'Lorem **ipsum *dolor* sit** amet';
350 428
 					let expected = '<p>Lorem <strong>ipsum <em>dolor</em> sit</strong> amet</p>';
351
-					let actual = normalizeWhitespace(Markdown.toHTML(markdown));
429
+					let actual = this.md(markdown);
352 430
 					this.assertEqual(actual, expected);
353 431
 				}
354 432
 
355
-				test_strongEmphasis_medium() {
433
+				test_strongEmphasis_cleanNesting2() {
434
+					let markdown = 'Lorem *ipsum **dolor** sit* amet';
435
+					let expected = '<p>Lorem <em>ipsum <strong>dolor</strong> sit</em> amet</p>';
436
+					let actual = this.md(markdown);
437
+					this.assertEqual(actual, expected);
438
+				}
439
+
440
+				test_strongEmphasis_tightNesting() {
356 441
 					let markdown = 'Lorem ***ipsum*** dolor';
357 442
 					let expected1 = '<p>Lorem <strong><em>ipsum</em></strong> dolor</p>';
358 443
 					let expected2 = '<p>Lorem <em><strong>ipsum</strong></em> dolor</p>';
359
-					let actual = normalizeWhitespace(Markdown.toHTML(markdown));
444
+					let actual = this.md(markdown);
360 445
 					this.assertTrue(actual == expected1 || actual == expected2);
361 446
 				}
362 447
 
363
-				test_strongEmphasis_hard1() {
448
+				test_strongEmphasis_lopsidedNesting1() {
364 449
 					let markdown = 'Lorem ***ipsum* dolor** sit';
365 450
 					let expected = '<p>Lorem <strong><em>ipsum</em> dolor</strong> sit</p>';
366
-					let actual = normalizeWhitespace(Markdown.toHTML(markdown));
451
+					let actual = this.md(markdown);
367 452
 					this.assertEqual(actual, expected);
368 453
 				}
369 454
 
370
-				test_strongEmphasis_hard2() {
455
+				test_strongEmphasis_lopsidedNesting2() {
371 456
 					let markdown = 'Lorem ***ipsum** dolor* sit';
372 457
 					let expected = '<p>Lorem <em><strong>ipsum</strong> dolor</em> sit</p>';
373
-					let actual = normalizeWhitespace(Markdown.toHTML(markdown));
458
+					let actual = this.md(markdown);
459
+					this.assertEqual(actual, expected);
460
+				}
461
+
462
+				test_strongEmphasis_lopsidedNesting3() {
463
+					let markdown = 'Lorem **ipsum *dolor*** sit';
464
+					let expected = '<p>Lorem <strong>ipsum <em>dolor</em></strong> sit</p>';
465
+					let actual = this.md(markdown);
466
+					this.assertEqual(actual, expected);
467
+				}
468
+
469
+				test_strongEmphasis_lopsidedNesting4() {
470
+					let markdown = 'Lorem *ipsum **dolor*** sit';
471
+					let expected = '<p>Lorem <em>ipsum <strong>dolor</strong></em> sit</p>';
472
+					let actual = this.md(markdown);
374 473
 					this.assertEqual(actual, expected);
375 474
 				}
376 475
 
377 476
 				test_inlineCode() {
378 477
 					let markdown = 'Lorem `ipsum` dolor';
379 478
 					let expected = '<p>Lorem <code>ipsum</code> dolor</p>';
380
-					let actual = normalizeWhitespace(Markdown.toHTML(markdown));
479
+					let actual = this.md(markdown);
381 480
 					this.assertEqual(actual, expected);
382 481
 				}
383 482
 
384 483
 				test_inlineCode_withInnerBacktick() {
385 484
 					let markdown = 'Lorem ``ip`su`m`` dolor';
386 485
 					let expected = '<p>Lorem <code>ip`su`m</code> dolor</p>';
387
-					let actual = normalizeWhitespace(Markdown.toHTML(markdown));
486
+					let actual = this.md(markdown);
388 487
 					this.assertEqual(actual, expected);
389 488
 				}
390 489
 
391 490
 				test_strikethrough_single() {
392 491
 					let markdown = 'Lorem ~ipsum~ dolor';
393 492
 					let expected = '<p>Lorem <strike>ipsum</strike> dolor</p>';
394
-					let actual = normalizeWhitespace(Markdown.toHTML(markdown));
493
+					let actual = this.md(markdown);
395 494
 					this.assertEqual(actual, expected);
396 495
 				}
397 496
 
398 497
 				test_strikethrough_double() {
399 498
 					let markdown = 'Lorem ~~ipsum~~ dolor';
400 499
 					let expected = '<p>Lorem <strike>ipsum</strike> dolor</p>';
401
-					let actual = normalizeWhitespace(Markdown.toHTML(markdown));
500
+					let actual = this.md(markdown);
402 501
 					this.assertEqual(actual, expected);
403 502
 				}
404 503
 
405 504
 				test_link_fullyQualified() {
406 505
 					let markdown = 'Lorem [ipsum](https://example.com/path/page.html) dolor';
407 506
 					let expected = '<p>Lorem <a href="https://example.com/path/page.html">ipsum</a> dolor</p>';
408
-					let actual = normalizeWhitespace(Markdown.toHTML(markdown));
507
+					let actual = this.md(markdown);
409 508
 					this.assertEqual(actual, expected);
410 509
 				}
411 510
 
412 511
 				test_link_relative() {
413 512
 					let markdown = 'Lorem [ipsum](page.html) dolor';
414 513
 					let expected = '<p>Lorem <a href="page.html">ipsum</a> dolor</p>';
415
-					let actual = normalizeWhitespace(Markdown.toHTML(markdown));
514
+					let actual = this.md(markdown);
416 515
 					this.assertEqual(actual, expected);
417 516
 				}
418 517
 
419 518
 				test_link_title() {
420 519
 					let markdown = 'Lorem [ipsum](page.html "link title") dolor';
421 520
 					let expected = '<p>Lorem <a href="page.html" title="link title">ipsum</a> dolor</p>';
422
-					let actual = normalizeWhitespace(Markdown.toHTML(markdown));
521
+					let actual = this.md(markdown);
423 522
 					this.assertEqual(actual, expected);
424 523
 				}
425 524
 
426 525
 				test_link_literal() {
427 526
 					let markdown = 'Lorem <https://example.com> dolor';
428 527
 					let expected = '<p>Lorem <a href="https://example.com">https://example.com</a> dolor</p>';
429
-					let actual = normalizeWhitespace(Markdown.toHTML(markdown));
528
+					let actual = this.md(markdown);
430 529
 					this.assertEqual(actual, expected);
431 530
 				}
432 531
 
433 532
 				test_link_ref() {
434 533
 					let markdown = "Lorem [ipsum][ref] dolor\n\n[ref]: https://example.com";
435 534
 					let expected = '<p>Lorem <a href="https://example.com">ipsum</a> dolor</p>';
436
-					let actual = normalizeWhitespace(Markdown.toHTML(markdown));
535
+					let actual = this.md(markdown);
437 536
 					this.assertEqual(actual, expected);
438 537
 				}
439 538
 
440 539
 				test_link_email() {
441 540
 					let markdown = 'Lorem [ipsum](user@example.com) dolor';
442 541
 					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>';
443
-					let actual = normalizeWhitespace(Markdown.toHTML(markdown));
542
+					let actual = this.md(markdown);
444 543
 					this.assertEqual(actual, expected);
445 544
 				}
446 545
 
447 546
 				test_link_email_withTitle() {
448 547
 					let markdown = 'Lorem [ipsum](user@example.com "title") dolor';
449 548
 					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>';
450
-					let actual = normalizeWhitespace(Markdown.toHTML(markdown));
549
+					let actual = this.md(markdown);
451 550
 					this.assertEqual(actual, expected);
452 551
 				}
453 552
 
454 553
 				test_link_literalEmail() {
455 554
 					let markdown = 'Lorem <user@example.com> dolor';
456 555
 					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>';
457
-					let actual = normalizeWhitespace(Markdown.toHTML(markdown));
556
+					let actual = this.md(markdown);
458 557
 					this.assertEqual(actual, expected);
459 558
 				}
460 559
 
461 560
 				test_image() {
462 561
 					let markdown = 'Lorem ![alt text](image.jpg) dolor';
463 562
 					let expected = '<p>Lorem <img src="image.jpg" alt="alt text"> dolor</p>';
464
-					let actual = normalizeWhitespace(Markdown.toHTML(markdown));
563
+					let actual = this.md(markdown);
465 564
 					this.assertEqual(actual, expected);
466 565
 				}
467 566
 
468 567
 				test_image_noAlt() {
469 568
 					let markdown = 'Lorem ![](image.jpg) dolor';
470 569
 					let expected = '<p>Lorem <img src="image.jpg"> dolor</p>';
471
-					let actual = normalizeWhitespace(Markdown.toHTML(markdown));
570
+					let actual = this.md(markdown);
472 571
 					this.assertEqual(actual, expected);
473 572
 				}
474 573
 
475 574
 				test_image_withTitle() {
476 575
 					let markdown = 'Lorem ![alt text](image.jpg "image title") dolor';
477 576
 					let expected = '<p>Lorem <img src="image.jpg" alt="alt text" title="image title"> dolor</p>';
478
-					let actual = normalizeWhitespace(Markdown.toHTML(markdown));
577
+					let actual = this.md(markdown);
479 578
 					this.assertEqual(actual, expected);
480 579
 				}
481 580
 			}
482 581
 
483 582
 			class BlockTests extends BaseTest {
583
+				/** @type {Markdown} */
584
+				parser;
585
+				md(markdown) {
586
+					return normalizeWhitespace(this.parser.toHTML(markdown));
587
+				}
588
+			
589
+				setUp() {
590
+					this.parser = Markdown.completeParser;
591
+				}
592
+
484 593
 				test_paragraphs() {
485 594
 					let markdown = "Lorem ipsum\n\nDolor sit amet";
486 595
 					let expected = "<p>Lorem ipsum</p> <p>Dolor sit amet</p>";
487
-					let actual = normalizeWhitespace(Markdown.toHTML(markdown));
596
+					let actual = this.md(markdown);
488 597
 					this.assertEqual(actual, expected);
489 598
 				}
490 599
 
491 600
 				test_paragraph_lineGrouping() {
492 601
 					let markdown = "Lorem ipsum\ndolor sit amet";
493 602
 					let expected = "<p>Lorem ipsum dolor sit amet</p>";
494
-					let actual = normalizeWhitespace(Markdown.toHTML(markdown));
603
+					let actual = this.md(markdown);
495 604
 					this.assertEqual(actual, expected);
496 605
 				}
497 606
 
498 607
 				test_unorderedList() {
499 608
 					let markdown = "* Lorem\n* Ipsum\n* Dolor";
500 609
 					let expected = '<ul> <li>Lorem</li> <li>Ipsum</li> <li>Dolor</li> </ul>';
501
-					let actual = normalizeWhitespace(Markdown.toHTML(markdown));
610
+					let actual = this.md(markdown);
502 611
 					this.assertEqual(actual, expected);
503 612
 				}
504 613
 
505 614
 				test_orderedList() {
506 615
 					let markdown = "1. Lorem\n1. Ipsum\n5. Dolor";
507 616
 					let expected = '<ol start="1"> <li>Lorem</li> <li>Ipsum</li> <li>Dolor</li> </ol>';
508
-					let actual = normalizeWhitespace(Markdown.toHTML(markdown));
617
+					let actual = this.md(markdown);
509 618
 					this.assertEqual(actual, expected);
510 619
 				}
511 620
 
512 621
 				test_orderedList_numbering() {
513 622
 					let markdown = "4. Lorem\n1. Ipsum\n9. Dolor";
514 623
 					let expected = '<ol start="4"> <li>Lorem</li> <li>Ipsum</li> <li>Dolor</li> </ol>';
515
-					let actual = normalizeWhitespace(Markdown.toHTML(markdown));
624
+					let actual = this.md(markdown);
516 625
 					this.assertEqual(actual, expected);
517 626
 				}
518 627
 
519 628
 				test_blockquote() {
520 629
 					let markdown = '> Lorem ipsum dolor';
521 630
 					let expected = '<blockquote> <p>Lorem ipsum dolor</p> </blockquote>';
522
-					let actual = normalizeWhitespace(Markdown.toHTML(markdown));
631
+					let actual = this.md(markdown);
523 632
 					this.assertEqual(actual, expected);
524 633
 				}
525 634
 			}
526
-
527
-			document.addEventListener('DOMContentLoaded', onLoad);
528 635
 		</script>
529 636
 	</head>
530 637
 	<body>

Loading…
Откажи
Сачувај