123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359 |
- // Copyright (c) Jupyter Development Team.
- // Distributed under the terms of the Modified BSD License.
- import { ISessionContext, SessionContext } from '@jupyterlab/apputils';
- import { CodeEditor, CodeEditorWrapper } from '@jupyterlab/codeeditor';
- import { CodeMirrorEditor } from '@jupyterlab/codemirror';
- import {
- Completer,
- CompleterModel,
- CompletionHandler,
- KernelConnector
- } from '@jupyterlab/completer';
- import { createSessionContext } from '@jupyterlab/testutils';
- function createEditorWidget(): CodeEditorWrapper {
- const model = new CodeEditor.Model();
- const factory = (options: CodeEditor.IOptions) => {
- return new CodeMirrorEditor(options);
- };
- return new CodeEditorWrapper({ factory, model });
- }
- class TestCompleterModel extends CompleterModel {
- methods: string[] = [];
- createPatch(patch: string): Completer.IPatch | undefined {
- this.methods.push('createPatch');
- return super.createPatch(patch);
- }
- handleTextChange(change: Completer.ITextState): void {
- this.methods.push('handleTextChange');
- super.handleTextChange(change);
- }
- }
- class TestCompletionHandler extends CompletionHandler {
- methods: string[] = [];
- onTextChanged(): void {
- super.onTextChanged();
- this.methods.push('onTextChanged');
- }
- onCompletionSelected(widget: Completer, value: string): void {
- super.onCompletionSelected(widget, value);
- this.methods.push('onCompletionSelected');
- }
- }
- describe('@jupyterlab/completer', () => {
- let connector: KernelConnector;
- let sessionContext: ISessionContext;
- beforeAll(async () => {
- sessionContext = await createSessionContext();
- await (sessionContext as SessionContext).initialize();
- connector = new KernelConnector({ session: sessionContext.session });
- });
- afterAll(() => sessionContext.shutdown());
- describe('CompletionHandler', () => {
- describe('#constructor()', () => {
- it('should create a completer handler', () => {
- const handler = new CompletionHandler({
- connector,
- completer: new Completer({ editor: null })
- });
- expect(handler).toBeInstanceOf(CompletionHandler);
- });
- });
- describe('#connector', () => {
- it('should be a data connector', () => {
- const handler = new CompletionHandler({
- connector,
- completer: new Completer({ editor: null })
- });
- expect(handler.connector).toHaveProperty('fetch');
- expect(handler.connector).toHaveProperty('remove');
- expect(handler.connector).toHaveProperty('save');
- });
- });
- describe('#editor', () => {
- it('should default to null', () => {
- const handler = new CompletionHandler({
- connector,
- completer: new Completer({ editor: null })
- });
- expect(handler.editor).toBeNull();
- });
- it('should be settable', () => {
- const handler = new CompletionHandler({
- connector,
- completer: new Completer({ editor: null })
- });
- const widget = createEditorWidget();
- expect(handler.editor).toBeNull();
- handler.editor = widget.editor;
- expect(handler.editor).toBe(widget.editor);
- });
- it('should be resettable', () => {
- const handler = new CompletionHandler({
- connector,
- completer: new Completer({ editor: null })
- });
- const one = createEditorWidget();
- const two = createEditorWidget();
- expect(handler.editor).toBeNull();
- handler.editor = one.editor;
- expect(handler.editor).toBe(one.editor);
- handler.editor = two.editor;
- expect(handler.editor).toBe(two.editor);
- });
- it('should remove the completer active and enabled classes of the old editor', () => {
- const handler = new CompletionHandler({
- connector,
- completer: new Completer({ editor: null })
- });
- const widget = createEditorWidget();
- handler.editor = widget.editor;
- widget.toggleClass('jp-mod-completer-enabled');
- widget.toggleClass('jp-mod-completer-active');
- handler.editor = null;
- expect(widget.hasClass('jp-mod-completer-enabled')).toBe(false);
- expect(widget.hasClass('jp-mod-completer-active')).toBe(false);
- });
- });
- describe('#isDisposed', () => {
- it('should be true if handler has been disposed', () => {
- const handler = new CompletionHandler({
- connector,
- completer: new Completer({ editor: null })
- });
- expect(handler.isDisposed).toBe(false);
- handler.dispose();
- expect(handler.isDisposed).toBe(true);
- });
- });
- describe('#dispose()', () => {
- it('should dispose of the handler resources', () => {
- const handler = new CompletionHandler({
- connector,
- completer: new Completer({ editor: null })
- });
- expect(handler.isDisposed).toBe(false);
- handler.dispose();
- expect(handler.isDisposed).toBe(true);
- });
- it('should be safe to call multiple times', () => {
- const handler = new CompletionHandler({
- connector,
- completer: new Completer({ editor: null })
- });
- expect(handler.isDisposed).toBe(false);
- handler.dispose();
- handler.dispose();
- expect(handler.isDisposed).toBe(true);
- });
- });
- describe('#onTextChanged()', () => {
- it('should fire when the active editor emits a text change', () => {
- const handler = new TestCompletionHandler({
- connector,
- completer: new Completer({ editor: null })
- });
- handler.editor = createEditorWidget().editor;
- expect(handler.methods).toEqual(
- expect.not.arrayContaining(['onTextChanged'])
- );
- handler.editor.model.value.text = 'foo';
- expect(handler.methods).toEqual(
- expect.arrayContaining(['onTextChanged'])
- );
- });
- it('should call model change handler if model exists', () => {
- const completer = new Completer({
- editor: null,
- model: new TestCompleterModel()
- });
- const handler = new TestCompletionHandler({ completer, connector });
- const editor = createEditorWidget().editor;
- const model = completer.model as TestCompleterModel;
- handler.editor = editor;
- expect(model.methods).toEqual(
- expect.not.arrayContaining(['handleTextChange'])
- );
- editor.model.value.text = 'bar';
- editor.setCursorPosition({ line: 0, column: 2 });
- // This signal is emitted (again) because the cursor position that
- // a natural user would create need to be recreated here.
- (editor.model.value.changed as any).emit({ type: 'set', value: 'bar' });
- expect(model.methods).toEqual(
- expect.arrayContaining(['handleTextChange'])
- );
- });
- });
- describe('#onCompletionSelected()', () => {
- it('should fire when the completer widget emits a signal', () => {
- const completer = new Completer({ editor: null });
- const handler = new TestCompletionHandler({ completer, connector });
- expect(handler.methods).toEqual(
- expect.not.arrayContaining(['onCompletionSelected'])
- );
- (completer.selected as any).emit('foo');
- expect(handler.methods).toEqual(
- expect.arrayContaining(['onCompletionSelected'])
- );
- });
- it('should call model create patch method if model exists', () => {
- const completer = new Completer({
- editor: null,
- model: new TestCompleterModel()
- });
- const handler = new TestCompletionHandler({ completer, connector });
- const model = completer.model as TestCompleterModel;
- handler.editor = createEditorWidget().editor;
- expect(model.methods).toEqual(
- expect.not.arrayContaining(['createPatch'])
- );
- (completer.selected as any).emit('foo');
- expect(model.methods).toEqual(expect.arrayContaining(['createPatch']));
- });
- it('should update cell if patch exists', () => {
- const model = new CompleterModel();
- const patch = 'foobar';
- const completer = new Completer({ editor: null, model });
- const handler = new TestCompletionHandler({ completer, connector });
- const editor = createEditorWidget().editor;
- const text = 'eggs\nfoo # comment\nbaz';
- const want = 'eggs\nfoobar # comment\nbaz';
- const line = 1;
- const column = 5; // this sets the cursor after the "#" sign - not in the mid of the replaced word
- const request: Completer.ITextState = {
- column,
- line,
- lineHeight: 0,
- charWidth: 0,
- coords: null,
- text
- };
- handler.editor = editor;
- handler.editor.model.value.text = text;
- handler.editor.setCursorPosition({ line, column: column + 3 });
- model.original = request;
- model.cursor = { start: column, end: column + 3 };
- (completer.selected as any).emit(patch);
- expect(handler.editor.model.value.text).toBe(want);
- expect(handler.editor.getCursorPosition()).toEqual({
- line,
- column: column + 6
- });
- });
- it('should be undoable and redoable', () => {
- const model = new CompleterModel();
- const patch = 'foobar';
- const completer = new Completer({ editor: null, model });
- const handler = new TestCompletionHandler({ completer, connector });
- const editor = createEditorWidget().editor;
- const text = 'eggs\nfoo # comment\nbaz';
- const want = 'eggs\nfoobar # comment\nbaz';
- const line = 1;
- const column = 5;
- const request: Completer.ITextState = {
- column,
- line,
- lineHeight: 0,
- charWidth: 0,
- coords: null,
- text
- };
- handler.editor = editor;
- handler.editor.model.value.text = text;
- handler.editor.model.sharedModel.clearUndoHistory();
- handler.editor.setCursorPosition({ line, column: column + 3 });
- model.original = request;
- model.cursor = { start: column, end: column + 3 };
- // Make the completion, check its value and cursor position.
- (completer.selected as any).emit(patch);
- expect(editor.model.value.text).toBe(want);
- expect(editor.getCursorPosition()).toEqual({
- line,
- column: column + 6
- });
- console.warn(editor.getCursorPosition());
- // Undo the completion, check its value and cursor position.
- editor.undo();
- expect(editor.model.value.text).toBe(text);
- expect(editor.getCursorPosition()).toEqual({
- line,
- column: column + 3
- });
- console.warn(editor.getCursorPosition());
- // Redo the completion, check its value and cursor position.
- editor.redo();
- expect(editor.model.value.text).toBe(want);
- expect(editor.getCursorPosition()).toEqual({
- line,
- column: column + 6
- });
- console.warn(editor.getCursorPosition());
- });
- });
- it('should update cursor position after autocomplete on empty word', () => {
- const model = new CompleterModel();
- const patch = 'foobar';
- const completer = new Completer({ editor: null, model });
- const handler = new TestCompletionHandler({ completer, connector });
- const editor = createEditorWidget().editor;
- const text = 'eggs\n # comment\nbaz';
- const want = 'eggs\n foobar # comment\nbaz';
- const line = 1;
- const column = 1;
- const request: Completer.ITextState = {
- column: column,
- line,
- lineHeight: 0,
- charWidth: 0,
- coords: null,
- text
- };
- handler.editor = editor;
- handler.editor.model.value.text = text;
- handler.editor.model.sharedModel.clearUndoHistory();
- handler.editor.setCursorPosition({ line, column });
- model.original = request;
- const offset = handler.editor.getOffsetAt({ line, column });
- model.cursor = { start: offset, end: offset };
- // Make the completion, check its value and cursor position.
- (completer.selected as any).emit(patch);
- expect(editor.model.value.text).toBe(want);
- expect(editor.getCursorPosition()).toEqual({
- line,
- column: column + 6
- });
- });
- });
- });
|