Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/client/repl/nativeRepl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class NativeRepl implements Disposable {
}

dispose(): void {
this.disposables.forEach((d) => d.dispose());
this.disposables.forEach((d) => d?.dispose());
}

/**
Expand Down
9 changes: 5 additions & 4 deletions src/client/repl/replController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,13 @@ export function createReplController(
const exec = controller.createNotebookCellExecution(cell);
exec.start(Date.now());

const result = await server.execute(cell.document.getText());
const code = cell.document.getText();
const result = await server.execute(code);

if (result?.output) {
exec.replaceOutput([
new vscode.NotebookCellOutput([vscode.NotebookCellOutputItem.text(result.output, 'text/plain')]),
]);
const stdoutItem = vscode.NotebookCellOutputItem.stdout(result.output);
Comment thread
edvilme marked this conversation as resolved.
const output = new vscode.NotebookCellOutput([stdoutItem], {});
exec.replaceOutput([output]);
// TODO: Properly update via NotebookCellOutputItem.error later.
}

Expand Down
58 changes: 58 additions & 0 deletions src/test/repl/nativeRepl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import * as TypeMoq from 'typemoq';
import * as sinon from 'sinon';
import * as vscode from 'vscode';
import { Disposable, EventEmitter, NotebookDocument, Uri } from 'vscode';
import { expect } from 'chai';

Expand Down Expand Up @@ -114,6 +115,63 @@ suite('REPL - Native REPL', () => {
expect(createReplControllerStub.calledOnce).to.be.true;
});

test('createReplController should publish stdout notebook output for REPL execution', async () => {
const mockServer = {
interrupt: sinon.stub(),
execute: sinon.stub().resolves({ status: true, output: 'hello\nworld' }),
dispose: sinon.stub(),
} as any;
const createPythonServerStub = sinon.stub(PythonServer, 'createPythonServer').returns(mockServer as any);

const replaceOutputStub = sinon.stub();
const execStub = {
start: sinon.stub(),
replaceOutput: replaceOutputStub,
end: sinon.stub(),
} as any;

const createNotebookCellExecutionStub = sinon.stub().returns(execStub);
const mockNotebookController = ({
id: 'mockController',
dispose: sinon.stub(),
updateNotebookAffinity: sinon.stub(),
createNotebookCellExecution: createNotebookCellExecutionStub,
supportedLanguages: [] as string[],
description: '',
interruptHandler: undefined,
executeHandler: undefined,
} as any) as vscode.NotebookController;

sinon.stub(vscode.notebooks, 'createNotebookController').returns(mockNotebookController);
createReplControllerStub.restore();

const disposables: Disposable[] = [];
try {
const controller = replController.createReplController('python', disposables, '/cwd');

const mockTextDocument = { getText: sinon.stub().returns('print("hi")') } as any;
const mockCell = { document: mockTextDocument } as any;
await (controller.executeHandler as any)([mockCell]);

expect(createPythonServerStub.calledOnce).to.be.true;
expect(mockServer.execute.calledOnceWithExactly('print("hi")')).to.be.true;
expect(createNotebookCellExecutionStub.calledOnce).to.be.true;
expect(replaceOutputStub.calledOnce).to.be.true;

const outputs = replaceOutputStub.firstCall.args[0] as vscode.NotebookCellOutput[];
expect(outputs).to.have.lengthOf(1);
const output = outputs[0];
expect(output.items).to.have.lengthOf(1);
expect(output.items[0].mime).to.equal('application/vnd.code.notebook.stdout');
expect((output.items[0] as any).metadata).to.be.undefined;
expect(output.metadata).to.deep.equal({});
const outputText = Buffer.from((output.items[0] as any).data).toString();
expect(outputText).to.equal('hello\nworld');
} finally {
disposables.forEach((disposable) => disposable.dispose());
}
});

test('watchNotebookClosed should clean up resources when notebook is closed', async () => {
const notebookCloseEmitter = new EventEmitter<NotebookDocument>();
sinon.stub(vscodeWorkspaceApis, 'onDidCloseNotebookDocument').callsFake((handler) => {
Expand Down
3 changes: 2 additions & 1 deletion src/test/repl/replCommand.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Create test suite and test cases for the `replUtils` module
import * as TypeMoq from 'typemoq';
import { commands, Disposable, Uri } from 'vscode';
import { commands, Disposable, Uri, window } from 'vscode';
import * as sinon from 'sinon';
import { expect } from 'chai';
import { IInterpreterService } from '../../client/interpreter/contracts';
Expand Down Expand Up @@ -163,6 +163,7 @@ suite('REPL - register native repl command', () => {

expect(commandHandler).not.to.be.an('undefined', 'Command handler not initialized');

sinon.stub(window, 'activeTextEditor').get(() => undefined);
await commandHandler!('uri');
sinon.assert.calledOnce(getNativeReplStub);
});
Expand Down
26 changes: 26 additions & 0 deletions src/test/vscode-mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,32 @@ export function initialize() {
generateMock('debug');
generateMock('scm');
generateMock('notebooks');
(mockedVSCode.commands as any).executeCommand = () => Promise.resolve(undefined);
(mockedVSCode.notebooks as any).createNotebookController = () => {
throw new Error('createNotebookController must be stubbed in tests that use it.');
};
when(mockedVSCodeNamespaces.window!.tabGroups).thenReturn({
all: [],
get activeTabGroup(): vscode.TabGroup {
throw new Error('activeTabGroup must be stubbed in tests that use it.');
},
onDidChangeTabGroups: new vscodeMocks.EventEmitter<any>().event,
onDidChangeTabs: new vscodeMocks.EventEmitter<any>().event,
close: () => Promise.resolve(true),
});

// Notebook output helpers used by REPL tests.
(mockedVSCode as any).NotebookCellOutputItem = {
stdout: (value: string) => ({ mime: 'application/vnd.code.notebook.stdout', data: Buffer.from(value) }),
};
(mockedVSCode as any).NotebookCellOutput = class {
public items: any[];
public metadata?: any;
constructor(items: any[], metadata?: any) {
this.items = items;
this.metadata = metadata;
}
};

// Use mock clipboard fo testing purposes.
const clipboard = new MockClipboard();
Expand Down
Loading