Browse Source

Merge pull request #79 from blink1073/use-dialog-tests

Adding more tests using dialog
A. Darian 9 years ago
parent
commit
7c29141ee9

+ 0 - 1
src/dialog/index.ts

@@ -184,7 +184,6 @@ function showDialog(options?: IDialogOptions): Promise<IButtonItem>{
   // Focus the ok button if given.
   let index = buttons.indexOf(okButton);
   if (index !== -1) buttonNodes[index].focus();
-
   return new Promise<IButtonItem>((resolve, reject) => {
     buttonNodes.map(node => {
       node.addEventListener('click', evt => {

+ 6 - 4
src/filehandler/creator.ts

@@ -114,7 +114,10 @@ class FileCreator {
       host: this._host,
       okText: 'CREATE'
     }).then(value => {
-      if (value.text === 'CREATE') {
+      if (value && value.text === 'CREATE') {
+        if (edit.value === contents.name) {
+          return contents;
+        }
         return this.manager.rename(contents.path, `${dname}/${edit.value}`);
       } else {
         return this.manager.delete(contents.path).then(() => void 0);
@@ -124,8 +127,7 @@ class FileCreator {
         return this.handleExisting(edit.value, contents);
       }
       return this.showErrorMessage(error);
-    }
-    );
+    });
   }
 
   /**
@@ -149,7 +151,7 @@ class FileCreator {
       body: `${this.displayName} "${name}" already exists, try again?`,
       host: this.host
     }).then(value => {
-      if (value.text === 'OK') {
+      if (value && value.text === 'OK') {
         return this.doRename(contents);
       } else {
         return this.manager.delete(contents.path).then(() => void 0);

+ 7 - 3
src/filehandler/handler.ts

@@ -115,8 +115,11 @@ abstract class AbstractFileHandler<T extends Widget> implements IMessageFilter {
 
   /**
    * Rename a file.
+   *
+   * #### Notes
+   * The file action itself must take place elsewhere.
    */
-  rename(oldPath: string, newPath: string): boolean {
+  rename(oldPath: string, newPath?: string): boolean {
     let widget = this.findWidget(oldPath);
     if (widget === void 0) {
       return false;
@@ -312,12 +315,13 @@ abstract class AbstractFileHandler<T extends Widget> implements IMessageFilter {
    * Ask the user whether to close an unsaved file.
    */
   private _maybeClose(widget: T): Promise<boolean> {
+    let host = widget.isAttached ? widget.node : document.body;
     return showDialog({
       title: 'Close without saving?',
       body: `File "${widget.title.text}" has unsaved changes, close without saving?`,
-      host: widget.node
+      host
     }).then(value => {
-      if (value.text === 'OK') {
+      if (value && value.text === 'OK') {
         this._close(widget);
         return true;
       }

+ 80 - 80
test/src/dialog/dialog.spec.ts

@@ -9,7 +9,7 @@ import {
 } from '../../../lib/dialog';
 
 import {
-  triggerMouseEvent, triggerKeyEvent, acceptDialog
+  triggerMouseEvent, triggerKeyEvent, acceptDialog, dismissDialog
 } from '../utils';
 
 
@@ -29,85 +29,85 @@ describe('jupyter-ui', () => {
       });
     });
 
-  //   it('should accept dialog options', (done) => {
-  //     let node = document.createElement('div');
-  //     document.body.appendChild(node);
-  //     let options = {
-  //       title: 'foo',
-  //       body: 'Hello',
-  //       host: node,
-  //       buttons: [okButton],
-  //       okText: 'Yep'
-  //     }
-  //     showDialog(options).then(result => {
-  //       expect(result).to.be(null);
-  //       done();
-  //     });
-  //     Promise.resolve().then(() => {
-  //       let target = document.body.getElementsByClassName('jp-Dialog')[0];
-  //       triggerKeyEvent(target as HTMLElement, 'keydown', { keyCode: 27 });
-  //     });
-  //   });
-
-  //   it('should accept an html body', (done) => {
-  //     let body = document.createElement('div');
-  //     let input = document.createElement('input');
-  //     let select = document.createElement('select');
-  //     body.appendChild(input);
-  //     body.appendChild(select);
-  //     showDialog({ body, okText: 'CONFIRM' }).then(result => {
-  //       expect(result.text).to.be('CONFIRM');
-  //       done();
-  //     });
-  //     acceptDialog();
-  //   });
-
-  //   it('should accept an input body', (done) => {
-  //     let body = document.createElement('input');
-  //     showDialog({ body }).then(result => {
-  //       expect(result.text).to.be('OK');
-  //       done();
-  //     });
-  //     acceptDialog();
-  //   });
-
-  //   it('should accept a select body', (done) => {
-  //     let body = document.createElement('select');
-  //     showDialog({ body }).then(result => {
-  //       expect(result.text).to.be('OK');
-  //       done();
-  //     });
-  //     acceptDialog();
-  //   });
-
-  //   it('should resolve with the clicked button result', (done) => {
-  //     let button = {
-  //       text: 'foo',
-  //       className: 'bar',
-  //       icon: 'baz'
-  //     }
-  //     showDialog({ buttons: [button] }).then(result => {
-  //       expect(result.text).to.be('foo');
-  //       done();
-  //     });
-  //     Promise.resolve().then(() => {
-  //       let node = document.body.getElementsByClassName('bar')[0];
-  //       (node as HTMLElement).click();
-  //     });
-  //   });
-
-  //   it('should ignore context menu events', (done) => {
-  //     let body = document.createElement('div');
-  //     showDialog({ body }).then(result => {
-  //       expect(result).to.be(null);
-  //       done();
-  //     });
-  //     Promise.resolve().then(() => {
-  //       let node = document.body.getElementsByClassName('jp-Dialog')[0];
-  //       triggerMouseEvent(node as HTMLElement, 'contextmenu');
-  //       triggerKeyEvent(node as HTMLElement, 'keydown', { keyCode: 27 });
-  //     });
-  //   });
+    it('should accept dialog options', (done) => {
+      let node = document.createElement('div');
+      document.body.appendChild(node);
+      let options = {
+        title: 'foo',
+        body: 'Hello',
+        host: node,
+        buttons: [okButton],
+        okText: 'Yep'
+      }
+      showDialog(options).then(result => {
+        expect(result).to.be(null);
+        done();
+      });
+      Promise.resolve().then(() => {
+        let target = document.body.getElementsByClassName('jp-Dialog')[0];
+        triggerKeyEvent(target as HTMLElement, 'keydown', { keyCode: 27 });
+      });
+    });
+
+    it('should accept an html body', (done) => {
+      let body = document.createElement('div');
+      let input = document.createElement('input');
+      let select = document.createElement('select');
+      body.appendChild(input);
+      body.appendChild(select);
+      showDialog({ body, okText: 'CONFIRM' }).then(result => {
+        expect(result.text).to.be('CONFIRM');
+        done();
+      });
+      acceptDialog();
+    });
+
+    it('should accept an input body', (done) => {
+      let body = document.createElement('input');
+      showDialog({ body }).then(result => {
+        expect(result).to.be(null);
+        done();
+      });
+      dismissDialog();
+    });
+
+    it('should accept a select body', (done) => {
+      let body = document.createElement('select');
+      showDialog({ body }).then(result => {
+        expect(result.text).to.be('OK');
+        done();
+      });
+      acceptDialog();
+    });
+
+    it('should resolve with the clicked button result', (done) => {
+      let button = {
+        text: 'foo',
+        className: 'bar',
+        icon: 'baz'
+      }
+      showDialog({ buttons: [button] }).then(result => {
+        expect(result.text).to.be('foo');
+        done();
+      });
+      Promise.resolve().then(() => {
+        let node = document.body.getElementsByClassName('bar')[0];
+        (node as HTMLElement).click();
+      });
+    });
+
+    it('should ignore context menu events', (done) => {
+      let body = document.createElement('div');
+      showDialog({ body }).then(result => {
+        expect(result).to.be(null);
+        done();
+      });
+      Promise.resolve().then(() => {
+        let node = document.body.getElementsByClassName('jp-Dialog')[0];
+        triggerMouseEvent(node as HTMLElement, 'contextmenu');
+        triggerKeyEvent(node as HTMLElement, 'keydown', { keyCode: 27 });
+      });
+    });
 
   });
 

+ 103 - 0
test/src/filehandler/creator.spec.ts

@@ -16,11 +16,19 @@ import {
   MockContentsManager
 } from '../mock';
 
+import {
+  acceptDialog, dismissDialog, waitForDialog
+} from '../utils';
+
 
 class MyFileCreator extends FileCreator {
 
   methods: string[] = [];
 
+  getFileNode(): HTMLInputElement {
+    return this.fileNode;
+  }
+
   doRename(contents: IContentsModel): Promise<IContentsModel> {
     this.methods.push('doRename');
     return super.doRename(contents);
@@ -74,6 +82,99 @@ describe('jupyter-ui', () => {
 
     });
 
+    describe('#createNew', () => {
+
+      it('should delete the temp file if the dialog is dismissed', (done) => {
+        let manager = new MockContentsManager();
+        let creator = new FileCreator(manager);
+        creator.createNew('foo').then(contents => {
+          expect(manager.methods.indexOf('delete')).to.not.be(-1);
+          expect(contents).to.be(void 0);
+          done();
+        });
+        dismissDialog();
+      });
+
+      it('should create the untitled file if dialog is accepted', (done) => {
+        let manager = new MockContentsManager();
+        let creator = new FileCreator(manager);
+        creator.createNew('foo').then(contents => {
+          expect(contents.content).to.be(manager.DEFAULT_TEXT);
+          done();
+        });
+        acceptDialog();
+      });
+
+      it('should create the untitled file if dialog is accepted', (done) => {
+        let manager = new MockContentsManager();
+        let creator = new FileCreator(manager);
+        creator.createNew('foo').then(contents => {
+          expect(contents.content).to.be(manager.DEFAULT_TEXT);
+          done();
+        });
+        acceptDialog();
+      });
+
+      it('should support an accepted rename', (done) => {
+        let manager = new MockContentsManager();
+        let creator = new MyFileCreator(manager);
+        creator.createNew('foo').then(contents => {
+          expect(contents.name).to.be('bar.txt');
+          expect(manager.methods.indexOf('rename')).to.not.be(-1);
+          done();
+        });
+        waitForDialog().then(() => {
+          let node = creator.getFileNode();
+          node.value = 'bar.txt';
+          acceptDialog();
+        });
+      });
+
+    });
+
+    describe('#showErrorMessage', () => {
+
+      it('should pop up a dialog', (done) => {
+        let manager = new MockContentsManager();
+        let creator = new MyFileCreator(manager);
+        creator.showErrorMessage(new Error('text')).then(() => {
+          done();
+        });
+        acceptDialog();
+      });
+
+    });
+
+    describe('#handleExisting()', () => {
+
+      it('should trigger a rename if accepted', (done) => {
+        let manager = new MockContentsManager();
+        let creator = new MyFileCreator(manager);
+        manager.newUntitled('file').then(contents => {
+          return creator.handleExisting('foo', contents);
+        }).then(() => {
+          expect(creator.methods.indexOf('doRename')).to.not.be(-1);
+          done();
+        });
+        acceptDialog().then(() => {
+          acceptDialog();
+        });
+      });
+
+      it('should delete the file if dismissed', (done) => {
+        let manager = new MockContentsManager();
+        let creator = new MyFileCreator(manager);
+        manager.newUntitled('file').then(contents => {
+          return creator.handleExisting('foo', contents);
+        }).then(() => {
+          expect(manager.methods.indexOf('delete')).to.not.be(-1);
+          done();
+        });
+        dismissDialog();
+      });
+
+    });
+
     describe('#createUntitled()', () => {
 
       it('should create a new untitled file on the given path', (done) => {
@@ -82,6 +183,7 @@ describe('jupyter-ui', () => {
         creator.createUntitled('foo/').then(contents => {
           expect(contents.type).to.be('file');
           expect(contents.name.indexOf('.txt')).to.not.be(-1);
+          expect(manager.methods.indexOf('newUntitled')).to.not.be(-1);
           done();
         });
       });
@@ -115,6 +217,7 @@ describe('jupyter-ui', () => {
         let creator = new MyDirectoryCreator(manager);
         creator.createUntitled('foo/').then(contents => {
           expect(contents.type).to.be('directory');
+          expect(manager.methods.indexOf('newUntitled')).to.not.be(-1);
           done();
         });
       });

+ 62 - 3
test/src/filehandler/filehandler.spec.ts

@@ -25,6 +25,10 @@ import {
   MockContentsManager
 } from '../mock';
 
+import {
+  acceptDialog, dismissDialog
+} from '../utils';
+
 
 class FileHandler extends AbstractFileHandler<Widget> {
 
@@ -203,7 +207,8 @@ describe('jupyter-ui', () => {
         let manager = new MockContentsManager();
         let handler = new FileHandler(manager);
         let widget = handler.open('foo.txt');
-        handler.rename('foo.txt', 'bar.txt');
+        let result = handler.rename('foo.txt', 'bar.txt');
+        expect(result).to.be(true);
         expect(handler.findWidget('bar.txt')).to.be(widget);
       });
 
@@ -215,6 +220,22 @@ describe('jupyter-ui', () => {
         expect(widget.title.text).to.be('bar.txt');
       });
 
+      it('shoud be a no-op if the file is not found', () => {
+        let manager = new MockContentsManager();
+        let handler = new FileHandler(manager);
+        let result = handler.rename('foo.txt', 'bar.txt');
+        expect(result).to.be(false);
+      });
+
+      it('should close the widget if the new path is undefined', () => {
+        let manager = new MockContentsManager();
+        let handler = new FileHandler(manager);
+        let widget = handler.open('foo.txt');
+        let result = handler.rename('foo.txt');
+        expect(result).to.be(true);
+        expect(handler.methods.indexOf('beforeClose')).to.not.be(-1);
+      });
+
     });
 
     describe('#save()', () => {
@@ -240,6 +261,15 @@ describe('jupyter-ui', () => {
         });
       });
 
+      it('should be a no-op if the file is not found', (done) => {
+        let manager = new MockContentsManager();
+        let handler = new FileHandler(manager);
+        handler.save('foo.txt').then(contents => {
+          expect(contents).to.be(void 0);
+          done();
+        });
+      });
+
     });
 
     describe('#revert()', () => {
@@ -265,6 +295,15 @@ describe('jupyter-ui', () => {
         });
       });
 
+      it('should be a no-op if the file is not found', (done) => {
+        let manager = new MockContentsManager();
+        let handler = new FileHandler(manager);
+        handler.revert('foo.txt').then(contents => {
+          expect(contents).to.be(void 0);
+          done();
+        });
+      });
+
     });
 
     describe('#close()', () => {
@@ -290,8 +329,28 @@ describe('jupyter-ui', () => {
         });
       });
 
-      it('should prompt the user if the file is dirty', () => {
-        // TODO
+      it('should prompt the user if the file is dirty', (done) => {
+        let manager = new MockContentsManager();
+        let handler = new FileHandler(manager);
+        handler.open('foo.txt');
+        handler.setDirty('foo.txt', true);
+        handler.close('foo.txt').then(result => {
+          expect(result).to.be(true);
+          done();
+        });
+        acceptDialog();
+      });
+
+      it('should not close if the user dismisses the dialog', (done) => {
+        let manager = new MockContentsManager();
+        let handler = new FileHandler(manager);
+        handler.open('foo.txt');
+        handler.setDirty('foo.txt', true);
+        handler.close('foo.txt').then(result => {
+          expect(result).to.be(false);
+          done();
+        });
+        dismissDialog();
       });
 
     });

+ 13 - 0
test/src/mock.ts

@@ -11,9 +11,12 @@ import {
 export
 class MockContentsManager implements IContentsManager {
 
+  methods: string[] = [];
+
   DEFAULT_TEXT = 'the quick brown fox jumped over the lazy dog';
 
   get(path: string, options?: IContentsOpts): Promise<IContentsModel> {
+    this.methods.push('get');
     return Promise.resolve({
       name: path.split('/').pop(),
       path: path,
@@ -23,6 +26,7 @@ class MockContentsManager implements IContentsManager {
   }
 
   newUntitled(path: string, options?: IContentsOpts): Promise<IContentsModel> {
+    this.methods.push('newUntitled');
     options = options || {};
     let ext = options.ext || '';
     let name = options.name || `untitled${ext}`;
@@ -37,10 +41,12 @@ class MockContentsManager implements IContentsManager {
   }
 
   delete(path: string): Promise<void> {
+    this.methods.push('delete');
     return Promise.resolve(void 0);
   }
 
   rename(path: string, newPath: string): Promise<IContentsModel> {
+    this.methods.push('rename');
     return Promise.resolve({
       name: newPath.split('/').pop(),
       path: newPath,
@@ -50,10 +56,12 @@ class MockContentsManager implements IContentsManager {
   }
 
   save(path: string, model: IContentsModel): Promise<IContentsModel> {
+    this.methods.push('save');
     return Promise.resolve(model);
   }
 
   copy(path: string, toDir: string): Promise<IContentsModel> {
+    this.methods.push('copy');
     let name = path.split('/').pop();
     return Promise.resolve({
       name,
@@ -64,6 +72,7 @@ class MockContentsManager implements IContentsManager {
   }
 
   listContents(path: string): Promise<IContentsModel> {
+    this.methods.push('listContents');
     return Promise.resolve({
       name: path.split('/').pop(),
       path,
@@ -73,18 +82,22 @@ class MockContentsManager implements IContentsManager {
   }
 
   createCheckpoint(path: string): Promise<ICheckpointModel> {
+    this.methods.push('createCheckpoint');
     return Promise.resolve(void 0);
   }
 
   listCheckpoints(path: string): Promise<ICheckpointModel[]> {
+    this.methods.push('listCheckpoints');
     return Promise.resolve(void 0);
   }
 
   restoreCheckpoint(path: string, checkpointID: string): Promise<void> {
+    this.methods.push('restoreCheckpoint');
     return Promise.resolve(void 0);
   }
 
   deleteCheckpoint(path: string, checkpointID: string): Promise<void> {
+    this.methods.push('deleteCheckpoint');
     return Promise.resolve(void 0);
   }
 

+ 29 - 2
test/src/utils.ts

@@ -21,10 +21,37 @@ function triggerKeyEvent(node: HTMLElement, eventType: string, options: any = {}
 }
 
 
+export
+function waitForDialog(host: HTMLElement = document.body): Promise<void> {
+  return new Promise<void>((resolve, reject) => {
+    let refresh = () => {
+      let node = host.getElementsByClassName('jp-Dialog')[0];
+      if (node) {
+        resolve(void 0);
+        return;
+      }
+      setTimeout(refresh, 100);
+    }
+    refresh();
+  });
+}
+
+
 export
 function acceptDialog(host: HTMLElement = document.body): Promise<void> {
-  return Promise.resolve().then(() => {
+  return waitForDialog().then(() => {
     let node = host.getElementsByClassName('jp-Dialog-okButton')[0];
-    (node as HTMLElement).click();
+    if (node) (node as HTMLElement).click();
+  });
+}
+
+
+export
+function dismissDialog(host: HTMLElement = document.body): Promise<void> {
+  return waitForDialog().then(() => {
+    let node = host.getElementsByClassName('jp-Dialog')[0];
+    if (node) {
+      triggerKeyEvent(node as HTMLElement, 'keydown', { keyCode: 27 });
+    }
   });
 }