Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/base/test/browser/markdownRenderer.test.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import assert from 'assert';
7
import { fillInIncompleteTokens, renderMarkdown, renderAsPlaintext } from '../../browser/markdownRenderer.js';
8
import { IMarkdownString, MarkdownString } from '../../common/htmlContent.js';
9
import * as marked from '../../common/marked/marked.js';
10
import { parse } from '../../common/marshalling.js';
11
import { isWeb } from '../../common/platform.js';
12
import { URI } from '../../common/uri.js';
13
import { ensureNoDisposablesAreLeakedInTestSuite } from '../common/utils.js';
14
15
function strToNode(str: string): HTMLElement {
16
return new DOMParser().parseFromString(str, 'text/html').body.firstChild as HTMLElement;
17
}
18
19
function assertNodeEquals(actualNode: HTMLElement, expectedHtml: string) {
20
const expectedNode = strToNode(expectedHtml);
21
assert.ok(
22
actualNode.isEqualNode(expectedNode),
23
`Expected: ${expectedNode.outerHTML}\nActual: ${actualNode.outerHTML}`);
24
}
25
26
suite('MarkdownRenderer', () => {
27
28
const store = ensureNoDisposablesAreLeakedInTestSuite();
29
30
suite('Sanitization', () => {
31
test('Should not render images with unknown schemes', () => {
32
const markdown = { value: `![image](no-such://example.com/cat.gif)` };
33
const result: HTMLElement = store.add(renderMarkdown(markdown)).element;
34
assert.strictEqual(result.innerHTML, '<p><img alt="image"></p>');
35
});
36
});
37
38
suite('Images', () => {
39
test('image rendering conforms to default', () => {
40
const markdown = { value: `![image](http://example.com/cat.gif 'caption')` };
41
const result: HTMLElement = store.add(renderMarkdown(markdown)).element;
42
assertNodeEquals(result, '<div><p><img title="caption" alt="image" src="http://example.com/cat.gif"></p></div>');
43
});
44
45
test('image rendering conforms to default without title', () => {
46
const markdown = { value: `![image](http://example.com/cat.gif)` };
47
const result: HTMLElement = store.add(renderMarkdown(markdown)).element;
48
assertNodeEquals(result, '<div><p><img alt="image" src="http://example.com/cat.gif"></p></div>');
49
});
50
51
test('image width from title params', () => {
52
const result: HTMLElement = store.add(renderMarkdown({ value: `![image](http://example.com/cat.gif|width=100px 'caption')` })).element;
53
assertNodeEquals(result, `<div><p><img width="100" title="caption" alt="image" src="http://example.com/cat.gif"></p></div>`);
54
});
55
56
test('image height from title params', () => {
57
const result: HTMLElement = store.add(renderMarkdown({ value: `![image](http://example.com/cat.gif|height=100 'caption')` })).element;
58
assertNodeEquals(result, `<div><p><img height="100" title="caption" alt="image" src="http://example.com/cat.gif"></p></div>`);
59
});
60
61
test('image width and height from title params', () => {
62
const result: HTMLElement = store.add(renderMarkdown({ value: `![image](http://example.com/cat.gif|height=200,width=100 'caption')` })).element;
63
assertNodeEquals(result, `<div><p><img height="200" width="100" title="caption" alt="image" src="http://example.com/cat.gif"></p></div>`);
64
});
65
66
test('image with file uri should render as same origin uri', () => {
67
if (isWeb) {
68
return;
69
}
70
const result: HTMLElement = store.add(renderMarkdown({ value: `![image](file:///images/cat.gif)` })).element;
71
assertNodeEquals(result, '<div><p><img src="vscode-file://vscode-app/images/cat.gif" alt="image"></p></div>');
72
});
73
});
74
75
suite('Code block renderer', () => {
76
const simpleCodeBlockRenderer = (lang: string, code: string): Promise<HTMLElement> => {
77
const element = document.createElement('code');
78
element.textContent = code;
79
return Promise.resolve(element);
80
};
81
82
test('asyncRenderCallback should be invoked for code blocks', () => {
83
const markdown = { value: '```js\n1 + 1;\n```' };
84
return new Promise<void>(resolve => {
85
store.add(renderMarkdown(markdown, {
86
asyncRenderCallback: resolve,
87
codeBlockRenderer: simpleCodeBlockRenderer
88
}));
89
});
90
});
91
92
test('asyncRenderCallback should not be invoked if result is immediately disposed', () => {
93
const markdown = { value: '```js\n1 + 1;\n```' };
94
return new Promise<void>((resolve, reject) => {
95
const result = renderMarkdown(markdown, {
96
asyncRenderCallback: reject,
97
codeBlockRenderer: simpleCodeBlockRenderer
98
});
99
result.dispose();
100
setTimeout(resolve, 10);
101
});
102
});
103
104
test('asyncRenderCallback should not be invoked if dispose is called before code block is rendered', () => {
105
const markdown = { value: '```js\n1 + 1;\n```' };
106
return new Promise<void>((resolve, reject) => {
107
let resolveCodeBlockRendering: (x: HTMLElement) => void;
108
const result = renderMarkdown(markdown, {
109
asyncRenderCallback: reject,
110
codeBlockRenderer: () => {
111
return new Promise(resolve => {
112
resolveCodeBlockRendering = resolve;
113
});
114
}
115
});
116
setTimeout(() => {
117
result.dispose();
118
resolveCodeBlockRendering(document.createElement('code'));
119
setTimeout(resolve, 10);
120
}, 10);
121
});
122
});
123
124
test('Code blocks should use leading language id (#157793)', async () => {
125
const markdown = { value: '```js some other stuff\n1 + 1;\n```' };
126
const lang = await new Promise<string>(resolve => {
127
store.add(renderMarkdown(markdown, {
128
codeBlockRenderer: async (lang, value) => {
129
resolve(lang);
130
return simpleCodeBlockRenderer(lang, value);
131
}
132
}));
133
});
134
assert.strictEqual(lang, 'js');
135
});
136
});
137
138
suite('ThemeIcons Support On', () => {
139
140
test('render appendText', () => {
141
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
142
mds.appendText('$(zap) $(not a theme icon) $(add)');
143
144
const result: HTMLElement = store.add(renderMarkdown(mds)).element;
145
assert.strictEqual(result.innerHTML, `<p>$(zap)&nbsp;$(not&nbsp;a&nbsp;theme&nbsp;icon)&nbsp;$(add)</p>`);
146
});
147
148
test('render appendMarkdown', () => {
149
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
150
mds.appendMarkdown('$(zap) $(not a theme icon) $(add)');
151
152
const result: HTMLElement = store.add(renderMarkdown(mds)).element;
153
assert.strictEqual(result.innerHTML, `<p><span class="codicon codicon-zap"></span> $(not a theme icon) <span class="codicon codicon-add"></span></p>`);
154
});
155
156
test('render appendMarkdown with escaped icon', () => {
157
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
158
mds.appendMarkdown('\\$(zap) $(not a theme icon) $(add)');
159
160
const result: HTMLElement = store.add(renderMarkdown(mds)).element;
161
assert.strictEqual(result.innerHTML, `<p>$(zap) $(not a theme icon) <span class="codicon codicon-add"></span></p>`);
162
});
163
164
test('render icon in link', () => {
165
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
166
mds.appendMarkdown(`[$(zap)-link](#link)`);
167
168
const result: HTMLElement = store.add(renderMarkdown(mds)).element;
169
assert.strictEqual(result.innerHTML, `<p><a draggable="false" title="#link" href="" data-href="#link"><span class="codicon codicon-zap"></span>-link</a></p>`);
170
});
171
172
test('render icon in table', () => {
173
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
174
mds.appendMarkdown(`
175
| text | text |
176
|--------|----------------------|
177
| $(zap) | [$(zap)-link](#link) |`);
178
179
const result: HTMLElement = store.add(renderMarkdown(mds)).element;
180
assert.strictEqual(result.innerHTML, `<table>
181
<thead>
182
<tr>
183
<th>text</th>
184
<th>text</th>
185
</tr>
186
</thead>
187
<tbody><tr>
188
<td><span class="codicon codicon-zap"></span></td>
189
<td><a draggable="false" title="#link" href="" data-href="#link"><span class="codicon codicon-zap"></span>-link</a></td>
190
</tr>
191
</tbody></table>
192
`);
193
});
194
195
test('render icon in <a> without href (#152170)', () => {
196
const mds = new MarkdownString(undefined, { supportThemeIcons: true, supportHtml: true });
197
mds.appendMarkdown(`<a>$(sync)</a>`);
198
199
const result: HTMLElement = store.add(renderMarkdown(mds)).element;
200
assert.strictEqual(result.innerHTML, `<p><span class="codicon codicon-sync"></span></p>`);
201
});
202
});
203
204
suite('ThemeIcons Support Off', () => {
205
206
test('render appendText', () => {
207
const mds = new MarkdownString(undefined, { supportThemeIcons: false });
208
mds.appendText('$(zap) $(not a theme icon) $(add)');
209
210
const result: HTMLElement = store.add(renderMarkdown(mds)).element;
211
assert.strictEqual(result.innerHTML, `<p>$(zap)&nbsp;$(not&nbsp;a&nbsp;theme&nbsp;icon)&nbsp;$(add)</p>`);
212
});
213
214
test('render appendMarkdown with escaped icon', () => {
215
const mds = new MarkdownString(undefined, { supportThemeIcons: false });
216
mds.appendMarkdown('\\$(zap) $(not a theme icon) $(add)');
217
218
const result: HTMLElement = store.add(renderMarkdown(mds)).element;
219
assert.strictEqual(result.innerHTML, `<p>$(zap) $(not a theme icon) $(add)</p>`);
220
});
221
});
222
223
test('npm Hover Run Script not working #90855', function () {
224
225
const md: IMarkdownString = JSON.parse('{"value":"[Run Script](command:npm.runScriptFromHover?%7B%22documentUri%22%3A%7B%22%24mid%22%3A1%2C%22fsPath%22%3A%22c%3A%5C%5CUsers%5C%5Cjrieken%5C%5CCode%5C%5C_sample%5C%5Cfoo%5C%5Cpackage.json%22%2C%22_sep%22%3A1%2C%22external%22%3A%22file%3A%2F%2F%2Fc%253A%2FUsers%2Fjrieken%2FCode%2F_sample%2Ffoo%2Fpackage.json%22%2C%22path%22%3A%22%2Fc%3A%2FUsers%2Fjrieken%2FCode%2F_sample%2Ffoo%2Fpackage.json%22%2C%22scheme%22%3A%22file%22%7D%2C%22script%22%3A%22echo%22%7D \\"Run the script as a task\\")","supportThemeIcons":false,"isTrusted":true,"uris":{"__uri_e49443":{"$mid":1,"fsPath":"c:\\\\Users\\\\jrieken\\\\Code\\\\_sample\\\\foo\\\\package.json","_sep":1,"external":"file:///c%3A/Users/jrieken/Code/_sample/foo/package.json","path":"/c:/Users/jrieken/Code/_sample/foo/package.json","scheme":"file"},"command:npm.runScriptFromHover?%7B%22documentUri%22%3A%7B%22%24mid%22%3A1%2C%22fsPath%22%3A%22c%3A%5C%5CUsers%5C%5Cjrieken%5C%5CCode%5C%5C_sample%5C%5Cfoo%5C%5Cpackage.json%22%2C%22_sep%22%3A1%2C%22external%22%3A%22file%3A%2F%2F%2Fc%253A%2FUsers%2Fjrieken%2FCode%2F_sample%2Ffoo%2Fpackage.json%22%2C%22path%22%3A%22%2Fc%3A%2FUsers%2Fjrieken%2FCode%2F_sample%2Ffoo%2Fpackage.json%22%2C%22scheme%22%3A%22file%22%7D%2C%22script%22%3A%22echo%22%7D":{"$mid":1,"path":"npm.runScriptFromHover","scheme":"command","query":"{\\"documentUri\\":\\"__uri_e49443\\",\\"script\\":\\"echo\\"}"}}}');
226
const element = store.add(renderMarkdown(md)).element;
227
228
const anchor = element.querySelector('a')!;
229
assert.ok(anchor);
230
assert.ok(anchor.dataset['href']);
231
232
const uri = URI.parse(anchor.dataset['href']!);
233
234
const data = <{ script: string; documentUri: URI }>parse(decodeURIComponent(uri.query));
235
assert.ok(data);
236
assert.strictEqual(data.script, 'echo');
237
assert.ok(data.documentUri.toString().startsWith('file:///c%3A/'));
238
});
239
240
test('Should not render command links by default', () => {
241
const md = new MarkdownString(`[command1](command:doFoo) <a href="command:doFoo">command2</a>`, {
242
supportHtml: true
243
});
244
245
const result: HTMLElement = store.add(renderMarkdown(md)).element;
246
assert.strictEqual(result.innerHTML, `<p>command1 command2</p>`);
247
});
248
249
test('Should render command links in trusted strings', () => {
250
const md = new MarkdownString(`[command1](command:doFoo) <a href="command:doFoo">command2</a>`, {
251
isTrusted: true,
252
supportHtml: true,
253
});
254
255
const result: HTMLElement = store.add(renderMarkdown(md)).element;
256
assert.strictEqual(result.innerHTML, `<p><a draggable="false" title="command:doFoo" href="" data-href="command:doFoo">command1</a> <a href="" data-href="command:doFoo">command2</a></p>`);
257
});
258
259
suite('PlaintextMarkdownRender', () => {
260
261
test('test code, blockquote, heading, list, listitem, paragraph, table, tablerow, tablecell, strong, em, br, del, text are rendered plaintext', () => {
262
const markdown = { value: '`code`\n>quote\n# heading\n- list\n\ntable | table2\n--- | --- \none | two\n\n\nbo**ld**\n_italic_\n~~del~~\nsome text' };
263
const expected = 'code\nquote\nheading\nlist\n\ntable table2\none two\nbold\nitalic\ndel\nsome text';
264
const result: string = renderAsPlaintext(markdown);
265
assert.strictEqual(result, expected);
266
});
267
268
test('test html, hr, image, link are rendered plaintext', () => {
269
const markdown = { value: '<div>html</div>\n\n---\n![image](imageLink)\n[text](textLink)' };
270
const expected = 'text';
271
const result: string = renderAsPlaintext(markdown);
272
assert.strictEqual(result, expected);
273
});
274
275
test(`Should not remove html inside of code blocks`, () => {
276
const markdown = {
277
value: [
278
'```html',
279
'<form>html</form>',
280
'```',
281
].join('\n')
282
};
283
const expected = [
284
'```',
285
'<form>html</form>',
286
'```',
287
].join('\n');
288
const result: string = renderAsPlaintext(markdown, { includeCodeBlocksFences: true });
289
assert.strictEqual(result, expected);
290
});
291
});
292
293
suite('supportHtml', () => {
294
test('supportHtml is disabled by default', () => {
295
const mds = new MarkdownString(undefined, {});
296
mds.appendMarkdown('a<b>b</b>c');
297
298
const result = store.add(renderMarkdown(mds)).element;
299
assert.strictEqual(result.innerHTML, `<p>abc</p>`);
300
});
301
302
test('Renders html when supportHtml=true', () => {
303
const mds = new MarkdownString(undefined, { supportHtml: true });
304
mds.appendMarkdown('a<b>b</b>c');
305
306
const result = store.add(renderMarkdown(mds)).element;
307
assert.strictEqual(result.innerHTML, `<p>a<b>b</b>c</p>`);
308
});
309
310
test('Should not include scripts even when supportHtml=true', () => {
311
const mds = new MarkdownString(undefined, { supportHtml: true });
312
mds.appendMarkdown('a<b onclick="alert(1)">b</b><script>alert(2)</script>c');
313
314
const result = store.add(renderMarkdown(mds)).element;
315
assert.strictEqual(result.innerHTML, `<p>a<b>b</b>c</p>`);
316
});
317
318
test('Should not render html appended as text', () => {
319
const mds = new MarkdownString(undefined, { supportHtml: true });
320
mds.appendText('a<b>b</b>c');
321
322
const result = store.add(renderMarkdown(mds)).element;
323
assert.strictEqual(result.innerHTML, `<p>a&lt;b&gt;b&lt;/b&gt;c</p>`);
324
});
325
326
test('Should render html images', () => {
327
if (isWeb) {
328
return;
329
}
330
331
const mds = new MarkdownString(undefined, { supportHtml: true });
332
mds.appendMarkdown(`<img src="http://example.com/cat.gif">`);
333
334
const result = store.add(renderMarkdown(mds)).element;
335
assert.strictEqual(result.innerHTML, `<img src="http://example.com/cat.gif">`);
336
});
337
338
test('Should render html images with file uri as same origin uri', () => {
339
if (isWeb) {
340
return;
341
}
342
343
const mds = new MarkdownString(undefined, { supportHtml: true });
344
mds.appendMarkdown(`<img src="file:///images/cat.gif">`);
345
346
const result = store.add(renderMarkdown(mds)).element;
347
assert.strictEqual(result.innerHTML, `<img src="vscode-file://vscode-app/images/cat.gif">`);
348
});
349
350
test('Should only allow checkbox inputs', () => {
351
const mds = new MarkdownString(
352
'text: <input type="text">\ncheckbox:<input type="checkbox">',
353
{ supportHtml: true });
354
355
const result = store.add(renderMarkdown(mds)).element;
356
357
// Inputs should always be disabled too
358
assert.strictEqual(result.innerHTML, `<p>text: \ncheckbox:<input type="checkbox" disabled=""></p>`);
359
});
360
});
361
362
suite('fillInIncompleteTokens', () => {
363
function ignoreRaw(...tokenLists: marked.Token[][]): void {
364
tokenLists.forEach(tokens => {
365
tokens.forEach(t => t.raw = '');
366
});
367
}
368
369
const completeTable = '| a | b |\n| --- | --- |';
370
371
suite('table', () => {
372
test('complete table', () => {
373
const tokens = marked.marked.lexer(completeTable);
374
const newTokens = fillInIncompleteTokens(tokens);
375
assert.equal(newTokens, tokens);
376
});
377
378
test('full header only', () => {
379
const incompleteTable = '| a | b |';
380
const tokens = marked.marked.lexer(incompleteTable);
381
const completeTableTokens = marked.marked.lexer(completeTable);
382
383
const newTokens = fillInIncompleteTokens(tokens);
384
assert.deepStrictEqual(newTokens, completeTableTokens);
385
});
386
387
test('full header only with trailing space', () => {
388
const incompleteTable = '| a | b | ';
389
const tokens = marked.marked.lexer(incompleteTable);
390
const completeTableTokens = marked.marked.lexer(completeTable);
391
392
const newTokens = fillInIncompleteTokens(tokens);
393
if (newTokens) {
394
ignoreRaw(newTokens, completeTableTokens);
395
}
396
assert.deepStrictEqual(newTokens, completeTableTokens);
397
});
398
399
test('incomplete header', () => {
400
const incompleteTable = '| a | b';
401
const tokens = marked.marked.lexer(incompleteTable);
402
const completeTableTokens = marked.marked.lexer(completeTable);
403
404
const newTokens = fillInIncompleteTokens(tokens);
405
406
if (newTokens) {
407
ignoreRaw(newTokens, completeTableTokens);
408
}
409
assert.deepStrictEqual(newTokens, completeTableTokens);
410
});
411
412
test('incomplete header one column', () => {
413
const incompleteTable = '| a ';
414
const tokens = marked.marked.lexer(incompleteTable);
415
const completeTableTokens = marked.marked.lexer(incompleteTable + '|\n| --- |');
416
417
const newTokens = fillInIncompleteTokens(tokens);
418
419
if (newTokens) {
420
ignoreRaw(newTokens, completeTableTokens);
421
}
422
assert.deepStrictEqual(newTokens, completeTableTokens);
423
});
424
425
test('full header with extras', () => {
426
const incompleteTable = '| a **bold** | b _italics_ |';
427
const tokens = marked.marked.lexer(incompleteTable);
428
const completeTableTokens = marked.marked.lexer(incompleteTable + '\n| --- | --- |');
429
430
const newTokens = fillInIncompleteTokens(tokens);
431
assert.deepStrictEqual(newTokens, completeTableTokens);
432
});
433
434
test('full header with leading text', () => {
435
// Parsing this gives one token and one 'text' subtoken
436
const incompleteTable = 'here is a table\n| a | b |';
437
const tokens = marked.marked.lexer(incompleteTable);
438
const completeTableTokens = marked.marked.lexer(incompleteTable + '\n| --- | --- |');
439
440
const newTokens = fillInIncompleteTokens(tokens);
441
assert.deepStrictEqual(newTokens, completeTableTokens);
442
});
443
444
test('full header with leading other stuff', () => {
445
// Parsing this gives one token and one 'text' subtoken
446
const incompleteTable = '```js\nconst xyz = 123;\n```\n| a | b |';
447
const tokens = marked.marked.lexer(incompleteTable);
448
const completeTableTokens = marked.marked.lexer(incompleteTable + '\n| --- | --- |');
449
450
const newTokens = fillInIncompleteTokens(tokens);
451
assert.deepStrictEqual(newTokens, completeTableTokens);
452
});
453
454
test('full header with incomplete separator', () => {
455
const incompleteTable = '| a | b |\n| ---';
456
const tokens = marked.marked.lexer(incompleteTable);
457
const completeTableTokens = marked.marked.lexer(completeTable);
458
459
const newTokens = fillInIncompleteTokens(tokens);
460
assert.deepStrictEqual(newTokens, completeTableTokens);
461
});
462
463
test('full header with incomplete separator 2', () => {
464
const incompleteTable = '| a | b |\n| --- |';
465
const tokens = marked.marked.lexer(incompleteTable);
466
const completeTableTokens = marked.marked.lexer(completeTable);
467
468
const newTokens = fillInIncompleteTokens(tokens);
469
assert.deepStrictEqual(newTokens, completeTableTokens);
470
});
471
472
test('full header with incomplete separator 3', () => {
473
const incompleteTable = '| a | b |\n|';
474
const tokens = marked.marked.lexer(incompleteTable);
475
const completeTableTokens = marked.marked.lexer(completeTable);
476
477
const newTokens = fillInIncompleteTokens(tokens);
478
assert.deepStrictEqual(newTokens, completeTableTokens);
479
});
480
481
test('not a table', () => {
482
const incompleteTable = '| a | b |\nsome text';
483
const tokens = marked.marked.lexer(incompleteTable);
484
485
const newTokens = fillInIncompleteTokens(tokens);
486
assert.deepStrictEqual(newTokens, tokens);
487
});
488
489
test('not a table 2', () => {
490
const incompleteTable = '| a | b |\n| --- |\nsome text';
491
const tokens = marked.marked.lexer(incompleteTable);
492
493
const newTokens = fillInIncompleteTokens(tokens);
494
assert.deepStrictEqual(newTokens, tokens);
495
});
496
});
497
498
function simpleMarkdownTestSuite(name: string, delimiter: string): void {
499
test(`incomplete ${name}`, () => {
500
const incomplete = `${delimiter}code`;
501
const tokens = marked.marked.lexer(incomplete);
502
const newTokens = fillInIncompleteTokens(tokens);
503
504
const completeTokens = marked.marked.lexer(incomplete + delimiter);
505
assert.deepStrictEqual(newTokens, completeTokens);
506
});
507
508
test(`complete ${name}`, () => {
509
const text = `leading text ${delimiter}code${delimiter} trailing text`;
510
const tokens = marked.marked.lexer(text);
511
const newTokens = fillInIncompleteTokens(tokens);
512
513
assert.deepStrictEqual(newTokens, tokens);
514
});
515
516
test(`${name} with leading text`, () => {
517
const incomplete = `some text and ${delimiter}some code`;
518
const tokens = marked.marked.lexer(incomplete);
519
const newTokens = fillInIncompleteTokens(tokens);
520
521
const completeTokens = marked.marked.lexer(incomplete + delimiter);
522
assert.deepStrictEqual(newTokens, completeTokens);
523
});
524
525
test(`${name} with trailing space`, () => {
526
const incomplete = `some text and ${delimiter}some code `;
527
const tokens = marked.marked.lexer(incomplete);
528
const newTokens = fillInIncompleteTokens(tokens);
529
530
const completeTokens = marked.marked.lexer(incomplete.trimEnd() + delimiter);
531
assert.deepStrictEqual(newTokens, completeTokens);
532
});
533
534
test(`single loose "${delimiter}"`, () => {
535
const text = `some text and ${delimiter}by itself\nmore text here`;
536
const tokens = marked.marked.lexer(text);
537
const newTokens = fillInIncompleteTokens(tokens);
538
539
assert.deepStrictEqual(newTokens, tokens);
540
});
541
542
test(`incomplete ${name} after newline`, () => {
543
const text = `some text\nmore text here and ${delimiter}text`;
544
const tokens = marked.marked.lexer(text);
545
const newTokens = fillInIncompleteTokens(tokens);
546
547
const completeTokens = marked.marked.lexer(text + delimiter);
548
assert.deepStrictEqual(newTokens, completeTokens);
549
});
550
551
test(`incomplete after complete ${name}`, () => {
552
const text = `leading text ${delimiter}code${delimiter} trailing text and ${delimiter}another`;
553
const tokens = marked.marked.lexer(text);
554
const newTokens = fillInIncompleteTokens(tokens);
555
556
const completeTokens = marked.marked.lexer(text + delimiter);
557
assert.deepStrictEqual(newTokens, completeTokens);
558
});
559
560
test(`incomplete ${name} in list`, () => {
561
const text = `- list item one\n- list item two and ${delimiter}text`;
562
const tokens = marked.marked.lexer(text);
563
const newTokens = fillInIncompleteTokens(tokens);
564
565
const completeTokens = marked.marked.lexer(text + delimiter);
566
assert.deepStrictEqual(newTokens, completeTokens);
567
});
568
569
test(`incomplete ${name} in asterisk list`, () => {
570
const text = `* list item one\n* list item two and ${delimiter}text`;
571
const tokens = marked.marked.lexer(text);
572
const newTokens = fillInIncompleteTokens(tokens);
573
574
const completeTokens = marked.marked.lexer(text + delimiter);
575
assert.deepStrictEqual(newTokens, completeTokens);
576
});
577
578
test(`incomplete ${name} in numbered list`, () => {
579
const text = `1. list item one\n2. list item two and ${delimiter}text`;
580
const tokens = marked.marked.lexer(text);
581
const newTokens = fillInIncompleteTokens(tokens);
582
583
const completeTokens = marked.marked.lexer(text + delimiter);
584
assert.deepStrictEqual(newTokens, completeTokens);
585
});
586
}
587
588
suite('list', () => {
589
test('list with complete codeblock', () => {
590
const list = `-
591
\`\`\`js
592
let x = 1;
593
\`\`\`
594
- list item two
595
`;
596
const tokens = marked.marked.lexer(list);
597
const newTokens = fillInIncompleteTokens(tokens);
598
599
assert.deepStrictEqual(newTokens, tokens);
600
});
601
602
test.skip('list with incomplete codeblock', () => {
603
const incomplete = `- list item one
604
605
\`\`\`js
606
let x = 1;`;
607
const tokens = marked.marked.lexer(incomplete);
608
const newTokens = fillInIncompleteTokens(tokens);
609
610
const completeTokens = marked.marked.lexer(incomplete + '\n ```');
611
assert.deepStrictEqual(newTokens, completeTokens);
612
});
613
614
test('list with subitems', () => {
615
const list = `- hello
616
- sub item
617
- text
618
newline for some reason
619
`;
620
const tokens = marked.marked.lexer(list);
621
const newTokens = fillInIncompleteTokens(tokens);
622
623
assert.deepStrictEqual(newTokens, tokens);
624
});
625
626
test('ordered list with subitems', () => {
627
const list = `1. hello
628
- sub item
629
2. text
630
newline for some reason
631
`;
632
const tokens = marked.marked.lexer(list);
633
const newTokens = fillInIncompleteTokens(tokens);
634
635
assert.deepStrictEqual(newTokens, tokens);
636
});
637
638
test('list with stuff', () => {
639
const list = `- list item one \`codespan\` **bold** [link](http://microsoft.com) more text`;
640
const tokens = marked.marked.lexer(list);
641
const newTokens = fillInIncompleteTokens(tokens);
642
643
assert.deepStrictEqual(newTokens, tokens);
644
});
645
646
test('list with incomplete link text', () => {
647
const incomplete = `- list item one
648
- item two [link`;
649
const tokens = marked.marked.lexer(incomplete);
650
const newTokens = fillInIncompleteTokens(tokens);
651
652
const completeTokens = marked.marked.lexer(incomplete + '](https://microsoft.com)');
653
assert.deepStrictEqual(newTokens, completeTokens);
654
});
655
656
test('list with incomplete link target', () => {
657
const incomplete = `- list item one
658
- item two [link](`;
659
const tokens = marked.marked.lexer(incomplete);
660
const newTokens = fillInIncompleteTokens(tokens);
661
662
const completeTokens = marked.marked.lexer(incomplete + ')');
663
assert.deepStrictEqual(newTokens, completeTokens);
664
});
665
666
test('ordered list with incomplete link target', () => {
667
const incomplete = `1. list item one
668
2. item two [link](`;
669
const tokens = marked.marked.lexer(incomplete);
670
const newTokens = fillInIncompleteTokens(tokens);
671
672
const completeTokens = marked.marked.lexer(incomplete + ')');
673
assert.deepStrictEqual(newTokens, completeTokens);
674
});
675
676
test('ordered list with extra whitespace', () => {
677
const incomplete = `1. list item one
678
2. item two [link](`;
679
const tokens = marked.marked.lexer(incomplete);
680
const newTokens = fillInIncompleteTokens(tokens);
681
682
const completeTokens = marked.marked.lexer(incomplete + ')');
683
assert.deepStrictEqual(newTokens, completeTokens);
684
});
685
686
test('list with extra whitespace', () => {
687
const incomplete = `- list item one
688
- item two [link](`;
689
const tokens = marked.marked.lexer(incomplete);
690
const newTokens = fillInIncompleteTokens(tokens);
691
692
const completeTokens = marked.marked.lexer(incomplete + ')');
693
assert.deepStrictEqual(newTokens, completeTokens);
694
});
695
696
test('list with incomplete link with other stuff', () => {
697
const incomplete = `- list item one
698
- item two [\`link`;
699
const tokens = marked.marked.lexer(incomplete);
700
const newTokens = fillInIncompleteTokens(tokens);
701
702
const completeTokens = marked.marked.lexer(incomplete + '\`](https://microsoft.com)');
703
assert.deepStrictEqual(newTokens, completeTokens);
704
});
705
706
test('ordered list with incomplete link with other stuff', () => {
707
const incomplete = `1. list item one
708
1. item two [\`link`;
709
const tokens = marked.marked.lexer(incomplete);
710
const newTokens = fillInIncompleteTokens(tokens);
711
712
const completeTokens = marked.marked.lexer(incomplete + '\`](https://microsoft.com)');
713
assert.deepStrictEqual(newTokens, completeTokens);
714
});
715
716
test('list with incomplete subitem', () => {
717
const incomplete = `1. list item one
718
- `;
719
const tokens = marked.marked.lexer(incomplete);
720
const newTokens = fillInIncompleteTokens(tokens);
721
722
const completeTokens = marked.marked.lexer(incomplete + '&nbsp;');
723
assert.deepStrictEqual(newTokens, completeTokens);
724
});
725
726
test('list with incomplete nested subitem', () => {
727
const incomplete = `1. list item one
728
- item 2
729
- `;
730
const tokens = marked.marked.lexer(incomplete);
731
const newTokens = fillInIncompleteTokens(tokens);
732
733
const completeTokens = marked.marked.lexer(incomplete + '&nbsp;');
734
assert.deepStrictEqual(newTokens, completeTokens);
735
});
736
737
test('text with start of list is not a heading', () => {
738
const incomplete = `hello\n- `;
739
const tokens = marked.marked.lexer(incomplete);
740
const newTokens = fillInIncompleteTokens(tokens);
741
742
const completeTokens = marked.marked.lexer(incomplete + ' &nbsp;');
743
assert.deepStrictEqual(newTokens, completeTokens);
744
});
745
746
test('even more text with start of list is not a heading', () => {
747
const incomplete = `# hello\n\ntext\n-`;
748
const tokens = marked.marked.lexer(incomplete);
749
const newTokens = fillInIncompleteTokens(tokens);
750
751
const completeTokens = marked.marked.lexer(incomplete + ' &nbsp;');
752
assert.deepStrictEqual(newTokens, completeTokens);
753
});
754
});
755
756
suite('codespan', () => {
757
simpleMarkdownTestSuite('codespan', '`');
758
759
test(`backtick between letters`, () => {
760
const text = 'a`b';
761
const tokens = marked.marked.lexer(text);
762
const newTokens = fillInIncompleteTokens(tokens);
763
764
const completeCodespanTokens = marked.marked.lexer(text + '`');
765
assert.deepStrictEqual(newTokens, completeCodespanTokens);
766
});
767
768
test(`nested pattern`, () => {
769
const text = 'sldkfjsd `abc __def__ ghi';
770
const tokens = marked.marked.lexer(text);
771
const newTokens = fillInIncompleteTokens(tokens);
772
773
const completeTokens = marked.marked.lexer(text + '`');
774
assert.deepStrictEqual(newTokens, completeTokens);
775
});
776
});
777
778
suite('star', () => {
779
simpleMarkdownTestSuite('star', '*');
780
781
test(`star between letters`, () => {
782
const text = 'sldkfjsd a*b';
783
const tokens = marked.marked.lexer(text);
784
const newTokens = fillInIncompleteTokens(tokens);
785
786
const completeTokens = marked.marked.lexer(text + '*');
787
assert.deepStrictEqual(newTokens, completeTokens);
788
});
789
790
test(`nested pattern`, () => {
791
const text = 'sldkfjsd *abc __def__ ghi';
792
const tokens = marked.marked.lexer(text);
793
const newTokens = fillInIncompleteTokens(tokens);
794
795
const completeTokens = marked.marked.lexer(text + '*');
796
assert.deepStrictEqual(newTokens, completeTokens);
797
});
798
});
799
800
suite('double star', () => {
801
simpleMarkdownTestSuite('double star', '**');
802
803
test(`double star between letters`, () => {
804
const text = 'a**b';
805
const tokens = marked.marked.lexer(text);
806
const newTokens = fillInIncompleteTokens(tokens);
807
808
const completeTokens = marked.marked.lexer(text + '**');
809
assert.deepStrictEqual(newTokens, completeTokens);
810
});
811
812
// TODO trim these patterns from end
813
test.skip(`ending in doublestar`, () => {
814
const incomplete = `some text and **`;
815
const tokens = marked.marked.lexer(incomplete);
816
const newTokens = fillInIncompleteTokens(tokens);
817
818
const completeTokens = marked.marked.lexer(incomplete.trimEnd() + '**');
819
assert.deepStrictEqual(newTokens, completeTokens);
820
});
821
});
822
823
suite('underscore', () => {
824
simpleMarkdownTestSuite('underscore', '_');
825
826
test(`underscore between letters`, () => {
827
const text = `this_not_italics`;
828
const tokens = marked.marked.lexer(text);
829
const newTokens = fillInIncompleteTokens(tokens);
830
831
assert.deepStrictEqual(newTokens, tokens);
832
});
833
});
834
835
suite('double underscore', () => {
836
simpleMarkdownTestSuite('double underscore', '__');
837
838
test(`double underscore between letters`, () => {
839
const text = `this__not__bold`;
840
const tokens = marked.marked.lexer(text);
841
const newTokens = fillInIncompleteTokens(tokens);
842
843
assert.deepStrictEqual(newTokens, tokens);
844
});
845
});
846
847
suite('link', () => {
848
test('incomplete link text', () => {
849
const incomplete = 'abc [text';
850
const tokens = marked.marked.lexer(incomplete);
851
const newTokens = fillInIncompleteTokens(tokens);
852
853
const completeTokens = marked.marked.lexer(incomplete + '](https://microsoft.com)');
854
assert.deepStrictEqual(newTokens, completeTokens);
855
});
856
857
test('incomplete link target', () => {
858
const incomplete = 'foo [text](http://microsoft';
859
const tokens = marked.marked.lexer(incomplete);
860
const newTokens = fillInIncompleteTokens(tokens);
861
862
const completeTokens = marked.marked.lexer(incomplete + ')');
863
assert.deepStrictEqual(newTokens, completeTokens);
864
});
865
866
test('incomplete link target 2', () => {
867
const incomplete = 'foo [text](http://microsoft.com';
868
const tokens = marked.marked.lexer(incomplete);
869
const newTokens = fillInIncompleteTokens(tokens);
870
871
const completeTokens = marked.marked.lexer(incomplete + ')');
872
assert.deepStrictEqual(newTokens, completeTokens);
873
});
874
875
test('incomplete link target with extra stuff', () => {
876
const incomplete = '[before `text` after](http://microsoft.com';
877
const tokens = marked.marked.lexer(incomplete);
878
const newTokens = fillInIncompleteTokens(tokens);
879
880
const completeTokens = marked.marked.lexer(incomplete + ')');
881
assert.deepStrictEqual(newTokens, completeTokens);
882
});
883
884
test('incomplete link target with extra stuff and incomplete arg', () => {
885
const incomplete = '[before `text` after](http://microsoft.com "more text ';
886
const tokens = marked.marked.lexer(incomplete);
887
const newTokens = fillInIncompleteTokens(tokens);
888
889
const completeTokens = marked.marked.lexer(incomplete + '")');
890
assert.deepStrictEqual(newTokens, completeTokens);
891
});
892
893
test('incomplete link target with incomplete arg', () => {
894
const incomplete = 'foo [text](http://microsoft.com "more text here ';
895
const tokens = marked.marked.lexer(incomplete);
896
const newTokens = fillInIncompleteTokens(tokens);
897
898
const completeTokens = marked.marked.lexer(incomplete + '")');
899
assert.deepStrictEqual(newTokens, completeTokens);
900
});
901
902
test('incomplete link target with incomplete arg 2', () => {
903
const incomplete = '[text](command:vscode.openRelativePath "arg';
904
const tokens = marked.marked.lexer(incomplete);
905
const newTokens = fillInIncompleteTokens(tokens);
906
907
const completeTokens = marked.marked.lexer(incomplete + '")');
908
assert.deepStrictEqual(newTokens, completeTokens);
909
});
910
911
test('incomplete link target with complete arg', () => {
912
const incomplete = 'foo [text](http://microsoft.com "more text here"';
913
const tokens = marked.marked.lexer(incomplete);
914
const newTokens = fillInIncompleteTokens(tokens);
915
916
const completeTokens = marked.marked.lexer(incomplete + ')');
917
assert.deepStrictEqual(newTokens, completeTokens);
918
});
919
920
test('link text with incomplete codespan', () => {
921
const incomplete = `text [\`codespan`;
922
const tokens = marked.marked.lexer(incomplete);
923
const newTokens = fillInIncompleteTokens(tokens);
924
925
const completeTokens = marked.marked.lexer(incomplete + '`](https://microsoft.com)');
926
assert.deepStrictEqual(newTokens, completeTokens);
927
});
928
929
test('link text with incomplete stuff', () => {
930
const incomplete = `text [more text \`codespan\` text **bold`;
931
const tokens = marked.marked.lexer(incomplete);
932
const newTokens = fillInIncompleteTokens(tokens);
933
934
const completeTokens = marked.marked.lexer(incomplete + '**](https://microsoft.com)');
935
assert.deepStrictEqual(newTokens, completeTokens);
936
});
937
938
test('Looks like incomplete link target but isn\'t', () => {
939
const complete = '**bold** `codespan` text](';
940
const tokens = marked.marked.lexer(complete);
941
const newTokens = fillInIncompleteTokens(tokens);
942
943
const completeTokens = marked.marked.lexer(complete);
944
assert.deepStrictEqual(newTokens, completeTokens);
945
});
946
947
test.skip('incomplete link in list', () => {
948
const incomplete = '- [text';
949
const tokens = marked.marked.lexer(incomplete);
950
const newTokens = fillInIncompleteTokens(tokens);
951
952
const completeTokens = marked.marked.lexer(incomplete + '](https://microsoft.com)');
953
assert.deepStrictEqual(newTokens, completeTokens);
954
});
955
956
test('square brace between letters', () => {
957
const incomplete = 'a[b';
958
const tokens = marked.marked.lexer(incomplete);
959
const newTokens = fillInIncompleteTokens(tokens);
960
961
assert.deepStrictEqual(newTokens, tokens);
962
});
963
964
test('square brace on previous line', () => {
965
const incomplete = 'text[\nmore text';
966
const tokens = marked.marked.lexer(incomplete);
967
const newTokens = fillInIncompleteTokens(tokens);
968
969
assert.deepStrictEqual(newTokens, tokens);
970
});
971
972
test('square braces in text', () => {
973
const incomplete = 'hello [what] is going on';
974
const tokens = marked.marked.lexer(incomplete);
975
const newTokens = fillInIncompleteTokens(tokens);
976
977
assert.deepStrictEqual(newTokens, tokens);
978
});
979
980
test('complete link', () => {
981
const incomplete = 'text [link](http://microsoft.com)';
982
const tokens = marked.marked.lexer(incomplete);
983
const newTokens = fillInIncompleteTokens(tokens);
984
985
assert.deepStrictEqual(newTokens, tokens);
986
});
987
});
988
});
989
});
990
991