Browse Source

Merge pull request #1543 from blink1073/codemirror-tests

Refactor CodeMirror Editor and finish tests
Afshin Darian 8 years ago
parent
commit
2f8d909a7a

+ 6 - 3
jupyterlab/labapp.py

@@ -225,14 +225,17 @@ def bootstrap_from_nbapp(nbapp):
     labapp.config_dir = nbapp.config_dir
     labapp.log = nbapp.log
     labapp.web_app = nbapp.web_app
+
     cli_config = nbapp.cli_config.get('NotebookApp', {})
-    cli_config.update = nbapp.cli_config.get('LabApp', {})
+    cli_config.update(nbapp.cli_config.get('LabApp', {}))
 
     # Handle config.
-    labapp.update_config(cli_config)
+    for (key, value) in cli_config.items():
+        setattr(labapp, key, value)
     labapp.load_config_file()
     # Enforce cli override.
-    labapp.update_config(cli_config)
+    for (key, value) in cli_config.items():
+        setattr(labapp, key, value)
 
     labapp.add_lab_handlers()
     labapp.add_labextensions()

+ 6 - 1
src/codeeditor/editor.ts

@@ -420,7 +420,7 @@ namespace CodeEditor {
     /**
      * The uuid of this selection owner.
      */
-    readonly uuid: string;
+    uuid: string;
 
     /**
      * Returns the primary position of the cursor, never `null`.
@@ -500,6 +500,11 @@ namespace CodeEditor {
      */
     readonly edgeRequested: ISignal<IEditor, EdgeLocation>;
 
+   /**
+    * The default selection style for the editor.
+    */
+    selectionStyle: CodeEditor.ISelectionStyle;
+
     /**
      * Whether line numbers should be displayed. Defaults to false.
      */

+ 152 - 119
src/codemirror/editor.ts

@@ -24,6 +24,10 @@ import {
   Vector
 } from 'phosphor/lib/collections/vector';
 
+import {
+  CodeEditor
+} from '../codeeditor';
+
 import {
   IChangedArgs
 } from '../common/interfaces';
@@ -36,10 +40,6 @@ import {
   loadModeByMIME
 } from './';
 
-import {
-  CodeEditor
-} from '../codeeditor';
-
 
 /**
  * The class name added to CodeMirrorWidget instances.
@@ -51,12 +51,6 @@ const EDITOR_CLASS = 'jp-CodeMirrorWidget';
  */
 const READ_ONLY_CLASS = 'jp-mod-readOnly';
 
-/**
- * The name of the default CodeMirror theme
- */
-export
-const DEFAULT_CODEMIRROR_THEME: string = 'jupyter';
-
 /**
  * The key code for the up arrow key.
  */
@@ -78,26 +72,6 @@ const TAB = 9;
  */
 export
 class CodeMirrorEditor implements CodeEditor.IEditor {
-  /**
-   * The uuid of this editor;
-   */
-  readonly uuid: string;
-
-  /**
-   * The selection style of this editor.
-   */
-  readonly selectionStyle?: CodeEditor.ISelectionStyle;
-
-  /**
-   * A signal emitted when a text completion is requested.
-   */
-  readonly completionRequested: ISignal<this, CodeEditor.IPosition>;
-
-  /**
-   * A signal emitted when either the top or bottom edge is requested.
-   */
-  readonly edgeRequested: ISignal<this, CodeEditor.EdgeLocation>;
-
   /**
    * Construct a CodeMirror editor.
    */
@@ -105,8 +79,8 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
     let host = this._host = options.host;
     host.classList.add(EDITOR_CLASS);
 
-    this.uuid = options.uuid || utils.uuid();
-    this.selectionStyle = options.selectionStyle;
+    this._uuid = options.uuid || utils.uuid();
+    this._selectionStyle = options.selectionStyle || {};
 
     Private.updateConfig(options, config);
 
@@ -125,6 +99,7 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
     model.selections.changed.connect(this._onSelectionsChanged, this);
 
     CodeMirror.on(editor, 'keydown', (editor, event) => {
+      this.onKeydown(event);
       let index = findIndex(this._keydownHandlers, handler => {
         if (handler(this, event) === true) {
           event.preventDefault();
@@ -142,27 +117,37 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
   }
 
   /**
-   * Tests whether the editor is disposed.
+   * A signal emitted when a text completion is requested.
    */
-  get isDisposed(): boolean {
-    return this._editor === null;
+  readonly completionRequested: ISignal<this, CodeEditor.IPosition>;
+
+  /**
+   * A signal emitted when either the top or bottom edge is requested.
+   */
+  readonly edgeRequested: ISignal<this, CodeEditor.EdgeLocation>;
+
+  /**
+   * The uuid of this editor;
+   */
+  get uuid(): string {
+    return this._uuid;
+  }
+  set uuid(value: string) {
+    this._uuid = value;
   }
 
   /**
-   * Dispose of the resources held by the widget.
+   * The selection style of this editor.
    */
-  dispose(): void {
-    if (this._editor === null) {
-      return;
-    }
-    this._editor = null;
-    this._model = null;
-    this._keydownHandlers.clear();
-    clearSignalData(this);
+  get selectionStyle(): CodeEditor.ISelectionStyle {
+    return this._selectionStyle;
+  }
+  set selectionStyle(value: CodeEditor.ISelectionStyle) {
+    this._selectionStyle = value;
   }
 
   /**
-   * Get the editor wrapped by the widget.
+   * Get the codemirror editor wrapped by the editor.
    */
   get editor(): CodeMirror.Editor {
     return this._editor;
@@ -196,10 +181,10 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
    * Set to false for horizontal scrolling. Defaults to true.
    */
   get wordWrap(): boolean {
-    return this._editor.getOption('wordWrap');
+    return this._editor.getOption('lineWrapping');
   }
   set wordWrap(value: boolean) {
-    this._editor.setOption('wordWrap', value);
+    this._editor.setOption('lineWrapping', value);
   }
 
   /**
@@ -238,6 +223,26 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
     return this._editor.defaultCharWidth();
   }
 
+  /**
+   * Tests whether the editor is disposed.
+   */
+  get isDisposed(): boolean {
+    return this._editor === null;
+  }
+
+  /**
+   * Dispose of the resources held by the widget.
+   */
+  dispose(): void {
+    if (this._editor === null) {
+      return;
+    }
+    this._editor = null;
+    this._model = null;
+    this._keydownHandlers.clear();
+    clearSignalData(this);
+  }
+
   /**
    * Returns the content for the given line number.
    */
@@ -334,7 +339,7 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
    * Reveal the given position in the editor.
    */
   revealPosition(position: CodeEditor.IPosition): void {
-    const cmPosition = this.toCodeMirrorPosition(position);
+    const cmPosition = this._toCodeMirrorPosition(position);
     this._editor.scrollIntoView(cmPosition);
   }
 
@@ -342,7 +347,7 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
    * Reveal the given selection in the editor.
    */
   revealSelection(selection: CodeEditor.IRange): void {
-    const range = this.toCodeMirrorRange(selection);
+    const range = this._toCodeMirrorRange(selection);
     this._editor.scrollIntoView(range);
   }
 
@@ -350,7 +355,7 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
    * Get the window coordinates given a cursor position.
    */
   getCoordinate(position: CodeEditor.IPosition): CodeEditor.ICoordinate {
-    return this.editor.charCoords(this.toCodeMirrorPosition(position), 'page');
+    return this.editor.charCoords(this._toCodeMirrorPosition(position), 'page');
   }
 
   /**
@@ -358,14 +363,14 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
    */
   getCursorPosition(): CodeEditor.IPosition {
     const cursor = this.doc.getCursor();
-    return this.toPosition(cursor);
+    return this._toPosition(cursor);
   }
 
   /**
    * Set the primary position of the cursor. This will remove any secondary cursors.
    */
   setCursorPosition(position: CodeEditor.IPosition): void {
-    const cursor = this.toCodeMirrorPosition(position);
+    const cursor = this._toCodeMirrorPosition(position);
     this.doc.setCursor(cursor);
   }
 
@@ -389,10 +394,10 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
   getSelections(): CodeEditor.ITextSelection[] {
     const selections = this.doc.listSelections();
     if (selections.length > 0) {
-      return selections.map(selection => this.toSelection(selection));
+      return selections.map(selection => this._toSelection(selection));
     }
     const cursor = this.doc.getCursor();
-    const selection = this.toSelection({ anchor: cursor, head: cursor });
+    const selection = this._toSelection({ anchor: cursor, head: cursor });
     return [selection];
   }
 
@@ -402,21 +407,10 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
    * Passing an empty array resets a cursor position to the start of a document.
    */
   setSelections(selections: CodeEditor.IRange[]): void {
-    const cmSelections = this.toCodeMirrorSelections(selections);
+    const cmSelections = this._toCodeMirrorSelections(selections);
     this.doc.setSelections(cmSelections, 0);
   }
 
-  /**
-   * Converts selections to code mirror selections.
-   */
-  protected toCodeMirrorSelections(selections: CodeEditor.IRange[]): CodeMirror.Selection[] {
-    if (selections.length > 0) {
-      return selections.map(selection => this.toCodeMirrorSelection(selection));
-    }
-    const position = { line: 0, ch: 0 };
-    return [{ anchor: position, head: position }];
-  }
-
   /**
    * Handle keydown events from the editor.
    */
@@ -480,10 +474,21 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
     this.completionRequested.emit(position);
   }
 
+  /**
+   * Converts selections to code mirror selections.
+   */
+  private _toCodeMirrorSelections(selections: CodeEditor.IRange[]): CodeMirror.Selection[] {
+    if (selections.length > 0) {
+      return selections.map(selection => this._toCodeMirrorSelection(selection));
+    }
+    const position = { line: 0, ch: 0 };
+    return [{ anchor: position, head: position }];
+  }
+
   /**
    * Handles a mime type change.
    */
-  protected _onMimeTypeChanged(): void {
+  private _onMimeTypeChanged(): void {
     const mime = this._model.mimeType;
     let editor = this._editor;
     loadModeByMIME(editor, mime);
@@ -502,18 +507,18 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
   /**
    * Handles a selections change.
    */
-  protected _onSelectionsChanged(selections: CodeEditor.ISelections, args: CodeEditor.ISelections.IChangedArgs): void {
+  private _onSelectionsChanged(selections: CodeEditor.ISelections, args: CodeEditor.ISelections.IChangedArgs): void {
     const uuid = args.uuid;
     if (uuid !== this.uuid) {
-      this.cleanSelections(uuid);
-      this.markSelections(uuid, args.newSelections);
+      this._cleanSelections(uuid);
+      this._markSelections(uuid, args.newSelections);
     }
   }
 
   /**
    * Clean selections for the given uuid.
    */
-  protected cleanSelections(uuid: string) {
+  private _cleanSelections(uuid: string) {
     const markers = this.selectionMarkers[uuid];
     if (markers) {
       markers.forEach(marker => marker.clear());
@@ -524,11 +529,11 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
   /**
    * Marks selections.
    */
-  protected markSelections(uuid: string, selections: CodeEditor.ITextSelection[]) {
+  private _markSelections(uuid: string, selections: CodeEditor.ITextSelection[]) {
     const markers: CodeMirror.TextMarker[] = [];
     for (const selection of selections) {
-      const { anchor, head } = this.toCodeMirrorSelection(selection);
-      const markerOptions = this.toTextMarkerOptions(selection);
+      const { anchor, head } = this._toCodeMirrorSelection(selection);
+      const markerOptions = this._toTextMarkerOptions(selection);
       this.doc.markText(anchor, head, markerOptions);
     }
     this.selectionMarkers[uuid] = markers;
@@ -537,7 +542,7 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
   /**
    * Handles a cursor activity event.
    */
-  protected _onCursorActivity(): void {
+  private _onCursorActivity(): void {
     const selections = this.getSelections();
     this.model.selections.setSelections(this.uuid, selections);
   }
@@ -545,11 +550,11 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
   /**
    * Converts a code mirror selection to an editor selection.
    */
-  protected toSelection(selection: CodeMirror.Selection): CodeEditor.ITextSelection {
+  private _toSelection(selection: CodeMirror.Selection): CodeEditor.ITextSelection {
     return {
       uuid: this.uuid,
-      start: this.toPosition(selection.anchor),
-      end: this.toPosition(selection.head),
+      start: this._toPosition(selection.anchor),
+      end: this._toPosition(selection.head),
       style: this.selectionStyle
     };
   }
@@ -557,7 +562,7 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
   /**
    * Converts the selection style to a text marker options.
    */
-  protected toTextMarkerOptions(style: CodeEditor.ISelectionStyle | undefined): CodeMirror.TextMarkerOptions | undefined {
+  private _toTextMarkerOptions(style: CodeEditor.ISelectionStyle | undefined): CodeMirror.TextMarkerOptions | undefined {
     if (style) {
       return {
         className: style.className,
@@ -570,27 +575,27 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
   /**
    * Converts an editor selection to a code mirror selection.
    */
-  protected toCodeMirrorSelection(selection: CodeEditor.IRange): CodeMirror.Selection {
+  private _toCodeMirrorSelection(selection: CodeEditor.IRange): CodeMirror.Selection {
     return {
-      anchor: this.toCodeMirrorPosition(selection.start),
-      head: this.toCodeMirrorPosition(selection.end)
+      anchor: this._toCodeMirrorPosition(selection.start),
+      head: this._toCodeMirrorPosition(selection.end)
     };
   }
 
   /**
    * Converts an editor selection to a code mirror selection.
    */
-  protected toCodeMirrorRange(range: CodeEditor.IRange): CodeMirror.Range {
+  private _toCodeMirrorRange(range: CodeEditor.IRange): CodeMirror.Range {
     return {
-      from: this.toCodeMirrorPosition(range.start),
-      to: this.toCodeMirrorPosition(range.end)
+      from: this._toCodeMirrorPosition(range.start),
+      to: this._toCodeMirrorPosition(range.end)
     };
   }
 
   /**
    * Convert a code mirror position to an editor position.
    */
-  protected toPosition(position: CodeMirror.Position) {
+  private _toPosition(position: CodeMirror.Position) {
     return {
       line: position.line,
       column: position.ch
@@ -600,7 +605,7 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
   /**
    * Convert an editor position to a code mirror position.
    */
-  protected toCodeMirrorPosition(position: CodeEditor.IPosition) {
+  private _toCodeMirrorPosition(position: CodeEditor.IPosition) {
     return {
       line: position.line,
       ch: position.column
@@ -664,46 +669,30 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
   protected selectionMarkers: { [key: string]: CodeMirror.TextMarker[] | undefined } = {};
   private _keydownHandlers = new Vector<CodeEditor.KeydownHandler>();
   private _changeGuard = false;
+  private _selectionStyle: CodeEditor.ISelectionStyle;
+  private _uuid = '';
   private _host: HTMLElement;
 }
 
 
+/**
+ * The namespace for `CodeMirrorEditor` statics.
+ */
+export
+namespace CodeMirrorEditor {
+  /**
+   * The name of the default CodeMirror theme
+   */
+  export
+  const DEFAULT_THEME: string = 'jupyter';
+}
+
+
 // Define the signals for the `CodeMirrorEditor` class.
 defineSignal(CodeMirrorEditor.prototype, 'completionRequested');
 defineSignal(CodeMirrorEditor.prototype, 'edgeRequested');
 
 
-/**
- * Add a CodeMirror command to delete until previous non blanking space
- * character or first multiple of 4 tabstop.
- */
-CodeMirror.commands['delSpaceToPrevTabStop'] = (cm: CodeMirror.Editor) => {
-  let doc = cm.getDoc();
-  let from = doc.getCursor('from');
-  let to = doc.getCursor('to');
-  let sel = !Private.posEq(from, to);
-  if (sel) {
-    let ranges = doc.listSelections();
-    for (let i = ranges.length - 1; i >= 0; i--) {
-      let head = ranges[i].head;
-      let anchor = ranges[i].anchor;
-      doc.replaceRange('', CodeMirror.Pos(head.line, head.ch), CodeMirror.Pos(anchor.line, anchor.ch));
-    }
-    return;
-  }
-  let cur = doc.getCursor();
-  let tabsize = cm.getOption('tabSize');
-  let chToPrevTabStop = cur.ch - (Math.ceil(cur.ch / tabsize) - 1) * tabsize;
-  from = {ch: cur.ch - chToPrevTabStop, line: cur.line};
-  let select = doc.getRange(from, cur);
-  if (select.match(/^\ +$/) !== null) {
-    doc.replaceRange('', from, cur);
-  } else {
-    CodeMirror.commands['delCharBefore'](cm);
-  }
-};
-
-
 /**
  * The namespace for module private data.
  */
@@ -715,17 +704,53 @@ namespace Private {
   function updateConfig(options: CodeEditor.IOptions, config: CodeMirror.EditorConfiguration): void {
     if (options.readOnly !== undefined) {
       config.readOnly = options.readOnly;
+    } else {
+      config.readOnly = false;
     }
     if (options.lineNumbers !== undefined) {
       config.lineNumbers = options.lineNumbers;
+    } else {
+      config.lineNumbers = false;
     }
     if (options.wordWrap !== undefined) {
       config.lineWrapping = options.wordWrap;
+    } else {
+      config.lineWrapping = true;
     }
-    config.theme = (config.theme || DEFAULT_CODEMIRROR_THEME);
+    config.theme = (config.theme || CodeMirrorEditor.DEFAULT_THEME);
     config.indentUnit = config.indentUnit || 4;
   }
 
+  /**
+   * Delete spaces to the previous tab stob in a codemirror editor.
+   */
+  export
+  function delSpaceToPrevTabStop(cm: CodeMirror.Editor): void {
+    let doc = cm.getDoc();
+    let from = doc.getCursor('from');
+    let to = doc.getCursor('to');
+    let sel = !posEq(from, to);
+    if (sel) {
+      let ranges = doc.listSelections();
+      for (let i = ranges.length - 1; i >= 0; i--) {
+        let head = ranges[i].head;
+        let anchor = ranges[i].anchor;
+        doc.replaceRange('', CodeMirror.Pos(head.line, head.ch), CodeMirror.Pos(anchor.line, anchor.ch));
+      }
+      return;
+    }
+    let cur = doc.getCursor();
+    let tabsize = cm.getOption('tabSize');
+    let chToPrevTabStop = cur.ch - (Math.ceil(cur.ch / tabsize) - 1) * tabsize;
+    from = {ch: cur.ch - chToPrevTabStop, line: cur.line};
+    let select = doc.getRange(from, cur);
+    if (select.match(/^\ +$/) !== null) {
+      doc.replaceRange('', from, cur);
+    } else {
+      CodeMirror.commands['delCharBefore'](cm);
+    }
+  };
+
   /**
    * Test whether two CodeMirror positions are equal.
    */
@@ -734,3 +759,11 @@ namespace Private {
     return a.line === b.line && a.ch === b.ch;
   };
 }
+
+
+/**
+ * Add a CodeMirror command to delete until previous non blanking space
+ * character or first multiple of 4 tabstop.
+ */
+CodeMirror.commands['delSpaceToPrevTabStop'] = Private.delSpaceToPrevTabStop;
+

+ 2 - 2
src/codemirror/plugin.ts

@@ -31,7 +31,7 @@ import {
 } from '../mainmenu';
 
 import {
-  editorServices, CodeMirrorEditor, DEFAULT_CODEMIRROR_THEME
+  editorServices, CodeMirrorEditor
 } from '.';
 
 
@@ -132,7 +132,7 @@ function activateEditorCommands(app: JupyterLab, tracker: IEditorTracker, mainMe
     commands.addCommand(cmdIds.changeTheme, {
       label: args => args['theme'] as string,
       execute: args => {
-        let name: string = args['theme'] as string || DEFAULT_CODEMIRROR_THEME;
+        let name: string = args['theme'] as string || CodeMirrorEditor.DEFAULT_THEME;
         tracker.forEach(widget => {
           if (widget.editor instanceof CodeMirrorEditor) {
             let cm = widget.editor.editor;

+ 2 - 2
src/renderers/widget.ts

@@ -15,7 +15,7 @@ import {
 } from '../codemirror';
 
 import {
-  DEFAULT_CODEMIRROR_THEME
+  CodeMirrorEditor
 } from '../codemirror/editor';
 
 import * as marked
@@ -91,7 +91,7 @@ marked.setOptions({
   sanitize: false,
   tables: true,
   // breaks: true; We can't use GFM breaks as it causes problems with HTML tables
-  langPrefix: `cm-s-${DEFAULT_CODEMIRROR_THEME} language-`,
+  langPrefix: `cm-s-${CodeMirrorEditor.DEFAULT_THEME} language-`,
   highlight: (code, lang, callback) => {
     if (!lang) {
         // no language, no highlight

+ 361 - 22
test/src/codemirror/editor.spec.ts

@@ -26,6 +26,8 @@ const DOWN_ARROW = 40;
 
 const TAB = 9;
 
+const ENTER = 13;
+
 
 class LogEditorWidget extends CodeMirrorEditor {
 
@@ -49,6 +51,7 @@ describe('CodeMirrorEditor', () => {
   let editor: LogEditorWidget;
   let host: HTMLElement;
   let model: CodeEditor.IModel;
+  const TEXT = new Array(100).join('foo bar baz\n');
 
   beforeEach(() => {
     host = document.createElement('div');
@@ -70,16 +73,6 @@ describe('CodeMirrorEditor', () => {
 
   });
 
-  describe('#isDisposed', () => {
-
-    it('should test whether the editor is disposed', () => {
-      expect(editor.isDisposed).to.be(false);
-      editor.dispose();
-      expect(editor.isDisposed).to.be(true);
-    });
-
-  });
-
   describe('#edgeRequested', () => {
 
     it('should emit a signal when the top edge is requested', () => {
@@ -127,28 +120,146 @@ describe('CodeMirrorEditor', () => {
 
   });
 
-  describe('#onKeydown()', () => {
+  describe('#uuid', () => {
 
-    it('should run when there is a keydown event on the editor', () => {
-      let event = generate('keydown', { keyCode: UP_ARROW });
-      expect(editor.methods).to.not.contain('onKeydown');
-      editor.editor.triggerOnKeyDown(event);
-      expect(editor.methods).to.contain('onKeydown');
+    it('should be the unique id of the editor', () => {
+      expect(editor.uuid).to.be.ok();
+      let uuid = 'foo';
+      editor = new LogEditorWidget({ model, host, uuid });
+      expect(editor.uuid).to.be('foo');
     });
 
   });
 
-  describe('#onTabEvent()', () => {
+  describe('#selectionStyle', () => {
 
-    it('should run when there is a tab keydown event on the editor', () => {
-      let event = generate('keydown', { keyCode: TAB });
-      expect(editor.methods).to.not.contain('onTabEvent');
-      editor.editor.triggerOnKeyDown(event);
-      expect(editor.methods).to.contain('onTabEvent');
+    it('should be the selection style of the editor', () => {
+      expect(editor.selectionStyle).to.eql({});
+    });
+
+    it('should be settable', () => {
+      let style = {
+        className: 'foo',
+        displayName: 'bar'
+      };
+      editor.selectionStyle = style;
+      expect(editor.selectionStyle).to.eql(style);
     });
 
   });
 
+  describe('#editor', () => {
+
+    it('should be the codemirror editor wrapped by the editor', () => {
+      let cm = editor.editor;
+      expect(cm.getDoc()).to.be(editor.doc);
+    });
+
+  });
+
+  describe('#doc', () => {
+
+    it('should be the codemirror doc wrapped by the editor', () => {
+      let doc = editor.doc;
+      expect(doc.getEditor()).to.be(editor.editor);
+    });
+
+  });
+
+  describe('#lineCount', () => {
+
+    it('should get the number of lines in the editor', () => {
+      expect(editor.lineCount).to.be(1);
+      editor.model.value.text = 'foo\nbar\nbaz';
+      expect(editor.lineCount).to.be(3);
+    });
+
+  });
+
+  describe('#lineNumbers', () => {
+
+    it('should get whether line numbers should be shown', () => {
+      expect(editor.lineNumbers).to.be(false);
+    });
+
+    it('should set whether line numbers should be shown', () => {
+      editor.lineNumbers = true;
+      expect(editor.lineNumbers).to.be(true);
+    });
+
+  });
+
+  describe('#wordWrap', () => {
+
+    it('should get whether horizontally scrolling should be used', () => {
+      expect(editor.wordWrap).to.be(true);
+    });
+
+    it('should set whether horizontally scrolling should be used', () => {
+      editor.wordWrap = false;
+      expect(editor.wordWrap).to.be(false);
+    });
+
+  });
+
+  describe('#readOnly', () => {
+
+    it('should get whether the editor is readonly', () => {
+      expect(editor.readOnly).to.be(false);
+    });
+
+    it('should set whether the editor is readonly', () => {
+      editor.readOnly = true;
+      expect(editor.readOnly).to.be(true);
+    });
+
+  });
+
+  describe('#model', () => {
+
+    it('should get the model used by the editor', () => {
+      expect(editor.model).to.be(model);
+    });
+
+  });
+
+  describe('#lineHeight', () => {
+
+    it('should get the text height of a line in the editor', () => {
+      expect(editor.lineHeight).to.be.above(0);
+    });
+
+  });
+
+  describe('#charWidth', () => {
+
+    it('should get the character width in the editor', () => {
+      expect(editor.charWidth).to.be.above(0);
+    });
+
+  });
+
+  describe('#isDisposed', () => {
+
+    it('should test whether the editor is disposed', () => {
+      expect(editor.isDisposed).to.be(false);
+      editor.dispose();
+      expect(editor.isDisposed).to.be(true);
+    });
+
+  });
+
+  describe('#dispose()', () => {
+
+    it('should dispose of the resources used by the editor', () => {
+      expect(editor.isDisposed).to.be(false);
+      editor.dispose();
+      expect(editor.isDisposed).to.be(true);
+      editor.dispose();
+      expect(editor.isDisposed).to.be(true);
+    });
+  });
+
   describe('#getLine()', () => {
 
     it('should get a line of text', () => {
@@ -223,4 +334,232 @@ describe('CodeMirrorEditor', () => {
 
   });
 
+  describe('#focus()', () => {
+
+    it('should give focus to the editor', () => {
+      expect(host.contains(document.activeElement)).to.be(false);
+      editor.focus();
+      expect(host.contains(document.activeElement)).to.be(true);
+    });
+
+  });
+
+  describe('#hasFocus()', () => {
+
+    it('should test whether the editor has focus', () => {
+      expect(editor.hasFocus()).to.be(false);
+      editor.focus();
+      expect(editor.hasFocus()).to.be(true);
+    });
+
+  });
+
+  describe('#refresh()', () => {
+
+    it('should repaint the editor', () => {
+      editor.refresh();
+      expect(editor).to.be.ok();
+    });
+
+  });
+
+  describe('#addKeydownHandler()', () => {
+
+    it('should add a keydown handler to the editor', () => {
+      let called = 0;
+      let handler = () => {
+        called++;
+        return true;
+      };
+      let disposable = editor.addKeydownHandler(handler);
+      let evt = generate('keydown', { keyCode: ENTER });
+      editor.editor.triggerOnKeyDown(evt);
+      expect(called).to.be(1);
+      disposable.dispose();
+      expect(disposable.isDisposed).to.be(true);
+
+      evt = generate('keydown', { keyCode: ENTER });
+      editor.editor.triggerOnKeyDown(evt);
+      expect(called).to.be(1);
+    });
+
+  });
+
+  describe('#setSize()', () => {
+
+    it('should set the size of the editor in pixels', () => {
+      editor.setSize({ width: 100, height: 100 });
+      editor.setSize(null);
+      expect(editor).to.be.ok();
+    });
+
+  });
+
+  describe('#revealPosition()', () => {
+
+    it('should reveal the given position in the editor', () => {
+      model.value.text = TEXT;
+      editor.revealPosition({ line: 50, column: 0 });
+      expect(editor).to.be.ok();
+    });
+
+  });
+
+  describe('#revealSelection()', () => {
+
+    it('should reveal the given selection in the editor', () => {
+      model.value.text = TEXT;
+      let start = { line: 50, column: 0 };
+      let end = { line: 52, column: 0 };
+      editor.setSelection({ start, end });
+      editor.revealSelection(editor.getSelection());
+      expect(editor).to.be.ok();
+    });
+
+  });
+
+  describe('#getCoordinate()', () => {
+
+    it('should get the window coordinates given a cursor position', () => {
+      let coord = editor.getCoordinate({ line: 10, column: 1 });
+      expect(coord.left).to.be.above(0);
+    });
+
+  });
+
+  describe('#getCursorPosition()', () => {
+
+    it('should get the primary position of the cursor', () => {
+      let pos = editor.getCursorPosition();
+      expect(pos.line).to.be(0);
+      expect(pos.column).to.be(0);
+      model.value.text = TEXT;
+      editor.setCursorPosition({ line: 12, column: 3 });
+      pos = editor.getCursorPosition();
+      expect(pos.line).to.be(12);
+      expect(pos.column).to.be(3);
+    });
+
+  });
+
+  describe('#setCursorPosition()', () => {
+
+    it('should set the primary position of the cursor', () => {
+      model.value.text = TEXT;
+      editor.setCursorPosition({ line: 12, column: 3 });
+      let pos = editor.getCursorPosition();
+      expect(pos.line).to.be(12);
+      expect(pos.column).to.be(3);
+    });
+
+  });
+
+  describe('#getSelection()', () => {
+
+    it('should get the primary selection of the editor', () => {
+      let selection = editor.getSelection();
+      expect(selection.start.line).to.be(0);
+      expect(selection.end.line).to.be(0);
+    });
+
+  });
+
+  describe('#setSelection()', () => {
+
+    it('should set the primary selection of the editor', () => {
+      model.value.text = TEXT;
+      let start = { line: 50, column: 0 };
+      let end = { line: 52, column: 0 };
+      editor.setSelection({ start, end });
+      expect(editor.getSelection().start).to.eql(start);
+      expect(editor.getSelection().end).to.eql(end);
+    });
+
+    it('should remove any secondary cursors', () => {
+      model.value.text = TEXT;
+      let range0 = {
+        start: { line: 50, column: 0 },
+        end: { line: 52, column: 0 }
+      };
+      let range1 = {
+        start: { line: 53, column: 0 },
+        end: { line: 54, column: 0 }
+      };
+      editor.setSelections([range0, range1]);
+      editor.setSelection(range1);
+      expect(editor.getSelections().length).to.be(1);
+    });
+
+  });
+
+  describe('#getSelections()', () => {
+
+    it('should get the selections for all the cursors', () => {
+      model.value.text = TEXT;
+      let range0 = {
+        start: { line: 50, column: 0 },
+        end: { line: 52, column: 0 }
+      };
+      let range1 = {
+        start: { line: 53, column: 0 },
+        end: { line: 54, column: 0 }
+      };
+      editor.setSelections([range0, range1]);
+      let selections = editor.getSelections();
+      expect(selections[0].start.line).to.be(50);
+      expect(selections[1].end.line).to.be(54);
+    });
+
+  });
+
+  describe('#setSelections()', () => {
+
+    it('should set the selections for all the cursors', () => {
+      model.value.text = TEXT;
+      let range0 = {
+        start: { line: 50, column: 0 },
+        end: { line: 52, column: 0 }
+      };
+      let range1 = {
+        start: { line: 53, column: 0 },
+        end: { line: 54, column: 0 }
+      };
+      editor.setSelections([range0, range1]);
+      let selections = editor.getSelections();
+      expect(selections[0].start.line).to.be(50);
+      expect(selections[1].end.line).to.be(54);
+    });
+
+    it('should set a default selection for an empty array', () => {
+      model.value.text = TEXT;
+      editor.setSelections([]);
+      let selection = editor.getSelection();
+      expect(selection.start.line).to.be(0);
+      expect(selection.end.line).to.be(0);
+    });
+
+  });
+
+  describe('#onKeydown()', () => {
+
+    it('should run when there is a keydown event on the editor', () => {
+      let event = generate('keydown', { keyCode: UP_ARROW });
+      expect(editor.methods).to.not.contain('onKeydown');
+      editor.editor.triggerOnKeyDown(event);
+      expect(editor.methods).to.contain('onKeydown');
+    });
+
+  });
+
+  describe('#onTabEvent()', () => {
+
+    it('should run when there is a tab keydown event on the editor', () => {
+      let event = generate('keydown', { keyCode: TAB });
+      expect(editor.methods).to.not.contain('onTabEvent');
+      editor.editor.triggerOnKeyDown(event);
+      expect(editor.methods).to.contain('onTabEvent');
+    });
+
+  });
+
 });