Commit 0df06b8a authored by Robert Knight's avatar Robert Knight

Simplify creation of a copy of the annotation with editor changes applied

Replace `updateDomainModel()` with a simpler function which returns a
copy of the input annotation with the changes from the editor applied.
parent debca3f9
......@@ -31,23 +31,20 @@ function errorMessage(reason) {
return message;
}
/** Update `annotation` from vm.
*
* Copy any properties from vm that might have been modified by the user into
* `annotation`, overwriting any existing properties in `annotation`.
*
* @param {object} annotation The object to copy properties to
* @param {object} vm The object to copy properties from
*
/**
* Return a copy of `annotation` with changes made in the editor applied.
*/
function updateDomainModel(annotation, vm, permissions) {
annotation.text = vm.state().text;
annotation.tags = vm.state().tags;
if (vm.state().isPrivate) {
annotation.permissions = permissions.private();
} else {
annotation.permissions = permissions.shared(annotation.group);
}
function updateModel(annotation, changes, permissions) {
return Object.assign({}, annotation, {
// Explicitly copy across the non-enumerable local tag for the annotation
$$tag: annotation.$$tag,
// Apply changes from the draft
tags: changes.tags,
text: changes.text,
permissions: changes.isPrivate ?
permissions.private() : permissions.shared(annotation.group),
});
}
/** Update the view model from the domain model changes. */
......@@ -65,11 +62,6 @@ function updateViewModel($scope, vm) {
vm.documentMeta = annotationMetadata.domainAndTitle(vm.annotation);
}
/**
* @ngdoc type
* @name annotation.AnnotationController
*
*/
// @ngInject
function AnnotationController(
$document, $q, $rootScope, $scope, $timeout, $window, annotationUI,
......@@ -423,11 +415,6 @@ function AnnotationController(
}
};
/**
* @ngdoc method
* @name annotation.AnnotationController#save
* @description Saves any edits and returns to the viewer.
*/
vm.save = function() {
if (!vm.annotation.user) {
flash.info('Please sign in to save your annotations.');
......@@ -438,29 +425,22 @@ function AnnotationController(
return Promise.resolve();
}
var updatedModel = angular.copy(vm.annotation);
// Copy across the non-enumerable local tag for the annotation
updatedModel.$$tag = vm.annotation.$$tag;
updateDomainModel(updatedModel, vm, permissions);
var saved = save(updatedModel).then(function (model) {
var isNew = !vm.annotation.id;
drafts.remove(vm.annotation);
if (isNew) {
$rootScope.$emit(events.ANNOTATION_CREATED, vm.annotation);
} else {
$rootScope.$emit(events.ANNOTATION_UPDATED, vm.annotation);
}
updateView();
});
var updatedModel = updateModel(vm.annotation, vm.state(), permissions);
// optimistically switch back to view mode and display the saving
// Optimistically switch back to view mode and display the saving
// indicator
vm.isSaving = true;
return saved.then(function () {
return save(updatedModel).then(function (model) {
Object.assign(updatedModel, model);
vm.isSaving = false;
var event = isNew(vm.annotation) ?
events.ANNOTATION_CREATED : events.ANNOTATION_UPDATED;
drafts.remove(vm.annotation);
$rootScope.$emit(event, updatedModel);
}).catch(function (reason) {
vm.isSaving = false;
vm.edit();
......@@ -590,7 +570,7 @@ module.exports = {
// to be unit tested.
// FIXME: The code should be refactored to enable unit testing without having
// to do this.
updateDomainModel: updateDomainModel,
updateModel: updateModel,
// These are meant to be the public API of this module.
directive: annotation,
......
......@@ -30,8 +30,8 @@ function annotationDirective() {
}
describe('annotation', function() {
describe('updateDomainModel()', function() {
var updateDomainModel = require('../annotation').updateDomainModel;
describe('updateModel()', function() {
var updateModel = require('../annotation').updateModel;
function fakePermissions() {
return {
......@@ -40,79 +40,30 @@ describe('annotation', function() {
};
}
it('copies text from viewModel into domainModel', function() {
var domainModel = {};
var viewModel = {state: sinon.stub.returns({text: 'bar', tags: []})};
updateDomainModel(domainModel, viewModel, fakePermissions());
assert.equal(domainModel.text, viewModel.state().text);
it('copies tags and text into the new model', function() {
var changes = {text: 'bar', tags: ['foo', 'bar']};
var newModel = updateModel(fixtures.defaultAnnotation(), changes,
fakePermissions());
assert.deepEqual(newModel.tags, changes.tags);
assert.equal(newModel.text, changes.text);
});
it('overwrites text in domainModel', function() {
var domainModel = {text: 'foo'};
var viewModel = {state: sinon.stub.returns({text: 'bar', tags: []})};
updateDomainModel(domainModel, viewModel, fakePermissions());
assert.equal(domainModel.text, viewModel.state().text);
});
it('doesn\'t touch other properties in domainModel', function() {
var domainModel = {foo: 'foo', bar: 'bar'};
var viewModel = {state: sinon.stub.returns({foo: 'FOO', tags: []})};
updateDomainModel(domainModel, viewModel, fakePermissions());
assert.equal(
domainModel.bar, 'bar',
'updateDomainModel() should not touch properties of domainModel' +
'that don\'t exist in viewModel');
});
it('copies tag texts from viewModel into domainModel', function() {
var domainModel = {};
var viewModel = {
state: sinon.stub().returns({
tags: ['foo', 'bar'],
})
};
updateDomainModel(domainModel, viewModel, fakePermissions());
assert.deepEqual(domainModel.tags, ['foo', 'bar']);
});
it('sets domainModel.permissions to private if vm.isPrivate', function() {
var domainModel = {};
var viewModel = {
state: sinon.stub().returns({
isPrivate: true,
text: 'foo',
}),
};
it('sets permissions to private if the draft is private', function() {
var changes = {isPrivate: true, text: 'bar', tags: ['foo', 'bar']};
var annot = fixtures.defaultAnnotation();
var permissions = fakePermissions();
permissions.private = sinon.stub().returns('private permissions');
updateDomainModel(domainModel, viewModel, permissions);
assert.equal(domainModel.permissions, 'private permissions');
var newModel = updateModel(annot, changes, permissions);
assert.equal(newModel.permissions, 'private permissions');
});
it('sets domainModel.permissions to shared if !vm.isPrivate', function() {
var domainModel = {};
var viewModel = {
state: sinon.stub().returns({
isPrivate: false,
text: 'foo',
}),
};
it('sets permissions to shared if the draft is shared', function() {
var changes = {isPrivate: false, text: 'bar', tags: ['foo', 'bar']};
var annot = fixtures.defaultAnnotation();
var permissions = fakePermissions();
permissions.shared = sinon.stub().returns('shared permissions');
updateDomainModel(domainModel, viewModel, permissions);
assert.equal(domainModel.permissions, 'shared permissions');
var newModel = updateModel(annot, changes, permissions);
assert.equal(newModel.permissions, 'shared permissions');
});
});
......@@ -150,8 +101,6 @@ describe('annotation', function() {
};
}
before(function() {
angular.module('h', [])
.directive('annotation', annotationDirective());
......@@ -238,11 +187,11 @@ describe('annotation', function() {
$provide.value('drafts', fakeDrafts);
$provide.value('features', fakeFeatures);
$provide.value('flash', fakeFlash);
$provide.value('groups', fakeGroups);
$provide.value('permissions', fakePermissions);
$provide.value('session', fakeSession);
$provide.value('settings', fakeSettings);
$provide.value('store', fakeStore);
$provide.value('groups', fakeGroups);
}));
beforeEach(
......@@ -800,7 +749,7 @@ describe('annotation', function() {
}));
var saved = controller.save();
assert.equal(controller.isSaving, true);
create();
create(Object.assign({}, controller.annotation, {id: 'new-id'}));
return saved.then(function () {
assert.equal(controller.isSaving, false);
});
......@@ -808,36 +757,19 @@ describe('annotation', function() {
it('does not remove the draft if saving fails', function () {
var controller = createController();
var failCreation;
fakeStore.annotation.create = sinon.stub().returns(new Promise(function (resolve, reject) {
failCreation = reject;
}));
var saved = controller.save();
assert.equal(controller.isSaving, true);
failCreation({status: -1});
return saved.then(function () {
assert.equal(controller.isSaving, false);
fakeStore.annotation.create = sinon.stub().returns(Promise.reject({status: -1}));
return controller.save().then(function () {
assert.notCalled(fakeDrafts.remove);
});
});
it(
'Passes group:<id> to the server when saving a new annotation',
function() {
it('sets the annotation\'s group to the focused group', function() {
fakeGroups.focused = function () {
return { id: 'test-id' };
};
var annotation = {
user: 'acct:fred@hypothes.is',
text: 'foo',
};
var controller = createDirective(annotation).controller;
return controller.save().then(function() {
assert.calledWith(fakeStore.annotation.create, sinon.match({}),
sinon.match({group: 'test-id'}));
var controller = createDirective(fixtures.newAnnotation()).controller;
assert.equal(controller.annotation.group, 'test-id');
});
}
);
});
describe('saving an edited an annotation', function() {
......@@ -852,9 +784,7 @@ describe('annotation', function() {
return createDirective(annotation).controller;
}
it(
'flashes a generic error if the server cannot be reached',
function() {
it('flashes a generic error if the server cannot be reached', function () {
var controller = createController();
fakeStore.annotation.update = sinon.stub().returns(Promise.reject({
status: -1
......@@ -863,8 +793,7 @@ describe('annotation', function() {
assert.calledWith(fakeFlash.error,
'Service unreachable.', 'Saving annotation failed');
});
}
);
});
it('flashes an error if saving the annotation fails on the server', function () {
var controller = createController();
......
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