Browse Source

Finish output model tests

Steven Silvester 9 years ago
parent
commit
b969e3b07b
3 changed files with 424 additions and 162 deletions
  1. 3 0
      test/src/index.ts
  2. 108 160
      test/src/notebook/output-area/model.spec.ts
  3. 313 2
      test/src/notebook/output-area/widget.spec.ts

+ 3 - 0
test/src/index.ts

@@ -11,3 +11,6 @@ import './notebook/notebook/nbformat.spec';
 import './notebook/notebook/model.spec';
 import './notebook/notebook/modelfactory.spec';
 import './notebook/notebook/widget.spec';
+
+import './notebook/output-area/model.spec';
+import './notebook/output-area/widget.spec';

+ 108 - 160
test/src/notebook/output-area/model.spec.ts

@@ -4,7 +4,7 @@
 import expect = require('expect.js');
 
 import {
-  ObservableList
+  ListChangeType
 } from 'phosphor-observablelist';
 
 import {
@@ -12,10 +12,59 @@ import {
 } from '../../../../lib/notebook/output-area/model';
 
 import {
-  IStream
+  nbformat
 } from '../../../../lib/notebook/notebook/nbformat';
 
 
+/**
+ * The default outputs used for testing.
+ */
+export
+const DEFAULT_OUTPUTS: nbformat.IOutput[] = [
+  {
+   name: 'stdout',
+   output_type: 'stream',
+   text: [
+    'hello world\n',
+    '0\n',
+    '1\n',
+    '2\n'
+   ]
+  },
+  {
+   name: 'stderr',
+   output_type: 'stream',
+   text: [
+    'output to stderr\n'
+   ]
+  },
+  {
+   name: 'stderr',
+   output_type: 'stream',
+   text: [
+    'output to stderr2\n'
+   ]
+  },
+  {
+    output_type: 'execute_result',
+    execution_count: 1,
+    data: { 'text/plain': 'foo' },
+    metadata: {}
+  },
+  {
+   output_type: 'display_data',
+   data: { 'text/plain': 'hello, world' },
+   metadata: {}
+  },
+  {
+    output_type: 'error',
+    ename: 'foo',
+    evalue: 'bar',
+    traceback: ['fizz', 'buzz']
+  }
+];
+
+
 describe('notebook/output-area/model', () => {
 
   describe('OutputAreaModel', () => {
@@ -24,164 +73,97 @@ describe('notebook/output-area/model', () => {
 
       it('should create an output area model', () => {
         let model = new OutputAreaModel();
-        expect(model instanceof OutputAreaModel).to.be(true);
+        expect(model).to.be.an(OutputAreaModel);
       });
 
     });
 
-    describe('#stateChanged', () => {
+    describe('#changed', () => {
 
-      it('should be emitted when the state changes', () => {
+      it('should be emitted when the model changes', () => {
         let model = new OutputAreaModel();
         let called = false;
-        model.stateChanged.connect((editor, change) => {
-          expect(change.name).to.be('trusted');
-          expect(change.oldValue).to.be(false);
-          expect(change.newValue).to.be(true);
+        model.changed.connect((sender, args) => {
+          expect(sender).to.be(model);
+          expect(args.type).to.be(ListChangeType.Add);
+          expect(args.oldIndex).to.be(-1);
+          expect(args.newIndex).to.be(0);
+          expect(args.oldValue).to.be(void 0);
+          // TODO: use deepEqual when we update nbformat
           called = true;
         });
-        model.trusted = true;
+        model.add(DEFAULT_OUTPUTS[0]);
         expect(called).to.be(true);
       });
 
     });
 
-    describe('#outputs', () => {
+    describe('#length', () => {
 
-      it('should be an observable list', () => {
+      it('should get the length of the items in the model', () => {
         let model = new OutputAreaModel();
-        expect(model.outputs instanceof ObservableList).to.be(true);
+        expect(model.length).to.be(0);
+        model.add(DEFAULT_OUTPUTS[0]);
+        expect(model.length).to.be(1);
       });
 
       it('should be read-only', () => {
         let model = new OutputAreaModel();
-        expect(() => { model.outputs = null; }).to.throwError();
-      });
-
-    });
-
-    describe('#trusted', () => {
-
-      it('should default to false', () => {
-        let model = new OutputAreaModel();
-        expect(model.trusted).to.be(false);
-      });
-
-      it('should emit a stateChanged signal when changed', () => {
-        let model = new OutputAreaModel();
-        let called = false;
-        model.stateChanged.connect((editor, change) => {
-          expect(change.name).to.be('trusted');
-          expect(change.oldValue).to.be(false);
-          expect(change.newValue).to.be(true);
-          called = true;
-        });
-        model.trusted = true;
-        expect(called).to.be(true);
-      });
-
-      it('should not emit the signal when there is no change', () => {
-        let model = new OutputAreaModel();
-        let called = false;
-        model.stateChanged.connect((editor, change) => {
-          called = true;
-        });
-        model.trusted = false;
-        expect(called).to.be(false);
+        expect(() => { model.length = 0; }).to.throwError();
       });
 
     });
 
-    describe('#fixedHeight', () => {
-
-      it('should default to false', () => {
-        let model = new OutputAreaModel();
-        expect(model.fixedHeight).to.be(false);
-      });
+    describe('#isDisposed', () => {
 
-      it('should emit a stateChanged signal when changed', () => {
+      it('should test whether the model is disposed', () => {
         let model = new OutputAreaModel();
-        let called = false;
-        model.stateChanged.connect((editor, change) => {
-          expect(change.name).to.be('fixedHeight');
-          expect(change.oldValue).to.be(false);
-          expect(change.newValue).to.be(true);
-          called = true;
-        });
-        model.fixedHeight = true;
-        expect(called).to.be(true);
+        expect(model.isDisposed).to.be(false);
+        model.dispose();
+        expect(model.isDisposed).to.be(true);
       });
 
-      it('should not emit the signal when there is no change', () => {
+      it('should be read-only', () => {
         let model = new OutputAreaModel();
-        let called = false;
-        model.stateChanged.connect((editor, change) => {
-          called = true;
-        });
-        model.fixedHeight = false;
-        expect(called).to.be(false);
+        expect(() => { model.isDisposed = true; }).to.throwError();
       });
 
     });
 
-    describe('#collapsed', () => {
-
-      it('should default to false', () => {
-        let model = new OutputAreaModel();
-        expect(model.collapsed).to.be(false);
-      });
-
-      it('should emit a stateChanged signal when changed', () => {
-        let model = new OutputAreaModel();
-        let called = false;
-        model.stateChanged.connect((editor, change) => {
-          expect(change.name).to.be('collapsed');
-          expect(change.oldValue).to.be(false);
-          expect(change.newValue).to.be(true);
-          called = true;
-        });
-        model.collapsed = true;
-        expect(called).to.be(true);
-      });
+    describe('#dispose()', () => {
 
-      it('should not emit the signal when there is no change', () => {
+      it('should dispose of the resources used by the model', () => {
         let model = new OutputAreaModel();
-        let called = false;
-        model.stateChanged.connect((editor, change) => {
-          called = true;
-        });
-        model.collapsed = false;
-        expect(called).to.be(false);
+        model.add(DEFAULT_OUTPUTS[0]);
+        model.dispose();
+        expect(model.isDisposed).to.be(true);
+        expect(model.length).to.be(0);
       });
 
-    });
-
-    describe('#isDisposed', () => {
-
-      it('should indicate whether the model is disposed', () => {
+      it('should be safe to call more than once', () => {
         let model = new OutputAreaModel();
-        expect(model.isDisposed).to.be(false);
+        model.dispose();
         model.dispose();
         expect(model.isDisposed).to.be(true);
       });
 
     });
 
-    describe('#dispose()', () => {
+    describe('#get()', () => {
 
-      it('should clear signal data on the model', () => {
+      it('should get the item at the specified index', () => {
         let model = new OutputAreaModel();
-        let called = false;
-        model.stateChanged.connect(() => { called = true; });
-        model.dispose();
-        model.collapsed = true;
-        expect(called).to.be(false);
+        model.add(DEFAULT_OUTPUTS[0]);
+        let output = model.get(0);
+        expect(output).to.not.be(DEFAULT_OUTPUTS[0]);
+        // TODO: use deepEqual when nbformat is updated.
+        expect(output.output_type).to.be(DEFAULT_OUTPUTS[0].output_type);
       });
 
-      it('should be safe to call multiple times', () => {
+      it('should return `undefined` if out of range', () => {
         let model = new OutputAreaModel();
-        model.dispose();
-        model.dispose();
+        model.add(DEFAULT_OUTPUTS[0]);
+        expect(model.get(1)).to.be(void 0);
       });
 
     });
@@ -189,38 +171,18 @@ describe('notebook/output-area/model', () => {
     describe('#add()', () => {
 
       it('should add an output', () => {
-        let output: IStream = {
-          output_type: 'stream',
-          name: 'stdout',
-          text: 'foo\nbar'
-        };
         let model = new OutputAreaModel();
-        model.add(output);
-        expect(model.outputs.length).to.be(1);
+        model.add(DEFAULT_OUTPUTS[0]);
+        expect(model.length).to.be(1);
       });
 
       it('should consolidate consecutive stream outputs of the same kind', () => {
-        let output: IStream = {
-          output_type: 'stream',
-          name: 'stdout',
-          text: 'foo\nbar'
-        };
         let model = new OutputAreaModel();
-        model.add(output);
-        output = {
-          output_type: 'stream',
-          name: 'stdout',
-          text: 'fizz\buzz'
-        };
-        model.add(output);
-        expect(model.outputs.length).to.be(1);
-        output  = {
-          output_type: 'stream',
-          name: 'stderr',
-          text: 'oh no!'
-        };
-        model.add(output);
-        expect(model.outputs.length).to.be(2);
+        model.add(DEFAULT_OUTPUTS[0]);
+        model.add(DEFAULT_OUTPUTS[1]);
+        expect(model.length).to.be(2);
+        model.add(DEFAULT_OUTPUTS[2]);
+        expect(model.length).to.be(2);
       });
 
     });
@@ -228,35 +190,21 @@ describe('notebook/output-area/model', () => {
     describe('#clear()', () => {
 
       it('should clear all of the output', () => {
-        let output: IStream = {
-          output_type: 'stream',
-          name: 'stdout',
-          text: 'foo\nbar'
-        };
         let model = new OutputAreaModel();
-        model.add(output);
+        for (let output of DEFAULT_OUTPUTS) {
+          model.add(output);
+        }
         model.clear();
-        expect(model.outputs.length).to.be(0);
+        expect(model.length).to.be(0);
       });
 
       it('should wait for next add if requested', () => {
-        let output: IStream = {
-          output_type: 'stream',
-          name: 'stdout',
-          text: 'foo\nbar'
-        };
         let model = new OutputAreaModel();
-        model.add(output);
+        model.add(DEFAULT_OUTPUTS[0]);
         model.clear(true);
-        expect(model.outputs.length).to.be(1);
-        let output2 = {
-          output_type: 'error',
-          ename: 'foo',
-          evalue: '',
-          traceback: ['']
-        };
-        model.add(output2);
-        expect(model.outputs.length).to.be(1);
+        expect(model.length).to.be(1);
+        model.add(DEFAULT_OUTPUTS[1]);
+        expect(model.length).to.be(1);
       });
     });
 

+ 313 - 2
test/src/notebook/output-area/widget.spec.ts

@@ -3,19 +3,330 @@
 
 import expect = require('expect.js');
 
+import {
+  Message
+} from 'phosphor-messaging';
+
+import {
+  ChildMessage, Widget
+} from 'phosphor-widget';
+
 import {
   OutputAreaModel, OutputAreaWidget
 } from '../../../../lib/notebook/output-area';
 
+import {
+  defaultRenderMime
+} from '../../rendermime/rendermime.spec';
+
+import {
+  DEFAULT_OUTPUTS
+} from './model.spec';
+
+
+/**
+ * The default rendermime instance to use for testing.
+ */
+const rendermime = defaultRenderMime();
+
+
+
+
+class LogOutputAreaWidget extends OutputAreaWidget {
+
+  methods: string[] = [];
+
+  protected onUpdateRequest(msg: Message): void {
+    super.onUpdateRequest(msg);
+    this.methods.push('onUpdateRequest');
+  }
+
+  protected onChildRemoved(msg: ChildMessage): void {
+    super.onChildRemoved(msg);
+    this.methods.push('onChildRemoved');
+  }
+}
+
+
+function createWidget(): LogOutputAreaWidget {
+  let widget = new LogOutputAreaWidget({ rendermime });
+  let model = new OutputAreaModel();
+  for (let output of DEFAULT_OUTPUTS) {
+    model.add(output);
+  }
+  widget.model = model;
+  return widget;
+}
+
 
 describe('notebook/output-area/widget', () => {
 
   describe('OutputAreaWidget', () => {
 
-  });
+    describe('#constructor()', () => {
 
-});
+      it('should take an options object', () => {
+        let widget = new OutputAreaWidget({ rendermime });
+        expect(widget).to.be.an(OutputAreaWidget);
+      });
+
+      it('should take an optional renderer', () => {
+        let renderer = Object.create(OutputAreaWidget.defaultRenderer);
+        let widget = new OutputAreaWidget({ rendermime, renderer });
+        expect(widget.renderer).to.be(renderer);
+      });
+
+      it('should add the `jp-OutputArea` class', () => {
+        let widget = new OutputAreaWidget({ rendermime });
+        expect(widget.hasClass('jp-OutputArea')).to.be(true);
+      });
+
+    });
+
+    describe('#modelChanged', () => {
+
+      it('should be emitted when the model of the widget changes', () => {
+        let widget = new OutputAreaWidget({ rendermime });
+        let called = false;
+        widget.modelChanged.connect((sender, args) => {
+          expect(sender).to.be(widget);
+          expect(args).to.be(void 0);
+          called = true;
+        });
+        widget.model = new OutputAreaModel();
+        expect(called).to.be(true);
+      });
+
+    });
+
+    describe('#model', () => {
+
+      it('should default to `null`', () => {
+        let widget = new OutputAreaWidget({ rendermime });
+        expect(widget.model).to.be(null);
+      });
+
+      it('should set the model', () => {
+        let widget = new OutputAreaWidget({ rendermime });
+        let model = new OutputAreaModel();
+        widget.model = model;
+        expect(widget.model).to.be(model);
+      });
+
+      it('should emit `modelChanged` when the model changes', () => {
+        let widget = new OutputAreaWidget({ rendermime });
+        let called = false;
+        widget.modelChanged.connect(() => { called = true; });
+        widget.model = new OutputAreaModel();
+        expect(called).to.be(true);
+      });
+
+      it('should not emit `modelChanged` when the model does not change', () => {
+        let widget = new OutputAreaWidget({ rendermime });
+        let called = false;
+        let model = new OutputAreaModel();
+        widget.model = model;
+        widget.modelChanged.connect(() => { called = true; });
+        widget.model = model;
+        expect(called).to.be(false);
+      });
+
+      it('should create widgets for the model items', () => {
+        let widget = createWidget();
+        expect(widget.childCount()).to.be(5);
+      });
+
+      it('should add the `jp-OutputArea-output` class to the child widgets', () => {
+        let widget = createWidget();
+        let child = widget.childAt(0);
+        expect(child.hasClass('jp-OutputArea-output')).to.be(true);
+      });
+
+      context('model `changed` signal', () => {
+
+        it('should add a new widget', () => {
+          let widget = createWidget();
+          widget.model.add(DEFAULT_OUTPUTS[0]);
+          expect(widget.childCount()).to.be(6);
+        });
+
+        it('should clear the widgets', () => {
+          let widget = createWidget();
+          widget.model.clear();
+          expect(widget.childCount()).to.be(0);
+        });
+
+      });
+
+    });
 
+    describe('#rendermime', () => {
 
+      it('should be the rendermime instance used by the widget', () => {
+        let widget = new OutputAreaWidget({ rendermime });
+        expect(widget.rendermime).to.be(rendermime);
+      });
 
+      it('should be read-only', () => {
+        let widget = new OutputAreaWidget({ rendermime });
+        expect(() => { widget.rendermime = null; }).to.throwError();
+      });
 
+    });
+
+    describe('#renderer', () => {
+
+      it('should be the renderer used by the widget', () => {
+        let renderer = Object.create(OutputAreaWidget.defaultRenderer);
+        let widget = new OutputAreaWidget({ rendermime, renderer });
+        expect(widget.renderer).to.be(renderer);
+      });
+
+      it('should be read-only', () => {
+        let widget = new OutputAreaWidget({ rendermime });
+        expect(() => { widget.renderer = null; }).to.throwError();
+      });
+
+    });
+
+    describe('#trusted', () => {
+
+      it('should get the trusted state of the widget', () => {
+        let widget = new OutputAreaWidget({ rendermime });
+        expect(widget.trusted).to.be(false);
+      });
+
+      it('should set the trusted state of the widget', () => {
+        let widget = new OutputAreaWidget({ rendermime });
+        widget.trusted = true;
+        expect(widget.trusted).to.be(true);
+      });
+
+      it('should re-render if trusted changes', () => {
+        let widget = createWidget();
+        let child = widget.childAt(0);
+        widget.trusted = true;
+        expect(child.isDisposed).to.be(true);
+        expect(widget.childCount()).to.be(7);
+      });
+
+    });
+
+    describe('#collapsed', () => {
+
+      it('should get the collapsed state of the widget', () => {
+        let widget = createWidget();
+        expect(widget.collapsed).to.be(false);
+      });
+
+      it('should set the collapsed state of the widget', () => {
+        let widget = createWidget();
+        widget.collapsed = true;
+        expect(widget.collapsed).to.be(true);
+      });
+
+      it('should post an update request', (done) => {
+        let widget = new LogOutputAreaWidget({ rendermime });
+        widget.collapsed = true;
+        requestAnimationFrame(() => {
+          expect(widget.methods).to.contain('onUpdateRequest');
+          done();
+        });
+      });
+
+    });
+
+    describe('#fixedHeight', () => {
+
+      it('should get the fixed height state of the widget', () => {
+        let widget = createWidget();
+        expect(widget.fixedHeight).to.be(false);
+      });
+
+      it('should set the fixed height state of the widget', () => {
+        let widget = createWidget();
+        widget.fixedHeight = true;
+        expect(widget.fixedHeight).to.be(true);
+      });
+
+      it('should post an update request', (done) => {
+        let widget = new LogOutputAreaWidget({ rendermime });
+        widget.fixedHeight = true;
+        requestAnimationFrame(() => {
+          expect(widget.methods).to.contain('onUpdateRequest');
+          done();
+        });
+      });
+
+    });
+
+    describe('#dispose()', () => {
+
+      it('should dispose of the resources held by the widget', () => {
+        let widget = createWidget();
+        widget.dispose();
+        expect(widget.model).to.be(null);
+        expect(widget.rendermime).to.be(null);
+        expect(widget.renderer).to.be(null);
+      });
+
+      it('should be safe to call more than once', () => {
+        let widget = createWidget();
+        widget.dispose();
+        widget.dispose();
+        expect(widget.isDisposed).to.be(true);
+      });
+
+    });
+
+    describe('#childAt()', () => {
+
+      it('should get the child widget at the specified index', () => {
+        let widget = createWidget();
+        expect(widget.childAt(0)).to.be.a(Widget);
+      });
+
+    });
+
+    describe('#childCount()', () => {
+
+      it('should get the number of child widgets', () => {
+        let widget = createWidget();
+        expect(widget.childCount()).to.be(5);
+        widget.model.clear();
+        expect(widget.childCount()).to.be(0);
+      });
+
+    });
+
+    describe('#onUpdateRequest()', () => {
+
+      it('should set the appropriate classes on the widget', (done) => {
+        let widget = createWidget();
+        widget.collapsed = true;
+        widget.fixedHeight = true;
+        requestAnimationFrame(() => {
+          expect(widget.methods).to.contain('onUpdateRequest');
+          expect(widget.hasClass('jp-mod-fixedHeight')).to.be(true);
+          expect(widget.hasClass('jp-mod-collapsed')).to.be(true);
+          done();
+        });
+      });
+
+    });
+
+    describe('#onChildRemoved()', () => {
+
+      it('should dispose of the child widget', () => {
+        let widget = createWidget();
+        let child = widget.childAt(0);
+        child.parent = null;
+        expect(widget.methods).to.contain('onChildRemoved');
+        expect(child.isDisposed).to.be(true);
+      });
+
+    });
+
+  });
+
+});