Commit 201e8dd0 authored by Robert Knight's avatar Robert Knight

Eliminate `vm.action` flag in AnnotationController

Instead of maintaining a state flag indicating whether an annotation is
being viewed, edited or created, derive that state from whether the
annotation has an ID and whether it has an unsaved draft or not.

In the process this commit simplifies the tests for reverting edits and
adds a missing test that the annotation is deleted if new when clicking
the Cancel button.
parent 34f6950f
...@@ -162,9 +162,6 @@ function AnnotationController( ...@@ -162,9 +162,6 @@ function AnnotationController(
* can call the methods. * can call the methods.
*/ */
function init() { function init() {
/** The currently active action - 'view', 'create' or 'edit'. */
vm.action = 'view';
/** vm.form is the read-write part of vm for the templates: it contains /** vm.form is the read-write part of vm for the templates: it contains
* the variables that the templates will write changes to via ng-model. */ * the variables that the templates will write changes to via ng-model. */
vm.form = {}; vm.form = {};
...@@ -324,14 +321,6 @@ function AnnotationController( ...@@ -324,14 +321,6 @@ function AnnotationController(
} }
} }
/** Switches the view to a viewer, closing the editor controls if they're
* open.
* @name annotation.AnnotationController#view
*/
function view() {
vm.action = 'view';
}
/** /**
* @ngdoc method * @ngdoc method
* @name annotation.AnnotationController#authorize * @name annotation.AnnotationController#authorize
...@@ -376,8 +365,15 @@ function AnnotationController( ...@@ -376,8 +365,15 @@ function AnnotationController(
* @description Switches the view to an editor. * @description Switches the view to an editor.
*/ */
vm.edit = function() { vm.edit = function() {
if (!drafts.get(vm.annotation)) {
drafts.update(vm.annotation, {
tags: vm.annotation.tags,
text: vm.annotation.text,
isPrivate: permissions.isPrivate(vm.annotation.permissions,
session.state.userid),
});
}
restoreFromDrafts(drafts, vm); restoreFromDrafts(drafts, vm);
vm.action = isNew(vm.annotation) ? 'create' : 'edit';
}; };
/** /**
...@@ -387,11 +383,7 @@ function AnnotationController( ...@@ -387,11 +383,7 @@ function AnnotationController(
* (i.e. the annotation editor form should be open), `false` otherwise. * (i.e. the annotation editor form should be open), `false` otherwise.
*/ */
vm.editing = function() { vm.editing = function() {
if (vm.action === 'create' || vm.action === 'edit') { return drafts.get(vm.annotation) && !vm.isSaving;
return true;
} else {
return false;
}
}; };
/** /**
...@@ -502,11 +494,10 @@ function AnnotationController( ...@@ -502,11 +494,10 @@ function AnnotationController(
*/ */
vm.revert = function() { vm.revert = function() {
drafts.remove(vm.annotation); drafts.remove(vm.annotation);
if (vm.action === 'create') { if (isNew(vm.annotation)) {
$rootScope.$emit(events.ANNOTATION_DELETED, vm.annotation); $rootScope.$emit(events.ANNOTATION_DELETED, vm.annotation);
} else { } else {
updateView(); updateView();
view();
} }
}; };
...@@ -520,8 +511,7 @@ function AnnotationController( ...@@ -520,8 +511,7 @@ function AnnotationController(
flash.info('Please sign in to save your annotations.'); flash.info('Please sign in to save your annotations.');
return Promise.resolve(); return Promise.resolve();
} }
if ((vm.action === 'create' || vm.action === 'edit') && if (!vm.hasContent() && vm.isShared()) {
!vm.hasContent() && vm.isShared()) {
flash.info('Please add text or a tag before publishing.'); flash.info('Please add text or a tag before publishing.');
return Promise.resolve(); return Promise.resolve();
} }
...@@ -546,7 +536,6 @@ function AnnotationController( ...@@ -546,7 +536,6 @@ function AnnotationController(
// optimistically switch back to view mode and display the saving // optimistically switch back to view mode and display the saving
// indicator // indicator
vm.isSaving = true; vm.isSaving = true;
view();
return saved.then(function () { return saved.then(function () {
vm.isSaving = false; vm.isSaving = false;
......
...@@ -380,12 +380,22 @@ describe('annotation', function() { ...@@ -380,12 +380,22 @@ describe('annotation', function() {
assert.notCalled(fakeStore.annotation.create); assert.notCalled(fakeStore.annotation.create);
}); });
it('edits new annotations on initialization', function() { it('creates drafts for new annotations on initialization', function() {
var annotation = fixtures.newAnnotation(); var annotation = fixtures.newAnnotation();
createDirective(annotation);
assert.calledWith(fakeDrafts.update, annotation, {
isPrivate: false,
tags: annotation.tags,
text: annotation.text,
});
});
it('does not create drafts for new highlights on initialization', function() {
var annotation = fixtures.newHighlight();
var controller = createDirective(annotation).controller; var controller = createDirective(annotation).controller;
assert.isTrue(controller.editing()); assert.notOk(controller.editing());
assert.notCalled(fakeDrafts.update);
}); });
it('edits annotations with drafts on initialization', function() { it('edits annotations with drafts on initialization', function() {
...@@ -397,47 +407,25 @@ describe('annotation', function() { ...@@ -397,47 +407,25 @@ describe('annotation', function() {
assert.isTrue(controller.editing()); assert.isTrue(controller.editing());
}); });
it('does not edit new highlights on initialization', function() {
var annotation = fixtures.newHighlight();
var controller = createDirective(annotation).controller;
assert.isFalse(controller.editing());
}); });
it('edits highlights with drafts on initialization', function() { describe('#editing()', function() {
var annotation = fixtures.oldHighlight(); it('returns false if the annotation does not have a draft', function () {
// You can edit a highlight, enter some text or tags, and save it (the
// highlight then becomes an annotation). You can also edit a highlight
// and then change focus to another group and back without saving the
// highlight, in which case the highlight will have draft edits.
// This highlight has draft edits.
fakeDrafts.get.returns({text: '', tags: []});
var controller = createDirective(annotation).controller;
assert.isTrue(controller.editing());
});
});
describe('.editing()', function() {
it('returns true if action is "create"', function() {
var controller = createDirective().controller; var controller = createDirective().controller;
controller.action = 'create'; assert.notOk(controller.editing());
assert.equal(controller.editing(), true);
}); });
it('returns true if action is "edit"', function() { it('returns true if the annotation has a draft', function () {
var controller = createDirective().controller; var controller = createDirective().controller;
controller.action = 'edit'; fakeDrafts.get.returns({tags: [], text: '', isPrivate: false});
assert.equal(controller.editing(), true); assert.isTrue(controller.editing());
}); });
it('returns false if action is "view"', function() { it('returns false if the annotation has a draft but is being saved', function () {
var controller = createDirective().controller; var controller = createDirective().controller;
controller.action = 'view'; fakeDrafts.get.returns({tags: [], text: '', isPrivate: false});
assert.equal(controller.editing(), false); controller.isSaving = true;
assert.isFalse(controller.editing());
}); });
}); });
...@@ -795,21 +783,24 @@ describe('annotation', function() { ...@@ -795,21 +783,24 @@ describe('annotation', function() {
function controllerWithActionCreate() { function controllerWithActionCreate() {
var controller = createDirective(annotation).controller; var controller = createDirective(annotation).controller;
controller.action = 'create';
controller.form.text = 'new annotation'; controller.form.text = 'new annotation';
return controller; return controller;
} }
it( it('removes the draft when saving an annotation succeeds', function () {
'emits annotationCreated when saving an annotation succeeds', var controller = controllerWithActionCreate();
function() { return controller.save().then(function () {
assert.calledWith(fakeDrafts.remove, annotation);
});
});
it('emits annotationCreated when saving an annotation succeeds', function () {
var controller = controllerWithActionCreate(); var controller = controllerWithActionCreate();
sandbox.spy($rootScope, '$emit'); sandbox.spy($rootScope, '$emit');
return controller.save().then(function() { return controller.save().then(function() {
assert.calledWith($rootScope.$emit, events.ANNOTATION_CREATED); assert.calledWith($rootScope.$emit, events.ANNOTATION_CREATED);
}); });
} });
);
it('flashes a generic error if the server can\'t be reached', function() { it('flashes a generic error if the server can\'t be reached', function() {
var controller = controllerWithActionCreate(); var controller = controllerWithActionCreate();
...@@ -849,14 +840,13 @@ describe('annotation', function() { ...@@ -849,14 +840,13 @@ describe('annotation', function() {
})); }));
var saved = controller.save(); var saved = controller.save();
assert.equal(controller.isSaving, true); assert.equal(controller.isSaving, true);
assert.equal(controller.action, 'view');
create(); create();
return saved.then(function () { return saved.then(function () {
assert.equal(controller.isSaving, false); assert.equal(controller.isSaving, false);
}); });
}); });
it('reverts to edit mode if saving fails', function () { it('does not remove the draft if saving fails', function () {
var controller = controllerWithActionCreate(); var controller = controllerWithActionCreate();
var failCreation; var failCreation;
fakeStore.annotation.create = sinon.stub().returns(new Promise(function (resolve, reject) { fakeStore.annotation.create = sinon.stub().returns(new Promise(function (resolve, reject) {
...@@ -867,7 +857,7 @@ describe('annotation', function() { ...@@ -867,7 +857,7 @@ describe('annotation', function() {
failCreation({status: -1}); failCreation({status: -1});
return saved.then(function () { return saved.then(function () {
assert.equal(controller.isSaving, false); assert.equal(controller.isSaving, false);
assert.ok(controller.editing()); assert.notCalled(fakeDrafts.remove);
}); });
}); });
...@@ -882,7 +872,6 @@ describe('annotation', function() { ...@@ -882,7 +872,6 @@ describe('annotation', function() {
text: 'foo', text: 'foo',
}; };
var controller = createDirective(annotation).controller; var controller = createDirective(annotation).controller;
controller.action = 'create';
return controller.save().then(function() { return controller.save().then(function() {
assert.calledWith(fakeStore.annotation.create, sinon.match({}), assert.calledWith(fakeStore.annotation.create, sinon.match({}),
sinon.match({group: 'test-id'})); sinon.match({group: 'test-id'}));
...@@ -900,7 +889,6 @@ describe('annotation', function() { ...@@ -900,7 +889,6 @@ describe('annotation', function() {
function controllerWithActionEdit() { function controllerWithActionEdit() {
var controller = createDirective(annotation).controller; var controller = createDirective(annotation).controller;
controller.action = 'edit';
controller.form.text = 'updated text'; controller.form.text = 'updated text';
return controller; return controller;
} }
...@@ -1106,71 +1094,34 @@ describe('annotation', function() { ...@@ -1106,71 +1094,34 @@ describe('annotation', function() {
describe('reverting edits', function () { describe('reverting edits', function () {
// Simulate what happens when the user edits an annotation,
// clicks Save, gets an error because the server fails to save the
// annotation, then clicks Cancel - in the frontend the annotation should
// be restored to its original value, the edits lost.
it('restores the original text', function() { it('restores the original text', function() {
var controller = createDirective({ var controller = createDirective({
id: 'test-annotation-id', id: 'test-annotation-id',
user: 'acct:bill@localhost', user: 'acct:bill@localhost',
text: 'Initial annotation body text', text: 'Initial annotation body text',
}).controller; }).controller;
fakeStore.annotation.update = function () {
return Promise.reject({
status: 500,
statusText: 'Server Error',
data: {}
});
};
var originalText = controller.form.text;
// Simulate the user clicking the Edit button on the annotation.
controller.edit(); controller.edit();
// Simulate the user typing some text into the annotation editor textarea.
controller.form.text = 'changed by test code'; controller.form.text = 'changed by test code';
// Simulate the user hitting the Save button and wait for the
// (unsuccessful) response from the server.
controller.save();
// At this point the annotation editor controls are still open, and the
// annotation's text is still the modified (unsaved) text.
assert.equal(controller.form.text, 'changed by test code');
// Simulate the user clicking the Cancel button.
controller.revert(); controller.revert();
assert.equal(controller.form.text, originalText); assert.equal(controller.form.text, controller.annotation.text);
}); });
// Test that editing reverting changes to an annotation with
// no text resets the text to be empty.
it('clears the text if the text was originally empty', function() { it('clears the text if the text was originally empty', function() {
var controller = createDirective({ var controller = createDirective({
id: 'test-annotation-id', id: 'test-annotation-id',
user: 'acct:bill@localhost', user: 'acct:bill@localhost',
}).controller; }).controller;
controller.edit(); controller.edit();
assert.equal(controller.action, 'edit');
controller.form.text = 'this should be reverted'; controller.form.text = 'this should be reverted';
controller.revert(); controller.revert();
assert.equal(controller.form.text, ''); assert.equal(controller.form.text, '');
}); });
it('reverts to the most recently saved version', function () { it('deletes the annotation if it was new', function () {
var controller = createDirective({ var controller = createDirective(fixtures.newAnnotation()).controller;
id: 'new-annot', sandbox.spy($rootScope, '$emit');
user: 'acct:bill@localhost',
text: 'saved-text',
}).controller;
controller.edit();
controller.form.text = 'New annotation text';
return controller.save().then(function () {
controller.edit();
controller.form.text = 'Updated annotation text';
return controller.save();
}).then(function () {
controller.edit();
controller.revert(); controller.revert();
assert.equal(controller.form.text, controller.annotation.text); assert.calledWith($rootScope.$emit, events.ANNOTATION_DELETED);
});
}); });
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment