@nteract/monaco-editor
Version:
A React component for the monaco editor, tailored for nteract
354 lines (322 loc) • 13.5 kB
text/typescript
import { createMessage, JupyterMessage } from "@nteract/messaging";
import { Subject } from "rxjs";
import * as Monaco from "monaco-editor/esm/vs/editor/editor.api";
import { completionProvider } from "../src/completions/completionItemProvider";
import * as editorBase from "../src/editor-base";
// Setup items shared by all tests
// Create Editor Model and Position
const testModel = Monaco.editor.createModel("some test code", "python");
const testPos = new Monaco.Position(1, 3);
// Mock the completion Request method
const mockFn = jest.spyOn(editorBase, "completionRequest");
const mockCompletionRequest = createMessage("complete_request", {
content: {
code: "foo",
cursor_pos: 2
}
});
mockFn.mockReturnValue(mockCompletionRequest);
describe("Completions should not get trigerred when channels/messages are missing", () => {
beforeAll(() => {
jest.clearAllMocks();
});
afterEach(() => {
jest.clearAllMocks();
});
it("Should not return any suggestions when channels is undefined", (done) => {
completionProvider.setChannels(undefined);
completionProvider.provideCompletionItems(testModel, testPos).then((result) => {
expect(result).toHaveProperty("suggestions");
expect(result.suggestions).toHaveLength(0);
done();
});
});
it("Should not return any suggestions when channels is empty", (done) => {
// Create an empty channel with no messages
const channels = new Subject<JupyterMessage>();
completionProvider.setChannels(channels);
completionProvider.provideCompletionItems(testModel, testPos).then((result) => {
expect(result).toHaveProperty("suggestions");
expect(result.suggestions).toHaveLength(0);
done();
});
channels.complete();
});
it("Should not return any suggestions when channels don't contain a complete_reply message", (done) => {
const testMessage = createMessage("kernel_info_reply");
const channels = new Subject<JupyterMessage>();
completionProvider.setChannels(channels);
completionProvider.provideCompletionItems(testModel, testPos).then((result) => {
expect(result).toHaveProperty("suggestions");
expect(result.suggestions).toHaveLength(0);
done();
});
// No suggestions should be provided for incompatible message type
channels.next(testMessage);
channels.complete();
});
it("Should not return any suggestions when channels don't have the correct response message", (done) => {
const testMessage = createMessage("complete_reply", {
content: {
status: "some_status",
cursor_start: 3,
cursor_end: 5,
matches: ["some_completion"]
}
});
const channels = new Subject<JupyterMessage>();
completionProvider.setChannels(channels);
completionProvider.provideCompletionItems(testModel, testPos).then((result) => {
expect(result).toHaveProperty("suggestions");
expect(result.suggestions).toHaveLength(0);
done();
});
// Although we have a complete reply message, it is not the child of the appropriate complete_request message
channels.next(testMessage);
channels.complete();
});
});
describe("Appropriate completions should be provided", () => {
beforeAll(() => {
jest.clearAllMocks();
});
afterEach(() => {
jest.clearAllMocks();
});
it("Should return a suggestion when channels contain a single reply with string match", (done) => {
const mockCompleteReply = createMessage("complete_reply", {
content: {
status: "some_status",
cursor_start: 3,
cursor_end: 5,
matches: ["some_completion"]
},
parent_header: mockCompletionRequest.header
});
const channels = new Subject<JupyterMessage>();
completionProvider.setChannels(channels);
completionProvider.provideCompletionItems(testModel, testPos).then((result) => {
expect(result).toHaveProperty("suggestions");
const returnedSuggestions = result.suggestions;
expect(returnedSuggestions).toHaveLength(1);
expect(returnedSuggestions[0].kind).toEqual(Monaco.languages.CompletionItemKind.Field);
expect(returnedSuggestions[0].insertText).toEqual("some_completion");
done();
});
// Set the reply message on channels and complete the stream
channels.next(mockCompleteReply);
channels.complete();
});
it("Should return all suggestions from the received matches", (done) => {
const mockCompleteReply = createMessage("complete_reply", {
content: {
status: "some_status",
cursor_start: 3,
cursor_end: 5,
matches: ["completion1", "completion2"]
},
parent_header: mockCompletionRequest.header
});
const channels = new Subject<JupyterMessage>();
completionProvider.setChannels(channels);
completionProvider.provideCompletionItems(testModel, testPos).then((result) => {
expect(result).toHaveProperty("suggestions");
const returnedSuggestions = result.suggestions;
expect(returnedSuggestions).toHaveLength(2);
expect(returnedSuggestions[0].kind).toEqual(Monaco.languages.CompletionItemKind.Field);
expect(returnedSuggestions[0].insertText).toEqual("completion1");
expect(returnedSuggestions[1].kind).toEqual(Monaco.languages.CompletionItemKind.Field);
expect(returnedSuggestions[1].insertText).toEqual("completion2");
done();
});
// Set the reply message on channels and complete the stream
channels.next(mockCompleteReply);
channels.complete();
});
it("Should return a suggestion when channels contain a single reply with a completionItem match", (done) => {
const mockCompleteReply = createMessage("complete_reply", {
content: {
status: "some_status",
cursor_start: 3,
cursor_end: 5,
matches: [
{
end: 5,
start: 3,
type: "keyword",
text: "some_completion"
}
]
},
parent_header: mockCompletionRequest.header
});
const channels = new Subject<JupyterMessage>();
completionProvider.setChannels(channels);
completionProvider.provideCompletionItems(testModel, testPos).then((result) => {
expect(result).toHaveProperty("suggestions");
const returnedSuggestions = result.suggestions;
expect(returnedSuggestions).toHaveLength(1);
expect(returnedSuggestions[0].kind).toEqual(Monaco.languages.CompletionItemKind.Keyword);
expect(returnedSuggestions[0].insertText).toEqual("some_completion");
done();
});
// Set the reply message on channels and complete the stream
channels.next(mockCompleteReply);
channels.complete();
});
it("Should return suggestions when matches received in _jupyter_types_experimental metadata property", (done) => {
const mockCompleteReply = createMessage("complete_reply", {
content: {
status: "some_status",
cursor_start: 3,
cursor_end: 5,
metadata: {
_jupyter_types_experimental: ["some_completion"]
}
},
parent_header: mockCompletionRequest.header
});
const channels = new Subject<JupyterMessage>();
completionProvider.setChannels(channels);
completionProvider.provideCompletionItems(testModel, testPos).then((result) => {
expect(result).toHaveProperty("suggestions");
const returnedSuggestions = result.suggestions;
expect(returnedSuggestions).toHaveLength(1);
expect(returnedSuggestions[0].kind).toEqual(Monaco.languages.CompletionItemKind.Field);
expect(returnedSuggestions[0].insertText).toEqual("some_completion");
done();
});
// Set the reply message on channels and complete the stream
channels.next(mockCompleteReply);
channels.complete();
});
it("Should return suggestions with content after any last dots", (done) => {
const mockCompleteReply = createMessage("complete_reply", {
content: {
status: "some_status",
cursor_start: 3,
cursor_end: 5,
matches: ["completion1.itemA.itemB", "completion2.itemC"]
},
parent_header: mockCompletionRequest.header
});
const channels = new Subject<JupyterMessage>();
completionProvider.setChannels(channels);
completionProvider.provideCompletionItems(testModel, testPos).then((result) => {
expect(result).toHaveProperty("suggestions");
const returnedSuggestions = result.suggestions;
expect(returnedSuggestions).toHaveLength(2);
expect(returnedSuggestions[0].kind).toEqual(Monaco.languages.CompletionItemKind.Field);
expect(returnedSuggestions[0].label).toEqual("itemB");
expect(returnedSuggestions[0].insertText).toEqual("itemB");
expect(returnedSuggestions[1].kind).toEqual(Monaco.languages.CompletionItemKind.Field);
expect(returnedSuggestions[1].label).toEqual("itemC");
expect(returnedSuggestions[1].insertText).toEqual("itemC");
done();
});
// Set the reply message on channels and complete the stream
channels.next(mockCompleteReply);
channels.complete();
});
it("Should return suggestions containing cell and line magics when position is at start of cell", (done) => {
const model = Monaco.editor.createModel("%m", "python");
const position = new Monaco.Position(1, 3);
const mockCompleteReply = createMessage("complete_reply", {
content: {
status: "some_status",
cursor_start: 0,
cursor_end: 2,
matches: ["%%magic1", "%magic2"]
},
parent_header: mockCompletionRequest.header
});
const channels = new Subject<JupyterMessage>();
completionProvider.setChannels(channels);
completionProvider.provideCompletionItems(model, position).then((result) => {
expect(result).toHaveProperty("suggestions");
const returnedSuggestions = result.suggestions;
expect(returnedSuggestions).toHaveLength(2);
expect(returnedSuggestions[0].label).toEqual("%%magic1");
expect(returnedSuggestions[1].label).toEqual("%magic2");
done();
});
// Set the reply message on channels and complete the stream
channels.next(mockCompleteReply);
channels.complete();
});
it("Should return suggestions containing cell and line magics when position is at start of 2nd line with blank 1st line", (done) => {
const model = Monaco.editor.createModel("\n%m", "python");
const position = new Monaco.Position(2, 4);
const mockCompleteReply = createMessage("complete_reply", {
content: {
status: "some_status",
cursor_start: 1,
cursor_end: 3,
matches: ["%%magic1", "%magic2"]
},
parent_header: mockCompletionRequest.header
});
const channels = new Subject<JupyterMessage>();
completionProvider.setChannels(channels);
completionProvider.provideCompletionItems(model, position).then((result) => {
expect(result).toHaveProperty("suggestions");
const returnedSuggestions = result.suggestions;
expect(returnedSuggestions).toHaveLength(2);
expect(returnedSuggestions[0].label).toEqual("%%magic1");
expect(returnedSuggestions[1].label).toEqual("%magic2");
done();
});
// Set the reply message on channels and complete the stream
channels.next(mockCompleteReply);
channels.complete();
});
it("Should return suggestions containing line magic when position is at start of 2nd line with non-blank 1st line", (done) => {
const model = Monaco.editor.createModel("print()\n%m", "python");
const position = new Monaco.Position(9, 11);
const mockCompleteReply = createMessage("complete_reply", {
content: {
status: "some_status",
cursor_start: 8,
cursor_end: 10,
matches: ["%%magic1", "%magic2"]
},
parent_header: mockCompletionRequest.header
});
const channels = new Subject<JupyterMessage>();
completionProvider.setChannels(channels);
completionProvider.provideCompletionItems(model, position).then((result) => {
expect(result).toHaveProperty("suggestions");
const returnedSuggestions = result.suggestions;
expect(returnedSuggestions).toHaveLength(1);
expect(returnedSuggestions[0].label).toEqual("%magic2");
done();
});
// Set the reply message on channels and complete the stream
channels.next(mockCompleteReply);
channels.complete();
});
it("Should return suggestions not containing cell and line magics when content before position contains non-whitespace characters", (done) => {
const model = Monaco.editor.createModel("print() %m", "python");
const position = new Monaco.Position(9, 11);
const mockCompleteReply = createMessage("complete_reply", {
content: {
status: "some_status",
cursor_start: 8,
cursor_end: 10,
matches: ["%%magic1", "%magic2"]
},
parent_header: mockCompletionRequest.header
});
const channels = new Subject<JupyterMessage>();
completionProvider.setChannels(channels);
completionProvider.provideCompletionItems(model, position).then((result) => {
expect(result).toHaveProperty("suggestions");
const returnedSuggestions = result.suggestions;
expect(returnedSuggestions).toHaveLength(0);
done();
});
// Set the reply message on channels and complete the stream
channels.next(mockCompleteReply);
channels.complete();
});
});