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
5240 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 href="" title="#link" draggable="false" 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 href="" title="#link" draggable="false" 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
suite('Alerts', () => {
224
test('Should render alert with data-severity attribute and icon', () => {
225
const markdown = new MarkdownString('> [!NOTE]\n> This is a note alert', { supportAlertSyntax: true });
226
const result = store.add(renderMarkdown(markdown)).element;
227
228
const blockquote = result.querySelector('blockquote[data-severity="note"]');
229
assert.ok(blockquote, 'Should have blockquote with data-severity="note"');
230
assert.ok(result.innerHTML.includes('This is a note alert'), 'Should contain alert text');
231
assert.ok(result.innerHTML.includes('codicon-info'), 'Should contain info icon');
232
});
233
234
test('Should render regular blockquote when supportAlertSyntax is disabled', () => {
235
const markdown = new MarkdownString('> [!NOTE]\n> This should be a regular blockquote');
236
const result = store.add(renderMarkdown(markdown)).element;
237
238
const blockquote = result.querySelector('blockquote');
239
assert.ok(blockquote, 'Should have blockquote');
240
assert.strictEqual(blockquote?.getAttribute('data-severity'), null, 'Should not have data-severity attribute');
241
assert.ok(result.innerHTML.includes('[!NOTE]'), 'Should contain literal [!NOTE] text');
242
});
243
244
test('Should not transform blockquotes without alert syntax', () => {
245
const markdown = new MarkdownString('> This is a regular blockquote', { supportAlertSyntax: true });
246
const result = store.add(renderMarkdown(markdown)).element;
247
248
const blockquote = result.querySelector('blockquote');
249
assert.strictEqual(blockquote?.getAttribute('data-severity'), null, 'Should not have data-severity attribute');
250
});
251
});
252
253
test('npm Hover Run Script not working #90855', function () {
254
255
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\\"}"}}}');
256
const element = store.add(renderMarkdown(md)).element;
257
258
const anchor = element.querySelector('a')!;
259
assert.ok(anchor);
260
assert.ok(anchor.dataset['href']);
261
262
const uri = URI.parse(anchor.dataset['href']!);
263
264
const data = <{ script: string; documentUri: URI }>parse(decodeURIComponent(uri.query));
265
assert.ok(data);
266
assert.strictEqual(data.script, 'echo');
267
assert.ok(data.documentUri.toString().startsWith('file:///c%3A/'));
268
});
269
270
test('Should not render command links by default', () => {
271
const md = new MarkdownString(`[command1](command:doFoo) <a href="command:doFoo">command2</a>`, {
272
supportHtml: true
273
});
274
275
const result: HTMLElement = store.add(renderMarkdown(md)).element;
276
assert.strictEqual(result.innerHTML, `<p>command1 command2</p>`);
277
});
278
279
test('Should render command links in trusted strings', () => {
280
const md = new MarkdownString(`[command1](command:doFoo) <a href="command:doFoo">command2</a>`, {
281
isTrusted: true,
282
supportHtml: true,
283
});
284
285
const result: HTMLElement = store.add(renderMarkdown(md)).element;
286
assert.strictEqual(result.innerHTML, `<p><a href="" title="command:doFoo" draggable="false" data-href="command:doFoo">command1</a> <a href="" data-href="command:doFoo">command2</a></p>`);
287
});
288
289
test('Should remove relative links if there is no base url', () => {
290
const md = new MarkdownString(`[text](./foo) <a href="./bar">bar</a>`, {
291
isTrusted: true,
292
supportHtml: true,
293
});
294
295
const result = store.add(renderMarkdown(md)).element;
296
assert.strictEqual(result.innerHTML, `<p>text bar</p>`);
297
});
298
299
test('Should support relative links if baseurl is set', () => {
300
const md = new MarkdownString(`[text](./foo) <a href="./bar">bar</a> <img src="cat.gif">`, {
301
isTrusted: true,
302
supportHtml: true,
303
});
304
md.baseUri = URI.parse('https://example.com/path/');
305
306
const result = store.add(renderMarkdown(md)).element;
307
assert.strictEqual(result.innerHTML, `<p><a href="" title="./foo" draggable="false" data-href="https://example.com/path/foo">text</a> <a href="" data-href="https://example.com/path/bar">bar</a> <img src="https://example.com/path/cat.gif"></p>`);
308
});
309
310
suite('PlaintextMarkdownRender', () => {
311
312
test('test code, blockquote, heading, list, listitem, paragraph, table, tablerow, tablecell, strong, em, br, del, text are rendered plaintext', () => {
313
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' };
314
const expected = 'code\nquote\nheading\nlist\n\ntable table2\none two\nbold\nitalic\ndel\nsome text';
315
const result: string = renderAsPlaintext(markdown);
316
assert.strictEqual(result, expected);
317
});
318
319
test('test html, hr, image, link are rendered plaintext', () => {
320
const markdown = { value: '<div>html</div>\n\n---\n![image](imageLink)\n[text](textLink)' };
321
const expected = 'text';
322
const result: string = renderAsPlaintext(markdown);
323
assert.strictEqual(result, expected);
324
});
325
326
test(`Should not remove html inside of code blocks`, () => {
327
const markdown = {
328
value: [
329
'```html',
330
'<form>html</form>',
331
'```',
332
].join('\n')
333
};
334
const expected = [
335
'```',
336
'<form>html</form>',
337
'```',
338
].join('\n');
339
const result: string = renderAsPlaintext(markdown, { includeCodeBlocksFences: true });
340
assert.strictEqual(result, expected);
341
});
342
});
343
344
suite('supportHtml', () => {
345
test('supportHtml is disabled by default', () => {
346
const mds = new MarkdownString(undefined, {});
347
mds.appendMarkdown('a<b>b</b>c');
348
349
const result = store.add(renderMarkdown(mds)).element;
350
assert.strictEqual(result.innerHTML, `<p>abc</p>`);
351
});
352
353
test('Renders html when supportHtml=true', () => {
354
const mds = new MarkdownString(undefined, { supportHtml: true });
355
mds.appendMarkdown('a<b>b</b>c');
356
357
const result = store.add(renderMarkdown(mds)).element;
358
assert.strictEqual(result.innerHTML, `<p>a<b>b</b>c</p>`);
359
});
360
361
test('Should not include scripts even when supportHtml=true', () => {
362
const mds = new MarkdownString(undefined, { supportHtml: true });
363
mds.appendMarkdown('a<b onclick="alert(1)">b</b><script>alert(2)</script>c');
364
365
const result = store.add(renderMarkdown(mds)).element;
366
assert.strictEqual(result.innerHTML, `<p>a<b>b</b>c</p>`);
367
});
368
369
test('Should not render html appended as text', () => {
370
const mds = new MarkdownString(undefined, { supportHtml: true });
371
mds.appendText('a<b>b</b>c');
372
373
const result = store.add(renderMarkdown(mds)).element;
374
assert.strictEqual(result.innerHTML, `<p>a&lt;b&gt;b&lt;/b&gt;c</p>`);
375
});
376
377
test('Should render html images', () => {
378
if (isWeb) {
379
return;
380
}
381
382
const mds = new MarkdownString(undefined, { supportHtml: true });
383
mds.appendMarkdown(`<img src="http://example.com/cat.gif">`);
384
385
const result = store.add(renderMarkdown(mds)).element;
386
assert.strictEqual(result.innerHTML, `<img src="http://example.com/cat.gif">`);
387
});
388
389
test('Should render html images with file uri as same origin uri', () => {
390
if (isWeb) {
391
return;
392
}
393
394
const mds = new MarkdownString(undefined, { supportHtml: true });
395
mds.appendMarkdown(`<img src="file:///images/cat.gif">`);
396
397
const result = store.add(renderMarkdown(mds)).element;
398
assert.strictEqual(result.innerHTML, `<img src="vscode-file://vscode-app/images/cat.gif">`);
399
});
400
401
test('Should only allow checkbox inputs', () => {
402
const mds = new MarkdownString(
403
'text: <input type="text">\ncheckbox:<input type="checkbox">',
404
{ supportHtml: true });
405
406
const result = store.add(renderMarkdown(mds)).element;
407
408
// Inputs should always be disabled too
409
assert.strictEqual(result.innerHTML, `<p>text: \ncheckbox:<input type="checkbox" disabled=""></p>`);
410
});
411
});
412
413
suite('fillInIncompleteTokens', () => {
414
function ignoreRaw(...tokenLists: marked.Token[][]): void {
415
tokenLists.forEach(tokens => {
416
tokens.forEach(t => t.raw = '');
417
});
418
}
419
420
const completeTable = '| a | b |\n| --- | --- |';
421
422
suite('table', () => {
423
test('complete table', () => {
424
const tokens = marked.marked.lexer(completeTable);
425
const newTokens = fillInIncompleteTokens(tokens);
426
assert.equal(newTokens, tokens);
427
});
428
429
test('full header only', () => {
430
const incompleteTable = '| a | b |';
431
const tokens = marked.marked.lexer(incompleteTable);
432
const completeTableTokens = marked.marked.lexer(completeTable);
433
434
const newTokens = fillInIncompleteTokens(tokens);
435
assert.deepStrictEqual(newTokens, completeTableTokens);
436
});
437
438
test('full header only with trailing space', () => {
439
const incompleteTable = '| a | b | ';
440
const tokens = marked.marked.lexer(incompleteTable);
441
const completeTableTokens = marked.marked.lexer(completeTable);
442
443
const newTokens = fillInIncompleteTokens(tokens);
444
if (newTokens) {
445
ignoreRaw(newTokens, completeTableTokens);
446
}
447
assert.deepStrictEqual(newTokens, completeTableTokens);
448
});
449
450
test('incomplete header', () => {
451
const incompleteTable = '| a | b';
452
const tokens = marked.marked.lexer(incompleteTable);
453
const completeTableTokens = marked.marked.lexer(completeTable);
454
455
const newTokens = fillInIncompleteTokens(tokens);
456
457
if (newTokens) {
458
ignoreRaw(newTokens, completeTableTokens);
459
}
460
assert.deepStrictEqual(newTokens, completeTableTokens);
461
});
462
463
test('incomplete header one column', () => {
464
const incompleteTable = '| a ';
465
const tokens = marked.marked.lexer(incompleteTable);
466
const completeTableTokens = marked.marked.lexer(incompleteTable + '|\n| --- |');
467
468
const newTokens = fillInIncompleteTokens(tokens);
469
470
if (newTokens) {
471
ignoreRaw(newTokens, completeTableTokens);
472
}
473
assert.deepStrictEqual(newTokens, completeTableTokens);
474
});
475
476
test('full header with extras', () => {
477
const incompleteTable = '| a **bold** | b _italics_ |';
478
const tokens = marked.marked.lexer(incompleteTable);
479
const completeTableTokens = marked.marked.lexer(incompleteTable + '\n| --- | --- |');
480
481
const newTokens = fillInIncompleteTokens(tokens);
482
assert.deepStrictEqual(newTokens, completeTableTokens);
483
});
484
485
test('full header with leading text', () => {
486
// Parsing this gives one token and one 'text' subtoken
487
const incompleteTable = 'here is a table\n| a | b |';
488
const tokens = marked.marked.lexer(incompleteTable);
489
const completeTableTokens = marked.marked.lexer(incompleteTable + '\n| --- | --- |');
490
491
const newTokens = fillInIncompleteTokens(tokens);
492
assert.deepStrictEqual(newTokens, completeTableTokens);
493
});
494
495
test('full header with leading other stuff', () => {
496
// Parsing this gives one token and one 'text' subtoken
497
const incompleteTable = '```js\nconst xyz = 123;\n```\n| a | b |';
498
const tokens = marked.marked.lexer(incompleteTable);
499
const completeTableTokens = marked.marked.lexer(incompleteTable + '\n| --- | --- |');
500
501
const newTokens = fillInIncompleteTokens(tokens);
502
assert.deepStrictEqual(newTokens, completeTableTokens);
503
});
504
505
test('full header with incomplete separator', () => {
506
const incompleteTable = '| a | b |\n| ---';
507
const tokens = marked.marked.lexer(incompleteTable);
508
const completeTableTokens = marked.marked.lexer(completeTable);
509
510
const newTokens = fillInIncompleteTokens(tokens);
511
assert.deepStrictEqual(newTokens, completeTableTokens);
512
});
513
514
test('full header with incomplete separator 2', () => {
515
const incompleteTable = '| a | b |\n| --- |';
516
const tokens = marked.marked.lexer(incompleteTable);
517
const completeTableTokens = marked.marked.lexer(completeTable);
518
519
const newTokens = fillInIncompleteTokens(tokens);
520
assert.deepStrictEqual(newTokens, completeTableTokens);
521
});
522
523
test('full header with incomplete separator 3', () => {
524
const incompleteTable = '| a | b |\n|';
525
const tokens = marked.marked.lexer(incompleteTable);
526
const completeTableTokens = marked.marked.lexer(completeTable);
527
528
const newTokens = fillInIncompleteTokens(tokens);
529
assert.deepStrictEqual(newTokens, completeTableTokens);
530
});
531
532
test('not a table', () => {
533
const incompleteTable = '| a | b |\nsome text';
534
const tokens = marked.marked.lexer(incompleteTable);
535
536
const newTokens = fillInIncompleteTokens(tokens);
537
assert.deepStrictEqual(newTokens, tokens);
538
});
539
540
test('not a table 2', () => {
541
const incompleteTable = '| a | b |\n| --- |\nsome text';
542
const tokens = marked.marked.lexer(incompleteTable);
543
544
const newTokens = fillInIncompleteTokens(tokens);
545
assert.deepStrictEqual(newTokens, tokens);
546
});
547
});
548
549
function simpleMarkdownTestSuite(name: string, delimiter: string): void {
550
test(`incomplete ${name}`, () => {
551
const incomplete = `${delimiter}code`;
552
const tokens = marked.marked.lexer(incomplete);
553
const newTokens = fillInIncompleteTokens(tokens);
554
555
const completeTokens = marked.marked.lexer(incomplete + delimiter);
556
assert.deepStrictEqual(newTokens, completeTokens);
557
});
558
559
test(`complete ${name}`, () => {
560
const text = `leading text ${delimiter}code${delimiter} trailing text`;
561
const tokens = marked.marked.lexer(text);
562
const newTokens = fillInIncompleteTokens(tokens);
563
564
assert.deepStrictEqual(newTokens, tokens);
565
});
566
567
test(`${name} with leading text`, () => {
568
const incomplete = `some text and ${delimiter}some code`;
569
const tokens = marked.marked.lexer(incomplete);
570
const newTokens = fillInIncompleteTokens(tokens);
571
572
const completeTokens = marked.marked.lexer(incomplete + delimiter);
573
assert.deepStrictEqual(newTokens, completeTokens);
574
});
575
576
test(`${name} with trailing space`, () => {
577
const incomplete = `some text and ${delimiter}some code `;
578
const tokens = marked.marked.lexer(incomplete);
579
const newTokens = fillInIncompleteTokens(tokens);
580
581
const completeTokens = marked.marked.lexer(incomplete.trimEnd() + delimiter);
582
assert.deepStrictEqual(newTokens, completeTokens);
583
});
584
585
test(`single loose "${delimiter}"`, () => {
586
const text = `some text and ${delimiter}by itself\nmore text here`;
587
const tokens = marked.marked.lexer(text);
588
const newTokens = fillInIncompleteTokens(tokens);
589
590
assert.deepStrictEqual(newTokens, tokens);
591
});
592
593
test(`incomplete ${name} after newline`, () => {
594
const text = `some text\nmore text here and ${delimiter}text`;
595
const tokens = marked.marked.lexer(text);
596
const newTokens = fillInIncompleteTokens(tokens);
597
598
const completeTokens = marked.marked.lexer(text + delimiter);
599
assert.deepStrictEqual(newTokens, completeTokens);
600
});
601
602
test(`incomplete after complete ${name}`, () => {
603
const text = `leading text ${delimiter}code${delimiter} trailing text and ${delimiter}another`;
604
const tokens = marked.marked.lexer(text);
605
const newTokens = fillInIncompleteTokens(tokens);
606
607
const completeTokens = marked.marked.lexer(text + delimiter);
608
assert.deepStrictEqual(newTokens, completeTokens);
609
});
610
611
test(`incomplete ${name} in list`, () => {
612
const text = `- list item one\n- list item two and ${delimiter}text`;
613
const tokens = marked.marked.lexer(text);
614
const newTokens = fillInIncompleteTokens(tokens);
615
616
const completeTokens = marked.marked.lexer(text + delimiter);
617
assert.deepStrictEqual(newTokens, completeTokens);
618
});
619
620
test(`incomplete ${name} in asterisk list`, () => {
621
const text = `* list item one\n* list item two and ${delimiter}text`;
622
const tokens = marked.marked.lexer(text);
623
const newTokens = fillInIncompleteTokens(tokens);
624
625
const completeTokens = marked.marked.lexer(text + delimiter);
626
assert.deepStrictEqual(newTokens, completeTokens);
627
});
628
629
test(`incomplete ${name} in numbered list`, () => {
630
const text = `1. list item one\n2. list item two and ${delimiter}text`;
631
const tokens = marked.marked.lexer(text);
632
const newTokens = fillInIncompleteTokens(tokens);
633
634
const completeTokens = marked.marked.lexer(text + delimiter);
635
assert.deepStrictEqual(newTokens, completeTokens);
636
});
637
}
638
639
suite('list', () => {
640
test('list with complete codeblock', () => {
641
const list = `-
642
\`\`\`js
643
let x = 1;
644
\`\`\`
645
- list item two
646
`;
647
const tokens = marked.marked.lexer(list);
648
const newTokens = fillInIncompleteTokens(tokens);
649
650
assert.deepStrictEqual(newTokens, tokens);
651
});
652
653
test.skip('list with incomplete codeblock', () => {
654
const incomplete = `- list item one
655
656
\`\`\`js
657
let x = 1;`;
658
const tokens = marked.marked.lexer(incomplete);
659
const newTokens = fillInIncompleteTokens(tokens);
660
661
const completeTokens = marked.marked.lexer(incomplete + '\n ```');
662
assert.deepStrictEqual(newTokens, completeTokens);
663
});
664
665
test('list with subitems', () => {
666
const list = `- hello
667
- sub item
668
- text
669
newline for some reason
670
`;
671
const tokens = marked.marked.lexer(list);
672
const newTokens = fillInIncompleteTokens(tokens);
673
674
assert.deepStrictEqual(newTokens, tokens);
675
});
676
677
test('ordered list with subitems', () => {
678
const list = `1. hello
679
- sub item
680
2. text
681
newline for some reason
682
`;
683
const tokens = marked.marked.lexer(list);
684
const newTokens = fillInIncompleteTokens(tokens);
685
686
assert.deepStrictEqual(newTokens, tokens);
687
});
688
689
test('list with stuff', () => {
690
const list = `- list item one \`codespan\` **bold** [link](http://microsoft.com) more text`;
691
const tokens = marked.marked.lexer(list);
692
const newTokens = fillInIncompleteTokens(tokens);
693
694
assert.deepStrictEqual(newTokens, tokens);
695
});
696
697
test('list with incomplete link text', () => {
698
const incomplete = `- list item one
699
- item two [link`;
700
const tokens = marked.marked.lexer(incomplete);
701
const newTokens = fillInIncompleteTokens(tokens);
702
703
const completeTokens = marked.marked.lexer(incomplete + '](https://microsoft.com)');
704
assert.deepStrictEqual(newTokens, completeTokens);
705
});
706
707
test('list with incomplete link target', () => {
708
const incomplete = `- list item one
709
- item two [link](`;
710
const tokens = marked.marked.lexer(incomplete);
711
const newTokens = fillInIncompleteTokens(tokens);
712
713
const completeTokens = marked.marked.lexer(incomplete + ')');
714
assert.deepStrictEqual(newTokens, completeTokens);
715
});
716
717
test('ordered list with incomplete link target', () => {
718
const incomplete = `1. list item one
719
2. item two [link](`;
720
const tokens = marked.marked.lexer(incomplete);
721
const newTokens = fillInIncompleteTokens(tokens);
722
723
const completeTokens = marked.marked.lexer(incomplete + ')');
724
assert.deepStrictEqual(newTokens, completeTokens);
725
});
726
727
test('ordered list with extra whitespace', () => {
728
const incomplete = `1. list item one
729
2. item two [link](`;
730
const tokens = marked.marked.lexer(incomplete);
731
const newTokens = fillInIncompleteTokens(tokens);
732
733
const completeTokens = marked.marked.lexer(incomplete + ')');
734
assert.deepStrictEqual(newTokens, completeTokens);
735
});
736
737
test('list with extra whitespace', () => {
738
const incomplete = `- list item one
739
- item two [link](`;
740
const tokens = marked.marked.lexer(incomplete);
741
const newTokens = fillInIncompleteTokens(tokens);
742
743
const completeTokens = marked.marked.lexer(incomplete + ')');
744
assert.deepStrictEqual(newTokens, completeTokens);
745
});
746
747
test('list with incomplete link with other stuff', () => {
748
const incomplete = `- list item one
749
- item two [\`link`;
750
const tokens = marked.marked.lexer(incomplete);
751
const newTokens = fillInIncompleteTokens(tokens);
752
753
const completeTokens = marked.marked.lexer(incomplete + '\`](https://microsoft.com)');
754
assert.deepStrictEqual(newTokens, completeTokens);
755
});
756
757
test('ordered list with incomplete link with other stuff', () => {
758
const incomplete = `1. list item one
759
1. item two [\`link`;
760
const tokens = marked.marked.lexer(incomplete);
761
const newTokens = fillInIncompleteTokens(tokens);
762
763
const completeTokens = marked.marked.lexer(incomplete + '\`](https://microsoft.com)');
764
assert.deepStrictEqual(newTokens, completeTokens);
765
});
766
767
test('list with incomplete subitem', () => {
768
const incomplete = `1. list item one
769
- `;
770
const tokens = marked.marked.lexer(incomplete);
771
const newTokens = fillInIncompleteTokens(tokens);
772
773
const completeTokens = marked.marked.lexer(incomplete + '&nbsp;');
774
assert.deepStrictEqual(newTokens, completeTokens);
775
});
776
777
test('list with incomplete nested subitem', () => {
778
const incomplete = `1. list item one
779
- item 2
780
- `;
781
const tokens = marked.marked.lexer(incomplete);
782
const newTokens = fillInIncompleteTokens(tokens);
783
784
const completeTokens = marked.marked.lexer(incomplete + '&nbsp;');
785
assert.deepStrictEqual(newTokens, completeTokens);
786
});
787
788
test('text with start of list is not a heading', () => {
789
const incomplete = `hello\n- `;
790
const tokens = marked.marked.lexer(incomplete);
791
const newTokens = fillInIncompleteTokens(tokens);
792
793
const completeTokens = marked.marked.lexer(incomplete + ' &nbsp;');
794
assert.deepStrictEqual(newTokens, completeTokens);
795
});
796
797
test('even more text with start of list is not a heading', () => {
798
const incomplete = `# hello\n\ntext\n-`;
799
const tokens = marked.marked.lexer(incomplete);
800
const newTokens = fillInIncompleteTokens(tokens);
801
802
const completeTokens = marked.marked.lexer(incomplete + ' &nbsp;');
803
assert.deepStrictEqual(newTokens, completeTokens);
804
});
805
});
806
807
suite('codespan', () => {
808
simpleMarkdownTestSuite('codespan', '`');
809
810
test(`backtick between letters`, () => {
811
const text = 'a`b';
812
const tokens = marked.marked.lexer(text);
813
const newTokens = fillInIncompleteTokens(tokens);
814
815
const completeCodespanTokens = marked.marked.lexer(text + '`');
816
assert.deepStrictEqual(newTokens, completeCodespanTokens);
817
});
818
819
test(`nested pattern`, () => {
820
const text = 'sldkfjsd `abc __def__ ghi';
821
const tokens = marked.marked.lexer(text);
822
const newTokens = fillInIncompleteTokens(tokens);
823
824
const completeTokens = marked.marked.lexer(text + '`');
825
assert.deepStrictEqual(newTokens, completeTokens);
826
});
827
});
828
829
suite('star', () => {
830
simpleMarkdownTestSuite('star', '*');
831
832
test(`star between letters`, () => {
833
const text = 'sldkfjsd a*b';
834
const tokens = marked.marked.lexer(text);
835
const newTokens = fillInIncompleteTokens(tokens);
836
837
const completeTokens = marked.marked.lexer(text + '*');
838
assert.deepStrictEqual(newTokens, completeTokens);
839
});
840
841
test(`nested pattern`, () => {
842
const text = 'sldkfjsd *abc __def__ ghi';
843
const tokens = marked.marked.lexer(text);
844
const newTokens = fillInIncompleteTokens(tokens);
845
846
const completeTokens = marked.marked.lexer(text + '*');
847
assert.deepStrictEqual(newTokens, completeTokens);
848
});
849
});
850
851
suite('double star', () => {
852
simpleMarkdownTestSuite('double star', '**');
853
854
test(`double star between letters`, () => {
855
const text = 'a**b';
856
const tokens = marked.marked.lexer(text);
857
const newTokens = fillInIncompleteTokens(tokens);
858
859
const completeTokens = marked.marked.lexer(text + '**');
860
assert.deepStrictEqual(newTokens, completeTokens);
861
});
862
863
// TODO trim these patterns from end
864
test.skip(`ending in doublestar`, () => {
865
const incomplete = `some text and **`;
866
const tokens = marked.marked.lexer(incomplete);
867
const newTokens = fillInIncompleteTokens(tokens);
868
869
const completeTokens = marked.marked.lexer(incomplete.trimEnd() + '**');
870
assert.deepStrictEqual(newTokens, completeTokens);
871
});
872
});
873
874
suite('underscore', () => {
875
simpleMarkdownTestSuite('underscore', '_');
876
877
test(`underscore between letters`, () => {
878
const text = `this_not_italics`;
879
const tokens = marked.marked.lexer(text);
880
const newTokens = fillInIncompleteTokens(tokens);
881
882
assert.deepStrictEqual(newTokens, tokens);
883
});
884
});
885
886
suite('double underscore', () => {
887
simpleMarkdownTestSuite('double underscore', '__');
888
889
test(`double underscore between letters`, () => {
890
const text = `this__not__bold`;
891
const tokens = marked.marked.lexer(text);
892
const newTokens = fillInIncompleteTokens(tokens);
893
894
assert.deepStrictEqual(newTokens, tokens);
895
});
896
});
897
898
suite('link', () => {
899
test('incomplete link text', () => {
900
const incomplete = 'abc [text';
901
const tokens = marked.marked.lexer(incomplete);
902
const newTokens = fillInIncompleteTokens(tokens);
903
904
const completeTokens = marked.marked.lexer(incomplete + '](https://microsoft.com)');
905
assert.deepStrictEqual(newTokens, completeTokens);
906
});
907
908
test('incomplete link target', () => {
909
const incomplete = 'foo [text](http://microsoft';
910
const tokens = marked.marked.lexer(incomplete);
911
const newTokens = fillInIncompleteTokens(tokens);
912
913
const completeTokens = marked.marked.lexer(incomplete + ')');
914
assert.deepStrictEqual(newTokens, completeTokens);
915
});
916
917
test('incomplete link target 2', () => {
918
const incomplete = 'foo [text](http://microsoft.com';
919
const tokens = marked.marked.lexer(incomplete);
920
const newTokens = fillInIncompleteTokens(tokens);
921
922
const completeTokens = marked.marked.lexer(incomplete + ')');
923
assert.deepStrictEqual(newTokens, completeTokens);
924
});
925
926
test('incomplete link target with extra stuff', () => {
927
const incomplete = '[before `text` after](http://microsoft.com';
928
const tokens = marked.marked.lexer(incomplete);
929
const newTokens = fillInIncompleteTokens(tokens);
930
931
const completeTokens = marked.marked.lexer(incomplete + ')');
932
assert.deepStrictEqual(newTokens, completeTokens);
933
});
934
935
test('incomplete link target with extra stuff and incomplete arg', () => {
936
const incomplete = '[before `text` after](http://microsoft.com "more text ';
937
const tokens = marked.marked.lexer(incomplete);
938
const newTokens = fillInIncompleteTokens(tokens);
939
940
const completeTokens = marked.marked.lexer(incomplete + '")');
941
assert.deepStrictEqual(newTokens, completeTokens);
942
});
943
944
test('incomplete link target with incomplete arg', () => {
945
const incomplete = 'foo [text](http://microsoft.com "more text here ';
946
const tokens = marked.marked.lexer(incomplete);
947
const newTokens = fillInIncompleteTokens(tokens);
948
949
const completeTokens = marked.marked.lexer(incomplete + '")');
950
assert.deepStrictEqual(newTokens, completeTokens);
951
});
952
953
test('incomplete link target with incomplete arg 2', () => {
954
const incomplete = '[text](command:vscode.openRelativePath "arg';
955
const tokens = marked.marked.lexer(incomplete);
956
const newTokens = fillInIncompleteTokens(tokens);
957
958
const completeTokens = marked.marked.lexer(incomplete + '")');
959
assert.deepStrictEqual(newTokens, completeTokens);
960
});
961
962
test('incomplete link target with complete arg', () => {
963
const incomplete = 'foo [text](http://microsoft.com "more text here"';
964
const tokens = marked.marked.lexer(incomplete);
965
const newTokens = fillInIncompleteTokens(tokens);
966
967
const completeTokens = marked.marked.lexer(incomplete + ')');
968
assert.deepStrictEqual(newTokens, completeTokens);
969
});
970
971
test('link text with incomplete codespan', () => {
972
const incomplete = `text [\`codespan`;
973
const tokens = marked.marked.lexer(incomplete);
974
const newTokens = fillInIncompleteTokens(tokens);
975
976
const completeTokens = marked.marked.lexer(incomplete + '`](https://microsoft.com)');
977
assert.deepStrictEqual(newTokens, completeTokens);
978
});
979
980
test('link text with incomplete stuff', () => {
981
const incomplete = `text [more text \`codespan\` text **bold`;
982
const tokens = marked.marked.lexer(incomplete);
983
const newTokens = fillInIncompleteTokens(tokens);
984
985
const completeTokens = marked.marked.lexer(incomplete + '**](https://microsoft.com)');
986
assert.deepStrictEqual(newTokens, completeTokens);
987
});
988
989
test('Looks like incomplete link target but isn\'t', () => {
990
const complete = '**bold** `codespan` text](';
991
const tokens = marked.marked.lexer(complete);
992
const newTokens = fillInIncompleteTokens(tokens);
993
994
const completeTokens = marked.marked.lexer(complete);
995
assert.deepStrictEqual(newTokens, completeTokens);
996
});
997
998
test.skip('incomplete link in list', () => {
999
const incomplete = '- [text';
1000
const tokens = marked.marked.lexer(incomplete);
1001
const newTokens = fillInIncompleteTokens(tokens);
1002
1003
const completeTokens = marked.marked.lexer(incomplete + '](https://microsoft.com)');
1004
assert.deepStrictEqual(newTokens, completeTokens);
1005
});
1006
1007
test('square brace between letters', () => {
1008
const incomplete = 'a[b';
1009
const tokens = marked.marked.lexer(incomplete);
1010
const newTokens = fillInIncompleteTokens(tokens);
1011
1012
assert.deepStrictEqual(newTokens, tokens);
1013
});
1014
1015
test('square brace on previous line', () => {
1016
const incomplete = 'text[\nmore text';
1017
const tokens = marked.marked.lexer(incomplete);
1018
const newTokens = fillInIncompleteTokens(tokens);
1019
1020
assert.deepStrictEqual(newTokens, tokens);
1021
});
1022
1023
test('square braces in text', () => {
1024
const incomplete = 'hello [what] is going on';
1025
const tokens = marked.marked.lexer(incomplete);
1026
const newTokens = fillInIncompleteTokens(tokens);
1027
1028
assert.deepStrictEqual(newTokens, tokens);
1029
});
1030
1031
test('complete link', () => {
1032
const incomplete = 'text [link](http://microsoft.com)';
1033
const tokens = marked.marked.lexer(incomplete);
1034
const newTokens = fillInIncompleteTokens(tokens);
1035
1036
assert.deepStrictEqual(newTokens, tokens);
1037
});
1038
});
1039
});
1040
});
1041
1042