UNPKG

@jupyter-lsp/jupyterlab-lsp

Version:

Language Server Protocol integration for JupyterLab

275 lines (267 loc) 11 kB
import { PageConfig } from '@jupyterlab/coreutils'; import { CodeExtractorsManager } from '@jupyterlab/lsp'; import { NotebookModel } from '@jupyterlab/notebook'; import { EditApplicator } from './edits'; import { codeCell, getCellsJSON, pythonNotebookMetadata, showAllCells, FileEditorTestEnvironment, NotebookTestEnvironment } from './testutils'; import { foreignCodeExtractors } from './transclusions/ipython/extractors'; import { overrides } from './transclusions/ipython/overrides'; const jsFibCode = `function fib(n) { return n<2?n:fib(n-1)+fib(n-2); } fib(5); window.location /;`; const jsFib2Code = `function fib2(n) { return n<2?n:fib2(n-1)+fib2(n-2); } fib2(5); window.location /;`; const jsPartialEdits = [ { range: { start: { line: 0, character: 9 }, end: { line: 0, character: 12 } }, newText: 'fib2' }, { range: { start: { line: 1, character: 15 }, end: { line: 1, character: 18 } }, newText: 'fib2' }, { range: { start: { line: 1, character: 24 }, end: { line: 1, character: 27 } }, newText: 'fib2' }, { range: { start: { line: 4, character: 0 }, end: { line: 4, character: 3 } }, newText: 'fib2' } ]; describe('EditApplicator', () => { PageConfig.setOption('rootUri', 'file://'); describe('applyEdit()', () => { describe('editing in FileEditor', () => { let applicator; let environment; beforeEach(async () => { environment = new FileEditorTestEnvironment(); await environment.init(); await environment.adapter.ready; applicator = new EditApplicator(environment.adapter.virtualDocument, environment.adapter); }); afterEach(() => { environment.dispose(); }); it('applies simple edit in FileEditor', async () => { environment.activeEditor.model.sharedModel.setSource('foo bar'); await environment.adapter.updateDocuments(); let outcome = await applicator.applyEdit({ changes: { ['file:///' + environment.documentOptions.path]: [ { range: { start: { line: 0, character: 0 }, end: { line: 1, character: 0 } }, newText: 'changed bar' } ] } }); expect(environment.activeEditor.model.sharedModel.source).toBe('changed bar'); let rawValue = environment.activeEditor.editor.state.doc.toString(); expect(rawValue).toBe('changed bar'); expect(outcome.wasGranular).toBe(false); expect(outcome.modifiedCells).toBe(1); expect(outcome.appliedChanges).toBe(1); }); it('correctly summarizes empty edit', async () => { environment.activeEditor.model.sharedModel.setSource('foo bar'); await environment.adapter.updateDocuments(); let outcome = await applicator.applyEdit({ changes: { ['file:///' + environment.documentOptions.path]: [] } }); let rawValue = environment.activeEditor.editor.state.doc.toString(); expect(rawValue).toBe('foo bar'); expect(environment.activeEditor.model.sharedModel.source).toBe('foo bar'); expect(outcome.wasGranular).toBe(false); expect(outcome.appliedChanges).toBe(0); expect(outcome.modifiedCells).toBe(0); }); it('applies partial edits', async () => { environment.activeEditor.model.sharedModel.setSource(jsFibCode); await environment.adapter.updateDocuments(); let result = await applicator.applyEdit({ changes: { ['file:///' + environment.documentOptions.path]: jsPartialEdits } }); let rawValue = environment.activeEditor.editor.state.doc.toString(); expect(rawValue).toBe(jsFib2Code); expect(environment.activeEditor.model.sharedModel.source).toBe(jsFib2Code); expect(result.appliedChanges).toBe(jsPartialEdits.length); expect(result.wasGranular).toBe(true); expect(result.modifiedCells).toBe(1); }); }); describe('editing in Notebook', () => { let applicator; let environment; beforeEach(async () => { const manager = new CodeExtractorsManager(); for (let language of Object.keys(foreignCodeExtractors)) { for (let extractor of foreignCodeExtractors[language]) { manager.register(extractor, language); } } environment = new NotebookTestEnvironment({ document: { overridesRegistry: { python: { cell: overrides.filter(override => override.scope == 'cell'), line: overrides.filter(override => override.scope == 'line') } }, foreignCodeExtractors: manager } }); await environment.init(); applicator = new EditApplicator( // TODO upstream, adatper should be parameterizable by virtual documentation class to allow overriding it more easily?? environment.adapter.virtualDocument, environment.adapter); }); afterEach(() => { environment.dispose(); }); async function synchronizeContent() { await environment.adapter.updateDocuments(); } it('applies edit across cells', async () => { let testNotebook = { cells: [ codeCell(['def a_function():\n', ' pass']), codeCell(['x = a_function()']) ], metadata: pythonNotebookMetadata }; let notebook = environment.notebook; notebook.model = new NotebookModel(); notebook.model.fromJSON(testNotebook); showAllCells(notebook); await synchronizeContent(); let mainDocument = environment.adapter.virtualDocument; let oldVirtualSource = 'def a_function():\n pass\n\n\nx = a_function()\n'; let newVirtualSource = 'def a_function_2():\n pass\n\n\nx = a_function_2()\n'; expect(mainDocument.value).toBe(oldVirtualSource); let outcome = await applicator.applyEdit({ changes: { [mainDocument.documentInfo.uri]: [ { range: { start: { line: 0, character: 0 }, end: { line: 5, character: 0 } }, newText: newVirtualSource } ] } }); await synchronizeContent(); let value = mainDocument.value; expect(value).toBe(newVirtualSource); let codeCells = getCellsJSON(notebook); expect(codeCells[0]).toHaveProperty('source', 'def a_function_2():\n pass'); expect(codeCells[1]).toHaveProperty('source', 'x = a_function_2()'); expect(outcome.appliedChanges).toBe(1); expect(outcome.wasGranular).toBe(false); expect(outcome.modifiedCells).toBe(2); }); it('handles IPython magics', async () => { let testNotebook = { cells: [ codeCell(['x = %ls\n', 'print(x)']), codeCell(['%%python\n', 'y = x\n', 'print(x)']) ], metadata: pythonNotebookMetadata }; let notebook = environment.notebook; notebook.model = new NotebookModel(); notebook.model.fromJSON(testNotebook); showAllCells(notebook); await environment.adapter.foreingDocumentOpened .promise; let mainDocument = environment.adapter.virtualDocument; let oldVirtualSource = `x = get_ipython().run_line_magic("ls", "") print(x) get_ipython().run_cell_magic("python", "", """y = x print(x)""") `; let newVirtualSource = `z = get_ipython().run_line_magic("ls", "") print(z) get_ipython().run_cell_magic("python", "", """y = x print(x)""") `; await synchronizeContent(); expect(mainDocument.value).toBe(oldVirtualSource); await applicator.applyEdit({ changes: { [mainDocument.documentInfo.uri]: [ { range: { start: { line: 0, character: 0 }, end: { line: 6, character: 10 } }, newText: newVirtualSource } ] } }); await environment.adapter.foreingDocumentOpened .promise; await synchronizeContent(); expect(mainDocument.value).toBe(newVirtualSource); let codeCells = getCellsJSON(notebook); expect(codeCells[0]).toHaveProperty('source', 'z = %ls\nprint(z)'); //expect(codeCells[1]).not.toHaveProperty( // 'source', // '%%python\ny = x\nprint(x)' //); }); }); }); }); //# sourceMappingURL=edits.spec.js.map