Browse Source

Clean up handlers and registry and add registry tests

Steven Silvester 9 years ago
parent
commit
c169fb9d6d

+ 0 - 1
src/filehandler/default.ts

@@ -45,7 +45,6 @@ class FileHandler extends AbstractFileHandler<CodeMirrorWidget> {
    */
   protected getSaveOptions(widget: CodeMirrorWidget, path: string): Promise<IContentsOpts> {
     let name = path.split('/').pop();
-    name = name.split('.')[0];
     let content = widget.editor.getDoc().getValue();
     return Promise.resolve({ path, content, name,
                              type: 'file', format: 'text' });

+ 30 - 5
src/filehandler/registry.ts

@@ -31,6 +31,13 @@ class FileHandlerRegistry {
     return Private.openedSignal.bind(this);
   }
 
+  /**
+   * A signal emitted when a file has finished opening.
+   */
+  get finished(): ISignal<FileHandlerRegistry, Widget> {
+    return Private.finishedSignal.bind(this);
+  }
+
   /**
    * A signal emitted when a file is created.
    */
@@ -43,6 +50,9 @@ class FileHandlerRegistry {
    */
   addHandler(handler: AbstractFileHandler<Widget>): void {
     this._handlers.push(handler);
+    handler.finished.connect((handler, widget) => {
+      this.finished.emit(widget);
+    });
   }
 
   /**
@@ -85,7 +95,7 @@ class FileHandlerRegistry {
   }
 
   /**
-   * Open a contents model by path.
+   * Open a file by path.
    */
   open(path: string): Widget {
     let handler = this.findHandler(path);
@@ -94,12 +104,13 @@ class FileHandlerRegistry {
     }
     let widget = handler.open(path);
     this.opened.emit(widget);
+    return widget;
   }
 
   /**
    * Rename a file.
    */
-  rename(oldPath: string, newPath: string): boolean {
+  rename(oldPath: string, newPath?: string): boolean {
     for (let h of this._handlers) {
       if (h.findWidget(oldPath)) {
         return h.rename(oldPath, newPath);
@@ -118,6 +129,8 @@ class FileHandlerRegistry {
         return h.save(path);
       }
     }
+    let msg = `No open widget for path '${path}'`;
+    return Promise.reject(new Error(msg));
   }
 
   /**
@@ -130,6 +143,8 @@ class FileHandlerRegistry {
         return h.revert(path);
       }
     }
+    let msg = `No open widget for path '${path}'`;
+    return Promise.reject(new Error(msg));
   }
 
   /**
@@ -142,15 +157,19 @@ class FileHandlerRegistry {
         return h.close(path);
       }
     }
+    let msg = `No open widget for path '${path}'`;
+    return Promise.reject(new Error(msg));
   }
 
   /**
    * Close all files.
    */
-  closeAll(): void {
+  closeAll(): Promise<void> {
+    let promises: Promise<void>[] = [];
     for (let h of this._handlers) {
-      h.closeAll();
+      promises.push(h.closeAll());
     }
+    return Promise.all(promises).then(() => { return void 0; });
   }
 
   /**
@@ -200,7 +219,7 @@ class FileHandlerRegistry {
       if (this._default) {
         return this._default;
       }
-      throw new Error(`Could not open file '${path}'`);
+      return;
     }
 
     // There are more than one possible handlers.
@@ -224,6 +243,12 @@ namespace Private {
   export
   const openedSignal = new Signal<FileHandlerRegistry, Widget>();
 
+  /**
+   * A signal emitted when a file is finished opening.
+   */
+  export
+  const finishedSignal = new Signal<FileHandlerRegistry, Widget>();
+
   /**
    * A signal emitted when a file is created.
    */

+ 476 - 0
test/src/filehandler/registry.spec.ts

@@ -0,0 +1,476 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+'use strict';
+
+import expect = require('expect.js');
+
+import {
+  IContentsModel, IContentsManager, IContentsOpts, ICheckpointModel,
+  IAjaxSettings, ContentsManager
+} from 'jupyter-js-services';
+
+import {
+  Widget
+} from 'phosphor-widget';
+
+import {
+  CodeMirrorWidget
+} from '../../../lib/codemirror/widget';
+
+import {
+  AbstractFileHandler
+} from '../../../lib/filehandler/handler';
+
+import {
+  FileHandler
+} from '../../../lib/filehandler/default';
+
+import {
+  FileCreator
+} from '../../../lib/filehandler/creator';
+
+import {
+  FileHandlerRegistry
+} from '../../../lib/filehandler/registry';
+
+import {
+  MockContentsManager
+} from '../mock';
+
+import {
+  acceptDialog, dismissDialog
+} from '../utils';
+
+
+class MyRegistry extends FileHandlerRegistry {
+
+  methods: string[] = [];
+  handlers: AbstractFileHandler<Widget>[] = [];
+
+  protected findHandler(path: string): AbstractFileHandler<Widget> {
+    this.methods.push('findHandler');
+    let value = super.findHandler(path);
+    this.handlers.push(value);
+    return value;
+  }
+}
+
+
+class MyHandler extends FileHandler {
+
+  methods: string[] = [];
+  handlers: AbstractFileHandler<Widget>[] = [];
+
+  get fileExtensions(): string[] {
+    return ['.txt'];
+  }
+
+  open(path: string): CodeMirrorWidget {
+    this.methods.push('open');
+    return super.open(path);
+  }
+
+}
+
+
+describe('jupyter-ui', () => {
+
+  describe('FileHandlerRegistry', () => {
+
+    describe('#opened()', () => {
+
+      it('should be emitted when a file is opened', () => {
+        let manager = new MockContentsManager();
+        let handler = new FileHandler(manager);
+        let registry = new MyRegistry();
+        registry.addDefaultHandler(handler);
+        let called = false;
+        registry.opened.connect((reg, widget) => {
+          expect(widget instanceof Widget).to.be(true);
+          called = true;
+        });
+        registry.open('foo.txt');
+        expect(called).to.be(true);
+      });
+
+    });
+
+    describe('#finished()', () => {
+
+      it('should be emitted when a file is finished opening', (done) => {
+        let manager = new MockContentsManager();
+        let handler = new FileHandler(manager);
+        let registry = new MyRegistry();
+        registry.addDefaultHandler(handler);
+        registry.finished.connect((reg, widget) => {
+          expect(widget instanceof Widget).to.be(true);
+          done();
+        });
+        registry.open('foo.txt');
+      });
+
+    });
+
+    describe('#created()', () => {
+
+      it('should be emitted when a file is created', (done) => {
+        let manager = new MockContentsManager();
+        let creator = new FileCreator(manager);
+        let registry = new MyRegistry();
+        registry.addCreator('file', creator.createNew.bind(creator));
+        registry.created.connect((reg, model) => {
+          expect(model.content).to.be(manager.DEFAULT_TEXT);
+          done();
+        });
+        registry.createNew('file', '/');
+        acceptDialog();
+      });
+
+    });
+
+    describe('#addHandler()', () => {
+
+      it('should add a handler to the registry', () => {
+        let manager = new MockContentsManager();
+        let handler = new FileHandler(manager);
+        let registry = new MyRegistry();
+        registry.addHandler(handler);
+        let value = registry.open('foo.txt');
+        expect(value).to.be(void 0);
+      });
+
+      it('should handle files that match its extension', () => {
+        let manager = new MockContentsManager();
+        let handler = new MyHandler(manager);
+        let registry = new MyRegistry();
+        registry.addHandler(handler);
+        let value = registry.open('foo.txt');
+        expect(value instanceof Widget).to.be(true);
+      });
+
+    });
+
+    describe('#addDefaultHandler()', () => {
+
+      it('should add a default handler to the registry', () => {
+        let manager = new MockContentsManager();
+        let handler = new FileHandler(manager);
+        let registry = new MyRegistry();
+        registry.addDefaultHandler(handler);
+        let value = registry.open('foo.txt');
+        expect(value instanceof Widget).to.be(true);
+      });
+
+      it('should be overruled by a handler with a matching extension', () => {
+        let manager = new MockContentsManager();
+        let handler = new MyHandler(manager);
+        let registry = new MyRegistry();
+        let main = new FileHandler(manager);
+        registry.addDefaultHandler(main);
+        registry.addHandler(handler);
+        let value = registry.open('foo.txt');
+        expect(handler.methods.indexOf('open')).to.not.be(-1);
+      });
+
+    });
+
+    describe('#addCreator()', () => {
+
+      it('should add a creator for a specific type', (done) => {
+        let manager = new MockContentsManager();
+        let creator = new FileCreator(manager);
+        let registry = new MyRegistry();
+        registry.addCreator('file', creator.createNew.bind(creator));
+        registry.created.connect((reg, model) => {
+          expect(model.content).to.be(manager.DEFAULT_TEXT);
+          done();
+        });
+        registry.createNew('file', '/');
+        acceptDialog();
+      });
+
+      it('should reject the promise if the type is not registered', (done) => {
+        let manager = new MockContentsManager();
+        let creator = new FileCreator(manager);
+        let registry = new MyRegistry();
+        registry.addCreator('file', creator.createNew.bind(creator));
+        registry.createNew('directory', '/').catch(error => {
+          done();
+        });
+      });
+
+    });
+
+    describe('#listCreator()', () => {
+
+      it('should get the list of creator names', () => {
+        let manager = new MockContentsManager();
+        let creator = new FileCreator(manager);
+        let registry = new MyRegistry();
+        registry.addCreator('file', creator.createNew.bind(creator));
+        registry.addCreator('foo', creator.createNew.bind(creator));
+        expect(registry.listCreators()).to.eql(['file', 'foo']);
+      });
+
+    });
+
+    describe('#createNew()', () => {
+
+      it('should create a new file', (done) => {
+        let manager = new MockContentsManager();
+        let creator = new FileCreator(manager);
+        let registry = new MyRegistry();
+        registry.addCreator('file', creator.createNew.bind(creator));
+        registry.created.connect((reg, model) => {
+          expect(model.content).to.be(manager.DEFAULT_TEXT);
+          done();
+        });
+        registry.createNew('file', '/');
+        acceptDialog();
+      });
+
+      it('should accept a host for the dialog', (done) => {
+        let node = document.createElement('div');
+        document.body.appendChild(node);
+        let manager = new MockContentsManager();
+        let creator = new FileCreator(manager);
+        let registry = new MyRegistry();
+        registry.addCreator('file', creator.createNew.bind(creator));
+        registry.created.connect((reg, model) => {
+          expect(model.content).to.be(manager.DEFAULT_TEXT);
+          document.body.removeChild(node);
+          done();
+        });
+        registry.createNew('file', '/', node);
+        acceptDialog(node);
+      });
+
+    });
+
+    describe('#open()', () => {
+
+      it('should open a file by path', (done) => {
+        let manager = new MockContentsManager();
+        let handler = new MyHandler(manager);
+        let registry = new MyRegistry();
+        registry.addHandler(handler);
+        let called = false;
+        registry.opened.connect(() => {
+          called = true;
+        });
+        let value = registry.open('foo.txt');
+        expect(value instanceof Widget).to.be(true);
+        registry.finished.connect((r, w) => {
+          expect(called).to.be(true);
+          expect(w).to.be(value);
+          done();
+        });
+      });
+
+      it('should bail if there is no handler', () => {
+        let manager = new MockContentsManager();
+        let handler = new MyHandler(manager);
+        let registry = new MyRegistry();
+        let value = registry.open('foo.txt');
+        expect(value).to.be(void 0);
+      });
+
+    });
+
+    describe('#rename()', () => {
+
+      it('should rename the appropriate widget', () => {
+        let manager = new MockContentsManager();
+        let handler = new MyHandler(manager);
+        let registry = new MyRegistry();
+        registry.addDefaultHandler(handler);
+        let widget = registry.open('foo.txt');
+        let response = registry.rename('foo.txt', 'bar.txt');
+        expect(widget.title.text).to.be('bar.txt');
+        expect(response).to.be(true);
+      });
+
+      it('should bail if the path is not opened', () => {
+        let manager = new MockContentsManager();
+        let handler = new MyHandler(manager);
+        let registry = new MyRegistry();
+        let response = registry.rename('foo.txt');
+        expect(response).to.be(false);
+      });
+
+    });
+
+    describe('#save()', () => {
+
+      it('should save the appropriate widget', (done) => {
+        let manager = new MockContentsManager();
+        let handler = new MyHandler(manager);
+        let registry = new MyRegistry();
+        registry.addDefaultHandler(handler);
+        registry.open('foo.txt');
+        registry.save('foo.txt').then(contents => {
+          expect(contents.name).to.be('foo.txt')
+          done();
+        });
+      });
+
+      it('should reject if the path is not opened', (done) => {
+        let manager = new MockContentsManager();
+        let handler = new MyHandler(manager);
+        let registry = new MyRegistry();
+        registry.save('foo.txt').catch(error => {
+          done();
+        });
+      });
+
+    });
+
+    describe('#revert()', () => {
+
+      it('should revert the appropriate widget', (done) => {
+        let manager = new MockContentsManager();
+        let handler = new MyHandler(manager);
+        let registry = new MyRegistry();
+        registry.addDefaultHandler(handler);
+        registry.open('foo.txt');
+        registry.revert('foo.txt').then(contents => {
+          expect(contents.name).to.be('foo.txt');
+          expect(contents.content).to.be(manager.DEFAULT_TEXT);
+          done();
+        });
+      });
+
+      it('should reject if the path is not opened', (done) => {
+        let manager = new MockContentsManager();
+        let handler = new MyHandler(manager);
+        let registry = new MyRegistry();
+        registry.revert('foo.txt').catch(error => {
+          done();
+        });
+      });
+
+    });
+
+    describe('#close()', () => {
+
+      it('should close the appropriate widget', (done) => {
+        let manager = new MockContentsManager();
+        let handler = new MyHandler(manager);
+        let registry = new MyRegistry();
+        registry.addDefaultHandler(handler);
+        registry.open('foo.txt');
+        registry.close('foo.txt').then(value => {
+          expect(value).to.be(true);
+          done();
+        });
+      });
+
+      it('should reject if the path is not opened', (done) => {
+        let manager = new MockContentsManager();
+        let handler = new MyHandler(manager);
+        let registry = new MyRegistry();
+        registry.close('foo.txt').catch(error => {
+          done();
+        });
+      });
+
+    });
+
+    describe('#closeAll', () => {
+
+      it('should close all open widgets across openers', (done) => {
+        let manager = new MockContentsManager();
+        let handler = new MyHandler(manager);
+        let registry = new MyRegistry();
+        let main = new FileHandler(manager);
+        registry.addDefaultHandler(main);
+        registry.addHandler(handler);
+        let widget0 = registry.open('foo.txt');
+        let widget1 = registry.open('foo.md');
+        registry.closeAll().then(() => {
+          expect(registry.findPath(widget0)).to.be(void 0);
+          expect(registry.findPath(widget1)).to.be(void 0);
+          done();
+        });
+      });
+
+    });
+
+    describe('#findPath()', () => {
+
+      it('should get the path for a given widget', () => {
+        let manager = new MockContentsManager();
+        let handler = new MyHandler(manager);
+        let registry = new MyRegistry();
+        registry.addDefaultHandler(handler);
+        let widget = registry.open('foo.txt');
+        expect(registry.findPath(widget)).to.be('foo.txt');
+      });
+
+      it('should return `undefined` if not found', () => {
+        let registry = new MyRegistry();
+        let widget = new Widget();
+        expect(registry.findPath(widget)).to.be(void 0);
+      });
+
+    });
+
+    describe('#findWidget()', () => {
+
+      it('should get the widget for a given path', () => {
+        let manager = new MockContentsManager();
+        let handler = new MyHandler(manager);
+        let registry = new MyRegistry();
+        registry.addDefaultHandler(handler);
+        let widget = registry.open('foo.txt');
+        expect(registry.findWidget('foo.txt')).to.be(widget);
+      });
+
+      it('should return `undefined` if not found', () => {
+        let registry = new MyRegistry();
+        expect(registry.findWidget('foo.txt')).to.be(void 0);
+      });
+
+    });
+
+    describe('#findHandler()', () => {
+
+      it('should use a lone registered handler', () => {
+        let manager = new MockContentsManager();
+        let handler = new MyHandler(manager);
+        let registry = new MyRegistry();
+        registry.addHandler(handler);
+        registry.open('foo.txt');
+        expect(registry.methods.indexOf('findHandler')).to.not.be(-1);
+        expect(registry.handlers.indexOf(handler)).to.not.be(-1);
+      });
+
+      it('should use the default handler if there is more than one', () => {
+        let manager = new MockContentsManager();
+        let handler = new MyHandler(manager);
+        let main = new FileHandler(manager);
+        let registry = new MyRegistry();
+        registry.addHandler(handler);
+        registry.addDefaultHandler(main);
+        registry.open('foo.md');
+        expect(registry.methods.indexOf('findHandler')).to.not.be(-1);
+        expect(registry.handlers.indexOf(main)).to.not.be(-1);
+      });
+
+      it('should use the more specifc handler by filename', () => {
+        let manager = new MockContentsManager();
+        let handler = new MyHandler(manager);
+        let main = new FileHandler(manager);
+        let registry = new MyRegistry();
+        registry.addHandler(handler);
+        registry.addDefaultHandler(handler);
+        registry.open('foo.txt');
+        expect(registry.methods.indexOf('findHandler')).to.not.be(-1);
+        expect(registry.handlers.indexOf(handler)).to.not.be(-1);
+      });
+
+    });
+
+  });
+
+});

+ 1 - 0
test/src/index.ts

@@ -6,5 +6,6 @@ import './dialog/dialog.spec';
 import './filehandler/creator.spec';
 import './filehandler/default.spec';
 import './filehandler/filehandler.spec';
+import './filehandler/registry.spec';
 import './renderers/renderers.spec';
 import './rendermime/rendermime.spec';

+ 1 - 1
test/src/utils.ts

@@ -30,7 +30,7 @@ function waitForDialog(host: HTMLElement = document.body): Promise<void> {
         resolve(void 0);
         return;
       }
-      setTimeout(refresh, 100);
+      setTimeout(refresh, 10);
     }
     refresh();
   });