浏览代码

Merge pull request #1143 from ian-r-rose/ObservableString

Initial implementation of ObservableString.
Steven Silvester 8 年之前
父节点
当前提交
90c356950a
共有 3 个文件被更改,包括 402 次插入0 次删除
  1. 226 0
      src/common/observablestring.ts
  2. 175 0
      test/src/common/observablestring.spec.ts
  3. 1 0
      test/src/index.ts

+ 226 - 0
src/common/observablestring.ts

@@ -0,0 +1,226 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import {
+  IDisposable
+} from 'phosphor/lib/core/disposable';
+
+import {
+  clearSignalData, defineSignal, ISignal
+} from 'phosphor/lib/core/signaling';
+
+
+/**
+ * A string which can be observed for changes.
+ */
+export
+interface IObservableString extends IDisposable {
+  /**
+   * A signal emitted when the string has changed.
+   */
+  changed: ISignal<IObservableString, ObservableString.IChangedArgs>;
+
+  /**
+   * The value of the string.
+   */
+  text: string;
+
+  /**
+   * Insert a substring.
+   *
+   * @param index - The starting index.
+   *
+   * @param text - The substring to insert.
+   */
+  insert(index: number, text: string): void;
+
+  /**
+   * Remove a substring.
+   *
+   * @param start - The starting index.
+   *
+   * @param end - The ending index.
+   */
+  remove(start: number, end: number): void;
+
+  /**
+   * Set the ObservableString to an empty string.
+   */
+  clear(): void;
+
+  /**
+   * Dispose of the resources held by the string.
+   */
+  dispose(): void;
+}
+
+/**
+ * A concrete implementation of [[IObservableString]] 
+ */
+export
+class ObservableString implements IObservableString {
+  /**
+   * Construct a new observable string.
+   */
+  constructor(initialText: string = '') {
+    this.text = initialText;
+  }
+
+  /**
+   * A signal emitted when the string has changed.
+   */
+  changed: ISignal<IObservableString, ObservableString.IChangedArgs>;
+
+
+  /**
+   * Set the value of the string.
+   */
+  set text( value: string ) {
+    this._text = value;
+    this.changed.emit({
+      type: 'set',
+      start: 0,
+      end: value.length,
+      value: value
+    });
+  }
+
+  /**
+   * Get the value of the string.
+   */
+  get text(): string {
+    return this._text;
+  }
+
+  /**
+   * Insert a substring.
+   *
+   * @param index - The starting index.
+   *
+   * @param text - The substring to insert.
+   */
+  insert(index: number, text: string): void {
+    this._text = this._text.slice(0, index) +
+                 text +
+                 this._text.slice(index);
+    this.changed.emit({
+      type: 'insert',
+      start: index,
+      end: index + text.length,
+      value: text
+    });
+  }
+
+  /**
+   * Remove a substring.
+   *
+   * @param start - The starting index.
+   *
+   * @param end - The ending index.
+   */
+  remove(start: number, end: number): void {
+    let oldValue: string = this._text.slice(start, end);
+    this._text = this._text.slice(0, start) +
+                 this._text.slice(end);
+    this.changed.emit({
+      type: 'remove',
+      start: start,
+      end: end,
+      value: oldValue
+    });
+  }
+
+  /**
+   * Set the ObservableString to an empty string.
+   */
+  clear(): void {
+    this.text = '';
+  }
+
+  /**
+   * Test whether the string has been disposed.
+   */
+  get isDisposed(): boolean {
+    return this._isDisposed;
+  }
+
+  /**
+   * Dispose of the resources held by the string.
+   */
+  dispose(): void {
+    if (this._isDisposed) {
+      return;
+    }
+    this._isDisposed = true;
+    this.clear();
+    clearSignalData(this);
+  }
+
+  private _text = '';
+  private _isDisposed : boolean = false;
+}
+
+/**
+ * The namespace for `ObservableVector` class statics.
+ */
+export
+namespace ObservableString {
+  /**
+   * The change types which occur on an observable string.
+   */
+  export
+  type ChangeType =
+    /**
+     * Text was inserted.
+     */
+    'insert' |
+
+    /**
+     * Text was removed.
+     */
+    'remove' |
+
+    /**
+     * Text was set.
+     */
+    'set';
+
+  /**
+   * The changed args object which is emitted by an observable string.
+   */
+  export
+  interface IChangedArgs {
+    /**
+     * The type of change undergone by the list.
+     */
+    type: ChangeType;
+
+    /**
+     * The starting index of the change.
+     */
+    start: number;
+
+    /**
+     * The end index of the change.
+     */
+    end: number;
+
+    /**
+     * The value of the change.
+     *
+     * ### Notes
+     * If `ChangeType` is `set`, then
+     * this is the new value of the string.
+     *
+     * If `ChangeType` is `insert` this is
+     * the value of the inserted string.
+     *
+     * If `ChangeType` is remove this is the
+     * value of the removed substring.
+     */
+    value: string;
+  }
+}
+
+// Define the signals for the `ObservableString` class.
+defineSignal(ObservableString.prototype, 'changed');

