From 721ef4ee2b19a676566ba07329c0ed847da74cf1 Mon Sep 17 00:00:00 2001 From: Sumedha Pramod Date: Tue, 1 Aug 2017 15:57:22 -0700 Subject: [PATCH] Chore: Initializing notifications in PreviewUI once rather than in each Annotator (#262) - There is one instance of PreviewUI per Preview instance - Notifications can now be triggered from any viewer with their own custom messages --- src/lib/Preview.js | 63 +++++---- src/lib/PreviewUI.js | 26 ++++ src/lib/__tests__/Preview-test.js | 67 +++++++++- src/lib/__tests__/PreviewUI-test.js | 25 ++++ src/lib/annotations/Annotator.js | 98 ++++++++------ .../annotations/__tests__/Annotator-test.js | 123 ++++++++++++++---- src/lib/viewers/BaseViewer.js | 58 +++++++-- src/lib/viewers/__tests__/BaseViewer-test.js | 78 ++++++++++- 8 files changed, 435 insertions(+), 103 deletions(-) diff --git a/src/lib/Preview.js b/src/lib/Preview.js index cd0d5afc7..9f46d2954 100644 --- a/src/lib/Preview.js +++ b/src/lib/Preview.js @@ -790,30 +790,45 @@ class Preview extends EventEmitter { attachViewerListeners() { // Node requires listener attached to 'error' this.viewer.addListener('error', this.triggerError); - this.viewer.addListener('viewerevent', (data) => { - /* istanbul ignore next */ - switch (data.event) { - case 'download': - this.download(); - break; - case 'reload': - this.show(this.file.id, this.previewOptions); - break; - case 'load': - this.finishLoading(data.data); - break; - case 'progressstart': - this.ui.startProgressBar(); - break; - case 'progressend': - this.ui.finishProgressBar(); - break; - default: - // This includes 'notification', 'preload' and others - this.emit(data.event, data.data); - this.emit('viewerevent', data); - } - }); + this.viewer.addListener('viewerevent', this.handleViewerEvents); + } + + /** + * Handle events emitted by the viewer + * + * @private + * @param {Object} [data] - Viewer event data + * @return {void} + */ + handleViewerEvents(data) { + /* istanbul ignore next */ + switch (data.event) { + case 'download': + this.download(); + break; + case 'reload': + this.show(this.file.id, this.previewOptions); + break; + case 'load': + this.finishLoading(data.data); + break; + case 'progressstart': + this.ui.startProgressBar(); + break; + case 'progressend': + this.ui.finishProgressBar(); + break; + case 'notificationshow': + this.ui.showNotification(data.data); + break; + case 'notificationhide': + this.ui.hideNotification(); + break; + default: + // This includes 'notification', 'preload' and others + this.emit(data.event, data.data); + this.emit('viewerevent', data); + } } /** diff --git a/src/lib/PreviewUI.js b/src/lib/PreviewUI.js index e8c26973d..c8a2168e7 100644 --- a/src/lib/PreviewUI.js +++ b/src/lib/PreviewUI.js @@ -1,5 +1,6 @@ import ProgressBar from './ProgressBar'; import shellTemplate from './shell.html'; +import Notification from './Notification'; import { insertTemplate } from './util'; import { CLASS_HIDDEN, @@ -116,6 +117,9 @@ class PreviewUI { // Setup loading indicator this.setupLoading(); + // Setup notification + this.notification = new Notification(this.contentContainer); + // Attach keyboard events document.addEventListener('keydown', this.keydownHandler); @@ -285,6 +289,28 @@ class PreviewUI { this.progressBar.finish(); } + /** + * Shows a notification message. + * + * @public + * @param {string} message - Notification message + * @param {string} [buttonText] - Optional text to show in button + * @return {void} + */ + showNotification(message, buttonText) { + this.notification.show(message, buttonText); + } + + /** + * Hides the notification message. Does nothing if the notification is already hidden. + * + * @public + * @return {void} + */ + hideNotification() { + this.notification.hide(); + } + //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- diff --git a/src/lib/__tests__/Preview-test.js b/src/lib/__tests__/Preview-test.js index dee226b62..a036e3369 100644 --- a/src/lib/__tests__/Preview-test.js +++ b/src/lib/__tests__/Preview-test.js @@ -1236,17 +1236,74 @@ describe('lib/Preview', () => { }); describe('attachViewerListeners()', () => { - beforeEach(() => { + it('should add listeners for error and viewer events', () => { stubs.download = sandbox.stub(preview, 'download'); preview.viewer = { addListener: sandbox.stub() }; - }); - it('should add listeners for error and viewer events', () => { preview.attachViewerListeners(); - expect(preview.viewer.addListener).to.be.calledWith('error'); - expect(preview.viewer.addListener).to.be.calledWith('viewerevent'); + expect(preview.viewer.addListener).to.be.calledWith('error', sinon.match.func); + expect(preview.viewer.addListener).to.be.calledWith('viewerevent', sinon.match.func); + }); + }); + + describe('handleViewerEvents()', () => { + it('should call download on download event', () => { + sandbox.stub(preview, 'download'); + preview.handleViewerEvents({ event: 'download' }); + expect(preview.download).to.be.called; + }); + + it('should reload preview on reload event', () => { + sandbox.stub(preview, 'show'); + preview.handleViewerEvents({ event: 'reload' }); + expect(preview.show).to.be.called; + }); + + it('should finish loading preview on load event', () => { + sandbox.stub(preview, 'finishLoading'); + preview.handleViewerEvents({ event: 'load' }); + expect(preview.finishLoading).to.be.called; + }); + + it('should start progress bar on progressstart event', () => { + sandbox.stub(preview.ui, 'startProgressBar'); + preview.handleViewerEvents({ event: 'progressstart' }); + expect(preview.ui.startProgressBar).to.be.called; + }); + + it('should finish progress bar on progressend event', () => { + sandbox.stub(preview.ui, 'finishProgressBar'); + preview.handleViewerEvents({ event: 'progressend' }); + expect(preview.ui.finishProgressBar).to.be.called; + }); + + it('should show notification with message on notificationshow event', () => { + const message = 'notification_message'; + sandbox.stub(preview.ui, 'showNotification'); + preview.handleViewerEvents({ + event: 'notificationshow', + data: message + }); + expect(preview.ui.showNotification).to.be.calledWith(message); + }); + + it('should hide notification on notificationhide event', () => { + sandbox.stub(preview.ui, 'hideNotification'); + preview.handleViewerEvents({ event: 'notificationhide' }); + expect(preview.ui.hideNotification).to.be.called; + }); + + it('should emit viewerevent when event does not match', () => { + sandbox.stub(preview, 'emit'); + const data = { + event: 'no match', + data: 'message' + }; + preview.handleViewerEvents(data); + expect(preview.emit).to.be.calledWith(data.event, data.data); + expect(preview.emit).to.be.calledWith('viewerevent', data); }); }); diff --git a/src/lib/__tests__/PreviewUI-test.js b/src/lib/__tests__/PreviewUI-test.js index 9a6779e05..eb71a2486 100644 --- a/src/lib/__tests__/PreviewUI-test.js +++ b/src/lib/__tests__/PreviewUI-test.js @@ -57,6 +57,9 @@ describe('lib/PreviewUI', () => { // Check progress bar expect(resultEl).to.contain(constants.SELECTOR_BOX_PREVIEW_PROGRESS_BAR); + // Check notification + expect(resultEl).to.contain('.bp-notification'); + // Check loading state const loadingWrapperEl = resultEl.querySelector(constants.SELECTOR_BOX_PREVIEW_LOADING_WRAPPER); expect(loadingWrapperEl).to.contain(constants.SELECTOR_BOX_PREVIEW_ICON); @@ -230,4 +233,26 @@ describe('lib/PreviewUI', () => { expect(ui.progressBar.finish).to.be.called; }); }); + + describe('showNotification()', () => { + it('should show a notification message', () => { + ui.notification = { + show: sandbox.stub() + }; + + ui.showNotification('message'); + expect(ui.notification.show).to.be.called; + }); + }); + + describe('hideNotification()', () => { + it('should hide the notification message', () => { + ui.notification = { + hide: sandbox.stub() + }; + + ui.hideNotification('message'); + expect(ui.notification.hide).to.be.called; + }); + }); }); diff --git a/src/lib/annotations/Annotator.js b/src/lib/annotations/Annotator.js index e8e8973d0..7418b63cb 100644 --- a/src/lib/annotations/Annotator.js +++ b/src/lib/annotations/Annotator.js @@ -1,6 +1,5 @@ import EventEmitter from 'events'; import autobind from 'autobind-decorator'; -import Notification from '../Notification'; import AnnotationService from './AnnotationService'; import * as annotatorUtil from './annotatorUtil'; import { @@ -57,7 +56,7 @@ class Annotator extends EventEmitter { this.options = data.options; this.fileVersionId = data.fileVersionId; this.locale = data.locale; - this.validationErrorDisplayed = false; + this.validationErrorEmitted = false; this.isMobile = data.isMobile; this.previewUI = data.previewUI; this.annotationModeHandlers = []; @@ -91,7 +90,6 @@ class Annotator extends EventEmitter { */ init(initialScale = 1) { this.annotatedElement = this.getAnnotatedEl(this.container); - this.notification = new Notification(this.annotatedElement); const { apiHost, fileId, token } = this.options; @@ -261,8 +259,6 @@ class Annotator extends EventEmitter { // If in annotation mode, turn it off if (this.isInPointMode()) { - this.notification.hide(); - this.emit('annotationmodeexit'); this.annotatedElement.classList.remove(CLASS_ANNOTATION_POINT_MODE); if (buttonEl) { @@ -274,8 +270,7 @@ class Annotator extends EventEmitter { // Otherwise, enable annotation mode } else { - this.notification.show(__('notification_annotation_point_mode')); - this.emit('annotationmodeenter'); + this.emit('annotationmodeenter', TYPES.point); this.annotatedElement.classList.add(CLASS_ANNOTATION_POINT_MODE); if (buttonEl) { buttonEl.classList.add(CLASS_ACTIVE); @@ -304,7 +299,6 @@ class Annotator extends EventEmitter { // Exit if in draw mode if (this.isInDrawMode()) { - this.notification.hide(); this.emit('annotationmodeexit'); this.annotatedElement.classList.remove(CLASS_ANNOTATION_DRAW_MODE); @@ -320,8 +314,7 @@ class Annotator extends EventEmitter { // Otherwise enter draw mode } else { - this.notification.show(__('notification_annotation_draw_mode')); - this.emit('annotationmodeenter'); + this.emit('annotationmodeenter', TYPES.draw); this.annotatedElement.classList.add(CLASS_ANNOTATION_DRAW_MODE); if (buttonEl) { @@ -453,30 +446,41 @@ class Annotator extends EventEmitter { } /* istanbul ignore next */ - service.addListener('annotationerror', (data) => { - let errorMessage = ''; - switch (data.reason) { - case 'read': - errorMessage = __('annotations_load_error'); - break; - case 'create': - errorMessage = __('annotations_create_error'); - this.showAnnotations(); - break; - case 'delete': - errorMessage = __('annotations_delete_error'); - this.showAnnotations(); - break; - case 'authorization': - errorMessage = __('annotations_authorization_error'); - break; - default: - } + service.addListener('annotatorerror', this.handleServiceEvents); + } - if (errorMessage) { - this.notification.show(errorMessage); - } - }); + /** + * Handle events emitted by the annotaiton service + * + * @private + * @param {Object} [data] - Annotation service event data + * @param {string} [data.event] - Annotation service event + * @param {string} [data.data] - + * @return {void} + */ + handleServiceEvents(data) { + let errorMessage = ''; + switch (data.reason) { + case 'read': + errorMessage = __('annotations_load_error'); + break; + case 'create': + errorMessage = __('annotations_create_error'); + this.showAnnotations(); + break; + case 'delete': + errorMessage = __('annotations_delete_error'); + this.showAnnotations(); + break; + case 'authorization': + errorMessage = __('annotations_authorization_error'); + break; + default: + } + + if (errorMessage) { + this.emit('annotatorerror', errorMessage); + } } /** @@ -490,7 +494,7 @@ class Annotator extends EventEmitter { if (!service || !(service instanceof AnnotationService)) { return; } - service.removeAllListeners('annotationerror'); + service.removeAllListeners('annotatorerror'); } /** @@ -721,12 +725,32 @@ class Annotator extends EventEmitter { * @return {void} */ handleValidationError() { - if (this.validationErrorDisplayed) { + if (this.validationErrorEmitted) { return; } - this.notification.show(__('annotations_load_error')); - this.validationErrorDisplayed = true; + this.emit('annotatorerror', __('annotations_load_error')); + this.validationErrorEmitted = true; + } + + /** + * Emits a generic viewer event + * + * @private + * @emits viewerevent + * @param {string} event - Event name + * @param {Object} data - Event data + * @return {void} + */ + emit(event, data) { + const { annotator, fileId } = this.options; + super.emit(event, data); + super.emit('annotatorevent', { + event, + data, + annotatorName: annotator ? annotator.NAME : '', + fileId + }); } } diff --git a/src/lib/annotations/__tests__/Annotator-test.js b/src/lib/annotations/__tests__/Annotator-test.js index 4d6ece2af..4fa3bea00 100644 --- a/src/lib/annotations/__tests__/Annotator-test.js +++ b/src/lib/annotations/__tests__/Annotator-test.js @@ -1,9 +1,11 @@ /* eslint-disable no-unused-expressions */ +import EventEmitter from 'events'; import Annotator from '../Annotator'; import * as annotatorUtil from '../annotatorUtil'; import AnnotationService from '../AnnotationService'; import { STATES, + TYPES, CLASS_ANNOTATION_POINT_MODE, CLASS_ANNOTATION_DRAW_MODE } from '../annotationConstants'; @@ -20,13 +22,18 @@ describe('lib/annotations/Annotator', () => { beforeEach(() => { fixture.load('annotations/__tests__/Annotator-test.html'); + const options = { + annotator: { + NAME: 'name' + } + }; annotator = new Annotator({ canAnnotate: true, container: document, annotationService: {}, fileVersionId: 1, isMobile: false, - options: {}, + options, previewUI: { getAnnotateButton: () => {} } @@ -61,7 +68,6 @@ describe('lib/annotations/Annotator', () => { type: 'type' }; stubs.threadMock3 = sandbox.mock(stubs.thread3); - sandbox.stub(annotator, 'emit'); }); afterEach(() => { @@ -258,12 +264,11 @@ describe('lib/annotations/Annotator', () => { describe('togglePointAnnotationHandler()', () => { beforeEach(() => { stubs.pointMode = sandbox.stub(annotator, 'isInPointMode'); - sandbox.stub(annotator.notification, 'show'); - sandbox.stub(annotator.notification, 'hide'); sandbox.stub(annotator, 'unbindDOMListeners'); sandbox.stub(annotator, 'bindDOMListeners'); sandbox.stub(annotator, 'bindPointModeListeners'); sandbox.stub(annotator, 'unbindModeListeners'); + sandbox.stub(annotator, 'emit'); }); it('should turn point annotation mode on if it is off', () => { @@ -274,8 +279,7 @@ describe('lib/annotations/Annotator', () => { const annotatedEl = document.querySelector('.annotated-element'); expect(destroyStub).to.be.called; - expect(annotator.notification.show).to.be.called; - expect(annotator.emit).to.be.calledWith('annotationmodeenter'); + expect(annotator.emit).to.be.calledWith('annotationmodeenter', TYPES.point); expect(annotatedEl).to.have.class(CLASS_ANNOTATION_POINT_MODE); expect(annotator.unbindDOMListeners).to.be.called; expect(annotator.bindPointModeListeners).to.be.called; @@ -289,7 +293,6 @@ describe('lib/annotations/Annotator', () => { const annotatedEl = document.querySelector('.annotated-element'); expect(destroyStub).to.be.called; - expect(annotator.notification.hide).to.be.called; expect(annotator.emit).to.be.calledWith('annotationmodeexit'); expect(annotatedEl).to.not.have.class(CLASS_ANNOTATION_POINT_MODE); expect(annotator.unbindModeListeners).to.be.called; @@ -300,13 +303,12 @@ describe('lib/annotations/Annotator', () => { describe('toggleDrawAnnotationHandler()', () => { beforeEach(() => { stubs.drawMode = sandbox.stub(annotator, 'isInDrawMode'); - sandbox.stub(annotator.notification, 'show'); - sandbox.stub(annotator.notification, 'hide'); sandbox.stub(annotator, 'unbindDOMListeners'); sandbox.stub(annotator, 'bindDOMListeners'); sandbox.stub(annotator, 'bindDrawModeListeners'); sandbox.stub(annotator, 'unbindModeListeners'); sandbox.stub(annotator, 'createAnnotationThread'); + sandbox.stub(annotator, 'emit'); }); it('should turn draw annotation mode on if it is off', () => { @@ -317,8 +319,7 @@ describe('lib/annotations/Annotator', () => { const annotatedEl = document.querySelector('.annotated-element'); expect(destroyStub).to.be.called; - expect(annotator.notification.show).to.be.called; - expect(annotator.emit).to.be.calledWith('annotationmodeenter'); + expect(annotator.emit).to.be.calledWith('annotationmodeenter', TYPES.draw); expect(annotatedEl).to.have.class(CLASS_ANNOTATION_DRAW_MODE); expect(annotator.unbindDOMListeners).to.be.called; expect(annotator.bindDrawModeListeners).to.be.called; @@ -333,7 +334,6 @@ describe('lib/annotations/Annotator', () => { const annotatedEl = document.querySelector('.annotated-element'); expect(destroyStub).to.be.called; - expect(annotator.notification.hide).to.be.called; expect(annotator.emit).to.be.calledWith('annotationmodeexit'); expect(annotatedEl).to.not.have.class(CLASS_ANNOTATION_DRAW_MODE); expect(annotator.unbindModeListeners).to.be.called; @@ -354,6 +354,7 @@ describe('lib/annotations/Annotator', () => { }; stubs.threadPromise = Promise.resolve(threadMap); stubs.serviceMock.expects('getThreadMap').returns(stubs.threadPromise); + sandbox.stub(annotator, 'emit'); }); it('should reset and create a new thread map by fetching annotation data from the server', () => { @@ -400,7 +401,40 @@ describe('lib/annotations/Annotator', () => { const addListenerStub = sandbox.stub(annotator.annotationService, 'addListener'); annotator.bindCustomListenersOnService(); - expect(addListenerStub).to.be.calledWith('annotationerror', sinon.match.func); + expect(addListenerStub).to.be.calledWith('annotatorerror', sinon.match.func); + }); + }); + + describe('handleServiceEvents()', () => { + beforeEach(() => { + sandbox.stub(annotator, 'emit'); + }); + + it('should emit annotatorerror on read error event', () => { + annotator.handleServiceEvents({ reason: 'read' }); + expect(annotator.emit).to.be.calledWith('annotatorerror', sinon.match.string); + }); + + it('should emit annotatorerror and show annotations on create error event', () => { + annotator.handleServiceEvents({ reason: 'create' }); + expect(annotator.emit).to.be.calledWith('annotatorerror', sinon.match.string); + expect(annotator.showAnnotations).to.be.called; + }); + + it('should emit annotatorerror and show annotations on delete error event', () => { + annotator.handleServiceEvents({ reason: 'delete' }); + expect(annotator.emit).to.be.calledWith('annotatorerror', sinon.match.string); + expect(annotator.showAnnotations).to.be.called; + }); + + it('should emit annotatorerror on authorization error event', () => { + annotator.handleServiceEvents({ reason: 'authorization' }); + expect(annotator.emit).to.be.calledWith('annotatorerror', sinon.match.string); + }); + + it('should not emit annotatorerror when event does not match', () => { + annotator.handleServiceEvents({ reason: 'no match' }); + expect(annotator.emit).to.not.be.called; }); }); @@ -732,20 +766,63 @@ describe('lib/annotations/Annotator', () => { }); describe('handleValidationError()', () => { - it('should do nothing if a validation notification was already displayed', () => { - annotator.validationErrorDisplayed = true; - stubs.showNotification = sandbox.stub(annotator.notification, 'show'); + it('should do nothing if a annotatorerror was already emitted', () => { + sandbox.stub(annotator, 'emit'); + annotator.validationErrorEmitted = true; annotator.handleValidationError(); - expect(stubs.showNotification).to.not.be.called; - expect(annotator.validationErrorDisplayed).to.be.true; + expect(annotator.emit).to.not.be.calledWith('annotatorerror'); + expect(annotator.validationErrorEmitted).to.be.true; }); - it('should display validation error notification on first error', () => { - annotator.validationErrorDisplayed = false; - stubs.showNotification = sandbox.stub(annotator.notification, 'show'); + it('should emit annotatorerror on first error', () => { + sandbox.stub(annotator, 'emit'); + annotator.validationErrorEmitted = false; annotator.handleValidationError(); - expect(stubs.showNotification).to.be.called; - expect(annotator.validationErrorDisplayed).to.be.true; + expect(annotator.emit).to.be.calledWith('annotatorerror', sinon.match.string); + expect(annotator.validationErrorEmitted).to.be.true; + }); + }); + + describe('emit()', () => { + const emitFunc = EventEmitter.prototype.emit; + + afterEach(() => { + Object.defineProperty(EventEmitter.prototype, 'emit', { value: emitFunc }); + }); + + it('should pass through the event as well as broadcast it as a annotator event', () => { + const fileId = '1'; + const event = 'someEvent'; + const data = {}; + const annotatorName = 'name'; + + annotator = new Annotator({ + canAnnotate: true, + container: document, + annotationService: {}, + fileVersionId: 1, + isMobile: false, + options: { + annotator: { NAME: annotatorName }, + fileId + }, + previewUI: { + getAnnotateButton: () => {} + } + }); + + const emitStub = sandbox.stub(); + Object.defineProperty(EventEmitter.prototype, 'emit', { value: emitStub }); + + annotator.emit(event, data); + + expect(emitStub).to.be.calledWith(event, data); + expect(emitStub).to.be.calledWithMatch('annotatorevent', { + event, + data, + annotatorName, + fileId + }); }); }); }); diff --git a/src/lib/viewers/BaseViewer.js b/src/lib/viewers/BaseViewer.js index b29692946..43bc46f6d 100644 --- a/src/lib/viewers/BaseViewer.js +++ b/src/lib/viewers/BaseViewer.js @@ -32,6 +32,8 @@ import { ICON_FILE_DEFAULT } from '../icons/icons'; const ANNOTATIONS_JS = ['annotations.js']; const ANNOTATIONS_CSS = ['annotations.css']; +const ANNOTATION_TYPE_DRAW = 'draw'; +const ANNOTATION_TYPE_POINT = 'point'; const LOAD_TIMEOUT_MS = 180000; // 3m const RESIZE_WAIT_TIME_IN_MILLIS = 300; @@ -656,6 +658,7 @@ class BaseViewer extends EventEmitter { canAnnotate: this.canAnnotate, container, options: { + annotator: this.annotatorConf, apiHost, fileId, token @@ -668,11 +671,7 @@ class BaseViewer extends EventEmitter { this.annotator.init(this.scale); - // Disables controls during point annotation mode - this.annotator.addListener('annotationmodeenter', this.disableViewerControls); - - this.annotator.addListener('annotationmodeexit', this.enableViewerControls); - + // Disables controls during annotation mode this.addListener('togglepointannotationmode', () => { this.annotator.togglePointAnnotationHandler(); }); @@ -684,12 +683,8 @@ class BaseViewer extends EventEmitter { // Add a custom listener for events related to scaling/orientation changes this.addListener('scale', this.scaleAnnotations.bind(this)); - this.annotator.addListener('annotationsfetched', () => { - this.scaleAnnotations({ - scale: this.scale, - rotationAngle: this.rotationAngle - }); - }); + // Add a custom listener for events emmited by the annotator + this.annotator.addListener('annotatorevent', this.handleAnnotatorNotifications); } /** @@ -816,6 +811,47 @@ class BaseViewer extends EventEmitter { } } /* eslint-enable no-unused-vars */ + + /** + * Handle events emitted by the annotator + * + * @private + * @param {Object} [data] - Annotator event data + * @param {string} [data.event] - Annotator event + * @param {string} [data.data] - + * @return {void} + */ + handleAnnotatorNotifications(data) { + /* istanbul ignore next */ + switch (data.event) { + case 'annotationmodeenter': + this.disableViewerControls(); + + if (data.data === ANNOTATION_TYPE_POINT) { + this.emit('notificationshow', __('notification_annotation_point_mode')); + } else if (data.data === ANNOTATION_TYPE_DRAW) { + this.emit('notificationshow', __('notification_annotation_draw_mode')); + } + break; + case 'annotationmodeexit': + this.enableViewerControls(); + this.emit('notificationhide'); + break; + case 'annotationerror': + this.emit('notificationshow', data.data); + break; + case 'annotationsfetched': + this.scaleAnnotations({ + scale: this.scale, + rotationAngle: this.rotationAngle + }); + break; + default: + this.emit(data.event, data.data); + this.emit('annotatorevent', data); + break; + } + } } export default BaseViewer; diff --git a/src/lib/viewers/__tests__/BaseViewer-test.js b/src/lib/viewers/__tests__/BaseViewer-test.js index a3f132fa9..9fdb2537e 100644 --- a/src/lib/viewers/__tests__/BaseViewer-test.js +++ b/src/lib/viewers/__tests__/BaseViewer-test.js @@ -283,6 +283,7 @@ describe('lib/viewers/BaseViewer', () => { sandbox.stub(fullscreen, 'addListener'); sandbox.stub(document.defaultView, 'addEventListener'); sandbox.stub(base, 'loadAnnotator'); + base.containerEl = containerEl; base.annotationsPromise = { then: (arg) => { expect(base.scale).to.equal(1.5); @@ -302,6 +303,7 @@ describe('lib/viewers/BaseViewer', () => { sandbox.stub(fullscreen, 'addListener'); sandbox.stub(document.defaultView, 'addEventListener'); sandbox.stub(base, 'loadAnnotator'); + base.containerEl = containerEl; base.annotationsPromise = { then: (arg) => { expect(arg).to.equal(base.loadAnnotator); @@ -819,13 +821,13 @@ describe('lib/viewers/BaseViewer', () => { }; base.initAnnotations(); }); + it('should initialize the annotator', () => { expect(base.annotator.init).to.be.calledWith(1.5); - expect(base.annotator.addListener).to.be.calledWith('annotationmodeenter', sinon.match.func); - expect(base.annotator.addListener).to.be.calledWith('annotationmodeexit', sinon.match.func); - expect(base.annotator.addListener).to.be.calledWith('annotationsfetched', sinon.match.func); expect(base.addListener).to.be.calledWith('togglepointannotationmode', sinon.match.func); + expect(base.addListener).to.be.calledWith('toggledrawannotationmode', sinon.match.func); expect(base.addListener).to.be.calledWith('scale', sinon.match.func); + expect(base.annotator.addListener).to.be.calledWith('annotatorevent', sinon.match.func); }); }); @@ -936,4 +938,74 @@ describe('lib/viewers/BaseViewer', () => { expect(base.emit).to.have.been.calledWith('togglepointannotationmode'); }); }); + + describe('handleAnnotatorNotifications()', () => { + const ANNOTATION_TYPE_DRAW = 'draw'; + const ANNOTATION_TYPE_POINT = 'point'; + + beforeEach(() => { + sandbox.stub(base, 'emit'); + }); + + it('should disable controls and show point mode notification on annotationmodeenter', () => { + sandbox.stub(base, 'disableViewerControls'); + base.handleAnnotatorNotifications({ + event: 'annotationmodeenter', + data: ANNOTATION_TYPE_POINT + }); + expect(base.disableViewerControls).to.be.called; + expect(base.emit).to.be.calledWith('notificationshow', sinon.match.string); + }); + + it('should disable controls and show draw mode notification on annotationmodeenter', () => { + sandbox.stub(base, 'disableViewerControls'); + base.handleAnnotatorNotifications({ + event: 'annotationmodeenter', + data: ANNOTATION_TYPE_DRAW + }); + expect(base.disableViewerControls).to.be.called; + expect(base.emit).to.be.calledWith('notificationshow', sinon.match.string); + }); + + it('should enable controls and hide notification on annotationmodeexit', () => { + sandbox.stub(base, 'enableViewerControls'); + base.handleAnnotatorNotifications({ + event: 'annotationmodeexit' + }); + expect(base.enableViewerControls).to.be.called; + expect(base.emit).to.be.calledWith('notificationhide'); + }); + + it('should show a notification on annotationerror', () => { + const data = { + event: 'annotationerror', + data: 'message' + }; + base.handleAnnotatorNotifications(data); + expect(base.emit).to.be.calledWith('notificationshow', data.data); + }); + + it('should scale annotations on annotationsfetched', () => { + sandbox.stub(base, 'scaleAnnotations'); + base.scale = 1; + base.rotationAngle = 90; + base.handleAnnotatorNotifications({ + event: 'annotationsfetched' + }); + expect(base.scaleAnnotations).to.be.calledWith({ + scale: base.scale, + rotationAngle: base.rotationAngle + }); + }); + + it('should emit annotatorevent when event does not match', () => { + const data = { + event: 'no match', + data: 'message' + }; + base.handleAnnotatorNotifications(data); + expect(base.emit).to.be.calledWith(data.event, data.data); + expect(base.emit).to.be.calledWith('annotatorevent', data); + }); + }); });