From 407d6f301b0b6863c7a6f38a9859a612a80ce552 Mon Sep 17 00:00:00 2001 From: rchiodo Date: Tue, 22 Sep 2020 11:35:33 -0700 Subject: [PATCH 1/4] Basic idea --- .../datascience/jupyter/jupyterNotebook.ts | 27 ++++++++++++------- .../datascience/jupyter/kernelVariables.ts | 6 +++-- .../jupyter/kernels/cellExecution.ts | 18 ++++++++++--- .../jupyter/oldJupyterVariables.ts | 6 +++-- .../interactive-common/cellOutput.tsx | 24 +++++++++++------ 5 files changed, 57 insertions(+), 24 deletions(-) diff --git a/src/client/datascience/jupyter/jupyterNotebook.ts b/src/client/datascience/jupyter/jupyterNotebook.ts index de8886658b00..ab1a015065e3 100644 --- a/src/client/datascience/jupyter/jupyterNotebook.ts +++ b/src/client/datascience/jupyter/jupyterNotebook.ts @@ -40,6 +40,10 @@ import { KernelConnectionMetadata } from './kernels/types'; // tslint:disable-next-line: no-require-imports import cloneDeep = require('lodash/cloneDeep'); +// tslint:disable-next-line: no-require-imports +import escape = require('lodash/escape'); +// tslint:disable-next-line: no-require-imports +import unescape = require('lodash/unescape'); import { concatMultilineString, formatStreamText } from '../../../datascience-ui/common'; import { RefBool } from '../../common/refBool'; import { PythonEnvironment } from '../../pythonEnvironments/info'; @@ -783,12 +787,12 @@ export class JupyterNotebookBase implements INotebook { outputs.forEach((o) => { if (o.output_type === 'stream') { const stream = o as nbformat.IStream; - result = result.concat(formatStreamText(concatMultilineString(stream.text, true))); + result = result.concat(formatStreamText(unescape(concatMultilineString(stream.text, true)))); } else { const data = o.data; if (data && data.hasOwnProperty('text/plain')) { // tslint:disable-next-line:no-any - result = result.concat((data as any)['text/plain']); + result = result.concat(unescape((data as any)['text/plain'])); } } }); @@ -1233,7 +1237,7 @@ export class JupyterNotebookBase implements INotebook { ) { // Check our length on text output if (msg.content.data && msg.content.data.hasOwnProperty('text/plain')) { - msg.content.data['text/plain'] = trimFunc(msg.content.data['text/plain'] as string); + msg.content.data['text/plain'] = escape(trimFunc(msg.content.data['text/plain'] as string)); } this.addToCellData( @@ -1262,7 +1266,7 @@ export class JupyterNotebookBase implements INotebook { if (o.data && o.data.hasOwnProperty('text/plain')) { // tslint:disable-next-line: no-any const str = (o.data as any)['text/plain'].toString(); - const data = trimFunc(str) as string; + const data = escape(trimFunc(str)) as string; this.addToCellData( cell, { @@ -1310,13 +1314,13 @@ export class JupyterNotebookBase implements INotebook { : undefined; if (existing) { // tslint:disable-next-line:restrict-plus-operands - existing.text = existing.text + msg.content.text; + existing.text = existing.text + escape(msg.content.text); const originalText = formatStreamText(concatMultilineString(existing.text)); originalTextLength = originalText.length; existing.text = trimFunc(originalText); trimmedTextLength = existing.text.length; } else { - const originalText = formatStreamText(concatMultilineString(msg.content.text)); + const originalText = formatStreamText(concatMultilineString(escape(msg.content.text))); originalTextLength = originalText.length; // Create a new stream entry const output: nbformat.IStream = { @@ -1346,6 +1350,11 @@ export class JupyterNotebookBase implements INotebook { } private handleDisplayData(msg: KernelMessage.IDisplayDataMsg, clearState: RefBool, cell: ICell) { + // Escape text output + if (msg.content.data && msg.content.data.hasOwnProperty('text/plain')) { + msg.content.data['text/plain'] = escape(msg.content.data['text/plain'] as string); + } + const output: nbformat.IDisplayData = { output_type: 'display_data', data: msg.content.data, @@ -1393,9 +1402,9 @@ export class JupyterNotebookBase implements INotebook { private handleError(msg: KernelMessage.IErrorMsg, clearState: RefBool, cell: ICell) { const output: nbformat.IError = { output_type: 'error', - ename: msg.content.ename, - evalue: msg.content.evalue, - traceback: msg.content.traceback + ename: escape(msg.content.ename), + evalue: escape(msg.content.evalue), + traceback: msg.content.traceback.map(escape) }; this.addToCellData(cell, output, clearState); cell.state = CellState.error; diff --git a/src/client/datascience/jupyter/kernelVariables.ts b/src/client/datascience/jupyter/kernelVariables.ts index 7d3d1b004bd3..501b3f25ef17 100644 --- a/src/client/datascience/jupyter/kernelVariables.ts +++ b/src/client/datascience/jupyter/kernelVariables.ts @@ -6,6 +6,8 @@ import { inject, injectable } from 'inversify'; import stripAnsi from 'strip-ansi'; import * as uuid from 'uuid/v4'; +// tslint:disable-next-line: no-require-imports +import unescape = require('lodash/unescape'); import { CancellationToken, Event, EventEmitter, Uri } from 'vscode'; import { PYTHON_LANGUAGE } from '../../common/constants'; import { traceError } from '../../common/logger'; @@ -246,7 +248,7 @@ export class KernelVariables implements IJupyterVariables { // Pull our text result out of the Jupyter cell private deserializeJupyterResult(cells: ICell[]): T { - const text = this.extractJupyterResultText(cells); + const text = unescape(this.extractJupyterResultText(cells)); return JSON.parse(text) as T; } @@ -371,7 +373,7 @@ export class KernelVariables implements IJupyterVariables { // Now execute the query if (notebook && query) { const cells = await notebook.execute(query.query, Identifiers.EmptyFileName, 0, uuid(), token, true); - const text = this.extractJupyterResultText(cells); + const text = unescape(this.extractJupyterResultText(cells)); // Apply the expression to it const matches = this.getAllMatches(query.parser, text); diff --git a/src/client/datascience/jupyter/kernels/cellExecution.ts b/src/client/datascience/jupyter/kernels/cellExecution.ts index f951d3979927..a0be2d08c54e 100644 --- a/src/client/datascience/jupyter/kernels/cellExecution.ts +++ b/src/client/datascience/jupyter/kernels/cellExecution.ts @@ -35,6 +35,8 @@ import { import { IKernel } from './types'; // tslint:disable-next-line: no-var-requires no-require-imports const vscodeNotebookEnums = require('vscode') as typeof import('vscode-proposed'); +// tslint:disable-next-line: no-require-imports +import escape = require('lodash/escape'); export class CellExecutionFactory { constructor( @@ -406,6 +408,11 @@ export class CellExecution { // See this for docs on the messages: // https://jupyter-client.readthedocs.io/en/latest/messaging.html#messaging-in-jupyter private handleExecuteResult(msg: KernelMessage.IExecuteResultMsg, clearState: RefBool) { + // Escape text output + if (msg.content.data && msg.content.data.hasOwnProperty('text/plain')) { + msg.content.data['text/plain'] = escape(msg.content.data['text/plain'] as string); + } + this.addToCellData( { output_type: 'execute_result', @@ -429,7 +436,7 @@ export class CellExecution { // Mark as stream output so the text is formatted because it likely has ansi codes in it. output_type: 'stream', // tslint:disable-next-line: no-any - text: (o.data as any)['text/plain'].toString(), + text: escape((o.data as any)['text/plain'].toString()), metadata: {}, execution_count: reply.execution_count }, @@ -463,10 +470,10 @@ export class CellExecution { lastOutput && lastOutput.outputKind === CellOutputKind.Text ? lastOutput : undefined; if (existing) { // tslint:disable-next-line:restrict-plus-operands - existing.text = formatStreamText(concatMultilineString(existing.text + msg.content.text)); + existing.text = formatStreamText(concatMultilineString(existing.text + escape(msg.content.text))); this.cell.outputs = [...this.cell.outputs]; // This is necessary to get VS code to update (for now) } else { - const originalText = formatStreamText(concatMultilineString(msg.content.text)); + const originalText = formatStreamText(concatMultilineString(escape(msg.content.text))); // Create a new stream entry const output: nbformat.IStream = { output_type: 'stream', @@ -478,6 +485,11 @@ export class CellExecution { } private handleDisplayData(msg: KernelMessage.IDisplayDataMsg, clearState: RefBool) { + // Escape text output + if (msg.content.data && msg.content.data.hasOwnProperty('text/plain')) { + msg.content.data['text/plain'] = escape(msg.content.data['text/plain'] as string); + } + const output: nbformat.IDisplayData = { output_type: 'display_data', data: msg.content.data, diff --git a/src/client/datascience/jupyter/oldJupyterVariables.ts b/src/client/datascience/jupyter/oldJupyterVariables.ts index 7c1d77de7135..6c25a3a3e547 100644 --- a/src/client/datascience/jupyter/oldJupyterVariables.ts +++ b/src/client/datascience/jupyter/oldJupyterVariables.ts @@ -11,6 +11,8 @@ import { Event, EventEmitter, Uri } from 'vscode'; import { PYTHON_LANGUAGE } from '../../common/constants'; import { traceError } from '../../common/logger'; +// tslint:disable-next-line: no-require-imports +import unescape = require('lodash/unescape'); import { IConfigurationService } from '../../common/types'; import * as localize from '../../common/utils/localize'; import { EXTENSION_ROOT_DIR } from '../../constants'; @@ -232,7 +234,7 @@ export class OldJupyterVariables implements IJupyterVariables { // Pull our text result out of the Jupyter cell private deserializeJupyterResult(cells: ICell[]): T { - const text = this.extractJupyterResultText(cells); + const text = unescape(this.extractJupyterResultText(cells)); return JSON.parse(text) as T; } @@ -357,7 +359,7 @@ export class OldJupyterVariables implements IJupyterVariables { // Now execute the query if (notebook && query) { const cells = await notebook.execute(query.query, Identifiers.EmptyFileName, 0, uuid(), undefined, true); - const text = this.extractJupyterResultText(cells); + const text = unescape(this.extractJupyterResultText(cells)); // Apply the expression to it const matches = this.getAllMatches(query.parser, text); diff --git a/src/datascience-ui/interactive-common/cellOutput.tsx b/src/datascience-ui/interactive-common/cellOutput.tsx index b48cc8d69cfd..b9a9d5bb2eee 100644 --- a/src/datascience-ui/interactive-common/cellOutput.tsx +++ b/src/datascience-ui/interactive-common/cellOutput.tsx @@ -314,26 +314,34 @@ export class CellOutput extends React.Component { input = JSON.stringify(output.data); renderWithScrollbars = true; isText = true; + } else if (output.output_type === 'execute_result' && input && input.hasOwnProperty('text/plain')) { + // Plain text should actually be shown as html so that escaped HTML shows up correctly + mimeType = 'text/html'; + isText = true; + isError = false; + renderWithScrollbars = true; + // tslint:disable-next-line: no-any + const text = (input as any)['text/plain']; + input = { + 'text/html': text // XML tags should have already been escaped. + }; } else if (output.output_type === 'stream') { - // Stream output needs to be wrapped in xmp so it - // show literally. Otherwise < chars start a new html element. mimeType = 'text/html'; isText = true; isError = false; renderWithScrollbars = true; // Sonar is wrong, TS won't compile without this AS const stream = output as nbformat.IStream; // NOSONAR - const formatted = concatMultilineString(stream.text); + const concatted = concatMultilineString(stream.text); input = { - 'text/html': formatted.includes('<') ? `${formatted}` : `
${formatted}
` + 'text/html': concatted // XML tags should have already been escaped. }; - // Output may have goofy ascii colorization chars in it. Try - // colorizing if we don't have html that needs around it (ex. <type ='string'>) + // Output may have ascii colorization chars in it. try { - if (ansiRegex().test(formatted)) { + if (ansiRegex().test(concatted)) { const converter = new CellOutput.ansiToHtmlClass(CellOutput.getAnsiToHtmlOptions()); - const html = converter.toHtml(formatted); + const html = converter.toHtml(concatted); input = { 'text/html': html }; From 02d0ea5b41c26256b2e63730d8bbd7522476ef38 Mon Sep 17 00:00:00 2001 From: rchiodo <rchiodo@users.noreply.github.com> Date: Tue, 22 Sep 2020 13:22:11 -0700 Subject: [PATCH 2/4] add some functional tests --- src/test/datascience/notebook.functional.test.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/test/datascience/notebook.functional.test.ts b/src/test/datascience/notebook.functional.test.ts index 5e89e9f675bd..6e57680c0059 100644 --- a/src/test/datascience/notebook.functional.test.ts +++ b/src/test/datascience/notebook.functional.test.ts @@ -6,6 +6,8 @@ import { assert } from 'chai'; import { ChildProcess } from 'child_process'; import * as fs from 'fs-extra'; import { injectable } from 'inversify'; +// tslint:disable-next-line: no-require-imports +import escape = require('lodash/escape'); import * as os from 'os'; import * as path from 'path'; import { SemVer } from 'semver'; @@ -1022,6 +1024,15 @@ a`, result: 1, verifyValue: (d) => assert.equal(d, 1, 'Plain text invalid') }, + { + markdownRegEx: undefined, + code: `a="<a href=f>" +a`, + mimeType: 'text/plain', + cellType: 'code', + result: `<a href=f>`, + verifyValue: (d) => assert.equal(d, escape(`<a href=f>`), 'XML not escaped') + }, { markdownRegEx: undefined, code: `import pandas as pd @@ -1032,7 +1043,7 @@ df.head()`, cellType: 'error', // tslint:disable-next-line:quotemark verifyValue: (d) => - assert.ok((d as string).includes("has no attribute 'read'"), 'Unexpected error result') + assert.ok((d as string).includes(escape("has no attribute 'read'")), 'Unexpected error result') }, { markdownRegEx: undefined, From fced3dced15b871ac6182963530cb8714c8c91ed Mon Sep 17 00:00:00 2001 From: rchiodo <rchiodo@users.noreply.github.com> Date: Tue, 22 Sep 2020 13:25:48 -0700 Subject: [PATCH 3/4] Add news entry --- news/2 Fixes/5678.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/2 Fixes/5678.md diff --git a/news/2 Fixes/5678.md b/news/2 Fixes/5678.md new file mode 100644 index 000000000000..1a7d1c5fd977 --- /dev/null +++ b/news/2 Fixes/5678.md @@ -0,0 +1 @@ +Fix escaping of output to encode HTML chars correctly. \ No newline at end of file From 3c65ad8a795d2f91ade2524377cac33232a16b11 Mon Sep 17 00:00:00 2001 From: rchiodo <rchiodo@users.noreply.github.com> Date: Tue, 22 Sep 2020 14:45:02 -0700 Subject: [PATCH 4/4] Fix functional tests --- src/test/datascience/Untitled-1.ipynb | 47 +++++++++++++++++++ .../interactiveWindow.functional.test.tsx | 34 +++++++------- .../datascience/liveshare.functional.test.tsx | 28 +++++------ .../nativeEditor.functional.test.tsx | 18 +++---- .../datascience/notebook.functional.test.ts | 2 +- .../trustedNotebooks.functional.test.tsx | 12 ++--- .../uiTests/ipywidget.ui.functional.test.ts | 2 +- 7 files changed, 95 insertions(+), 48 deletions(-) create mode 100644 src/test/datascience/Untitled-1.ipynb diff --git a/src/test/datascience/Untitled-1.ipynb b/src/test/datascience/Untitled-1.ipynb new file mode 100644 index 000000000000..603be536258d --- /dev/null +++ b/src/test/datascience/Untitled-1.ipynb @@ -0,0 +1,47 @@ +{ + "metadata": { + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.6-final" + }, + "orig_nbformat": 2 + }, + "nbformat": 4, + "nbformat_minor": 2, + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": "1" + }, + "metadata": {}, + "execution_count": 1 + } + ], + "source": [ + "a=1\n", + "a" + ] + } + ] +} \ No newline at end of file diff --git a/src/test/datascience/interactiveWindow.functional.test.tsx b/src/test/datascience/interactiveWindow.functional.test.tsx index 504e3499f401..d40cb82906ac 100644 --- a/src/test/datascience/interactiveWindow.functional.test.tsx +++ b/src/test/datascience/interactiveWindow.functional.test.tsx @@ -128,7 +128,7 @@ suite('DataScience Interactive Window output tests', () => { async () => { await addCode(ioc, 'a=1\na'); - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); }, () => { return ioc; @@ -171,7 +171,7 @@ for i in range(10): addMockData(ioc, 'a=1', undefined, 'text/plain'); await addCode(ioc, 'a=1'); - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.First); + verifyHtmlOnInteractiveCell('1', CellPosition.First); verifyHtmlOnInteractiveCell(undefined, CellPosition.Last); }, () => { @@ -236,7 +236,7 @@ for i in range(10): } } - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); }, () => { return ioc; @@ -300,7 +300,7 @@ for i in range(10): } } - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); }, () => { return ioc; @@ -698,7 +698,7 @@ for i in range(0, 100): const window = (await interactiveWindowProvider.getOrCreate(undefined)) as InteractiveWindow; await addCode(ioc, 'a=1\na'); const activeInterpreter = await interpreterService.getActiveInterpreter(window.owningResource); - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); assert.equal( window.notebook!.getMatchingInterpreter()?.path, activeInterpreter?.path, @@ -724,7 +724,7 @@ for i in range(0, 100): activeInterpreter?.path, 'Active intrepreter used to launch second notebook when it should not have' ); - verifyHtmlOnCell(ioc.getWrapper('interactive'), 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(ioc.getWrapper('interactive'), 'InteractiveCell', '1', CellPosition.Last); } else { context.skip(); } @@ -867,7 +867,7 @@ for i in range(0, 100): // Then enter some code. await enterInput(mount, 'a=1\na', 'InteractiveCell'); - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); }, () => { return ioc; @@ -888,7 +888,7 @@ for i in range(0, 100): // Then enter some code. await enterInput(mount, 'a=1\na', 'InteractiveCell'); - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); const ImageButtons = getLastOutputCell(mount.wrapper, 'InteractiveCell').find(ImageButton); assert.equal(ImageButtons.length, 4, 'Cell buttons not found'); const copyToSource = ImageButtons.at(2); @@ -911,7 +911,7 @@ for i in range(0, 100): // Then enter some code. await enterInput(mount, 'a=1\na', 'InteractiveCell'); - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); // Then delete the node const lastCell = getLastOutputCell(mount.wrapper, 'InteractiveCell'); @@ -928,7 +928,7 @@ for i in range(0, 100): // Should be able to enter again await enterInput(mount, 'a=1\na', 'InteractiveCell'); - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); // Try a 3rd time with some new input addMockData(ioc, 'print("hello")', 'hello'); @@ -952,7 +952,7 @@ for i in range(0, 100): async () => { // Prime the pump await addCode(ioc, 'a=1\na'); - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); // Then something that could possibly timeout addContinuousMockData(ioc, 'import time\r\ntime.sleep(1000)', (_c) => { @@ -979,7 +979,7 @@ for i in range(0, 100): // Now see if our wrapper still works. Interactive window should have forced a restart await window.addCode('a=1\na', Uri.file('foo'), 0); - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); }, () => { return ioc; @@ -1071,7 +1071,7 @@ for i in range(0, 100): // Then enter some code. await enterInput(mount, 'a=1\na', 'InteractiveCell'); - verifyHtmlOnInteractiveCell('<span>1</span>', CellPosition.Last); + verifyHtmlOnInteractiveCell('1', CellPosition.Last); const ImageButtons = getLastOutputCell(mount.wrapper, 'InteractiveCell').find(ImageButton); assert.equal(ImageButtons.length, 4, 'Cell buttons not found'); const gatherCode = ImageButtons.at(0); @@ -1198,21 +1198,21 @@ for i in range(0, 100): await addCell(ne.mount, 'a=1\na', true); // Make sure both are correct - verifyHtmlOnCell(iw.mount.wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); - verifyHtmlOnCell(ne.mount.wrapper, 'NativeCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(iw.mount.wrapper, 'InteractiveCell', '1', CellPosition.Last); + verifyHtmlOnCell(ne.mount.wrapper, 'NativeCell', '1', CellPosition.Last); // Close the interactive editor. await closeInteractiveWindow(ioc, iw.window); // Run another cell and make sure it works in the notebook await addCell(ne.mount, 'b=2\nb', true); - verifyHtmlOnCell(ne.mount.wrapper, 'NativeCell', '<span>2</span>', CellPosition.Last); + verifyHtmlOnCell(ne.mount.wrapper, 'NativeCell', '2', CellPosition.Last); // Rerun the interactive window iw = await getOrCreateInteractiveWindow(ioc); await addCode(ioc, 'a=1\na'); - verifyHtmlOnCell(iw.mount.wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(iw.mount.wrapper, 'InteractiveCell', '1', CellPosition.Last); }); test('Multiple interactive windows', async () => { ioc.forceDataScienceSettingsChanged({ interactiveWindowMode: 'multiple' }); diff --git a/src/test/datascience/liveshare.functional.test.tsx b/src/test/datascience/liveshare.functional.test.tsx index 0a8b6a0fa3dc..55d45c04742c 100644 --- a/src/test/datascience/liveshare.functional.test.tsx +++ b/src/test/datascience/liveshare.functional.test.tsx @@ -217,7 +217,7 @@ suite('DataScience LiveShare tests', () => { // Just run some code in the host const wrapper = await addCodeToRole(vsls.Role.Host, 'a=1\na'); - verifyHtmlOnCell(wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(wrapper, 'InteractiveCell', '1', CellPosition.Last); }); test('Host & Guest Simple', async function () { @@ -234,14 +234,14 @@ suite('DataScience LiveShare tests', () => { // Send code through the host const wrapper = await addCodeToRole(vsls.Role.Host, 'a=1\na'); - verifyHtmlOnCell(wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(wrapper, 'InteractiveCell', '1', CellPosition.Last); // Verify it ended up on the guest too assert.ok(guestContainer.getInteractiveWebPanel(undefined), 'Guest wrapper not created'); verifyHtmlOnCell( guestContainer.getInteractiveWebPanel(undefined).wrapper, 'InteractiveCell', - '<span>1</span>', + '1', CellPosition.Last ); }); @@ -253,7 +253,7 @@ suite('DataScience LiveShare tests', () => { addMockData(hostContainer!, 'b=2\nb', 2); await getOrCreateInteractiveWindow(vsls.Role.Host); let wrapper = await addCodeToRole(vsls.Role.Host, 'a=1\na'); - verifyHtmlOnCell(wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(wrapper, 'InteractiveCell', '1', CellPosition.Last); await startSession(vsls.Role.Host); await getOrCreateInteractiveWindow(vsls.Role.Guest); @@ -265,7 +265,7 @@ suite('DataScience LiveShare tests', () => { verifyHtmlOnCell( guestContainer.getInteractiveWebPanel(undefined).wrapper, 'InteractiveCell', - '<span>2</span>', + '2', CellPosition.Last ); }); @@ -280,14 +280,14 @@ suite('DataScience LiveShare tests', () => { // Send code through the host let wrapper = await addCodeToRole(vsls.Role.Host, 'a=1\na'); - verifyHtmlOnCell(wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(wrapper, 'InteractiveCell', '1', CellPosition.Last); // Stop the session await stopSession(vsls.Role.Host); // Send code again. It should still work. wrapper = await addCodeToRole(vsls.Role.Host, 'a=1\na'); - verifyHtmlOnCell(wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(wrapper, 'InteractiveCell', '1', CellPosition.Last); }); test('Host startup and guest restart', async function () { @@ -302,7 +302,7 @@ suite('DataScience LiveShare tests', () => { // Send code through the host let wrapper = await addCodeToRole(vsls.Role.Host, 'a=1\na'); - verifyHtmlOnCell(wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(wrapper, 'InteractiveCell', '1', CellPosition.Last); // Shutdown the host host.window.dispose(); @@ -310,13 +310,13 @@ suite('DataScience LiveShare tests', () => { // Startup a guest and run some code. await startSession(vsls.Role.Guest); wrapper = await addCodeToRole(vsls.Role.Guest, 'a=1\na'); - verifyHtmlOnCell(wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(wrapper, 'InteractiveCell', '1', CellPosition.Last); assert.ok(hostContainer.getInteractiveWebPanel(undefined), 'Host wrapper not created'); verifyHtmlOnCell( hostContainer.getInteractiveWebPanel(undefined).wrapper, 'InteractiveCell', - '<span>1</span>', + '1', CellPosition.Last ); }); @@ -349,12 +349,12 @@ suite('DataScience LiveShare tests', () => { assert.ok(both, 'Expected both guest and host to be used'); await codeWatcher.runAllCells(); }); - verifyHtmlOnCell(wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(wrapper, 'InteractiveCell', '1', CellPosition.Last); assert.ok(hostContainer.getInteractiveWebPanel(undefined), 'Host wrapper not created for some reason'); verifyHtmlOnCell( hostContainer.getInteractiveWebPanel(undefined).wrapper, 'InteractiveCell', - '<span>1</span>', + '1', CellPosition.Last ); }); @@ -425,7 +425,7 @@ suite('DataScience LiveShare tests', () => { // Start just the host and verify it works await startSession(vsls.Role.Host); let wrapper = await addCodeToRole(vsls.Role.Host, '#%%\na=1\na'); - verifyHtmlOnCell(wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(wrapper, 'InteractiveCell', '1', CellPosition.Last); // Disable guest checking on the guest (same as if the guest doesn't have the python extension) await startSession(vsls.Role.Guest); @@ -434,7 +434,7 @@ suite('DataScience LiveShare tests', () => { // Host should now be in a state that if any code runs, the session should end. However // the code should still run wrapper = await addCodeToRole(vsls.Role.Host, '#%%\na=1\na'); - verifyHtmlOnCell(wrapper, 'InteractiveCell', '<span>1</span>', CellPosition.Last); + verifyHtmlOnCell(wrapper, 'InteractiveCell', '1', CellPosition.Last); assert.equal(isSessionStarted(vsls.Role.Host), false, 'Host should have exited session'); assert.equal(isSessionStarted(vsls.Role.Guest), false, 'Guest should have exited session'); assert.ok(lastErrorMessage, 'Error was not set during session shutdown'); diff --git a/src/test/datascience/nativeEditor.functional.test.tsx b/src/test/datascience/nativeEditor.functional.test.tsx index f2ec387bd364..82e4f21ad91a 100644 --- a/src/test/datascience/nativeEditor.functional.test.tsx +++ b/src/test/datascience/nativeEditor.functional.test.tsx @@ -223,7 +223,7 @@ suite('DataScience Native Editor', () => { // Add a cell into the UI and wait for it to render await addCell(mount, 'a=1\na'); - verifyHtmlOnCell(mount.wrapper, 'NativeCell', '<span>1</span>', 1); + verifyHtmlOnCell(mount.wrapper, 'NativeCell', '1', 1); }); runMountedTest('Invalid session still runs', async (context) => { @@ -238,7 +238,7 @@ suite('DataScience Native Editor', () => { // Run the first cell. Should fail but then ask for another await addCell(mount, 'a=1\na'); - verifyHtmlOnCell(mount.wrapper, 'NativeCell', '<span>1</span>', 1); + verifyHtmlOnCell(mount.wrapper, 'NativeCell', '1', 1); } else { context.skip(); } @@ -414,7 +414,7 @@ suite('DataScience Native Editor', () => { // Run the first cell. Should fail but then ask for another await addCell(ne.mount, 'a=1\na'); - verifyHtmlOnCell(ne.mount.wrapper, 'NativeCell', '<span>1</span>', 1); + verifyHtmlOnCell(ne.mount.wrapper, 'NativeCell', '1', 1); } else { context.skip(); } @@ -448,7 +448,7 @@ suite('DataScience Native Editor', () => { // Verify we picked the valid kernel. await addCell(ne.mount, 'a=1\na'); - verifyHtmlOnCell(ne.mount.wrapper, 'NativeCell', '<span>1</span>', 2); + verifyHtmlOnCell(ne.mount.wrapper, 'NativeCell', '1', 2); } else { context.skip(); } @@ -1694,7 +1694,7 @@ df.head()`; wrapper.update(); // Ensure cell was executed. - verifyHtmlOnCell(wrapper, 'NativeCell', '<span>2</span>', 1); + verifyHtmlOnCell(wrapper, 'NativeCell', '2', 1); // The third cell should be selected. assert.ok(isCellSelected(wrapper, 'NativeCell', 2)); @@ -1732,7 +1732,7 @@ df.head()`; await update; // Ensure cell was executed. - verifyHtmlOnCell(wrapper, 'NativeCell', '<span>2</span>', 1); + verifyHtmlOnCell(wrapper, 'NativeCell', '2', 1); // The first cell should be selected. assert.ok(isCellSelected(wrapper, 'NativeCell', 1)); @@ -1962,7 +1962,7 @@ df.head()`; await update; // Ensure cell was executed. - verifyHtmlOnCell(wrapper, 'NativeCell', '<span>3</span>', 2); + verifyHtmlOnCell(wrapper, 'NativeCell', '3', 2); // Hide the output update = waitForMessage(ioc, InteractiveWindowMessages.OutputToggled); @@ -1970,7 +1970,7 @@ df.head()`; await update; // Ensure cell output is hidden (looking for cell results will throw an exception). - assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '<span>3</span>', 2)); + assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '3', 2)); // Display the output update = waitForMessage(ioc, InteractiveWindowMessages.OutputToggled); @@ -1978,7 +1978,7 @@ df.head()`; await update; // Ensure cell output is visible again. - verifyHtmlOnCell(wrapper, 'NativeCell', '<span>3</span>', 2); + verifyHtmlOnCell(wrapper, 'NativeCell', '3', 2); }); test("Toggle line numbers using the 'l' key", async () => { diff --git a/src/test/datascience/notebook.functional.test.ts b/src/test/datascience/notebook.functional.test.ts index 6e57680c0059..aeec51594400 100644 --- a/src/test/datascience/notebook.functional.test.ts +++ b/src/test/datascience/notebook.functional.test.ts @@ -161,7 +161,7 @@ suite('DataScience notebook tests', () => { const data = extractDataOutput(cells[0]); if (pathVerify) { // For a path comparison normalize output - const normalizedOutput = path.normalize(data).toUpperCase().replace(/'/g, ''); + const normalizedOutput = path.normalize(data).toUpperCase().replace(/&#39;/g, ''); const normalizedTarget = path.normalize(expectedValue).toUpperCase().replace(/'/g, ''); assert.equal(normalizedOutput, normalizedTarget, 'Cell path values does not match'); } else { diff --git a/src/test/datascience/trustedNotebooks.functional.test.tsx b/src/test/datascience/trustedNotebooks.functional.test.tsx index 57d5daef5269..302ad724e214 100644 --- a/src/test/datascience/trustedNotebooks.functional.test.tsx +++ b/src/test/datascience/trustedNotebooks.functional.test.tsx @@ -273,9 +273,9 @@ suite('Notebook trust', () => { suite('Open an untrusted notebook', async () => { test('Outputs are not rendered', () => { // No outputs should have rendered - assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '<span>1</span>', 0)); - assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '<span>2</span>', 1)); - assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '<span>3</span>', 2)); + assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '1', 0)); + assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '2', 1)); + assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '3', 2)); }); test('Cannot edit cell contents', async () => { await focusCell(0); @@ -332,7 +332,7 @@ suite('Notebook trust', () => { // Waiting for an execution rendered message should timeout await expect(promise).to.eventually.be.rejected; // No output should have been rendered - assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '<span>2</span>', cellIndex)); + assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '2', cellIndex)); }); test('Shift+enter does not execute cell or advance to next cell', async () => { const cellIndex = 1; @@ -344,7 +344,7 @@ suite('Notebook trust', () => { // Waiting for an execution rendered message should timeout await expect(promise).to.eventually.be.rejected; // No output should have been rendered - assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '<span>2</span>', cellIndex)); + assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '2', cellIndex)); // 3rd cell should be neither selected nor focused assert.isFalse(isCellSelected(wrapper, 'NativeCell', cellIndex + 1)); assert.isFalse(isCellFocused(wrapper, 'NativeCell', cellIndex + 1)); @@ -360,7 +360,7 @@ suite('Notebook trust', () => { // Waiting for an execution rendered message should timeout await expect(promise).to.eventually.be.rejected; // No output should have been rendered - assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '<span>2</span>', cellIndex)); + assert.throws(() => verifyHtmlOnCell(wrapper, 'NativeCell', '2', cellIndex)); // No cell should have been added assert.equal(wrapper.find('NativeCell').length, 3, 'Cell added'); }); diff --git a/src/test/datascience/uiTests/ipywidget.ui.functional.test.ts b/src/test/datascience/uiTests/ipywidget.ui.functional.test.ts index f6916d491134..8c5850e37309 100644 --- a/src/test/datascience/uiTests/ipywidget.ui.functional.test.ts +++ b/src/test/datascience/uiTests/ipywidget.ui.functional.test.ts @@ -155,7 +155,7 @@ use(chaiAsPromised); await retryIfFail(async () => { await assert.eventually.isTrue(notebookUI.cellHasOutput(0)); const outputHtml = await notebookUI.getCellOutputHTML(0); - assert.include(outputHtml, '<span>1</span>'); + assert.include(outputHtml, '1'); }); });