diff --git a/build/webpack.config.js b/build/webpack.config.js index 8fef3595d..f577ad3e4 100644 --- a/build/webpack.config.js +++ b/build/webpack.config.js @@ -54,7 +54,8 @@ function updateConfig(conf, language, index) { const config = Object.assign(conf, { entry: { preview: [`${lib}/Preview.js`], - csv: [`${lib}/viewers/text/BoxCSV.js`] + csv: [`${lib}/viewers/text/BoxCSV.js`], + annotations: [`${lib}/annotations/BoxAnnotations.js`] }, output: { path: path.resolve('dist', version, language), diff --git a/src/lib/Preview.js b/src/lib/Preview.js index 840f4bc0e..491e151e8 100644 --- a/src/lib/Preview.js +++ b/src/lib/Preview.js @@ -36,7 +36,6 @@ import { hideLoadingIndicator, showDownloadButton, showLoadingDownloadButton, - showAnnotateButton, showPrintButton, showNavigation } from './ui'; @@ -46,7 +45,6 @@ import { CLASS_NAVIGATION_VISIBILITY, FILE_EXT_ERROR_MAP, PERMISSION_DOWNLOAD, - PERMISSION_ANNOTATE, PERMISSION_PREVIEW, PREVIEW_SCRIPT_NAME, X_REP_HINT_BASE, @@ -924,10 +922,6 @@ class Preview extends EventEmitter { } } - if (checkPermission(this.file, PERMISSION_ANNOTATE) && !Browser.isMobile() && checkFeature(this.viewer, 'isAnnotatable', 'point')) { - showAnnotateButton(this.viewer.getPointModeClickHandler()); - } - const { error } = data; if (error) { // Bump up preview count diff --git a/src/lib/__tests__/Preview-test.js b/src/lib/__tests__/Preview-test.js index e299097e0..e0907c79a 100644 --- a/src/lib/__tests__/Preview-test.js +++ b/src/lib/__tests__/Preview-test.js @@ -1285,7 +1285,6 @@ describe('lib/Preview', () => { stubs.canDownload = sandbox.stub(Browser, 'canDownload'); stubs.showDownloadButton = sandbox.stub(ui, 'showDownloadButton'); stubs.showPrintButton = sandbox.stub(ui, 'showPrintButton'); - stubs.showAnnotateButton = sandbox.stub(ui, 'showAnnotateButton'); stubs.hideLoadingIndicator = sandbox.stub(ui, 'hideLoadingIndicator'); stubs.emit = sandbox.stub(preview, 'emit'); stubs.logPreviewEvent = sandbox.stub(preview, 'logPreviewEvent'); @@ -1369,43 +1368,6 @@ describe('lib/Preview', () => { expect(stubs.showPrintButton).to.be.called; }); - it('should show the annotation button if you have annotation permissions', () => { - stubs.checkPermission.onCall(1).returns(false).onCall(2).returns(true); - - - preview.finishLoading(); - expect(stubs.showAnnotateButton).to.not.be.called; - - stubs.checkPermission.onCall(3).returns(true); - - preview.finishLoading(); - expect(stubs.showAnnotateButton).to.be.called; - }); - - it('should show the annotation button if you are not on a mobile browser', () => { - stubs.isMobile.returns(true); - - preview.finishLoading(); - expect(stubs.showAnnotateButton).to.not.be.called; - - stubs.isMobile.returns(false); - - preview.finishLoading(); - expect(stubs.showAnnotateButton).to.be.called; - }); - - it('should show the annotation button if the viewer has annotation functionality', () => { - stubs.checkFeature.returns(false); - - preview.finishLoading(); - expect(stubs.showAnnotateButton).to.not.be.called; - - stubs.checkFeature.returns(true); - - preview.finishLoading(); - expect(stubs.showAnnotateButton).to.be.called; - }); - it('should increment the preview count', () => { preview.count.success = 0; diff --git a/src/lib/__tests__/ui-test.js b/src/lib/__tests__/ui-test.js index 040ec58e3..3cbf5a88a 100644 --- a/src/lib/__tests__/ui-test.js +++ b/src/lib/__tests__/ui-test.js @@ -127,19 +127,6 @@ describe('lib/ui', () => { }); }); - describe('showAnnotateButton()', () => { - it('should set up and show annotate button', () => { - const buttonEl = containerEl.querySelector(constants.SELECTOR_BOX_PREVIEW_BTN_ANNOTATE); - buttonEl.classList.add(constants.CLASS_HIDDEN); - sandbox.mock(buttonEl).expects('addEventListener').withArgs('click', handler); - - ui.showAnnotateButton(handler); - - expect(buttonEl.title).to.equal('Point annotation mode'); - expect(buttonEl.classList.contains(constants.CLASS_HIDDEN)).to.be.false; - }); - }); - describe('showPrintButton()', () => { it('should set up and show print button', () => { const buttonEl = containerEl.querySelector(constants.SELECTOR_BOX_PREVIEW_BTN_PRINT); diff --git a/src/lib/annotations/Annotator.js b/src/lib/annotations/Annotator.js index 7ce70672a..de82d5bf6 100644 --- a/src/lib/annotations/Annotator.js +++ b/src/lib/annotations/Annotator.js @@ -32,13 +32,13 @@ class Annotator extends EventEmitter { */ constructor(data) { super(); - this.annotatedElement = data.annotatedElement; - this.annotationService = data.annotationService; + + this.canAnnotate = data.canAnnotate; + this.container = data.container; + this.options = data.options; this.fileVersionID = data.fileVersionID; this.locale = data.locale; this.validationErrorDisplayed = false; - - this.notification = new Notification(this.annotatedElement); } /** @@ -65,8 +65,29 @@ class Annotator extends EventEmitter { * @return {void} */ init() { + this.annotatedElement = this.getAnnotatedEl(this.container); + this.annotationService = this.initAnnotationService(this.options); + this.notification = new Notification(this.annotatedElement); + this.setScale(1); this.setupAnnotations(); + this.showAnnotations(); + } + + /** + * Initializes the Annotation Service with appropriate options + * + * @param {Object} options - Options passed from the viewer to the annotator + * @return {AnnotationService} AnnotationService instance + */ + initAnnotationService(options) { + const { apiHost, fileId, token } = options; + return new AnnotationService({ + apiHost, + fileId, + token, + canAnnotate: this.canAnnotate + }); } /** @@ -219,6 +240,16 @@ class Annotator extends EventEmitter { createAnnotationThread(annotations, location, type) {} /* eslint-enable no-unused-vars */ + /** + * Must be implemented to determine the annotated element in the viewer. + * + * @param {HTMLElement} containerEl - Container element for the viewer + * @return {HTMLElement} Annotated element in the viewer + */ + /* eslint-disable no-unused-vars */ + getAnnotatedEl(containerEl) {} + /* eslint-enable no-unused-vars */ + //-------------------------------------------------------------------------- // Protected //-------------------------------------------------------------------------- diff --git a/src/lib/annotations/BoxAnnotations.js b/src/lib/annotations/BoxAnnotations.js new file mode 100644 index 000000000..f0ed8e7a7 --- /dev/null +++ b/src/lib/annotations/BoxAnnotations.js @@ -0,0 +1,54 @@ +import DocAnnotator from './doc/DocAnnotator'; +import ImageAnnotator from './image/ImageAnnotator'; + +const ANNOTATORS = [ + { + NAME: 'Document', + CONSTRUCTOR: DocAnnotator, + VIEWER: ['Document', 'Presentation'], + TYPE: ['point', 'highlight'] + }, + { + NAME: 'Image', + CONSTRUCTOR: ImageAnnotator, + VIEWER: ['Image'], + TYPE: ['point'] + } +]; + +class BoxAnnotations { + + /** + * [constructor] + * + * @return {BoxAnnotations} BoxAnnotations instance + */ + constructor() { + this.annotators = ANNOTATORS; + } + + /** + * Returns the available annotators + * + * @return {Array} List of supported annotators + */ + getAnnotators() { + return Array.isArray(this.annotators) ? this.annotators : []; + } + + /** + * Chooses a annotator based on file extension. + * + * @param {Object} file - Box file + * @param {Array} [disabledAnnotators] - List of disabled annotators + * @return {Object} The annotator to use + */ + determineAnnotator(viewer, disabledAnnotators = []) { + return this.annotators.find((annotator) => + !(disabledAnnotators.includes(annotator.NAME)) && annotator.VIEWER.includes(viewer) + ); + } +} + +global.BoxAnnotations = BoxAnnotations; +export default BoxAnnotations; diff --git a/src/lib/annotations/__tests__/Annotator-test.js b/src/lib/annotations/__tests__/Annotator-test.js index 3c68d4e43..51f7bc5bf 100644 --- a/src/lib/annotations/__tests__/Annotator-test.js +++ b/src/lib/annotations/__tests__/Annotator-test.js @@ -16,9 +16,11 @@ describe('lib/annotations/Annotator', () => { fixture.load('annotations/__tests__/Annotator-test.html'); annotator = new Annotator({ - annotatedElement: document.querySelector('.annotated-element'), + canAnnotate: true, + container: document, annotationService: {}, - fileVersionID: 1 + fileVersionID: 1, + options: {} }); stubs.thread = { @@ -63,12 +65,6 @@ describe('lib/annotations/Annotator', () => { stubs = {}; }); - describe('constructor()', () => { - it('should initialize a notification', () => { - expect(annotator.notification).to.not.be.null; - }); - }); - describe('destroy()', () => { it('should unbind custom listeners on thread and unbind DOM listeners', () => { annotator.threads = { @@ -89,13 +85,31 @@ describe('lib/annotations/Annotator', () => { describe('init()', () => { it('should set scale and setup annotations', () => { + const annotatedEl = document.querySelector('.annotated-element'); + sandbox.stub(annotator, 'getAnnotatedEl').returns(annotatedEl); + sandbox.stub(annotator, 'initAnnotationService').returns({}); const scaleStub = sandbox.stub(annotator, 'setScale'); const setupAnnotations = sandbox.stub(annotator, 'setupAnnotations'); + const showAnnotations = sandbox.stub(annotator, 'showAnnotations'); annotator.init(); expect(scaleStub).to.be.called; expect(setupAnnotations).to.be.called; + expect(showAnnotations).to.be.called; + }); + }); + + describe('initAnnotationService()', () => { + it('should initialize the anontation service with the appropriate options', () => { + const options = { + apiHost: 'url', + fileId: 123, + token: 'token' + }; + annotator.canAnnotate = true; + annotator.initAnnotationService(options); + expect(annotator.annotationService).to.not.be.null; }); }); @@ -114,414 +128,412 @@ describe('lib/annotations/Annotator', () => { }); }); - describe('hideAnnotations()', () => { - it('should call hide on each thread in map', () => { - annotator.threads = { - 1: [stubs.thread], - 2: [stubs.thread2, stubs.thread3] - }; - - stubs.threadMock.expects('hide'); - stubs.threadMock2.expects('hide'); - stubs.threadMock3.expects('hide'); - annotator.hideAnnotations(); - }); - }); + describe('setupAnnotations', () => { + it('should initialize thread map and bind DOM listeners', () => { + sandbox.stub(annotator, 'bindDOMListeners'); + sandbox.stub(annotator, 'bindCustomListenersOnService'); + sandbox.stub(annotator, 'addListener'); - describe('hideAnnotationsOnPage()', () => { - it('should call hide on each thread in map on page 1', () => { - annotator.threads = { - 1: [stubs.thread], - 2: [stubs.thread2, stubs.thread3] - }; + annotator.setupAnnotations(); - stubs.threadMock.expects('hide'); - stubs.threadMock2.expects('hide').never(); - stubs.threadMock3.expects('hide').never(); - annotator.hideAnnotationsOnPage('1'); + expect(Object.keys(annotator.threads).length === 0).to.be.true; + expect(annotator.bindDOMListeners).to.be.called; + expect(annotator.bindCustomListenersOnService).to.be.called; }); }); - describe('renderAnnotations()', () => { - it('should call show on each thread', () => { - annotator.threads = { - 1: [stubs.thread], - 2: [stubs.thread2, stubs.thread3] - }; - - stubs.threadMock.expects('show'); - stubs.threadMock2.expects('show'); - stubs.threadMock3.expects('show'); - annotator.renderAnnotations(); - }); - }); + describe('once annotator is initialized', () => { + beforeEach(() => { + const annotatedEl = document.querySelector('.annotated-element'); + sandbox.stub(annotator, 'getAnnotatedEl').returns(annotatedEl); + sandbox.stub(annotator, 'initAnnotationService').returns({}); + sandbox.stub(annotator, 'setupAnnotations'); + sandbox.stub(annotator, 'showAnnotations'); - describe('renderAnnotationsOnPage()', () => { - it('should call show on each thread', () => { - stubs.thread2.location = { page: 2 }; - stubs.thread3.location = { page: 2 }; annotator.threads = { 1: [stubs.thread], 2: [stubs.thread2, stubs.thread3] }; - stubs.threadMock.expects('show'); - stubs.threadMock2.expects('show').never(); - stubs.threadMock3.expects('show').never(); - annotator.renderAnnotationsOnPage('1'); + annotator.init(); }); - }); - describe('setScale()', () => { - it('should set a data-scale attribute on the annotated element', () => { - annotator.setScale(10); - const annotatedEl = document.querySelector('.annotated-element'); - expect(annotatedEl).to.have.attribute('data-scale', '10'); + describe('hideAnnotations()', () => { + it('should call hide on each thread in map', () => { + stubs.threadMock.expects('hide'); + stubs.threadMock2.expects('hide'); + stubs.threadMock3.expects('hide'); + annotator.hideAnnotations(); + }); }); - }); - describe('togglePointModeHandler()', () => { - 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, 'unbindPointModeListeners'); + describe('hideAnnotationsOnPage()', () => { + it('should call hide on each thread in map on page 1', () => { + stubs.threadMock.expects('hide'); + stubs.threadMock2.expects('hide').never(); + stubs.threadMock3.expects('hide').never(); + annotator.hideAnnotationsOnPage('1'); + }); }); - it('should turn annotation mode on if it is off', () => { - const destroyStub = sandbox.stub(annotator, 'destroyPendingThreads'); - stubs.pointMode.returns(false); + describe('renderAnnotations()', () => { + it('should call show on each thread', () => { + stubs.threadMock.expects('show'); + stubs.threadMock2.expects('show'); + stubs.threadMock3.expects('show'); + annotator.renderAnnotations(); + }); + }); - annotator.togglePointModeHandler(); + describe('renderAnnotationsOnPage()', () => { + it('should call show on each thread', () => { + stubs.thread2.location = { page: 2 }; + stubs.thread3.location = { page: 2 }; - const annotatedEl = document.querySelector('.annotated-element'); - expect(destroyStub).to.be.called; - expect(annotator.notification.show).to.be.called; - expect(annotator.emit).to.be.calledWith('pointmodeenter'); - expect(annotatedEl).to.have.class(constants.CLASS_ANNOTATION_POINT_MODE); - expect(annotator.unbindDOMListeners).to.be.called; - expect(annotator.bindPointModeListeners).to.be.called; + stubs.threadMock.expects('show'); + stubs.threadMock2.expects('show').never(); + stubs.threadMock3.expects('show').never(); + annotator.renderAnnotationsOnPage('1'); + }); }); - it('should turn annotation mode off if it is on', () => { - const destroyStub = sandbox.stub(annotator, 'destroyPendingThreads'); - stubs.pointMode.returns(true); + describe('setScale()', () => { + it('should set a data-scale attribute on the annotated element', () => { + annotator.setScale(10); + const annotatedEl = document.querySelector('.annotated-element'); + expect(annotatedEl).to.have.attribute('data-scale', '10'); + }); + }); - annotator.togglePointModeHandler(); + describe('togglePointModeHandler()', () => { + 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, 'unbindPointModeListeners'); + }); - const annotatedEl = document.querySelector('.annotated-element'); - expect(destroyStub).to.be.called; - expect(annotator.notification.hide).to.be.called; - expect(annotator.emit).to.be.calledWith('pointmodeexit'); - expect(annotatedEl).to.not.have.class(constants.CLASS_ANNOTATION_POINT_MODE); - expect(annotator.unbindPointModeListeners).to.be.called; - expect(annotator.bindDOMListeners).to.be.called; - }); - }); + it('should turn annotation mode on if it is off', () => { + const destroyStub = sandbox.stub(annotator, 'destroyPendingThreads'); + stubs.pointMode.returns(false); - describe('setupAnnotations', () => { - it('should initialize thread map and bind DOM listeners', () => { - sandbox.stub(annotator, 'bindDOMListeners'); - sandbox.stub(annotator, 'bindCustomListenersOnService'); - sandbox.stub(annotator, 'addListener'); + annotator.togglePointModeHandler(); - annotator.setupAnnotations(); + const annotatedEl = document.querySelector('.annotated-element'); + expect(destroyStub).to.be.called; + expect(annotator.notification.show).to.be.called; + expect(annotator.emit).to.be.calledWith('pointmodeenter'); + expect(annotatedEl).to.have.class(constants.CLASS_ANNOTATION_POINT_MODE); + expect(annotator.unbindDOMListeners).to.be.called; + expect(annotator.bindPointModeListeners).to.be.called; + }); - expect(Object.keys(annotator.threads).length === 0).to.be.true; - expect(annotator.bindDOMListeners).to.be.called; - expect(annotator.bindCustomListenersOnService).to.be.called; - }); - }); + it('should turn annotation mode off if it is on', () => { + const destroyStub = sandbox.stub(annotator, 'destroyPendingThreads'); + stubs.pointMode.returns(true); - describe('fetchAnnotations', () => { - beforeEach(() => { - annotator.annotationService = { - getThreadMap: () => {} - }; - stubs.serviceMock = sandbox.mock(annotator.annotationService); + annotator.togglePointModeHandler(); - const threadMap = { - someID: [{}, {}], - someID2: [{}] - }; - stubs.threadPromise = Promise.resolve(threadMap); - stubs.serviceMock.expects('getThreadMap').returns(stubs.threadPromise); + const annotatedEl = document.querySelector('.annotated-element'); + expect(destroyStub).to.be.called; + expect(annotator.notification.hide).to.be.called; + expect(annotator.emit).to.be.calledWith('pointmodeexit'); + expect(annotatedEl).to.not.have.class(constants.CLASS_ANNOTATION_POINT_MODE); + expect(annotator.unbindPointModeListeners).to.be.called; + expect(annotator.bindDOMListeners).to.be.called; + }); }); - it('should reset and create a new thread map by fetching annotation data from the server', () => { - stubs.createThread = sandbox.stub(annotator, 'createAnnotationThread'); - stubs.createThread.onFirstCall(); - stubs.createThread.onSecondCall().returns(stubs.thread); - sandbox.stub(annotator, 'bindCustomListenersOnThread'); + describe('fetchAnnotations', () => { + beforeEach(() => { + annotator.annotationService = { + getThreadMap: () => {} + }; + stubs.serviceMock = sandbox.mock(annotator.annotationService); - const result = annotator.fetchAnnotations(); + const threadMap = { + someID: [{}, {}], + someID2: [{}] + }; + stubs.threadPromise = Promise.resolve(threadMap); + stubs.serviceMock.expects('getThreadMap').returns(stubs.threadPromise); + }); - return stubs.threadPromise.then(() => { - expect(Object.keys(annotator.threads).length === 0).to.be.true; - expect(annotator.createAnnotationThread).to.be.calledTwice; - expect(annotator.bindCustomListenersOnThread).to.be.calledOnce; - expect(result).to.be.an.object; + it('should reset and create a new thread map by fetching annotation data from the server', () => { + stubs.createThread = sandbox.stub(annotator, 'createAnnotationThread'); + stubs.createThread.onFirstCall(); + stubs.createThread.onSecondCall().returns(stubs.thread); + sandbox.stub(annotator, 'bindCustomListenersOnThread'); + + const result = annotator.fetchAnnotations(); + return stubs.threadPromise.then(() => { + expect(Object.keys(annotator.threads).length === 0).to.be.true; + expect(annotator.createAnnotationThread).to.be.calledTwice; + expect(annotator.bindCustomListenersOnThread).to.be.calledOnce; + expect(result).to.be.an.object; + }); }); }); - }); - describe('bindCustomListenersOnService', () => { - it('should do nothing if the service does not exist', () => { - annotator.annotationService = { - addListener: sandbox.stub() - }; + describe('bindCustomListenersOnService', () => { + it('should do nothing if the service does not exist', () => { + annotator.annotationService = { + addListener: sandbox.stub() + }; - annotator.bindCustomListenersOnService(); - expect(annotator.annotationService.addListener).to.not.be.called; - }); - - it('should add an event listener', () => { - annotator.annotationService = new AnnotationService({ - apiHost: 'API', - fileId: 1, - token: 'someToken', - canAnnotate: true, - canDelete: true + annotator.bindCustomListenersOnService(); + expect(annotator.annotationService.addListener).to.not.be.called; }); - const addListenerStub = sandbox.stub(annotator.annotationService, 'addListener'); - annotator.bindCustomListenersOnService(); - expect(addListenerStub).to.be.calledWith('annotationerror', sinon.match.func); + it('should add an event listener', () => { + annotator.annotationService = new AnnotationService({ + apiHost: 'API', + fileId: 1, + token: 'someToken', + canAnnotate: true, + canDelete: true + }); + const addListenerStub = sandbox.stub(annotator.annotationService, 'addListener'); + + annotator.bindCustomListenersOnService(); + expect(addListenerStub).to.be.calledWith('annotationerror', sinon.match.func); + }); }); - }); - describe('unbindCustomListenersOnService', () => { - it('should do nothing if the service does not exist', () => { - annotator.annotationService = { - removeListener: sandbox.stub() - }; - annotator.unbindCustomListenersOnService(); - expect(annotator.annotationService.removeListener).to.not.be.called; - }); + describe('unbindCustomListenersOnService', () => { + it('should do nothing if the service does not exist', () => { + annotator.annotationService = { + removeListener: sandbox.stub() + }; - it('should remove an event listener', () => { - annotator.annotationService = new AnnotationService({ - apiHost: 'API', - fileId: 1, - token: 'someToken', - canAnnotate: true, - canDelete: true + annotator.unbindCustomListenersOnService(); + expect(annotator.annotationService.removeListener).to.not.be.called; }); - const removeListenerStub = sandbox.stub(annotator.annotationService, 'removeAllListeners'); - annotator.unbindCustomListenersOnService(); - expect(removeListenerStub).to.be.called; + it('should remove an event listener', () => { + annotator.annotationService = new AnnotationService({ + apiHost: 'API', + fileId: 1, + token: 'someToken', + canAnnotate: true, + canDelete: true + }); + const removeListenerStub = sandbox.stub(annotator.annotationService, 'removeAllListeners'); + + annotator.unbindCustomListenersOnService(); + expect(removeListenerStub).to.be.called; + }); }); - }); - describe('bindCustomListenersOnThread', () => { - it('should bind custom listeners on the thread', () => { - stubs.threadMock.expects('addListener').withArgs('threaddeleted', sinon.match.func); - stubs.threadMock.expects('addListener').withArgs('threadcleanup', sinon.match.func); - annotator.bindCustomListenersOnThread(stubs.thread); + describe('bindCustomListenersOnThread', () => { + it('should bind custom listeners on the thread', () => { + stubs.threadMock.expects('addListener').withArgs('threaddeleted', sinon.match.func); + stubs.threadMock.expects('addListener').withArgs('threadcleanup', sinon.match.func); + annotator.bindCustomListenersOnThread(stubs.thread); + }); }); - }); - describe('unbindCustomListenersOnThread', () => { - it('should unbind custom listeners from the thread', () => { - stubs.threadMock.expects('removeAllListeners').withArgs('threaddeleted'); - stubs.threadMock.expects('removeAllListeners').withArgs('threadcleanup'); - annotator.unbindCustomListenersOnThread(stubs.thread); + describe('unbindCustomListenersOnThread', () => { + it('should unbind custom listeners from the thread', () => { + stubs.threadMock.expects('removeAllListeners').withArgs('threaddeleted'); + stubs.threadMock.expects('removeAllListeners').withArgs('threadcleanup'); + annotator.unbindCustomListenersOnThread(stubs.thread); + }); }); - }); - describe('bindPointModeListeners', () => { - it('should bind point mode click handler', () => { - sandbox.stub(annotator.annotatedElement, 'addEventListener'); - annotator.bindPointModeListeners(); - expect(annotator.annotatedElement.addEventListener).to.be.calledWith('click', annotator.pointClickHandler); + describe('bindPointModeListeners', () => { + it('should bind point mode click handler', () => { + sandbox.stub(annotator.annotatedElement, 'addEventListener'); + annotator.bindPointModeListeners(); + expect(annotator.annotatedElement.addEventListener).to.be.calledWith('click', annotator.pointClickHandler); + }); }); - }); - describe('unbindPointModeListeners', () => { - it('should unbind point mode click handler', () => { - sandbox.stub(annotator.annotatedElement, 'removeEventListener'); - annotator.unbindPointModeListeners(); - expect(annotator.annotatedElement.removeEventListener).to.be.calledWith('click', annotator.pointClickHandler); + describe('unbindPointModeListeners', () => { + it('should unbind point mode click handler', () => { + sandbox.stub(annotator.annotatedElement, 'removeEventListener'); + annotator.unbindPointModeListeners(); + expect(annotator.annotatedElement.removeEventListener).to.be.calledWith('click', annotator.pointClickHandler); + }); }); - }); - describe('pointClickHandler', () => { - const event = { - stopPropagation: () => {} - }; + describe('pointClickHandler', () => { + const event = { + stopPropagation: () => {} + }; - beforeEach(() => { - stubs.destroy = sandbox.stub(annotator, 'destroyPendingThreads'); - stubs.create = sandbox.stub(annotator, 'createAnnotationThread'); - stubs.getLocation = sandbox.stub(annotator, 'getLocationFromEvent'); - sandbox.stub(annotator, 'bindCustomListenersOnThread'); - sandbox.stub(annotator, 'togglePointModeHandler'); - }); + beforeEach(() => { + stubs.destroy = sandbox.stub(annotator, 'destroyPendingThreads'); + stubs.create = sandbox.stub(annotator, 'createAnnotationThread'); + stubs.getLocation = sandbox.stub(annotator, 'getLocationFromEvent'); + sandbox.stub(annotator, 'bindCustomListenersOnThread'); + sandbox.stub(annotator, 'togglePointModeHandler'); + }); - it('should not do anything if there are pending threads', () => { - stubs.destroy.returns(true); - stubs.create.returns(stubs.thread); + it('should not do anything if there are pending threads', () => { + stubs.destroy.returns(true); + stubs.create.returns(stubs.thread); - stubs.threadMock.expects('show').never(); - annotator.pointClickHandler(event); + stubs.threadMock.expects('show').never(); + annotator.pointClickHandler(event); - expect(annotator.getLocationFromEvent).to.not.be.called; - expect(annotator.bindCustomListenersOnThread).to.not.be.called; - expect(annotator.togglePointModeHandler).to.not.be.called; - }); + expect(annotator.getLocationFromEvent).to.not.be.called; + expect(annotator.bindCustomListenersOnThread).to.not.be.called; + expect(annotator.togglePointModeHandler).to.not.be.called; + }); - it('should not do anything if thread is invalid', () => { - stubs.destroy.returns(false); + it('should not do anything if thread is invalid', () => { + stubs.destroy.returns(false); - stubs.threadMock.expects('show').never(); - annotator.pointClickHandler(event); + stubs.threadMock.expects('show').never(); + annotator.pointClickHandler(event); - expect(annotator.getLocationFromEvent).to.be.called; - expect(annotator.togglePointModeHandler).to.be.called; - expect(annotator.bindCustomListenersOnThread).to.not.be.called; - }); + expect(annotator.getLocationFromEvent).to.be.called; + expect(annotator.togglePointModeHandler).to.be.called; + expect(annotator.bindCustomListenersOnThread).to.not.be.called; + }); - it('should not create a thread if a location object cannot be inferred from the event', () => { - stubs.destroy.returns(false); - stubs.getLocation.returns(null); - stubs.create.returns(stubs.thread); + it('should not create a thread if a location object cannot be inferred from the event', () => { + stubs.destroy.returns(false); + stubs.getLocation.returns(null); + stubs.create.returns(stubs.thread); - stubs.threadMock.expects('show').never(); - annotator.pointClickHandler(event); + stubs.threadMock.expects('show').never(); + annotator.pointClickHandler(event); - expect(annotator.getLocationFromEvent).to.be.called; - expect(annotator.bindCustomListenersOnThread).to.not.be.called; - expect(annotator.togglePointModeHandler).to.be.called; - }); + expect(annotator.getLocationFromEvent).to.be.called; + expect(annotator.bindCustomListenersOnThread).to.not.be.called; + expect(annotator.togglePointModeHandler).to.be.called; + }); - it('should create, show, and bind listeners to a thread', () => { - stubs.destroy.returns(false); - stubs.getLocation.returns({}); - stubs.create.returns(stubs.thread); + it('should create, show, and bind listeners to a thread', () => { + stubs.destroy.returns(false); + stubs.getLocation.returns({}); + stubs.create.returns(stubs.thread); - stubs.threadMock.expects('show'); - annotator.pointClickHandler(event); + stubs.threadMock.expects('show'); + annotator.pointClickHandler(event); - expect(annotator.getLocationFromEvent).to.be.called; - expect(annotator.bindCustomListenersOnThread).to.be.called; - expect(annotator.togglePointModeHandler).to.be.called; + expect(annotator.getLocationFromEvent).to.be.called; + expect(annotator.bindCustomListenersOnThread).to.be.called; + expect(annotator.togglePointModeHandler).to.be.called; + }); }); - }); - describe('addToThreadMap', () => { - it('should add valid threads to the thread map', () => { - stubs.thread.location = { page: 2 }; - stubs.thread2.location = { page: 3 }; - stubs.thread3.location = { page: 2 }; + describe('addToThreadMap', () => { + it('should add valid threads to the thread map', () => { + stubs.thread.location = { page: 2 }; + stubs.thread2.location = { page: 3 }; + stubs.thread3.location = { page: 2 }; - annotator.init(); - annotator.addThreadToMap(stubs.thread); + annotator.threads = {}; + annotator.addThreadToMap(stubs.thread); - expect(annotator.threads).to.deep.equal({ - 2: [stubs.thread] - }); + expect(annotator.threads).to.deep.equal({ + 2: [stubs.thread] + }); - annotator.addThreadToMap(stubs.thread2); - annotator.addThreadToMap(stubs.thread3); + annotator.addThreadToMap(stubs.thread2); + annotator.addThreadToMap(stubs.thread3); - expect(annotator.threads).to.deep.equal({ - 2: [stubs.thread, stubs.thread3], - 3: [stubs.thread2] + expect(annotator.threads).to.deep.equal({ + 2: [stubs.thread, stubs.thread3], + 3: [stubs.thread2] + }); }); }); - }); - describe('isInPointMode', () => { - it('should return whether the annotator is in point mode or not', () => { - annotator.annotatedElement.classList.add(constants.CLASS_ANNOTATION_POINT_MODE); - expect(annotator.isInPointMode()).to.be.true; + describe('isInPointMode', () => { + it('should return whether the annotator is in point mode or not', () => { + annotator.annotatedElement.classList.add(constants.CLASS_ANNOTATION_POINT_MODE); + expect(annotator.isInPointMode()).to.be.true; - annotator.annotatedElement.classList.remove(constants.CLASS_ANNOTATION_POINT_MODE); - expect(annotator.isInPointMode()).to.be.false; - }); - }); - - describe('destroyPendingThreads', () => { - beforeEach(() => { - stubs.thread = { - location: { page: 2 }, - type: 'type', - state: constants.ANNOTATION_STATE_PENDING, - destroy: () => {}, - unbindCustomListenersOnThread: () => {}, - removeAllListeners: () => {} - }; - stubs.threadMock = sandbox.mock(stubs.thread); - }); - - it('should destroy and return true if there are any pending threads', () => { - annotator.init(); - annotator.addThreadToMap(stubs.thread); - stubs.threadMock.expects('destroy'); - const destroyed = annotator.destroyPendingThreads(); - expect(destroyed).to.equal(true); - }); - - it('should not destroy and return false if there are no threads', () => { - annotator.init(); - stubs.threadMock.expects('destroy').never(); - const destroyed = annotator.destroyPendingThreads(); - expect(destroyed).to.equal(false); + annotator.annotatedElement.classList.remove(constants.CLASS_ANNOTATION_POINT_MODE); + expect(annotator.isInPointMode()).to.be.false; + }); }); - it('should not destroy and return false if the threads are not pending', () => { - stubs.thread.state = 'NOT_PENDING'; - annotator.init(); - annotator.addThreadToMap(stubs.thread); - stubs.threadMock.expects('destroy').never(); - const destroyed = annotator.destroyPendingThreads(); - expect(destroyed).to.equal(false); - }); + describe('destroyPendingThreads', () => { + beforeEach(() => { + stubs.thread = { + location: { page: 2 }, + type: 'type', + state: constants.ANNOTATION_STATE_PENDING, + destroy: () => {}, + unbindCustomListenersOnThread: () => {}, + removeAllListeners: () => {} + }; + stubs.threadMock = sandbox.mock(stubs.thread); + }); - it('should destroy only pending threads, and return true', () => { - stubs.thread.state = 'NOT_PENDING'; - const pendingThread = { - location: { page: 2 }, - type: 'type', - state: constants.ANNOTATION_STATE_PENDING, - destroy: () => {}, - unbindCustomListenersOnThread: () => {}, - removeAllListeners: () => {} - }; - stubs.pendingMock = sandbox.mock(pendingThread); + it('should destroy and return true if there are any pending threads', () => { + annotator.init(); + annotator.addThreadToMap(stubs.thread); + stubs.threadMock.expects('destroy'); + const destroyed = annotator.destroyPendingThreads(); + expect(destroyed).to.equal(true); + }); - annotator.init(); - annotator.addThreadToMap(stubs.thread); - annotator.addThreadToMap(pendingThread); + it('should not destroy and return false if there are no threads', () => { + annotator.init(); + stubs.threadMock.expects('destroy').never(); + const destroyed = annotator.destroyPendingThreads(); + expect(destroyed).to.equal(false); + }); - stubs.threadMock.expects('destroy').never(); - stubs.pendingMock.expects('destroy'); - const destroyed = annotator.destroyPendingThreads(); + it('should not destroy and return false if the threads are not pending', () => { + stubs.thread.state = 'NOT_PENDING'; + annotator.init(); + annotator.addThreadToMap(stubs.thread); + stubs.threadMock.expects('destroy').never(); + const destroyed = annotator.destroyPendingThreads(); + expect(destroyed).to.equal(false); + }); - expect(destroyed).to.equal(true); + it('should destroy only pending threads, and return true', () => { + stubs.thread.state = 'NOT_PENDING'; + const pendingThread = { + location: { page: 2 }, + type: 'type', + state: constants.ANNOTATION_STATE_PENDING, + destroy: () => {}, + unbindCustomListenersOnThread: () => {}, + removeAllListeners: () => {} + }; + stubs.pendingMock = sandbox.mock(pendingThread); + + annotator.init(); + annotator.addThreadToMap(stubs.thread); + annotator.addThreadToMap(pendingThread); + + stubs.threadMock.expects('destroy').never(); + stubs.pendingMock.expects('destroy'); + const destroyed = annotator.destroyPendingThreads(); + + expect(destroyed).to.equal(true); + }); }); - }); - describe('handleValidationError()', () => { - it('should do nothing if a validation notification was already displayed', () => { - annotator.validationErrorDisplayed = true; - stubs.showNotification = sandbox.stub(annotator.notification, 'show'); - annotator.handleValidationError(); - expect(stubs.showNotification).to.not.be.called; - expect(annotator.validationErrorDisplayed).to.be.true; - }); + describe('handleValidationError()', () => { + it('should do nothing if a validation notification was already displayed', () => { + annotator.validationErrorDisplayed = true; + stubs.showNotification = sandbox.stub(annotator.notification, 'show'); + annotator.handleValidationError(); + expect(stubs.showNotification).to.not.be.called; + expect(annotator.validationErrorDisplayed).to.be.true; + }); - it('should display validation error notification on first error', () => { - annotator.validationErrorDisplayed = false; - stubs.showNotification = sandbox.stub(annotator.notification, 'show'); - annotator.handleValidationError(); - expect(stubs.showNotification).to.be.called; - expect(annotator.validationErrorDisplayed).to.be.true; + it('should display validation error notification on first error', () => { + annotator.validationErrorDisplayed = false; + stubs.showNotification = sandbox.stub(annotator.notification, 'show'); + annotator.handleValidationError(); + expect(stubs.showNotification).to.be.called; + expect(annotator.validationErrorDisplayed).to.be.true; + }); }); }); }); diff --git a/src/lib/annotations/__tests__/BoxAnnotations-test.js b/src/lib/annotations/__tests__/BoxAnnotations-test.js new file mode 100644 index 000000000..ace2f55c2 --- /dev/null +++ b/src/lib/annotations/__tests__/BoxAnnotations-test.js @@ -0,0 +1,50 @@ +/* eslint-disable no-unused-expressions */ +import BoxAnnotations from '../BoxAnnotations'; + +let loader; +const sandbox = sinon.sandbox.create(); + +describe('lib/annotators/BoxAnnotations', () => { + beforeEach(() => { + loader = new BoxAnnotations(); + }); + + afterEach(() => { + sandbox.verifyAndRestore(); + + if (typeof loader.destroy === 'function') { + loader.destroy(); + } + + loader = null; + }); + + describe('getAnnotators()', () => { + it('should return the loader\'s annotators', () => { + expect(loader.getAnnotators()).to.deep.equal(loader.annotators); + }); + + it('should return an empty array if the loader doesn\'t have annotators', () => { + loader.annotators = []; + expect(loader.getAnnotators()).to.deep.equal([]); + }); + }); + + describe('determineAnnotator()', () => { + it('should choose the first annotator that matches the viewer', () => { + const viewer = 'Document'; + const annotator = loader.determineAnnotator(viewer); + expect(annotator.NAME).to.equal(viewer); + }); + + it('should not choose a disabled annotator', () => { + const annotator = loader.determineAnnotator('Image', ['Image']); + expect(annotator).to.be.undefined; + }); + + it('should not return a annotator if no matching annotator is found', () => { + const annotator = loader.determineAnnotator('Swf'); + expect(annotator).to.be.undefined; + }); + }); +}); diff --git a/src/lib/annotations/doc/DocAnnotator.js b/src/lib/annotations/doc/DocAnnotator.js index 4d4ca623d..436dd88c6 100644 --- a/src/lib/annotations/doc/DocAnnotator.js +++ b/src/lib/annotations/doc/DocAnnotator.js @@ -27,6 +27,16 @@ class DocAnnotator extends Annotator { // Abstract Implementations //-------------------------------------------------------------------------- + /** + * Determines the annotated element in the viewer + * + * @param {HTMLElement} containerEl - Container element for the viewer + * @return {HTMLElement} Annotated element in the viewer + */ + getAnnotatedEl(containerEl) { + return containerEl.querySelector('.bp-doc'); + } + /** * Returns an annotation location on a document from the DOM event or null * if no correct annotation location can be inferred from the event. For diff --git a/src/lib/annotations/doc/__tests__/DocAnnotator-test.html b/src/lib/annotations/doc/__tests__/DocAnnotator-test.html index 5ed98fbe4..9e0fcdc12 100644 --- a/src/lib/annotations/doc/__tests__/DocAnnotator-test.html +++ b/src/lib/annotations/doc/__tests__/DocAnnotator-test.html @@ -1 +1 @@ -
+ diff --git a/src/lib/annotations/doc/__tests__/DocAnnotator-test.js b/src/lib/annotations/doc/__tests__/DocAnnotator-test.js index 9f90e8ff3..53c9d5dc8 100644 --- a/src/lib/annotations/doc/__tests__/DocAnnotator-test.js +++ b/src/lib/annotations/doc/__tests__/DocAnnotator-test.js @@ -27,10 +27,14 @@ describe('lib/annotations/doc/DocAnnotator', () => { sandbox.stub(Browser, 'isMobile').returns(false); annotator = new DocAnnotator({ - annotatedElement: document.querySelector('.annotated-element'), + canAnnotate: true, + container: document, annotationService: {}, - fileVersionID: 1 + fileVersionID: 1, + options: {} }); + annotator.annotatedElement = annotator.getAnnotatedEl(document); + annotator.annotationService = {}; stubs.getPage = sandbox.stub(docAnnotatorUtil, 'getPageElAndPageNumber'); }); @@ -44,6 +48,12 @@ describe('lib/annotations/doc/DocAnnotator', () => { stubs = {}; }); + describe('getAnnotatedEl()', () => { + it('should return the annotated element as the document', () => { + expect(annotator.annotatedElement).to.not.be.null; + }); + }); + describe('getLocationFromEvent()', () => { const x = 100; const y = 200; diff --git a/src/lib/annotations/image/ImageAnnotator.js b/src/lib/annotations/image/ImageAnnotator.js index 843de1788..545fca256 100644 --- a/src/lib/annotations/image/ImageAnnotator.js +++ b/src/lib/annotations/image/ImageAnnotator.js @@ -12,6 +12,16 @@ class ImageAnnotator extends Annotator { // Abstract Implementations //-------------------------------------------------------------------------- + /** + * Determines the annotated element in the viewer + * + * @param {HTMLElement} containerEl - Container element for the viewer + * @return {HTMLElement} Annotated element in the viewer + */ + getAnnotatedEl(containerEl) { + return containerEl.querySelector('.bp-image'); + } + /** * Returns an annotation location on an image from the DOM event or null * if no correct annotation location can be inferred from the event. For diff --git a/src/lib/annotations/image/__tests__/ImageAnnotator-test.html b/src/lib/annotations/image/__tests__/ImageAnnotator-test.html index 5c2c3bab2..96e739ba9 100644 --- a/src/lib/annotations/image/__tests__/ImageAnnotator-test.html +++ b/src/lib/annotations/image/__tests__/ImageAnnotator-test.html @@ -1,4 +1,4 @@ -