+ 175 - 0
test/src/common/observablestring.spec.ts

@@ -0,0 +1,175 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import expect = require('expect.js');
+
+import {
+  ObservableString 
+} from '../../../lib/common/observablestring';
+
+
+describe('common/ObservableString', () => {
+
+  describe('ObservableString', () => {
+
+    describe('#constructor()', () => {
+
+      it('should accept no arguments', () => {
+        let value = new ObservableString();
+        expect(value instanceof ObservableString).to.be(true);
+      });
+
+      it('should accept a string argument', () => {
+        let value = new ObservableString("hello");
+        expect(value instanceof ObservableString).to.be(true);
+      });
+
+      it('should initialize the string value', () => {
+        let value = new ObservableString("hello");
+        expect(value.text).to.eql("hello");
+      });
+
+    });
+
+    describe('#changed', () => {
+
+      it('should be emitted when the string changes', () => {
+        let called = false;
+        let value = new ObservableString();
+        value.changed.connect(() => { called = true; });
+        value.text = "change";
+        expect(called).to.be(true);
+      });
+
+      it('should have value changed args', () => {
+        let called = false;
+        let value = new ObservableString();
+        value.changed.connect((sender, args) => {
+          expect(sender).to.be(value);
+          expect(args.type).to.be('set');
+          expect(args.start).to.be(0);
+          expect(args.end).to.be(3);
+          expect(args.value).to.be('new');
+          called = true;
+        });
+        value.text = 'new';
+        expect(called).to.be(true);
+      });
+
+    });
+
+    describe('#isDisposed', () => {
+
+      it('should test whether the string is disposed', () => {
+        let value = new ObservableString();
+        expect(value.isDisposed).to.be(false);
+        value.dispose();
+        expect(value.isDisposed).to.be(true);
+      });
+
+    });
+
+    describe('#setter()', () => {
+
+      it('should set the item at a specific index', () => {
+        let value = new ObservableString('old');
+        value.text = 'new';
+        expect(value.text).to.eql('new');
+      });
+
+      it('should trigger a changed signal', () => {
+        let called = false;
+        let value = new ObservableString('old');
+        value.changed.connect((sender, args) => {
+          expect(sender).to.be(value);
+          expect(args.type).to.be('set');
+          expect(args.start).to.be(0);
+          expect(args.end).to.be(3);
+          expect(args.value).to.be('new');
+          called = true;
+        });
+        value.text = 'new';
+        expect(called).to.be(true);
+      });
+
+    });
+
+    describe('#insert()', () => {
+
+      it('should insert an substring into the string at a specific index', () => {
+        let value = new ObservableString('one three');
+        value.insert(4, 'two ');
+        expect(value.text).to.eql('one two three');
+      });
+
+      it('should trigger a changed signal', () => {
+        let called = false;
+        let value = new ObservableString('one three');
+        value.changed.connect((sender, args) => {
+          expect(sender).to.be(value);
+          expect(args.type).to.be('insert');
+          expect(args.start).to.be(4);
+          expect(args.end).to.be(8);
+          expect(args.value).to.be('two ');
+          called = true;
+        });
+        value.insert(4, 'two ');
+        expect(called).to.be(true);
+      });
+
+    });
+
+    describe('#remove()', () => {
+
+      it('should remove a substring from the string', () => {
+        let value = new ObservableString('one two two three');
+        value.remove(4,8);
+        expect(value.text).to.eql('one two three');
+      });
+
+      it('should trigger a changed signal', () => {
+        let called = false;
+        let value = new ObservableString('one two two three');
+        value.changed.connect((sender, args) => {
+          expect(sender).to.be(value);
+          expect(args.type).to.be('remove');
+          expect(args.start).to.be(4);
+          expect(args.end).to.be(8);
+          expect(args.value).to.be('two ');
+          called = true;
+        });
+        value.remove(4,8);
+        expect(called).to.be(true);
+      });
+
+    });
+
+    describe('#clear()', () => {
+
+      it('should empty the string', () => {
+        let value = new ObservableString('full');
+        value.clear();
+        expect(value.text.length).to.be(0);
+        expect(value.text).to.be('');
+      });
+
+      it('should trigger a changed signal', () => {
+        let called = false;
+        let value = new ObservableString('full');
+        value.changed.connect((sender, args) => {
+          expect(sender).to.be(value);
+          expect(args.type).to.be('set');
+          expect(args.start).to.be(0);
+          expect(args.end).to.be(0);
+          expect(args.value).to.be('');
+          called = true;
+        });
+        value.clear();
+        expect(called).to.be(true);
+      });
+
+    });
+
+  });
+
+});

+ 1 - 0
test/src/index.ts

@@ -5,6 +5,7 @@ import './commandlinker/commandlinker.spec';
 
 import './common/activitymonitor.spec';
 import './common/instancetracker.spec';
+import './common/observablestring.spec';
 import './common/observablevector.spec';
 import './common/vdom.spec';