123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516 |
- // Copyright (c) Jupyter Development Team.
- // Distributed under the terms of the Modified BSD License.
- import { toArray } from '@lumino/algorithm';
- import { JSONExt } from '@lumino/coreutils';
- import { CodeEditor } from '@jupyterlab/codeeditor';
- import {
- CompleterModel,
- Completer,
- CompletionHandler
- } from '@jupyterlab/completer';
- function makeState(text: string): Completer.ITextState {
- return {
- column: 0,
- lineHeight: 0,
- charWidth: 0,
- line: 0,
- coords: { left: 0, right: 0, top: 0, bottom: 0 } as CodeEditor.ICoordinate,
- text
- };
- }
- describe('completer/model', () => {
- describe('CompleterModel', () => {
- describe('#constructor()', () => {
- it('should create a completer model', () => {
- const model = new CompleterModel();
- expect(model).toBeInstanceOf(CompleterModel);
- expect(model.setCompletionItems).toBeDefined();
- });
- });
- describe('#stateChanged', () => {
- it('should signal when model options have changed', () => {
- const model = new CompleterModel();
- let called = 0;
- const listener = (sender: any, args: void) => {
- called++;
- };
- model.stateChanged.connect(listener);
- expect(called).toBe(0);
- model.setOptions(['foo']);
- expect(called).toBe(1);
- model.setOptions(['foo'], { foo: 'instance' });
- expect(called).toBe(2);
- });
- it('should signal when model items have changed', () => {
- let model = new CompleterModel();
- let called = 0;
- let listener = (sender: any, args: void) => {
- called++;
- };
- model.stateChanged.connect(listener);
- expect(called).toBe(0);
- model.setCompletionItems!([{ label: 'foo' }]);
- expect(called).toBe(1);
- model.setCompletionItems!([{ label: 'foo' }]);
- model.setCompletionItems!([{ label: 'foo' }, { label: 'bar' }]);
- expect(called).toBe(2);
- });
- it('should not signal when options have not changed', () => {
- const model = new CompleterModel();
- let called = 0;
- const listener = (sender: any, args: void) => {
- called++;
- };
- model.stateChanged.connect(listener);
- expect(called).toBe(0);
- model.setOptions(['foo']);
- model.setOptions(['foo']);
- expect(called).toBe(1);
- model.setOptions(['foo'], { foo: 'instance' });
- model.setOptions(['foo'], { foo: 'instance' });
- expect(called).toBe(2);
- model.setOptions([], {});
- model.setOptions([], {});
- expect(called).toBe(3);
- });
- it('should not signal when items have not changed', () => {
- let model = new CompleterModel();
- let called = 0;
- let listener = (sender: any, args: void) => {
- called++;
- };
- model.stateChanged.connect(listener);
- expect(called).toBe(0);
- model.setCompletionItems!([{ label: 'foo' }]);
- model.setCompletionItems!([{ label: 'foo' }]);
- expect(called).toBe(1);
- model.setCompletionItems!([{ label: 'foo' }, { label: 'bar' }]);
- model.setCompletionItems!([{ label: 'foo' }, { label: 'bar' }]);
- expect(called).toBe(2);
- model.setCompletionItems!([]);
- model.setCompletionItems!([]);
- expect(called).toBe(3);
- });
- it('should signal when original request changes', () => {
- const model = new CompleterModel();
- let called = 0;
- const listener = (sender: any, args: void) => {
- called++;
- };
- model.stateChanged.connect(listener);
- expect(called).toBe(0);
- model.original = makeState('foo');
- expect(called).toBe(1);
- model.original = null;
- expect(called).toBe(2);
- });
- it('should not signal when original request has not changed', () => {
- const model = new CompleterModel();
- let called = 0;
- const listener = (sender: any, args: void) => {
- called++;
- };
- model.stateChanged.connect(listener);
- expect(called).toBe(0);
- model.original = makeState('foo');
- model.original = makeState('foo');
- expect(called).toBe(1);
- model.original = null;
- model.original = null;
- expect(called).toBe(2);
- });
- it('should signal when current text changes', () => {
- const model = new CompleterModel();
- let called = 0;
- const currentValue = 'foo';
- const newValue = 'foob';
- const cursor: Completer.ICursorSpan = { start: 0, end: 0 };
- const request = makeState(currentValue);
- const change = makeState(newValue);
- const listener = (sender: any, args: void) => {
- called++;
- };
- model.stateChanged.connect(listener);
- expect(called).toBe(0);
- model.original = request;
- expect(called).toBe(1);
- model.cursor = cursor;
- model.current = change;
- expect(called).toBe(2);
- model.current = null;
- expect(called).toBe(3);
- });
- it('should not signal when current text is unchanged', () => {
- const model = new CompleterModel();
- let called = 0;
- const currentValue = 'foo';
- const newValue = 'foob';
- const cursor: Completer.ICursorSpan = { start: 0, end: 0 };
- const request = makeState(currentValue);
- const change = makeState(newValue);
- const listener = (sender: any, args: void) => {
- called++;
- };
- model.stateChanged.connect(listener);
- expect(called).toBe(0);
- model.original = request;
- expect(called).toBe(1);
- model.cursor = cursor;
- model.current = change;
- model.current = change;
- expect(called).toBe(2);
- model.current = null;
- model.current = null;
- expect(called).toBe(3);
- });
- });
- describe('#completionItems()', () => {
- it('should default to { items: [] }', () => {
- let model = new CompleterModel();
- let want: CompletionHandler.ICompletionItems = [];
- expect(model.completionItems!()).toEqual(want);
- });
- it('should return unmarked ICompletionItems if query is blank', () => {
- let model = new CompleterModel();
- let want: CompletionHandler.ICompletionItems = [
- { label: 'foo' },
- { label: 'bar' },
- { label: 'baz' }
- ];
- model.setCompletionItems!([
- { label: 'foo' },
- { label: 'bar' },
- { label: 'baz' }
- ]);
- expect(model.completionItems!()).toEqual(want);
- });
- it('should return a marked list of items if query is set', () => {
- let model = new CompleterModel();
- let want = '<mark>f</mark>oo';
- model.setCompletionItems!([
- { label: 'foo' },
- { label: 'bar' },
- { label: 'baz' }
- ]);
- model.query = 'f';
- expect(model.completionItems!().length).toEqual(1);
- expect(model.completionItems!()[0].label).toEqual(want);
- });
- it('should order list based on score', () => {
- const model = new CompleterModel();
- const want: CompletionHandler.ICompletionItems = [
- { insertText: 'qux', label: '<mark>qux</mark>' },
- { insertText: 'quux', label: '<mark>qu</mark>u<mark>x</mark>' }
- ];
- model.setCompletionItems!([
- { label: 'foo' },
- { label: 'bar' },
- { label: 'baz' },
- { label: 'quux' },
- { label: 'qux' }
- ]);
- model.query = 'qux';
- expect(model.completionItems!()).toEqual(want);
- });
- it('should break ties in score by locale sort', () => {
- const model = new CompleterModel();
- const want: CompletionHandler.ICompletionItems = [
- { insertText: 'quux', label: '<mark>qu</mark>ux' },
- { insertText: 'qux', label: '<mark>qu</mark>x' }
- ];
- model.setCompletionItems!([
- { label: 'foo' },
- { label: 'bar' },
- { label: 'baz' },
- { label: 'quux' },
- { label: 'qux' }
- ]);
- model.query = 'qu';
- expect(model.completionItems!()).toEqual(want);
- });
- it('should return { items: [] } if reset', () => {
- let model = new CompleterModel();
- let want: CompletionHandler.ICompletionItems = [];
- model.setCompletionItems!([
- { label: 'foo' },
- { label: 'bar' },
- { label: 'baz' }
- ]);
- model.reset();
- expect(model.completionItems!()).toEqual(want);
- });
- });
- describe('#items()', () => {
- it('should return an unfiltered list of items if query is blank', () => {
- const model = new CompleterModel();
- const want: Completer.IItem[] = [
- { raw: 'foo', text: 'foo' },
- { raw: 'bar', text: 'bar' },
- { raw: 'baz', text: 'baz' }
- ];
- model.setOptions(['foo', 'bar', 'baz']);
- expect(toArray(model.items())).toEqual(want);
- });
- it('should return a filtered list of items if query is set', () => {
- const model = new CompleterModel();
- const want: Completer.IItem[] = [
- { raw: 'foo', text: '<mark>f</mark>oo' }
- ];
- model.setOptions(['foo', 'bar', 'baz']);
- model.query = 'f';
- expect(toArray(model.items())).toEqual(want);
- });
- it('should order list based on score', () => {
- const model = new CompleterModel();
- const want: Completer.IItem[] = [
- { raw: 'qux', text: '<mark>qux</mark>' },
- { raw: 'quux', text: '<mark>qu</mark>u<mark>x</mark>' }
- ];
- model.setOptions(['foo', 'bar', 'baz', 'quux', 'qux']);
- model.query = 'qux';
- expect(toArray(model.items())).toEqual(want);
- });
- it('should break ties in score by locale sort', () => {
- const model = new CompleterModel();
- const want: Completer.IItem[] = [
- { raw: 'quux', text: '<mark>qu</mark>ux' },
- { raw: 'qux', text: '<mark>qu</mark>x' }
- ];
- model.setOptions(['foo', 'bar', 'baz', 'qux', 'quux']);
- model.query = 'qu';
- expect(toArray(model.items())).toEqual(want);
- });
- });
- describe('#options()', () => {
- it('should default to an empty iterator', () => {
- const model = new CompleterModel();
- expect(model.options().next()).toBeUndefined();
- });
- it('should return model options', () => {
- const model = new CompleterModel();
- const options = ['foo'];
- model.setOptions(options, {});
- expect(toArray(model.options())).not.toBe(options);
- expect(toArray(model.options())).toEqual(options);
- });
- it('should return the typeMap', () => {
- const model = new CompleterModel();
- const options = ['foo'];
- const typeMap = { foo: 'instance' };
- model.setOptions(options, typeMap);
- expect(JSONExt.deepEqual(model.typeMap(), typeMap)).toBeTruthy();
- });
- });
- describe('#original', () => {
- it('should default to null', () => {
- const model = new CompleterModel();
- expect(model.original).toBeNull();
- });
- it('should return the original request', () => {
- const model = new CompleterModel();
- const request = makeState('foo');
- model.original = request;
- expect(model.original).toBe(request);
- });
- });
- describe('#current', () => {
- it('should default to null', () => {
- const model = new CompleterModel();
- expect(model.current).toBeNull();
- });
- it('should initially equal the original request', () => {
- const model = new CompleterModel();
- const request = makeState('foo');
- model.original = request;
- expect(model.current).toBe(request);
- });
- it('should not set if original request is nonexistent', () => {
- const model = new CompleterModel();
- const currentValue = 'foo';
- const newValue = 'foob';
- const cursor: Completer.ICursorSpan = { start: 0, end: 0 };
- const request = makeState(currentValue);
- const change = makeState(newValue);
- model.current = change;
- expect(model.current).toBeNull();
- model.original = request;
- model.cursor = cursor;
- model.current = change;
- expect(model.current).toBe(change);
- });
- it('should not set if cursor is nonexistent', () => {
- const model = new CompleterModel();
- const currentValue = 'foo';
- const newValue = 'foob';
- const request = makeState(currentValue);
- const change = makeState(newValue);
- model.original = request;
- model.cursor = null;
- model.current = change;
- expect(model.current).not.toBe(change);
- });
- it('should reset model if change is shorter than original', () => {
- const model = new CompleterModel();
- const currentValue = 'foo';
- const newValue = 'fo';
- const cursor: Completer.ICursorSpan = { start: 0, end: 0 };
- const request = makeState(currentValue);
- const change = makeState(newValue);
- model.original = request;
- model.cursor = cursor;
- model.current = change;
- expect(model.current).toBeNull();
- expect(model.original).toBeNull();
- expect(model.options().next()).toBeUndefined();
- });
- });
- describe('#cursor', () => {
- it('should default to null', () => {
- const model = new CompleterModel();
- expect(model.cursor).toBeNull();
- });
- it('should not set if original request is nonexistent', () => {
- const model = new CompleterModel();
- const cursor: Completer.ICursorSpan = { start: 0, end: 0 };
- const request = makeState('foo');
- model.cursor = cursor;
- expect(model.cursor).toBeNull();
- model.original = request;
- model.cursor = cursor;
- expect(model.cursor).toBe(cursor);
- });
- });
- describe('#isDisposed', () => {
- it('should be true if model has been disposed', () => {
- const model = new CompleterModel();
- expect(model.isDisposed).toBe(false);
- model.dispose();
- expect(model.isDisposed).toBe(true);
- });
- });
- describe('#dispose()', () => {
- it('should dispose of the model resources', () => {
- const model = new CompleterModel();
- model.setOptions(['foo'], { foo: 'instance' });
- expect(model.isDisposed).toBe(false);
- model.dispose();
- expect(model.isDisposed).toBe(true);
- });
- it('should be safe to call multiple times', () => {
- const model = new CompleterModel();
- expect(model.isDisposed).toBe(false);
- model.dispose();
- model.dispose();
- expect(model.isDisposed).toBe(true);
- });
- });
- describe('#handleTextChange()', () => {
- it('should set current change value', () => {
- const model = new CompleterModel();
- const currentValue = 'foo';
- const newValue = 'foob';
- const cursor: Completer.ICursorSpan = { start: 0, end: 0 };
- const request = makeState(currentValue);
- const change = makeState(newValue);
- (change as any).column = 4;
- model.original = request;
- model.cursor = cursor;
- expect(model.current).toBe(request);
- model.handleTextChange(change);
- expect(model.current).toBe(change);
- });
- it('should reset if last char is whitespace && column < original', () => {
- const model = new CompleterModel();
- const currentValue = 'foo';
- const newValue = 'foo ';
- const request = makeState(currentValue);
- (request as any).column = 3;
- const change = makeState(newValue);
- (change as any).column = 0;
- model.original = request;
- expect(model.original).toBe(request);
- model.handleTextChange(change);
- expect(model.original).toBeNull();
- });
- });
- describe('#createPatch()', () => {
- it('should return a patch value', () => {
- const model = new CompleterModel();
- const patch = 'foobar';
- const want: Completer.IPatch = {
- start: 0,
- end: 3,
- value: patch
- };
- const cursor: Completer.ICursorSpan = { start: 0, end: 3 };
- model.original = makeState('foo');
- model.cursor = cursor;
- expect(model.createPatch(patch)).toEqual(want);
- });
- it('should return undefined if original request or cursor are null', () => {
- const model = new CompleterModel();
- expect(model.createPatch('foo')).toBeUndefined();
- });
- it('should handle line breaks in original value', () => {
- const model = new CompleterModel();
- const currentValue = 'foo\nbar';
- const patch = 'barbaz';
- const start = currentValue.length;
- const end = currentValue.length;
- const want: Completer.IPatch = {
- start,
- end,
- value: patch
- };
- const cursor: Completer.ICursorSpan = { start, end };
- model.original = makeState(currentValue);
- model.cursor = cursor;
- expect(model.createPatch(patch)).toEqual(want);
- });
- });
- });
- });
|