Path: blob/main/src/vs/base/test/browser/markdownRenderer.test.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import assert from 'assert';6import { fillInIncompleteTokens, renderMarkdown, renderAsPlaintext } from '../../browser/markdownRenderer.js';7import { IMarkdownString, MarkdownString } from '../../common/htmlContent.js';8import * as marked from '../../common/marked/marked.js';9import { parse } from '../../common/marshalling.js';10import { isWeb } from '../../common/platform.js';11import { URI } from '../../common/uri.js';12import { ensureNoDisposablesAreLeakedInTestSuite } from '../common/utils.js';1314function strToNode(str: string): HTMLElement {15return new DOMParser().parseFromString(str, 'text/html').body.firstChild as HTMLElement;16}1718function assertNodeEquals(actualNode: HTMLElement, expectedHtml: string) {19const expectedNode = strToNode(expectedHtml);20assert.ok(21actualNode.isEqualNode(expectedNode),22`Expected: ${expectedNode.outerHTML}\nActual: ${actualNode.outerHTML}`);23}2425suite('MarkdownRenderer', () => {2627const store = ensureNoDisposablesAreLeakedInTestSuite();2829suite('Sanitization', () => {30test('Should not render images with unknown schemes', () => {31const markdown = { value: `` };32const result: HTMLElement = store.add(renderMarkdown(markdown)).element;33assert.strictEqual(result.innerHTML, '<p><img alt="image"></p>');34});35});3637suite('Images', () => {38test('image rendering conforms to default', () => {39const markdown = { value: `` };40const result: HTMLElement = store.add(renderMarkdown(markdown)).element;41assertNodeEquals(result, '<div><p><img title="caption" alt="image" src="http://example.com/cat.gif"></p></div>');42});4344test('image rendering conforms to default without title', () => {45const markdown = { value: `` };46const result: HTMLElement = store.add(renderMarkdown(markdown)).element;47assertNodeEquals(result, '<div><p><img alt="image" src="http://example.com/cat.gif"></p></div>');48});4950test('image width from title params', () => {51const result: HTMLElement = store.add(renderMarkdown({ value: `` })).element;52assertNodeEquals(result, `<div><p><img width="100" title="caption" alt="image" src="http://example.com/cat.gif"></p></div>`);53});5455test('image height from title params', () => {56const result: HTMLElement = store.add(renderMarkdown({ value: `` })).element;57assertNodeEquals(result, `<div><p><img height="100" title="caption" alt="image" src="http://example.com/cat.gif"></p></div>`);58});5960test('image width and height from title params', () => {61const result: HTMLElement = store.add(renderMarkdown({ value: `` })).element;62assertNodeEquals(result, `<div><p><img height="200" width="100" title="caption" alt="image" src="http://example.com/cat.gif"></p></div>`);63});6465test('image with file uri should render as same origin uri', () => {66if (isWeb) {67return;68}69const result: HTMLElement = store.add(renderMarkdown({ value: `` })).element;70assertNodeEquals(result, '<div><p><img src="vscode-file://vscode-app/images/cat.gif" alt="image"></p></div>');71});72});7374suite('Code block renderer', () => {75const simpleCodeBlockRenderer = (lang: string, code: string): Promise<HTMLElement> => {76const element = document.createElement('code');77element.textContent = code;78return Promise.resolve(element);79};8081test('asyncRenderCallback should be invoked for code blocks', () => {82const markdown = { value: '```js\n1 + 1;\n```' };83return new Promise<void>(resolve => {84store.add(renderMarkdown(markdown, {85asyncRenderCallback: resolve,86codeBlockRenderer: simpleCodeBlockRenderer87}));88});89});9091test('asyncRenderCallback should not be invoked if result is immediately disposed', () => {92const markdown = { value: '```js\n1 + 1;\n```' };93return new Promise<void>((resolve, reject) => {94const result = renderMarkdown(markdown, {95asyncRenderCallback: reject,96codeBlockRenderer: simpleCodeBlockRenderer97});98result.dispose();99setTimeout(resolve, 10);100});101});102103test('asyncRenderCallback should not be invoked if dispose is called before code block is rendered', () => {104const markdown = { value: '```js\n1 + 1;\n```' };105return new Promise<void>((resolve, reject) => {106let resolveCodeBlockRendering: (x: HTMLElement) => void;107const result = renderMarkdown(markdown, {108asyncRenderCallback: reject,109codeBlockRenderer: () => {110return new Promise(resolve => {111resolveCodeBlockRendering = resolve;112});113}114});115setTimeout(() => {116result.dispose();117resolveCodeBlockRendering(document.createElement('code'));118setTimeout(resolve, 10);119}, 10);120});121});122123test('Code blocks should use leading language id (#157793)', async () => {124const markdown = { value: '```js some other stuff\n1 + 1;\n```' };125const lang = await new Promise<string>(resolve => {126store.add(renderMarkdown(markdown, {127codeBlockRenderer: async (lang, value) => {128resolve(lang);129return simpleCodeBlockRenderer(lang, value);130}131}));132});133assert.strictEqual(lang, 'js');134});135});136137suite('ThemeIcons Support On', () => {138139test('render appendText', () => {140const mds = new MarkdownString(undefined, { supportThemeIcons: true });141mds.appendText('$(zap) $(not a theme icon) $(add)');142143const result: HTMLElement = store.add(renderMarkdown(mds)).element;144assert.strictEqual(result.innerHTML, `<p>$(zap) $(not a theme icon) $(add)</p>`);145});146147test('render appendMarkdown', () => {148const mds = new MarkdownString(undefined, { supportThemeIcons: true });149mds.appendMarkdown('$(zap) $(not a theme icon) $(add)');150151const result: HTMLElement = store.add(renderMarkdown(mds)).element;152assert.strictEqual(result.innerHTML, `<p><span class="codicon codicon-zap"></span> $(not a theme icon) <span class="codicon codicon-add"></span></p>`);153});154155test('render appendMarkdown with escaped icon', () => {156const mds = new MarkdownString(undefined, { supportThemeIcons: true });157mds.appendMarkdown('\\$(zap) $(not a theme icon) $(add)');158159const result: HTMLElement = store.add(renderMarkdown(mds)).element;160assert.strictEqual(result.innerHTML, `<p>$(zap) $(not a theme icon) <span class="codicon codicon-add"></span></p>`);161});162163test('render icon in link', () => {164const mds = new MarkdownString(undefined, { supportThemeIcons: true });165mds.appendMarkdown(`[$(zap)-link](#link)`);166167const result: HTMLElement = store.add(renderMarkdown(mds)).element;168assert.strictEqual(result.innerHTML, `<p><a draggable="false" title="#link" href="" data-href="#link"><span class="codicon codicon-zap"></span>-link</a></p>`);169});170171test('render icon in table', () => {172const mds = new MarkdownString(undefined, { supportThemeIcons: true });173mds.appendMarkdown(`174| text | text |175|--------|----------------------|176| $(zap) | [$(zap)-link](#link) |`);177178const result: HTMLElement = store.add(renderMarkdown(mds)).element;179assert.strictEqual(result.innerHTML, `<table>180<thead>181<tr>182<th>text</th>183<th>text</th>184</tr>185</thead>186<tbody><tr>187<td><span class="codicon codicon-zap"></span></td>188<td><a draggable="false" title="#link" href="" data-href="#link"><span class="codicon codicon-zap"></span>-link</a></td>189</tr>190</tbody></table>191`);192});193194test('render icon in <a> without href (#152170)', () => {195const mds = new MarkdownString(undefined, { supportThemeIcons: true, supportHtml: true });196mds.appendMarkdown(`<a>$(sync)</a>`);197198const result: HTMLElement = store.add(renderMarkdown(mds)).element;199assert.strictEqual(result.innerHTML, `<p><span class="codicon codicon-sync"></span></p>`);200});201});202203suite('ThemeIcons Support Off', () => {204205test('render appendText', () => {206const mds = new MarkdownString(undefined, { supportThemeIcons: false });207mds.appendText('$(zap) $(not a theme icon) $(add)');208209const result: HTMLElement = store.add(renderMarkdown(mds)).element;210assert.strictEqual(result.innerHTML, `<p>$(zap) $(not a theme icon) $(add)</p>`);211});212213test('render appendMarkdown with escaped icon', () => {214const mds = new MarkdownString(undefined, { supportThemeIcons: false });215mds.appendMarkdown('\\$(zap) $(not a theme icon) $(add)');216217const result: HTMLElement = store.add(renderMarkdown(mds)).element;218assert.strictEqual(result.innerHTML, `<p>$(zap) $(not a theme icon) $(add)</p>`);219});220});221222test('npm Hover Run Script not working #90855', function () {223224const 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\\"}"}}}');225const element = store.add(renderMarkdown(md)).element;226227const anchor = element.querySelector('a')!;228assert.ok(anchor);229assert.ok(anchor.dataset['href']);230231const uri = URI.parse(anchor.dataset['href']!);232233const data = <{ script: string; documentUri: URI }>parse(decodeURIComponent(uri.query));234assert.ok(data);235assert.strictEqual(data.script, 'echo');236assert.ok(data.documentUri.toString().startsWith('file:///c%3A/'));237});238239test('Should not render command links by default', () => {240const md = new MarkdownString(`[command1](command:doFoo) <a href="command:doFoo">command2</a>`, {241supportHtml: true242});243244const result: HTMLElement = store.add(renderMarkdown(md)).element;245assert.strictEqual(result.innerHTML, `<p>command1 command2</p>`);246});247248test('Should render command links in trusted strings', () => {249const md = new MarkdownString(`[command1](command:doFoo) <a href="command:doFoo">command2</a>`, {250isTrusted: true,251supportHtml: true,252});253254const result: HTMLElement = store.add(renderMarkdown(md)).element;255assert.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>`);256});257258suite('PlaintextMarkdownRender', () => {259260test('test code, blockquote, heading, list, listitem, paragraph, table, tablerow, tablecell, strong, em, br, del, text are rendered plaintext', () => {261const 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' };262const expected = 'code\nquote\nheading\nlist\n\ntable table2\none two\nbold\nitalic\ndel\nsome text';263const result: string = renderAsPlaintext(markdown);264assert.strictEqual(result, expected);265});266267test('test html, hr, image, link are rendered plaintext', () => {268const markdown = { value: '<div>html</div>\n\n---\n\n[text](textLink)' };269const expected = 'text';270const result: string = renderAsPlaintext(markdown);271assert.strictEqual(result, expected);272});273274test(`Should not remove html inside of code blocks`, () => {275const markdown = {276value: [277'```html',278'<form>html</form>',279'```',280].join('\n')281};282const expected = [283'```',284'<form>html</form>',285'```',286].join('\n');287const result: string = renderAsPlaintext(markdown, { includeCodeBlocksFences: true });288assert.strictEqual(result, expected);289});290});291292suite('supportHtml', () => {293test('supportHtml is disabled by default', () => {294const mds = new MarkdownString(undefined, {});295mds.appendMarkdown('a<b>b</b>c');296297const result = store.add(renderMarkdown(mds)).element;298assert.strictEqual(result.innerHTML, `<p>abc</p>`);299});300301test('Renders html when supportHtml=true', () => {302const mds = new MarkdownString(undefined, { supportHtml: true });303mds.appendMarkdown('a<b>b</b>c');304305const result = store.add(renderMarkdown(mds)).element;306assert.strictEqual(result.innerHTML, `<p>a<b>b</b>c</p>`);307});308309test('Should not include scripts even when supportHtml=true', () => {310const mds = new MarkdownString(undefined, { supportHtml: true });311mds.appendMarkdown('a<b onclick="alert(1)">b</b><script>alert(2)</script>c');312313const result = store.add(renderMarkdown(mds)).element;314assert.strictEqual(result.innerHTML, `<p>a<b>b</b>c</p>`);315});316317test('Should not render html appended as text', () => {318const mds = new MarkdownString(undefined, { supportHtml: true });319mds.appendText('a<b>b</b>c');320321const result = store.add(renderMarkdown(mds)).element;322assert.strictEqual(result.innerHTML, `<p>a<b>b</b>c</p>`);323});324325test('Should render html images', () => {326if (isWeb) {327return;328}329330const mds = new MarkdownString(undefined, { supportHtml: true });331mds.appendMarkdown(`<img src="http://example.com/cat.gif">`);332333const result = store.add(renderMarkdown(mds)).element;334assert.strictEqual(result.innerHTML, `<img src="http://example.com/cat.gif">`);335});336337test('Should render html images with file uri as same origin uri', () => {338if (isWeb) {339return;340}341342const mds = new MarkdownString(undefined, { supportHtml: true });343mds.appendMarkdown(`<img src="file:///images/cat.gif">`);344345const result = store.add(renderMarkdown(mds)).element;346assert.strictEqual(result.innerHTML, `<img src="vscode-file://vscode-app/images/cat.gif">`);347});348349test('Should only allow checkbox inputs', () => {350const mds = new MarkdownString(351'text: <input type="text">\ncheckbox:<input type="checkbox">',352{ supportHtml: true });353354const result = store.add(renderMarkdown(mds)).element;355356// Inputs should always be disabled too357assert.strictEqual(result.innerHTML, `<p>text: \ncheckbox:<input type="checkbox" disabled=""></p>`);358});359});360361suite('fillInIncompleteTokens', () => {362function ignoreRaw(...tokenLists: marked.Token[][]): void {363tokenLists.forEach(tokens => {364tokens.forEach(t => t.raw = '');365});366}367368const completeTable = '| a | b |\n| --- | --- |';369370suite('table', () => {371test('complete table', () => {372const tokens = marked.marked.lexer(completeTable);373const newTokens = fillInIncompleteTokens(tokens);374assert.equal(newTokens, tokens);375});376377test('full header only', () => {378const incompleteTable = '| a | b |';379const tokens = marked.marked.lexer(incompleteTable);380const completeTableTokens = marked.marked.lexer(completeTable);381382const newTokens = fillInIncompleteTokens(tokens);383assert.deepStrictEqual(newTokens, completeTableTokens);384});385386test('full header only with trailing space', () => {387const incompleteTable = '| a | b | ';388const tokens = marked.marked.lexer(incompleteTable);389const completeTableTokens = marked.marked.lexer(completeTable);390391const newTokens = fillInIncompleteTokens(tokens);392if (newTokens) {393ignoreRaw(newTokens, completeTableTokens);394}395assert.deepStrictEqual(newTokens, completeTableTokens);396});397398test('incomplete header', () => {399const incompleteTable = '| a | b';400const tokens = marked.marked.lexer(incompleteTable);401const completeTableTokens = marked.marked.lexer(completeTable);402403const newTokens = fillInIncompleteTokens(tokens);404405if (newTokens) {406ignoreRaw(newTokens, completeTableTokens);407}408assert.deepStrictEqual(newTokens, completeTableTokens);409});410411test('incomplete header one column', () => {412const incompleteTable = '| a ';413const tokens = marked.marked.lexer(incompleteTable);414const completeTableTokens = marked.marked.lexer(incompleteTable + '|\n| --- |');415416const newTokens = fillInIncompleteTokens(tokens);417418if (newTokens) {419ignoreRaw(newTokens, completeTableTokens);420}421assert.deepStrictEqual(newTokens, completeTableTokens);422});423424test('full header with extras', () => {425const incompleteTable = '| a **bold** | b _italics_ |';426const tokens = marked.marked.lexer(incompleteTable);427const completeTableTokens = marked.marked.lexer(incompleteTable + '\n| --- | --- |');428429const newTokens = fillInIncompleteTokens(tokens);430assert.deepStrictEqual(newTokens, completeTableTokens);431});432433test('full header with leading text', () => {434// Parsing this gives one token and one 'text' subtoken435const incompleteTable = 'here is a table\n| a | b |';436const tokens = marked.marked.lexer(incompleteTable);437const completeTableTokens = marked.marked.lexer(incompleteTable + '\n| --- | --- |');438439const newTokens = fillInIncompleteTokens(tokens);440assert.deepStrictEqual(newTokens, completeTableTokens);441});442443test('full header with leading other stuff', () => {444// Parsing this gives one token and one 'text' subtoken445const incompleteTable = '```js\nconst xyz = 123;\n```\n| a | b |';446const tokens = marked.marked.lexer(incompleteTable);447const completeTableTokens = marked.marked.lexer(incompleteTable + '\n| --- | --- |');448449const newTokens = fillInIncompleteTokens(tokens);450assert.deepStrictEqual(newTokens, completeTableTokens);451});452453test('full header with incomplete separator', () => {454const incompleteTable = '| a | b |\n| ---';455const tokens = marked.marked.lexer(incompleteTable);456const completeTableTokens = marked.marked.lexer(completeTable);457458const newTokens = fillInIncompleteTokens(tokens);459assert.deepStrictEqual(newTokens, completeTableTokens);460});461462test('full header with incomplete separator 2', () => {463const incompleteTable = '| a | b |\n| --- |';464const tokens = marked.marked.lexer(incompleteTable);465const completeTableTokens = marked.marked.lexer(completeTable);466467const newTokens = fillInIncompleteTokens(tokens);468assert.deepStrictEqual(newTokens, completeTableTokens);469});470471test('full header with incomplete separator 3', () => {472const incompleteTable = '| a | b |\n|';473const tokens = marked.marked.lexer(incompleteTable);474const completeTableTokens = marked.marked.lexer(completeTable);475476const newTokens = fillInIncompleteTokens(tokens);477assert.deepStrictEqual(newTokens, completeTableTokens);478});479480test('not a table', () => {481const incompleteTable = '| a | b |\nsome text';482const tokens = marked.marked.lexer(incompleteTable);483484const newTokens = fillInIncompleteTokens(tokens);485assert.deepStrictEqual(newTokens, tokens);486});487488test('not a table 2', () => {489const incompleteTable = '| a | b |\n| --- |\nsome text';490const tokens = marked.marked.lexer(incompleteTable);491492const newTokens = fillInIncompleteTokens(tokens);493assert.deepStrictEqual(newTokens, tokens);494});495});496497function simpleMarkdownTestSuite(name: string, delimiter: string): void {498test(`incomplete ${name}`, () => {499const incomplete = `${delimiter}code`;500const tokens = marked.marked.lexer(incomplete);501const newTokens = fillInIncompleteTokens(tokens);502503const completeTokens = marked.marked.lexer(incomplete + delimiter);504assert.deepStrictEqual(newTokens, completeTokens);505});506507test(`complete ${name}`, () => {508const text = `leading text ${delimiter}code${delimiter} trailing text`;509const tokens = marked.marked.lexer(text);510const newTokens = fillInIncompleteTokens(tokens);511512assert.deepStrictEqual(newTokens, tokens);513});514515test(`${name} with leading text`, () => {516const incomplete = `some text and ${delimiter}some code`;517const tokens = marked.marked.lexer(incomplete);518const newTokens = fillInIncompleteTokens(tokens);519520const completeTokens = marked.marked.lexer(incomplete + delimiter);521assert.deepStrictEqual(newTokens, completeTokens);522});523524test(`${name} with trailing space`, () => {525const incomplete = `some text and ${delimiter}some code `;526const tokens = marked.marked.lexer(incomplete);527const newTokens = fillInIncompleteTokens(tokens);528529const completeTokens = marked.marked.lexer(incomplete.trimEnd() + delimiter);530assert.deepStrictEqual(newTokens, completeTokens);531});532533test(`single loose "${delimiter}"`, () => {534const text = `some text and ${delimiter}by itself\nmore text here`;535const tokens = marked.marked.lexer(text);536const newTokens = fillInIncompleteTokens(tokens);537538assert.deepStrictEqual(newTokens, tokens);539});540541test(`incomplete ${name} after newline`, () => {542const text = `some text\nmore text here and ${delimiter}text`;543const tokens = marked.marked.lexer(text);544const newTokens = fillInIncompleteTokens(tokens);545546const completeTokens = marked.marked.lexer(text + delimiter);547assert.deepStrictEqual(newTokens, completeTokens);548});549550test(`incomplete after complete ${name}`, () => {551const text = `leading text ${delimiter}code${delimiter} trailing text and ${delimiter}another`;552const tokens = marked.marked.lexer(text);553const newTokens = fillInIncompleteTokens(tokens);554555const completeTokens = marked.marked.lexer(text + delimiter);556assert.deepStrictEqual(newTokens, completeTokens);557});558559test(`incomplete ${name} in list`, () => {560const text = `- list item one\n- list item two and ${delimiter}text`;561const tokens = marked.marked.lexer(text);562const newTokens = fillInIncompleteTokens(tokens);563564const completeTokens = marked.marked.lexer(text + delimiter);565assert.deepStrictEqual(newTokens, completeTokens);566});567568test(`incomplete ${name} in asterisk list`, () => {569const text = `* list item one\n* list item two and ${delimiter}text`;570const tokens = marked.marked.lexer(text);571const newTokens = fillInIncompleteTokens(tokens);572573const completeTokens = marked.marked.lexer(text + delimiter);574assert.deepStrictEqual(newTokens, completeTokens);575});576577test(`incomplete ${name} in numbered list`, () => {578const text = `1. list item one\n2. list item two and ${delimiter}text`;579const tokens = marked.marked.lexer(text);580const newTokens = fillInIncompleteTokens(tokens);581582const completeTokens = marked.marked.lexer(text + delimiter);583assert.deepStrictEqual(newTokens, completeTokens);584});585}586587suite('list', () => {588test('list with complete codeblock', () => {589const list = `-590\`\`\`js591let x = 1;592\`\`\`593- list item two594`;595const tokens = marked.marked.lexer(list);596const newTokens = fillInIncompleteTokens(tokens);597598assert.deepStrictEqual(newTokens, tokens);599});600601test.skip('list with incomplete codeblock', () => {602const incomplete = `- list item one603604\`\`\`js605let x = 1;`;606const tokens = marked.marked.lexer(incomplete);607const newTokens = fillInIncompleteTokens(tokens);608609const completeTokens = marked.marked.lexer(incomplete + '\n ```');610assert.deepStrictEqual(newTokens, completeTokens);611});612613test('list with subitems', () => {614const list = `- hello615- sub item616- text617newline for some reason618`;619const tokens = marked.marked.lexer(list);620const newTokens = fillInIncompleteTokens(tokens);621622assert.deepStrictEqual(newTokens, tokens);623});624625test('ordered list with subitems', () => {626const list = `1. hello627- sub item6282. text629newline for some reason630`;631const tokens = marked.marked.lexer(list);632const newTokens = fillInIncompleteTokens(tokens);633634assert.deepStrictEqual(newTokens, tokens);635});636637test('list with stuff', () => {638const list = `- list item one \`codespan\` **bold** [link](http://microsoft.com) more text`;639const tokens = marked.marked.lexer(list);640const newTokens = fillInIncompleteTokens(tokens);641642assert.deepStrictEqual(newTokens, tokens);643});644645test('list with incomplete link text', () => {646const incomplete = `- list item one647- item two [link`;648const tokens = marked.marked.lexer(incomplete);649const newTokens = fillInIncompleteTokens(tokens);650651const completeTokens = marked.marked.lexer(incomplete + '](https://microsoft.com)');652assert.deepStrictEqual(newTokens, completeTokens);653});654655test('list with incomplete link target', () => {656const incomplete = `- list item one657- item two [link](`;658const tokens = marked.marked.lexer(incomplete);659const newTokens = fillInIncompleteTokens(tokens);660661const completeTokens = marked.marked.lexer(incomplete + ')');662assert.deepStrictEqual(newTokens, completeTokens);663});664665test('ordered list with incomplete link target', () => {666const incomplete = `1. list item one6672. item two [link](`;668const tokens = marked.marked.lexer(incomplete);669const newTokens = fillInIncompleteTokens(tokens);670671const completeTokens = marked.marked.lexer(incomplete + ')');672assert.deepStrictEqual(newTokens, completeTokens);673});674675test('ordered list with extra whitespace', () => {676const incomplete = `1. list item one6772. item two [link](`;678const tokens = marked.marked.lexer(incomplete);679const newTokens = fillInIncompleteTokens(tokens);680681const completeTokens = marked.marked.lexer(incomplete + ')');682assert.deepStrictEqual(newTokens, completeTokens);683});684685test('list with extra whitespace', () => {686const incomplete = `- list item one687- item two [link](`;688const tokens = marked.marked.lexer(incomplete);689const newTokens = fillInIncompleteTokens(tokens);690691const completeTokens = marked.marked.lexer(incomplete + ')');692assert.deepStrictEqual(newTokens, completeTokens);693});694695test('list with incomplete link with other stuff', () => {696const incomplete = `- list item one697- item two [\`link`;698const tokens = marked.marked.lexer(incomplete);699const newTokens = fillInIncompleteTokens(tokens);700701const completeTokens = marked.marked.lexer(incomplete + '\`](https://microsoft.com)');702assert.deepStrictEqual(newTokens, completeTokens);703});704705test('ordered list with incomplete link with other stuff', () => {706const incomplete = `1. list item one7071. item two [\`link`;708const tokens = marked.marked.lexer(incomplete);709const newTokens = fillInIncompleteTokens(tokens);710711const completeTokens = marked.marked.lexer(incomplete + '\`](https://microsoft.com)');712assert.deepStrictEqual(newTokens, completeTokens);713});714715test('list with incomplete subitem', () => {716const incomplete = `1. list item one717- `;718const tokens = marked.marked.lexer(incomplete);719const newTokens = fillInIncompleteTokens(tokens);720721const completeTokens = marked.marked.lexer(incomplete + ' ');722assert.deepStrictEqual(newTokens, completeTokens);723});724725test('list with incomplete nested subitem', () => {726const incomplete = `1. list item one727- item 2728- `;729const tokens = marked.marked.lexer(incomplete);730const newTokens = fillInIncompleteTokens(tokens);731732const completeTokens = marked.marked.lexer(incomplete + ' ');733assert.deepStrictEqual(newTokens, completeTokens);734});735736test('text with start of list is not a heading', () => {737const incomplete = `hello\n- `;738const tokens = marked.marked.lexer(incomplete);739const newTokens = fillInIncompleteTokens(tokens);740741const completeTokens = marked.marked.lexer(incomplete + ' ');742assert.deepStrictEqual(newTokens, completeTokens);743});744745test('even more text with start of list is not a heading', () => {746const incomplete = `# hello\n\ntext\n-`;747const tokens = marked.marked.lexer(incomplete);748const newTokens = fillInIncompleteTokens(tokens);749750const completeTokens = marked.marked.lexer(incomplete + ' ');751assert.deepStrictEqual(newTokens, completeTokens);752});753});754755suite('codespan', () => {756simpleMarkdownTestSuite('codespan', '`');757758test(`backtick between letters`, () => {759const text = 'a`b';760const tokens = marked.marked.lexer(text);761const newTokens = fillInIncompleteTokens(tokens);762763const completeCodespanTokens = marked.marked.lexer(text + '`');764assert.deepStrictEqual(newTokens, completeCodespanTokens);765});766767test(`nested pattern`, () => {768const text = 'sldkfjsd `abc __def__ ghi';769const tokens = marked.marked.lexer(text);770const newTokens = fillInIncompleteTokens(tokens);771772const completeTokens = marked.marked.lexer(text + '`');773assert.deepStrictEqual(newTokens, completeTokens);774});775});776777suite('star', () => {778simpleMarkdownTestSuite('star', '*');779780test(`star between letters`, () => {781const text = 'sldkfjsd a*b';782const tokens = marked.marked.lexer(text);783const newTokens = fillInIncompleteTokens(tokens);784785const completeTokens = marked.marked.lexer(text + '*');786assert.deepStrictEqual(newTokens, completeTokens);787});788789test(`nested pattern`, () => {790const text = 'sldkfjsd *abc __def__ ghi';791const tokens = marked.marked.lexer(text);792const newTokens = fillInIncompleteTokens(tokens);793794const completeTokens = marked.marked.lexer(text + '*');795assert.deepStrictEqual(newTokens, completeTokens);796});797});798799suite('double star', () => {800simpleMarkdownTestSuite('double star', '**');801802test(`double star between letters`, () => {803const text = 'a**b';804const tokens = marked.marked.lexer(text);805const newTokens = fillInIncompleteTokens(tokens);806807const completeTokens = marked.marked.lexer(text + '**');808assert.deepStrictEqual(newTokens, completeTokens);809});810811// TODO trim these patterns from end812test.skip(`ending in doublestar`, () => {813const incomplete = `some text and **`;814const tokens = marked.marked.lexer(incomplete);815const newTokens = fillInIncompleteTokens(tokens);816817const completeTokens = marked.marked.lexer(incomplete.trimEnd() + '**');818assert.deepStrictEqual(newTokens, completeTokens);819});820});821822suite('underscore', () => {823simpleMarkdownTestSuite('underscore', '_');824825test(`underscore between letters`, () => {826const text = `this_not_italics`;827const tokens = marked.marked.lexer(text);828const newTokens = fillInIncompleteTokens(tokens);829830assert.deepStrictEqual(newTokens, tokens);831});832});833834suite('double underscore', () => {835simpleMarkdownTestSuite('double underscore', '__');836837test(`double underscore between letters`, () => {838const text = `this__not__bold`;839const tokens = marked.marked.lexer(text);840const newTokens = fillInIncompleteTokens(tokens);841842assert.deepStrictEqual(newTokens, tokens);843});844});845846suite('link', () => {847test('incomplete link text', () => {848const incomplete = 'abc [text';849const tokens = marked.marked.lexer(incomplete);850const newTokens = fillInIncompleteTokens(tokens);851852const completeTokens = marked.marked.lexer(incomplete + '](https://microsoft.com)');853assert.deepStrictEqual(newTokens, completeTokens);854});855856test('incomplete link target', () => {857const incomplete = 'foo [text](http://microsoft';858const tokens = marked.marked.lexer(incomplete);859const newTokens = fillInIncompleteTokens(tokens);860861const completeTokens = marked.marked.lexer(incomplete + ')');862assert.deepStrictEqual(newTokens, completeTokens);863});864865test('incomplete link target 2', () => {866const incomplete = 'foo [text](http://microsoft.com';867const tokens = marked.marked.lexer(incomplete);868const newTokens = fillInIncompleteTokens(tokens);869870const completeTokens = marked.marked.lexer(incomplete + ')');871assert.deepStrictEqual(newTokens, completeTokens);872});873874test('incomplete link target with extra stuff', () => {875const incomplete = '[before `text` after](http://microsoft.com';876const tokens = marked.marked.lexer(incomplete);877const newTokens = fillInIncompleteTokens(tokens);878879const completeTokens = marked.marked.lexer(incomplete + ')');880assert.deepStrictEqual(newTokens, completeTokens);881});882883test('incomplete link target with extra stuff and incomplete arg', () => {884const incomplete = '[before `text` after](http://microsoft.com "more text ';885const tokens = marked.marked.lexer(incomplete);886const newTokens = fillInIncompleteTokens(tokens);887888const completeTokens = marked.marked.lexer(incomplete + '")');889assert.deepStrictEqual(newTokens, completeTokens);890});891892test('incomplete link target with incomplete arg', () => {893const incomplete = 'foo [text](http://microsoft.com "more text here ';894const tokens = marked.marked.lexer(incomplete);895const newTokens = fillInIncompleteTokens(tokens);896897const completeTokens = marked.marked.lexer(incomplete + '")');898assert.deepStrictEqual(newTokens, completeTokens);899});900901test('incomplete link target with incomplete arg 2', () => {902const incomplete = '[text](command:vscode.openRelativePath "arg';903const tokens = marked.marked.lexer(incomplete);904const newTokens = fillInIncompleteTokens(tokens);905906const completeTokens = marked.marked.lexer(incomplete + '")');907assert.deepStrictEqual(newTokens, completeTokens);908});909910test('incomplete link target with complete arg', () => {911const incomplete = 'foo [text](http://microsoft.com "more text here"';912const tokens = marked.marked.lexer(incomplete);913const newTokens = fillInIncompleteTokens(tokens);914915const completeTokens = marked.marked.lexer(incomplete + ')');916assert.deepStrictEqual(newTokens, completeTokens);917});918919test('link text with incomplete codespan', () => {920const incomplete = `text [\`codespan`;921const tokens = marked.marked.lexer(incomplete);922const newTokens = fillInIncompleteTokens(tokens);923924const completeTokens = marked.marked.lexer(incomplete + '`](https://microsoft.com)');925assert.deepStrictEqual(newTokens, completeTokens);926});927928test('link text with incomplete stuff', () => {929const incomplete = `text [more text \`codespan\` text **bold`;930const tokens = marked.marked.lexer(incomplete);931const newTokens = fillInIncompleteTokens(tokens);932933const completeTokens = marked.marked.lexer(incomplete + '**](https://microsoft.com)');934assert.deepStrictEqual(newTokens, completeTokens);935});936937test('Looks like incomplete link target but isn\'t', () => {938const complete = '**bold** `codespan` text](';939const tokens = marked.marked.lexer(complete);940const newTokens = fillInIncompleteTokens(tokens);941942const completeTokens = marked.marked.lexer(complete);943assert.deepStrictEqual(newTokens, completeTokens);944});945946test.skip('incomplete link in list', () => {947const incomplete = '- [text';948const tokens = marked.marked.lexer(incomplete);949const newTokens = fillInIncompleteTokens(tokens);950951const completeTokens = marked.marked.lexer(incomplete + '](https://microsoft.com)');952assert.deepStrictEqual(newTokens, completeTokens);953});954955test('square brace between letters', () => {956const incomplete = 'a[b';957const tokens = marked.marked.lexer(incomplete);958const newTokens = fillInIncompleteTokens(tokens);959960assert.deepStrictEqual(newTokens, tokens);961});962963test('square brace on previous line', () => {964const incomplete = 'text[\nmore text';965const tokens = marked.marked.lexer(incomplete);966const newTokens = fillInIncompleteTokens(tokens);967968assert.deepStrictEqual(newTokens, tokens);969});970971test('square braces in text', () => {972const incomplete = 'hello [what] is going on';973const tokens = marked.marked.lexer(incomplete);974const newTokens = fillInIncompleteTokens(tokens);975976assert.deepStrictEqual(newTokens, tokens);977});978979test('complete link', () => {980const incomplete = 'text [link](http://microsoft.com)';981const tokens = marked.marked.lexer(incomplete);982const newTokens = fillInIncompleteTokens(tokens);983984assert.deepStrictEqual(newTokens, tokens);985});986});987});988});989990991