Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: Don't save empty drawing threads #138

Merged
merged 7 commits into from
Mar 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion src/__tests__/util-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ import {
clearCanvas,
replaceHeader,
isInDialog,
focusTextArea
focusTextArea,
hasValidBoundaryCoordinates
} from '../util';
import {
STATES,
Expand Down Expand Up @@ -807,4 +808,26 @@ describe('util', () => {
expect(focusTextArea(el)).to.have.class(CLASS_ACTIVE);
});
});

describe('hasValidBoundaryCoordinates()', () => {
it('return true only if boundary coordinates are valid', () => {
expect(hasValidBoundaryCoordinates({})).to.be.false;
expect(hasValidBoundaryCoordinates({
minX: NaN,
minY: 1,
maxX: 1,
maxY: 1
})).to.be.false;
expect(hasValidBoundaryCoordinates({
minX: 1,
minY: 1,
maxX: 1,
maxY: 1
})).to.be.true;
});

it('should return true for highlight annotations', () => {
expect(hasValidBoundaryCoordinates({ type: TYPES.highlight })).to.be.true;
})
});
});
4 changes: 2 additions & 2 deletions src/controllers/AnnotationModeController.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import rbush from 'rbush';
import EventEmitter from 'events';
import { insertTemplate, isPending, replaceHeader } from '../util';
import { insertTemplate, isPending, replaceHeader, hasValidBoundaryCoordinates } from '../util';
import {
CLASS_HIDDEN,
CLASS_ACTIVE,
Expand Down Expand Up @@ -207,7 +207,7 @@ class AnnotationModeController extends EventEmitter {
* @return {void}
*/
registerThread(thread) {
if (!thread || !thread.location) {
if (!thread || !thread.location || !hasValidBoundaryCoordinates(thread)) {
return;
}

Expand Down
16 changes: 13 additions & 3 deletions src/controllers/DrawingModeController.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import AnnotationModeController from './AnnotationModeController';
import shell from './drawingShell.html';
import DocDrawingThread from '../doc/DocDrawingThread';
import { replaceHeader, enableElement, disableElement, eventToLocationHandler, clearCanvas } from '../util';
import {
replaceHeader,
enableElement,
disableElement,
eventToLocationHandler,
clearCanvas,
hasValidBoundaryCoordinates
} from '../util';
import {
TYPES,
STATES,
Expand Down Expand Up @@ -134,7 +141,10 @@ class DrawingModeController extends AnnotationModeController {
});

this.pushElementHandler(this.postButtonEl, 'click', () => {
this.saveThread(this.currentThread);
if (this.currentThread && this.currentThread.state === STATES.pending) {
this.saveThread(this.currentThread);
}

this.toggleMode();
});

Expand Down Expand Up @@ -306,7 +316,7 @@ class DrawingModeController extends AnnotationModeController {
* @return {void}
*/
saveThread(thread) {
if (!thread) {
if (!thread || !hasValidBoundaryCoordinates(thread)) {
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,12 @@ describe('controllers/AnnotationModeController', () => {
expect(controller.emit).to.not.be.called;
});

it('should do nothing if thread has invalid boundary', () => {
controller.registerThread({ minX: NaN, minY: 1, maxX: 1, maxY: 1 });
controller.registerThread({ type: 'someType' });
expect(controller.emit).to.not.be.called;
});

it('should create a new rbush for the thread\'s page location', () => {
stubs.threadMock.expects('addListener').withArgs('threadevent', sinon.match.func);

Expand Down Expand Up @@ -550,7 +556,7 @@ describe('controllers/AnnotationModeController', () => {
removeAllListeners: () => {}
};
stubs.pendingMock = sandbox.mock(pendingThread);
controller.registerThread(pendingThread);
controller.threads[1].insert(pendingThread);

stubs.threadMock.expects('destroy').never();
stubs.pendingMock.expects('destroy');
Expand Down
76 changes: 41 additions & 35 deletions src/controllers/__tests__/DrawingModeController-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import AnnotationModeController from '../AnnotationModeController';
import DrawingModeController from '../DrawingModeController';
import * as util from '../../util';
import {
TYPES,
THREAD_EVENT,
SELECTOR_ANNOTATION_BUTTON_DRAW_CANCEL,
SELECTOR_ANNOTATION_BUTTON_DRAW_POST,
Expand Down Expand Up @@ -30,14 +31,17 @@ describe('controllers/DrawingModeController', () => {
page: 1
},
info: 'I am a thread',
addListener: sandbox.stub(),
removeListener: sandbox.stub(),
saveAnnotation: sandbox.stub(),
handleStart: sandbox.stub(),
destroy: sandbox.stub(),
deleteThread: sandbox.stub(),
show: sandbox.stub()
addListener: () => {},
removeListener: () => {},
saveAnnotation: () => {},
handleStart: () => {},
destroy: () => {},
deleteThread: () => {},
clearBoundary: () => {},
drawBoundary: () => {},
show: () => {}
};
stubs.threadMock = sandbox.mock(stubs.thread);

sandbox.stub(controller, 'emit');
});
Expand Down Expand Up @@ -209,12 +213,12 @@ describe('controllers/DrawingModeController', () => {
});

it('should save thread on softcommit', () => {
stubs.threadMock.expects('handleStart').never();
controller.handleThreadEvents(stubs.thread, {
event: 'softcommit'
});
expect(controller.unbindListeners).to.be.called;
expect(controller.bindListeners).to.be.called;
expect(stubs.thread.handleStart).to.not.be.called;
expect(controller.saveThread).to.be.called;
});

Expand Down Expand Up @@ -271,10 +275,10 @@ describe('controllers/DrawingModeController', () => {

it('should soft delete a pending thread and restart mode listeners', () => {
stubs.thread.state = 'pending';
stubs.threadMock.expects('destroy');
controller.handleThreadEvents(stubs.thread, {
event: 'dialogdelete'
});
expect(stubs.thread.destroy).to.be.called;
expect(controller.unbindListeners).to.be.called;
expect(controller.bindListeners).to.be.called;
});
Expand All @@ -285,10 +289,10 @@ describe('controllers/DrawingModeController', () => {
controller.registerThread(stubs.thread);
const unregisterThreadStub = sandbox.stub(controller, 'unregisterThread');

stubs.threadMock.expects('deleteThread');
controller.handleThreadEvents(stubs.thread, {
event: 'dialogdelete'
});
expect(stubs.thread.deleteThread).to.be.called;
expect(unregisterThreadStub).to.be.called;
});

Expand Down Expand Up @@ -340,18 +344,9 @@ describe('controllers/DrawingModeController', () => {
});

describe('renderPage()', () => {
const thread = {
threadID: '123abc',
location: { page: 1 },
show: () => {},
addListener: () => {}
};

beforeEach(() => {
controller.annotatedElement = document.createElement('div');
controller.annotatedElement.setAttribute('data-page-number', 1);

stubs.threadMock = sandbox.mock(thread);
sandbox.stub(util, 'clearCanvas');
});

Expand All @@ -360,14 +355,14 @@ describe('controllers/DrawingModeController', () => {
controller.renderPage(1);

controller.threads = {};
controller.registerThread(thread);
controller.registerThread(stubs.thread);
controller.renderPage(2);
expect(util.clearCanvas).to.be.calledTwice;
});

it('should render the annotations on every page', () => {
controller.threads = {};
controller.registerThread(thread);
controller.registerThread(stubs.thread);
stubs.threadMock.expects('show').once();
controller.renderPage(1);
expect(util.clearCanvas).to.be.called;
Expand All @@ -376,27 +371,19 @@ describe('controllers/DrawingModeController', () => {

describe('removeSelection()', () => {
it('should clean a selected thread boundary', () => {
const thread = {
clearBoundary: sandbox.stub()
};
controller.selectedThread = thread;

controller.selectedThread = stubs.thread;
stubs.threadMock.expects('clearBoundary');
controller.removeSelection();
expect(thread.clearBoundary).to.be.called;
expect(controller.selectedThread).to.be.undefined;
});
});

describe('select()', () => {
it('should draw the boundary', () => {
const thread = {
drawBoundary: sandbox.stub()
}

expect(controller.selectedThread).to.not.deep.equal(thread);
controller.select(thread);
expect(thread.drawBoundary).to.be.called;
expect(controller.selectedThread).to.deep.equal(thread);
stubs.threadMock.expects('drawBoundary');
expect(controller.selectedThread).to.not.deep.equal(stubs.thread);
controller.select(stubs.thread);
expect(controller.selectedThread).to.deep.equal(stubs.thread);
});
});

Expand Down Expand Up @@ -428,4 +415,23 @@ describe('controllers/DrawingModeController', () => {
expect(stubs.disable).to.not.be.called;
});
});

describe('saveThread()', () => {
beforeEach(() => {
sandbox.stub(controller, 'registerThread');
});

it('should do nothing if thread has invalid boundary', () => {
stubs.threadMock.expects('saveAnnotation').withArgs(TYPES.draw).never();
controller.saveThread({ minX: NaN, minY: 1, maxX: 1, maxY: 1 });
controller.saveThread({ type: TYPES.draw });
expect(controller.registerThread).to.not.be.called;
});

it('should save and register the annotation thread', () => {
stubs.threadMock.expects('saveAnnotation').withArgs(TYPES.draw);
controller.saveThread(stubs.thread);
expect(controller.registerThread).to.be.called;
});
});
});
5 changes: 3 additions & 2 deletions src/controllers/__tests__/HighlightModeController-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
CLASS_ANNOTATION_MODE,
THREAD_EVENT,
STATES,
TYPES,
CONTROLLER_EVENT,
CLASS_ANNOTATION_LAYER_HIGHLIGHT
} from '../../constants';
Expand All @@ -22,6 +23,7 @@ describe('controllers/HighlightModeController', () => {
stubs.thread = {
annotations: {},
location: { page: 1 },
type: TYPES.highlight,
show: () => {},
addListener: () => {}
};
Expand Down Expand Up @@ -115,7 +117,6 @@ describe('controllers/HighlightModeController', () => {
beforeEach(() => {
controller.annotatedElement = document.createElement('div');
controller.annotatedElement.setAttribute('data-page-number', 1);

sandbox.stub(util, 'clearCanvas');
});

Expand All @@ -127,7 +128,7 @@ describe('controllers/HighlightModeController', () => {

it('should render the annotations on the specified page', () => {
controller.registerThread(stubs.thread);
stubs.threadMock.expects('show').once();
stubs.threadMock.expects('show');
controller.renderPage(1);
expect(util.clearCanvas).to.be.called;
});
Expand Down
1 change: 1 addition & 0 deletions src/doc/DocDrawingThread.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ class DocDrawingThread extends DrawingThread {

// Start drawing rendering
this.lastAnimationRequestId = window.requestAnimationFrame(this.render);
this.state = STATES.pending;
}

/**
Expand Down
5 changes: 5 additions & 0 deletions src/doc/__tests__/DocDrawingThread-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import AnnotationThread from '../../AnnotationThread';
import DrawingPath from '../../drawing/DrawingPath';
import {
DRAW_STATES,
THREAD_EVENT,
STATES
} from '../../constants';

Expand Down Expand Up @@ -101,18 +102,21 @@ describe('doc/DocDrawingThread', () => {
it('should do nothing if no location is provided', () => {
thread.handleStart();
expect(stubs.pageChange).to.not.be.called;
expect(thread.state).to.equal(STATES.inactive);
});

it('should set the drawingFlag, pendingPath, and context if they do not exist', () => {
stubs.pageChange.returns(false);
thread.drawingFlag = DRAW_STATES.idle;
thread.pendingPath = undefined;
expect(thread.state).to.equal(STATES.inactive);
thread.handleStart(thread.location);

expect(window.requestAnimationFrame).to.be.called;
expect(thread.drawingFlag).to.equal(DRAW_STATES.drawing);
expect(thread.hasPageChanged).to.be.called;
expect(thread.pendingPath).to.be.an.instanceof(DrawingPath);
expect(thread.state).to.equal(STATES.pending);
});

it('should commit the thread when the page changes', () => {
Expand All @@ -125,6 +129,7 @@ describe('doc/DocDrawingThread', () => {
expect(thread.hasPageChanged).to.be.called;
expect(thread.onPageChange).to.be.called;
expect(thread.checkAndHandleScaleUpdate).to.not.be.called;
expect(thread.state).to.equal(STATES.inactive);
});

});
Expand Down
3 changes: 3 additions & 0 deletions src/drawing/DrawingThread.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ class DrawingThread extends AnnotationThread {
constructor(data) {
super(data);

// Default drawing thread state to inactive until user begins drawing
this.state = STATES.inactive;

this.render = this.render.bind(this);
this.handleStart = this.handleStart.bind(this);
this.handleMove = this.handleMove.bind(this);
Expand Down
1 change: 1 addition & 0 deletions src/drawing/__tests__/DrawingThread-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ describe('drawing/DrawingThread', () => {
threadID: 2,
type: 'draw'
});
expect(thread.state).to.equal(STATES.inactive);
});

afterEach(() => {
Expand Down
10 changes: 10 additions & 0 deletions src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,16 @@ export function isPending(threadState) {
return PENDING_STATES.indexOf(threadState) > -1;
}

/**
* Checks whether an annotation thread has valid min/max boundary coordinates
*
* @param {AnnotationThread} thread Annotation thread location object
* @return {boolean} Whether or not the annotation has valid boundary coordinates
*/
export function hasValidBoundaryCoordinates(thread) {
return !!(isHighlightAnnotation(thread.type) || (thread.minX && thread.minY && thread.maxX && thread.maxY));
}

/**
* Checks whether a point annotation thread has the correct location params
*
Expand Down