Bläddra i källkod

Update unit tests for completion items

Edward Zhao 5 år sedan
förälder
incheckning
174cace4ba
2 ändrade filer med 649 tillägg och 2 borttagningar
  1. 87 1
      tests/test-completer/src/model.spec.ts
  2. 562 1
      tests/test-completer/src/widget.spec.ts

+ 87 - 1
tests/test-completer/src/model.spec.ts

@@ -9,7 +9,11 @@ import { JSONExt } from '@lumino/coreutils';
 
 import { CodeEditor } from '@jupyterlab/codeeditor';
 
-import { CompleterModel, Completer } from '@jupyterlab/completer';
+import {
+  CompleterModel,
+  Completer,
+  CompletionHandler
+} from '@jupyterlab/completer';
 
 function makeState(text: string): Completer.ITextState {
   return {
@@ -28,6 +32,7 @@ describe('completer/model', () => {
       it('should create a completer model', () => {
         let model = new CompleterModel();
         expect(model).to.be.an.instanceof(CompleterModel);
+        expect(model.setCompletionItems).to.be.ok;
       });
     });
 
@@ -46,6 +51,23 @@ describe('completer/model', () => {
         expect(called).to.equal(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).to.equal(0);
+        model.setCompletionItems!({ items: [{ label: 'foo' }] });
+        expect(called).to.equal(1);
+        model.setCompletionItems!({ items: [{ label: 'foo' }] });
+        model.setCompletionItems!({
+          items: [{ label: 'foo' }, { label: 'bar' }]
+        });
+        expect(called).to.equal(2);
+      });
+
       it('should not signal when options have not changed', () => {
         let model = new CompleterModel();
         let called = 0;
@@ -65,6 +87,29 @@ describe('completer/model', () => {
         expect(called).to.equal(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).to.equal(0);
+        model.setCompletionItems!({ items: [{ label: 'foo' }] });
+        model.setCompletionItems!({ items: [{ label: 'foo' }] });
+        expect(called).to.equal(1);
+        model.setCompletionItems!({
+          items: [{ label: 'foo' }, { label: 'bar' }]
+        });
+        model.setCompletionItems!({
+          items: [{ label: 'foo' }, { label: 'bar' }]
+        });
+        expect(called).to.equal(2);
+        model.setCompletionItems!({ items: [] });
+        model.setCompletionItems!({ items: [] });
+        expect(called).to.equal(3);
+      });
+
       it('should signal when original request changes', () => {
         let model = new CompleterModel();
         let called = 0;
@@ -142,6 +187,47 @@ describe('completer/model', () => {
       });
     });
 
+    describe('#completionItems()', () => {
+      it('should default to { items: [] }', () => {
+        let model = new CompleterModel();
+        let want: CompletionHandler.ICompletionItems = { items: [] };
+        expect(model.completionItems!()).to.deep.equal(want);
+      });
+
+      it('should return unmarked ICompletionItems if query is blank', () => {
+        let model = new CompleterModel();
+        let want: CompletionHandler.ICompletionItems = {
+          items: [{ label: 'foo' }, { label: 'bar' }, { label: 'baz' }]
+        };
+        model.setCompletionItems!({
+          items: [{ label: 'foo' }, { label: 'bar' }, { label: 'baz' }]
+        });
+        expect(model.completionItems!()).to.deep.equal(want);
+      });
+
+      it('should return a marked list of items if query is set', () => {
+        let model = new CompleterModel();
+        let want: CompletionHandler.ICompletionItems = {
+          items: [{ label: '<mark>f</mark>oo', insertText: 'foo' }]
+        };
+        model.setCompletionItems!({
+          items: [{ label: 'foo' }, { label: 'bar' }, { label: 'baz' }]
+        });
+        model.query = 'f';
+        expect(model.completionItems!()).to.deep.equal(want);
+      });
+
+      it('should return { items: [] } if reset', () => {
+        let model = new CompleterModel();
+        let want: CompletionHandler.ICompletionItems = { items: [] };
+        model.setCompletionItems!({
+          items: [{ label: 'foo' }, { label: 'bar' }, { label: 'baz' }]
+        });
+        model.reset();
+        expect(model.completionItems!()).to.deep.equal(want);
+      });
+    });
+
     describe('#items()', () => {
       it('should return an unfiltered list of items if query is blank', () => {
         let model = new CompleterModel();

+ 562 - 1
tests/test-completer/src/widget.spec.ts

@@ -15,7 +15,11 @@ import { CodeEditor, CodeEditorWrapper } from '@jupyterlab/codeeditor';
 
 import { CodeMirrorEditor } from '@jupyterlab/codemirror';
 
-import { Completer, CompleterModel } from '@jupyterlab/completer';
+import {
+  Completer,
+  CompletionHandler,
+  CompleterModel
+} from '@jupyterlab/completer';
 
 import { framePromise, sleep } from '@jupyterlab/testutils';
 
@@ -34,6 +38,15 @@ function createEditorWidget(): CodeEditorWrapper {
 }
 
 class CustomRenderer extends Completer.Renderer {
+  createCompletionItemNode(
+    item: CompletionHandler.ICompletionItem,
+    orderedTypes: string[]
+  ): HTMLLIElement {
+    let li = super.createCompletionItemNode!(item, orderedTypes);
+    li.classList.add(TEST_ITEM_CLASS);
+    return li;
+  }
+
   createItemNode(
     item: Completer.IItem,
     typeMap: Completer.TypeMap,
@@ -101,6 +114,25 @@ describe('completer/widget', () => {
         expect(items).to.have.length(2);
         expect(Array.from(items[0].classList)).to.contain(TEST_ITEM_CLASS);
       });
+
+      it('should accept completion items with a renderer', () => {
+        let options: Completer.IOptions = {
+          editor: null,
+          model: new CompleterModel(),
+          renderer: new CustomRenderer()
+        };
+        options.model!.setCompletionItems!({
+          items: [{ label: 'foo' }, { label: 'bar' }]
+        });
+
+        let widget = new Completer(options);
+        expect(widget).to.be.an.instanceof(Completer);
+        MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
+
+        let items = widget.node.querySelectorAll(`.${ITEM_CLASS}`);
+        expect(items).to.have.length(2);
+        expect(Array.from(items[0].classList)).to.contain(TEST_ITEM_CLASS);
+      });
     });
 
     describe('#selected', () => {
@@ -128,6 +160,62 @@ describe('completer/widget', () => {
         widget.dispose();
         anchor.dispose();
       });
+
+      describe('#selected with completion items', () => {
+        it('should emit the insert text if it is present', () => {
+          let anchor = createEditorWidget();
+          let options: Completer.IOptions = {
+            editor: anchor.editor,
+            model: new CompleterModel()
+          };
+          let value = '';
+          let listener = (sender: any, selected: string) => {
+            value = selected;
+          };
+          options.model!.setCompletionItems!({
+            items: [{ label: 'foo', insertText: 'bar' }, { label: 'baz' }]
+          });
+          Widget.attach(anchor, document.body);
+
+          let widget = new Completer(options);
+
+          widget.selected.connect(listener);
+          Widget.attach(widget, document.body);
+          MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
+          expect(value).to.equal('');
+          widget.selectActive();
+          expect(value).to.equal('bar');
+          widget.dispose();
+          anchor.dispose();
+        });
+
+        it('should emit the label if insert text is not present', () => {
+          let anchor = createEditorWidget();
+          let options: Completer.IOptions = {
+            editor: anchor.editor,
+            model: new CompleterModel()
+          };
+          let value = '';
+          let listener = (sender: any, selected: string) => {
+            value = selected;
+          };
+          options.model!.setCompletionItems!({
+            items: [{ label: 'foo' }, { label: 'baz' }]
+          });
+          Widget.attach(anchor, document.body);
+
+          let widget = new Completer(options);
+
+          widget.selected.connect(listener);
+          Widget.attach(widget, document.body);
+          MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
+          expect(value).to.equal('');
+          widget.selectActive();
+          expect(value).to.equal('foo');
+          widget.dispose();
+          anchor.dispose();
+        });
+      });
     });
 
     describe('#visibilityChanged', () => {
@@ -180,6 +268,63 @@ describe('completer/widget', () => {
         code.dispose();
         panel.dispose();
       });
+
+      it('should emit a signal when completion items completer visibility changes', async () => {
+        let panel = new Panel();
+        let code = createEditorWidget();
+        let editor = code.editor;
+        let model = new CompleterModel();
+        let called = false;
+
+        editor.model.value.text = 'a';
+        panel.node.style.position = 'absolute';
+        panel.node.style.top = '0px';
+        panel.node.style.left = '0px';
+        panel.node.style.height = '1000px';
+        code.node.style.height = '900px';
+        panel.addWidget(code);
+        Widget.attach(panel, document.body);
+        panel.node.scrollTop = 0;
+        document.body.scrollTop = 0;
+
+        let position = code.editor.getPositionAt(1)!;
+
+        editor.setCursorPosition(position);
+
+        let request: Completer.ITextState = {
+          column: position.column,
+          lineHeight: editor.lineHeight,
+          charWidth: editor.charWidth,
+          line: position.line,
+          text: 'a'
+        };
+
+        model.original = request;
+        model.cursor = { start: 0, end: 1 };
+        model.setCompletionItems!({
+          items: [
+            { label: 'abc' },
+            { label: 'abd' },
+            { label: 'abe' },
+            { label: 'abi' }
+          ]
+        });
+
+        let widget = new Completer({ model, editor: code.editor });
+        widget.hide();
+        expect(called).to.equal(false);
+        widget.visibilityChanged.connect(() => {
+          called = true;
+        });
+        Widget.attach(widget, document.body);
+        MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
+
+        await framePromise();
+        expect(called).to.equal(true);
+        widget.dispose();
+        code.dispose();
+        panel.dispose();
+      });
     });
 
     describe('#model', () => {
@@ -269,6 +414,36 @@ describe('completer/widget', () => {
         widget.dispose();
         anchor.dispose();
       });
+
+      it('should reset the completer widget and its completion items', () => {
+        let anchor = createEditorWidget();
+        let model = new CompleterModel();
+        let options: Completer.IOptions = {
+          editor: anchor.editor,
+          model
+        };
+        model.setCompletionItems!({
+          items: [{ label: 'foo' }, { label: 'bar' }]
+        });
+        Widget.attach(anchor, document.body);
+
+        let widget = new Completer(options);
+
+        Widget.attach(widget, document.body);
+        MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
+        expect(widget.isHidden).to.equal(false);
+        expect(model.completionItems!()).to.deep.equal({
+          items: [{ label: 'foo' }, { label: 'bar' }]
+        });
+        widget.reset();
+        MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
+        expect(widget.isHidden).to.equal(true);
+        expect(model.completionItems!()).to.deep.equal({
+          items: []
+        });
+        widget.dispose();
+        anchor.dispose();
+      });
     });
 
     describe('#handleEvent()', () => {
@@ -313,6 +488,36 @@ describe('completer/widget', () => {
           anchor.dispose();
         });
 
+        it('should reset completion items if keydown is outside anchor', () => {
+          let model = new CompleterModel();
+          let anchor = createEditorWidget();
+          let options: Completer.IOptions = {
+            editor: anchor.editor,
+            model
+          };
+          model.setCompletionItems!({
+            items: [{ label: 'foo' }, { label: 'bar' }]
+          });
+          Widget.attach(anchor, document.body);
+
+          let widget = new Completer(options);
+
+          Widget.attach(widget, document.body);
+          MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
+          expect(widget.isHidden).to.equal(false);
+          expect(model.completionItems!()).to.deep.equal({
+            items: [{ label: 'foo' }, { label: 'bar' }]
+          });
+          simulate(document.body, 'keydown', { keyCode: 70 }); // F
+          MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
+          expect(widget.isHidden).to.equal(true);
+          expect(model.completionItems!()).to.deep.equal({
+            items: []
+          });
+          widget.dispose();
+          anchor.dispose();
+        });
+
         it('should select the item below and not progress past last', () => {
           let anchor = createEditorWidget();
           let model = new CompleterModel();
@@ -354,6 +559,46 @@ describe('completer/widget', () => {
           anchor.dispose();
         });
 
+        it('should select the completion item below and not progress past last', () => {
+          let anchor = createEditorWidget();
+          let model = new CompleterModel();
+          let options: Completer.IOptions = {
+            editor: anchor.editor,
+            model
+          };
+          model.setCompletionItems!({
+            items: [{ label: 'foo' }, { label: 'bar' }, { label: 'baz' }]
+          });
+          Widget.attach(anchor, document.body);
+
+          let widget = new Completer(options);
+          let target = document.createElement('div');
+
+          anchor.node.appendChild(target);
+          Widget.attach(widget, document.body);
+          MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
+
+          let items = widget.node.querySelectorAll(`.${ITEM_CLASS}`);
+
+          expect(Array.from(items[0].classList)).to.contain(ACTIVE_CLASS);
+          expect(Array.from(items[1].classList)).to.not.contain(ACTIVE_CLASS);
+          expect(Array.from(items[2].classList)).to.not.contain(ACTIVE_CLASS);
+          simulate(target, 'keydown', { keyCode: 40 }); // Down
+          expect(Array.from(items[0].classList)).to.not.contain(ACTIVE_CLASS);
+          expect(Array.from(items[1].classList)).to.contain(ACTIVE_CLASS);
+          expect(Array.from(items[2].classList)).to.not.contain(ACTIVE_CLASS);
+          simulate(target, 'keydown', { keyCode: 40 }); // Down
+          expect(Array.from(items[0].classList)).to.not.contain(ACTIVE_CLASS);
+          expect(Array.from(items[1].classList)).to.not.contain(ACTIVE_CLASS);
+          expect(Array.from(items[2].classList)).to.contain(ACTIVE_CLASS);
+          simulate(target, 'keydown', { keyCode: 40 }); // Down
+          expect(Array.from(items[0].classList)).to.not.contain(ACTIVE_CLASS);
+          expect(Array.from(items[1].classList)).to.not.contain(ACTIVE_CLASS);
+          expect(Array.from(items[2].classList)).to.contain(ACTIVE_CLASS);
+          widget.dispose();
+          anchor.dispose();
+        });
+
         it('should select the item above and not progress beyond first', () => {
           let anchor = createEditorWidget();
           let model = new CompleterModel();
@@ -401,6 +646,52 @@ describe('completer/widget', () => {
           anchor.dispose();
         });
 
+        it('should select the completion item above and not progress beyond first', () => {
+          let anchor = createEditorWidget();
+          let model = new CompleterModel();
+          let options: Completer.IOptions = {
+            editor: anchor.editor,
+            model
+          };
+          model.setCompletionItems!({
+            items: [{ label: 'foo' }, { label: 'bar' }, { label: 'baz' }]
+          });
+          Widget.attach(anchor, document.body);
+
+          let widget = new Completer(options);
+
+          Widget.attach(widget, document.body);
+          MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
+
+          let items = widget.node.querySelectorAll(`.${ITEM_CLASS}`);
+
+          expect(Array.from(items[0].classList)).to.contain(ACTIVE_CLASS);
+          expect(Array.from(items[1].classList)).to.not.contain(ACTIVE_CLASS);
+          expect(Array.from(items[2].classList)).to.not.contain(ACTIVE_CLASS);
+          simulate(anchor.node, 'keydown', { keyCode: 40 }); // Down
+          expect(Array.from(items[0].classList)).to.not.contain(ACTIVE_CLASS);
+          expect(Array.from(items[1].classList)).to.contain(ACTIVE_CLASS);
+          expect(Array.from(items[2].classList)).to.not.contain(ACTIVE_CLASS);
+          simulate(anchor.node, 'keydown', { keyCode: 40 }); // Down
+          expect(Array.from(items[0].classList)).to.not.contain(ACTIVE_CLASS);
+          expect(Array.from(items[1].classList)).to.not.contain(ACTIVE_CLASS);
+          expect(Array.from(items[2].classList)).to.contain(ACTIVE_CLASS);
+          simulate(anchor.node, 'keydown', { keyCode: 38 }); // Up
+          expect(Array.from(items[0].classList)).to.not.contain(ACTIVE_CLASS);
+          expect(Array.from(items[1].classList)).to.contain(ACTIVE_CLASS);
+          expect(Array.from(items[2].classList)).to.not.contain(ACTIVE_CLASS);
+          simulate(anchor.node, 'keydown', { keyCode: 38 }); // Up
+          expect(Array.from(items[0].classList)).to.contain(ACTIVE_CLASS);
+          expect(Array.from(items[1].classList)).to.not.contain(ACTIVE_CLASS);
+          expect(Array.from(items[2].classList)).to.not.contain(ACTIVE_CLASS);
+          simulate(anchor.node, 'keydown', { keyCode: 38 }); // Up
+          expect(Array.from(items[0].classList)).to.contain(ACTIVE_CLASS);
+          expect(Array.from(items[1].classList)).to.not.contain(ACTIVE_CLASS);
+          expect(Array.from(items[2].classList)).to.not.contain(ACTIVE_CLASS);
+          widget.dispose();
+          anchor.dispose();
+        });
+
         it('should mark common subset on start and complete that subset on tab', async () => {
           let anchor = createEditorWidget();
           let model = new CompleterModel();
@@ -437,6 +728,47 @@ describe('completer/widget', () => {
           widget.dispose();
           anchor.dispose();
         });
+
+        it('should mark common subset of completion items on start and complete that subset on tab', async () => {
+          let anchor = createEditorWidget();
+          let model = new CompleterModel();
+          let options: Completer.IOptions = {
+            editor: anchor.editor,
+            model
+          };
+          let value = '';
+          let listener = (sender: any, selected: string) => {
+            value = selected;
+          };
+          model.setCompletionItems!({
+            items: [
+              { label: 'fo' },
+              { label: 'foo' },
+              { label: 'foo' },
+              { label: 'fooo' }
+            ]
+          });
+          Widget.attach(anchor, document.body);
+
+          let widget = new Completer(options);
+
+          widget.selected.connect(listener);
+          Widget.attach(widget, document.body);
+          MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
+          await framePromise();
+          let marked = widget.node.querySelectorAll(`.${ITEM_CLASS} mark`);
+          expect(value).to.be.empty;
+          expect(marked).to.have.length(4);
+          expect(marked[0].textContent).to.equal('fo');
+          expect(marked[1].textContent).to.equal('fo');
+          expect(marked[2].textContent).to.equal('fo');
+          expect(marked[3].textContent).to.equal('fo');
+          simulate(anchor.node, 'keydown', { keyCode: 9 }); // Tab key
+          MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
+          expect(value).to.equal('fo');
+          widget.dispose();
+          anchor.dispose();
+        });
       });
 
       describe('mousedown', () => {
@@ -474,6 +806,39 @@ describe('completer/widget', () => {
           anchor.dispose();
         });
 
+        it('should trigger a selected signal on mouse down of completion item', () => {
+          let anchor = createEditorWidget();
+          let model = new CompleterModel();
+          let options: Completer.IOptions = {
+            editor: anchor.editor,
+            model
+          };
+          let value = '';
+          let listener = (sender: any, selected: string) => {
+            value = selected;
+          };
+          model.setCompletionItems!({
+            items: [{ label: 'foo' }, { label: 'bar' }, { label: 'baz' }]
+          });
+          model.query = 'b';
+          Widget.attach(anchor, document.body);
+
+          let widget = new Completer(options);
+
+          widget.selected.connect(listener);
+          Widget.attach(widget, document.body);
+          MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
+
+          let item = widget.node.querySelectorAll(`.${ITEM_CLASS} mark`)[1];
+
+          simulate(anchor.node, 'keydown', { keyCode: 9 }); // Tab key
+          expect(model.query).to.equal('ba');
+          simulate(item, 'mousedown');
+          expect(value).to.equal('baz');
+          widget.dispose();
+          anchor.dispose();
+        });
+
         it('should ignore nonstandard mouse clicks (e.g., right click)', () => {
           let anchor = createEditorWidget();
           let model = new CompleterModel();
@@ -500,6 +865,34 @@ describe('completer/widget', () => {
           anchor.dispose();
         });
 
+        it('should ignore nonstandard mouse clicks (e.g., right click) on completion item', () => {
+          let anchor = createEditorWidget();
+          let model = new CompleterModel();
+          let options: Completer.IOptions = {
+            editor: anchor.editor,
+            model
+          };
+          let value = '';
+          let listener = (sender: any, selected: string) => {
+            value = selected;
+          };
+          model.setCompletionItems!({
+            items: [{ label: 'foo' }, { label: 'bar' }]
+          });
+          Widget.attach(anchor, document.body);
+
+          let widget = new Completer(options);
+
+          widget.selected.connect(listener);
+          Widget.attach(widget, document.body);
+          MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
+          expect(value).to.equal('');
+          simulate(widget.node, 'mousedown', { button: 1 });
+          expect(value).to.equal('');
+          widget.dispose();
+          anchor.dispose();
+        });
+
         it('should ignore a mouse down that misses an item', () => {
           let anchor = createEditorWidget();
           let model = new CompleterModel();
@@ -526,6 +919,34 @@ describe('completer/widget', () => {
           anchor.dispose();
         });
 
+        it('should ignore a mouse down that misses a completion item', () => {
+          let anchor = createEditorWidget();
+          let model = new CompleterModel();
+          let options: Completer.IOptions = {
+            editor: anchor.editor,
+            model
+          };
+          let value = '';
+          let listener = (sender: any, selected: string) => {
+            value = selected;
+          };
+          model.setCompletionItems!({
+            items: [{ label: 'foo' }, { label: 'bar' }]
+          });
+          Widget.attach(anchor, document.body);
+
+          let widget = new Completer(options);
+
+          widget.selected.connect(listener);
+          Widget.attach(widget, document.body);
+          MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
+          expect(value).to.equal('');
+          simulate(widget.node, 'mousedown');
+          expect(value).to.equal('');
+          widget.dispose();
+          anchor.dispose();
+        });
+
         it('should hide widget if mouse down misses it', () => {
           let anchor = createEditorWidget();
           let model = new CompleterModel();
@@ -551,6 +972,34 @@ describe('completer/widget', () => {
           widget.dispose();
           anchor.dispose();
         });
+
+        it('should hide completion items widget if mouse down misses it', () => {
+          let anchor = createEditorWidget();
+          let model = new CompleterModel();
+          let options: Completer.IOptions = {
+            editor: anchor.editor,
+            model
+          };
+          let listener = (sender: any, selected: string) => {
+            // no op
+          };
+          model.setCompletionItems!({
+            items: [{ label: 'foo' }, { label: 'bar' }]
+          });
+          Widget.attach(anchor, document.body);
+
+          let widget = new Completer(options);
+
+          widget.selected.connect(listener);
+          Widget.attach(widget, document.body);
+          MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
+          expect(widget.isHidden).to.equal(false);
+          simulate(anchor.node, 'mousedown');
+          MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
+          expect(widget.isHidden).to.equal(true);
+          widget.dispose();
+          anchor.dispose();
+        });
       });
 
       describe('scroll', () => {
@@ -653,6 +1102,84 @@ describe('completer/widget', () => {
         anchor.dispose();
       });
 
+      it('should emit label if there is only one completion item and insert Text is not set', () => {
+        let anchor = createEditorWidget();
+        let model = new CompleterModel();
+        let coords = { left: 0, right: 0, top: 100, bottom: 120 };
+        let request: Completer.ITextState = {
+          column: 0,
+          lineHeight: 0,
+          charWidth: 0,
+          line: 0,
+          coords: coords as CodeEditor.ICoordinate,
+          text: 'f'
+        };
+
+        let value = '';
+        let options: Completer.IOptions = {
+          editor: anchor.editor,
+          model
+        };
+        let listener = (sender: any, selected: string) => {
+          value = selected;
+        };
+
+        Widget.attach(anchor, document.body);
+        model.original = request;
+        model.setCompletionItems!({ items: [{ label: 'foo' }] });
+
+        let widget = new Completer(options);
+        widget.selected.connect(listener);
+        Widget.attach(widget, document.body);
+
+        expect(value).to.equal('');
+        MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
+        expect(value).to.equal('foo');
+
+        widget.dispose();
+        anchor.dispose();
+      });
+
+      it('should emit insert text if there is only one completion item and insert Text is set', () => {
+        let anchor = createEditorWidget();
+        let model = new CompleterModel();
+        let coords = { left: 0, right: 0, top: 100, bottom: 120 };
+        let request: Completer.ITextState = {
+          column: 0,
+          lineHeight: 0,
+          charWidth: 0,
+          line: 0,
+          coords: coords as CodeEditor.ICoordinate,
+          text: 'f'
+        };
+
+        let value = '';
+        let options: Completer.IOptions = {
+          editor: anchor.editor,
+          model
+        };
+        let listener = (sender: any, selected: string) => {
+          value = selected;
+        };
+
+        Widget.attach(anchor, document.body);
+        model.original = request;
+        model.setCompletionItems!({
+          items: [{ label: 'foo', insertText: 'bar' }]
+        });
+
+        let widget = new Completer(options);
+        widget.selected.connect(listener);
+        Widget.attach(widget, document.body);
+
+        expect(value).to.equal('');
+        MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
+        expect(value).to.equal('bar');
+
+        widget.dispose();
+        anchor.dispose();
+      });
+
       it('should do nothing if a model does not exist', () => {
         let widget = new LogWidget({ editor: null });
         MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
@@ -690,6 +1217,40 @@ describe('completer/widget', () => {
         widget.dispose();
         anchor.dispose();
       });
+
+      it('should un-hide widget if multiple completion items are available', () => {
+        let anchor = createEditorWidget();
+        let model = new CompleterModel();
+        let coords = { left: 0, right: 0, top: 100, bottom: 120 };
+        let request: Completer.ITextState = {
+          column: 0,
+          lineHeight: 0,
+          charWidth: 0,
+          line: 0,
+          coords: coords as CodeEditor.ICoordinate,
+          text: 'f'
+        };
+
+        let options: Completer.IOptions = {
+          editor: anchor.editor,
+          model
+        };
+
+        Widget.attach(anchor, document.body);
+        model.original = request;
+        model.setCompletionItems!({
+          items: [{ label: 'foo' }, { label: 'bar' }, { label: 'baz' }]
+        });
+
+        let widget = new Completer(options);
+        widget.hide();
+        expect(widget.isHidden).to.equal(true);
+        Widget.attach(widget, document.body);
+        MessageLoop.sendMessage(widget, Widget.Msg.UpdateRequest);
+        expect(widget.isVisible).to.equal(true);
+        widget.dispose();
+        anchor.dispose();
+      });
     });
   });
 });