Commit 696909f7 authored by Robert Knight's avatar Robert Knight

Merge pull request #2763 from hypothesis/2728-refactor-annotation-controller-tests

Refactor annotation controller tests
parents d16f39bb 8b1f01ba
...@@ -6,183 +6,25 @@ var events = require('../../events'); ...@@ -6,183 +6,25 @@ var events = require('../../events');
var module = angular.mock.module; var module = angular.mock.module;
var inject = angular.mock.inject; var inject = angular.mock.inject;
describe('annotation', function() { /** Return Angular's $compile service. */
function compileService() {
var $compile; var $compile;
var $document; inject(function(_$compile_) {
var $element; $compile = _$compile_;
var $q;
var $rootScope;
var $scope;
var $timeout;
var $window;
var annotation;
var controller;
var createDirective;
var fakeAnnotationMapper;
var fakeAnnotationUI;
var fakeDocumentDomainFilter;
var fakeDocumentTitleFilter;
var fakeDrafts;
var fakeFeatures;
var fakeFlash;
var fakeGroups;
var fakeMomentFilter;
var fakePermissions;
var fakePersonaFilter;
var fakeSession;
var fakeStore;
var fakeTags;
var fakeTime;
var fakeUrlEncodeFilter;
var isolateScope;
var sandbox;
createDirective = function() {
$element = angular.element('<div annotation="annotation">');
$compile($element)($scope);
$scope.$digest();
controller = $element.controller('annotation');
isolateScope = $element.isolateScope();
};
before(function() {
angular.module('h', [])
.directive('annotation', require('../annotation').directive);
}); });
return $compile;
}
beforeEach(module('h')); /** Return Angular's $document service. */
function documentService() {
beforeEach(module('h.templates')); var $document;
inject(function(_$document_) {
beforeEach(module(function($provide) {
sandbox = sinon.sandbox.create();
fakeAnnotationMapper = {
createAnnotation: sandbox.stub().returns({
permissions: {
read: ['acct:bill@localhost'],
update: ['acct:bill@localhost'],
destroy: ['acct:bill@localhost'],
admin: ['acct:bill@localhost']
}
}),
deleteAnnotation: sandbox.stub()
};
fakeAnnotationUI = {};
fakeDrafts = {
update: sandbox.stub(),
remove: sandbox.stub(),
get: sandbox.stub()
};
fakeFeatures = {
flagEnabled: sandbox.stub().returns(true)
};
fakeFlash = sandbox.stub();
fakeMomentFilter = sandbox.stub().returns('ages ago');
fakePermissions = {
isShared: sandbox.stub().returns(true),
isPrivate: sandbox.stub().returns(false),
permits: sandbox.stub().returns(true),
shared: sandbox.stub().returns({
read: ['everybody']
}),
'private': sandbox.stub().returns({
read: ['justme']
}),
'default': sandbox.stub().returns({
read: ['default']
}),
setDefault: sandbox.stub()
};
fakePersonaFilter = sandbox.stub().returnsArg(0);
fakeDocumentTitleFilter = function(arg) {
return '';
};
fakeDocumentDomainFilter = function(arg) {
return '';
};
fakeSession = {
state: {
userid: 'acct:bill@localhost'
}
};
fakeTags = {
filter: sandbox.stub().returns('a while ago'),
store: sandbox.stub()
};
fakeTime = {
toFuzzyString: sandbox.stub().returns('a while ago'),
nextFuzzyUpdate: sandbox.stub().returns(30)
};
fakeUrlEncodeFilter = function(v) {
return encodeURIComponent(v);
};
fakeGroups = {
focused: function() {
return {};
},
get: function() {}
};
$provide.value('annotationMapper', fakeAnnotationMapper);
$provide.value('annotationUI', fakeAnnotationUI);
$provide.value('drafts', fakeDrafts);
$provide.value('features', fakeFeatures);
$provide.value('flash', fakeFlash);
$provide.value('momentFilter', fakeMomentFilter);
$provide.value('permissions', fakePermissions);
$provide.value('personaFilter', fakePersonaFilter);
$provide.value('documentTitleFilter', fakeDocumentTitleFilter);
$provide.value('documentDomainFilter', fakeDocumentDomainFilter);
$provide.value('session', fakeSession);
$provide.value('store', fakeStore);
$provide.value('tags', fakeTags);
$provide.value('time', fakeTime);
$provide.value('urlencodeFilter', fakeUrlEncodeFilter);
$provide.value('groups', fakeGroups);
}));
beforeEach(
inject(
function(_$compile_, _$document_, _$q_, _$rootScope_, _$timeout_,
_$window_) {
$compile = _$compile_;
$document = _$document_; $document = _$document_;
$window = _$window_;
$q = _$q_;
$timeout = _$timeout_;
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
$scope.annotation = annotation = {
id: 'deadbeef',
document: {
title: 'A special document'
},
target: [{}],
uri: 'http://example.com',
user: 'acct:bill@localhost'
};
}
)
);
afterEach(function() {
sandbox.restore();
}); });
return $document;
}
describe('annotation.js', function() {
describe('extractDocumentMetadata()', function() { describe('extractDocumentMetadata()', function() {
var extractDocumentMetadata = require('../annotation') var extractDocumentMetadata = require('../annotation')
...@@ -310,66 +152,339 @@ describe('annotation', function() { ...@@ -310,66 +152,339 @@ describe('annotation', function() {
describe('updateDomainModel()', function() { describe('updateDomainModel()', function() {
var updateDomainModel = require('../annotation').updateDomainModel; var updateDomainModel = require('../annotation').updateDomainModel;
it('copies top-level keys form viewModel into domainModel', function() { it('copies top-level keys form viewModel into domainModel', function() {
var domainModel = {}; var domainModel = {};
var viewModel = {foo: 'bar', tags: []}; var viewModel = {foo: 'bar', tags: []};
updateDomainModel(domainModel, viewModel);
assert.equal(domainModel.foo, viewModel.foo);
});
it('overwrites existing keys in domainModel', function() {
var domainModel = {foo: 'foo'};
var viewModel = {foo: 'bar', tags: []};
updateDomainModel(domainModel, viewModel);
assert.equal(domainModel.foo, viewModel.foo);
});
it('doesn\'t touch other properties in domainModel', function() {
var domainModel = {foo: 'foo', bar: 'bar'};
var viewModel = {foo: 'FOO', tags: []};
updateDomainModel(domainModel, viewModel);
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 = {
tags: [
{text: 'foo'},
{text: 'bar'}
]
};
updateDomainModel(domainModel, viewModel);
assert.deepEqual(
domainModel.tags, ['foo', 'bar'],
'The array of {tag: "text"} objects in viewModel becomes an array ' +
'of "text" strings in domainModel');
});
});
describe('validate()', function() {
var validate = require('../annotation').validate;
it('returns undefined if value is not an object', function() {
var i;
var values = [2, 'foo', true, null];
for (i = 0; i < values.length; i++) {
assert.equal(validate(values[i]), undefined);
}
});
it(
'returns the length if the value contains a non-empty tags array',
function() {
assert.equal(
validate({
tags: ['foo', 'bar'],
permissions: {
read: ['group:__world__']
},
target: [1, 2, 3]
}),
2);
}
);
it(
'returns the length if the value contains a non-empty text string',
function() {
assert.equal(
validate({
text: 'foobar',
permissions: {
read: ['group:__world__']
},
target: [1, 2, 3]
}),
6);
}
);
it('returns true for private highlights', function() {
assert.equal(
validate({
permissions: {
read: ['acct:seanh@hypothes.is']
},
target: [1, 2, 3]
}),
true);
});
it('returns true for group highlights', function() {
assert.equal(
validate({
permissions: {
read: ['group:foo']
},
target: [1, 2, 3]
}),
true);
});
it('returns false for public highlights', function() {
assert.equal(
validate({
text: void 0,
tags: void 0,
permissions: {
read: ['group:__world__']
},
target: [1, 2, 3]
}),
false);
});
it('handles values with no permissions', function() {
assert.equal(
validate({
permissions: void 0,
target: [1, 2, 3]
}),
true);
});
it('handles permissions objects with no read', function() {
assert.equal(
validate({
permissions: {
read: void 0
},
target: [1, 2, 3]
}),
true);
});
});
describe('AnnotationController', function() {
var $q;
var $rootScope;
var $scope;
var $timeout;
var $window;
var annotation;
var fakeAnnotationMapper;
var fakeAnnotationUI;
var fakeDocumentDomainFilter;
var fakeDocumentTitleFilter;
var fakeDrafts;
var fakeFeatures;
var fakeFlash;
var fakeGroups;
var fakeMomentFilter;
var fakePermissions;
var fakePersonaFilter;
var fakeSession;
var fakeTags;
var fakeTime;
var fakeUrlEncodeFilter;
var sandbox;
function createDirective() {
var element = angular.element('<div annotation="annotation">');
compileService()(element)($scope);
$scope.$digest();
var controller = element.controller('annotation');
var scope = element.isolateScope();
return {
controller: controller,
element: element,
scope: scope
};
}
before(function() {
angular.module('h', [])
.directive('annotation', require('../annotation').directive);
});
beforeEach(module('h'));
beforeEach(module('h.templates'));
beforeEach(module(function($provide) {
sandbox = sinon.sandbox.create();
fakeAnnotationMapper = {
createAnnotation: sandbox.stub().returns({
permissions: {
read: ['acct:bill@localhost'],
update: ['acct:bill@localhost'],
destroy: ['acct:bill@localhost'],
admin: ['acct:bill@localhost']
}
}),
deleteAnnotation: sandbox.stub()
};
fakeAnnotationUI = {};
fakeDrafts = {
update: sandbox.stub(),
remove: sandbox.stub(),
get: sandbox.stub()
};
fakeFeatures = {
flagEnabled: sandbox.stub().returns(true)
};
fakeFlash = sandbox.stub();
fakeMomentFilter = sandbox.stub().returns('ages ago');
fakePermissions = {
isShared: sandbox.stub().returns(true),
isPrivate: sandbox.stub().returns(false),
permits: sandbox.stub().returns(true),
shared: sandbox.stub().returns({
read: ['everybody']
}),
'private': sandbox.stub().returns({
read: ['justme']
}),
'default': sandbox.stub().returns({
read: ['default']
}),
setDefault: sandbox.stub()
};
fakePersonaFilter = sandbox.stub().returnsArg(0);
updateDomainModel(domainModel, viewModel); fakeDocumentTitleFilter = function(arg) {
return '';
};
assert.equal(domainModel.foo, viewModel.foo); fakeDocumentDomainFilter = function(arg) {
}); return '';
};
it('overwrites existing keys in domainModel', function() { fakeSession = {
var domainModel = {foo: 'foo'}; state: {
var viewModel = {foo: 'bar', tags: []}; userid: 'acct:bill@localhost'
}
};
updateDomainModel(domainModel, viewModel); fakeTags = {
filter: sandbox.stub().returns('a while ago'),
store: sandbox.stub()
};
assert.equal(domainModel.foo, viewModel.foo); fakeTime = {
}); toFuzzyString: sandbox.stub().returns('a while ago'),
nextFuzzyUpdate: sandbox.stub().returns(30)
};
it('doesn\'t touch other properties in domainModel', function() { fakeUrlEncodeFilter = function(v) {
var domainModel = {foo: 'foo', bar: 'bar'}; return encodeURIComponent(v);
var viewModel = {foo: 'FOO', tags: []}; };
updateDomainModel(domainModel, viewModel); fakeGroups = {
focused: function() {
return {};
},
get: function() {}
};
assert.equal( $provide.value('annotationMapper', fakeAnnotationMapper);
domainModel.bar, 'bar', $provide.value('annotationUI', fakeAnnotationUI);
'updateDomainModel() should not touch properties of domainModel' + $provide.value('drafts', fakeDrafts);
'that don\'t exist in viewModel'); $provide.value('features', fakeFeatures);
}); $provide.value('flash', fakeFlash);
$provide.value('momentFilter', fakeMomentFilter);
$provide.value('permissions', fakePermissions);
$provide.value('personaFilter', fakePersonaFilter);
$provide.value('documentTitleFilter', fakeDocumentTitleFilter);
$provide.value('documentDomainFilter', fakeDocumentDomainFilter);
$provide.value('session', fakeSession);
$provide.value('tags', fakeTags);
$provide.value('time', fakeTime);
$provide.value('urlencodeFilter', fakeUrlEncodeFilter);
$provide.value('groups', fakeGroups);
}));
it('copies tag texts from viewModel into domainModel', function() { beforeEach(
var domainModel = {}; inject(
var viewModel = { function(_$q_, _$rootScope_, _$timeout_,
tags: [ _$window_) {
{text: 'foo'}, $window = _$window_;
{text: 'bar'} $q = _$q_;
] $timeout = _$timeout_;
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
$scope.annotation = annotation = {
id: 'deadbeef',
document: {
title: 'A special document'
},
target: [{}],
uri: 'http://example.com',
user: 'acct:bill@localhost'
}; };
}
)
);
updateDomainModel(domainModel, viewModel); afterEach(function() {
sandbox.restore();
assert.deepEqual(
domainModel.tags, ['foo', 'bar'],
'The array of {tag: "text"} objects in viewModel becomes an array ' +
'of "text" strings in domainModel');
});
}); });
describe('AnnotationController.editing()', function() { describe('AnnotationController.editing()', function() {
it('returns true if action is "create"', function() { it('returns true if action is "create"', function() {
var controller = createDirective().controller;
controller.action = 'create'; controller.action = 'create';
assert(controller.editing()); assert(controller.editing());
}); });
it('returns true if action is "edit"', function() { it('returns true if action is "edit"', function() {
var controller = createDirective().controller;
controller.action = 'edit'; controller.action = 'edit';
assert(controller.editing()); assert(controller.editing());
}); });
it('returns false if action is "view"', function() { it('returns false if action is "view"', function() {
var controller = createDirective().controller;
controller.action = 'view'; controller.action = 'view';
assert(!controller.editing()); assert(!controller.editing());
}); });
...@@ -410,7 +525,6 @@ describe('annotation', function() { ...@@ -410,7 +525,6 @@ describe('annotation', function() {
describe('#reply', function() { describe('#reply', function() {
beforeEach(function() { beforeEach(function() {
createDirective();
annotation.permissions = { annotation.permissions = {
read: ['acct:joe@localhost'], read: ['acct:joe@localhost'],
update: ['acct:joe@localhost'], update: ['acct:joe@localhost'],
...@@ -420,6 +534,7 @@ describe('annotation', function() { ...@@ -420,6 +534,7 @@ describe('annotation', function() {
}); });
it('creates a new reply with the proper uri and references', function() { it('creates a new reply with the proper uri and references', function() {
var controller = createDirective().controller;
controller.reply(); controller.reply();
var match = sinon.match({ var match = sinon.match({
references: [annotation.id], references: [annotation.id],
...@@ -429,6 +544,7 @@ describe('annotation', function() { ...@@ -429,6 +544,7 @@ describe('annotation', function() {
}); });
it('makes the annotation shared if the parent is shared', function() { it('makes the annotation shared if the parent is shared', function() {
var controller = createDirective().controller;
var reply = {}; var reply = {};
fakeAnnotationMapper.createAnnotation.returns(reply); fakeAnnotationMapper.createAnnotation.returns(reply);
fakePermissions.isShared.returns(true); fakePermissions.isShared.returns(true);
...@@ -439,6 +555,7 @@ describe('annotation', function() { ...@@ -439,6 +555,7 @@ describe('annotation', function() {
}); });
it('makes the annotation shared if the parent is shared', function() { it('makes the annotation shared if the parent is shared', function() {
var controller = createDirective().controller;
$scope.annotation.group = 'my group'; $scope.annotation.group = 'my group';
$scope.annotation.permissions = { $scope.annotation.permissions = {
read: ['my group'] read: ['my group']
...@@ -460,6 +577,7 @@ describe('annotation', function() { ...@@ -460,6 +577,7 @@ describe('annotation', function() {
it( it(
'does not add the world readable principal if the parent is private', 'does not add the world readable principal if the parent is private',
function() { function() {
var controller = createDirective().controller;
var reply = {}; var reply = {};
fakeAnnotationMapper.createAnnotation.returns(reply); fakeAnnotationMapper.createAnnotation.returns(reply);
fakePermissions.isShared.returns(false); fakePermissions.isShared.returns(false);
...@@ -471,6 +589,7 @@ describe('annotation', function() { ...@@ -471,6 +589,7 @@ describe('annotation', function() {
); );
it('sets the reply\'s group to be the same as its parent\'s', function() { it('sets the reply\'s group to be the same as its parent\'s', function() {
var controller = createDirective().controller;
$scope.annotation.group = 'my group'; $scope.annotation.group = 'my group';
var reply = {}; var reply = {};
fakeAnnotationMapper.createAnnotation.returns(reply); fakeAnnotationMapper.createAnnotation.returns(reply);
...@@ -480,11 +599,8 @@ describe('annotation', function() { ...@@ -480,11 +599,8 @@ describe('annotation', function() {
}); });
describe('#setPrivacy', function() { describe('#setPrivacy', function() {
beforeEach(function() {
createDirective();
});
it('makes the annotation private when level is "private"', function() { it('makes the annotation private when level is "private"', function() {
var controller = createDirective().controller;
annotation.$update = sinon.stub().returns(Promise.resolve()); annotation.$update = sinon.stub().returns(Promise.resolve());
controller.edit(); controller.edit();
controller.setPrivacy('private'); controller.setPrivacy('private');
...@@ -498,6 +614,7 @@ describe('annotation', function() { ...@@ -498,6 +614,7 @@ describe('annotation', function() {
}); });
it('makes the annotation shared when level is "shared"', function() { it('makes the annotation shared when level is "shared"', function() {
var controller = createDirective().controller;
annotation.$update = sinon.stub().returns(Promise.resolve()); annotation.$update = sinon.stub().returns(Promise.resolve());
controller.edit(); controller.edit();
controller.setPrivacy('shared'); controller.setPrivacy('shared');
...@@ -509,6 +626,7 @@ describe('annotation', function() { ...@@ -509,6 +626,7 @@ describe('annotation', function() {
}); });
it('saves the "shared" visibility level to localStorage', function() { it('saves the "shared" visibility level to localStorage', function() {
var controller = createDirective().controller;
annotation.$update = sinon.stub().returns(Promise.resolve()); annotation.$update = sinon.stub().returns(Promise.resolve());
controller.edit(); controller.edit();
controller.setPrivacy('shared'); controller.setPrivacy('shared');
...@@ -518,6 +636,7 @@ describe('annotation', function() { ...@@ -518,6 +636,7 @@ describe('annotation', function() {
}); });
it('saves the "private" visibility level to localStorage', function() { it('saves the "private" visibility level to localStorage', function() {
var controller = createDirective().controller;
annotation.$update = sinon.stub().returns(Promise.resolve()); annotation.$update = sinon.stub().returns(Promise.resolve());
controller.edit(); controller.edit();
controller.setPrivacy('private'); controller.setPrivacy('private');
...@@ -527,6 +646,7 @@ describe('annotation', function() { ...@@ -527,6 +646,7 @@ describe('annotation', function() {
}); });
it('doesn\'t save the visibility if the annotation is a reply', function() { it('doesn\'t save the visibility if the annotation is a reply', function() {
var controller = createDirective().controller;
annotation.$update = sinon.stub().returns(Promise.resolve()); annotation.$update = sinon.stub().returns(Promise.resolve());
annotation.references = ['parent id']; annotation.references = ['parent id'];
controller.edit(); controller.edit();
...@@ -538,17 +658,15 @@ describe('annotation', function() { ...@@ -538,17 +658,15 @@ describe('annotation', function() {
}); });
describe('#hasContent', function() { describe('#hasContent', function() {
beforeEach(function() {
createDirective();
});
it('returns false if the annotation has no tags or text', function() { it('returns false if the annotation has no tags or text', function() {
var controller = createDirective().controller;
controller.annotation.text = ''; controller.annotation.text = '';
controller.annotation.tags = []; controller.annotation.tags = [];
assert.ok(!controller.hasContent()); assert.ok(!controller.hasContent());
}); });
it('returns true if the annotation has tags or text', function() { it('returns true if the annotation has tags or text', function() {
var controller = createDirective().controller;
controller.annotation.text = 'bar'; controller.annotation.text = 'bar';
assert.ok(controller.hasContent()); assert.ok(controller.hasContent());
controller.annotation.text = ''; controller.annotation.text = '';
...@@ -562,16 +680,14 @@ describe('annotation', function() { ...@@ -562,16 +680,14 @@ describe('annotation', function() {
}); });
describe('#hasQuotes', function() { describe('#hasQuotes', function() {
beforeEach(function() {
createDirective();
});
it('returns false if the annotation has no quotes', function() { it('returns false if the annotation has no quotes', function() {
var controller = createDirective().controller;
controller.annotation.target = [{}]; controller.annotation.target = [{}];
assert.isFalse(controller.hasQuotes()); assert.isFalse(controller.hasQuotes());
}); });
it('returns true if the annotation has quotes', function() { it('returns true if the annotation has quotes', function() {
var controller = createDirective().controller;
controller.annotation.target = [ controller.annotation.target = [
{ {
selector: [ selector: [
...@@ -586,16 +702,13 @@ describe('annotation', function() { ...@@ -586,16 +702,13 @@ describe('annotation', function() {
}); });
describe('#render', function() { describe('#render', function() {
beforeEach(function() {
createDirective();
sandbox.spy(controller, 'render');
});
afterEach(function() { afterEach(function() {
sandbox.restore(); sandbox.restore();
}); });
it('is called exactly once on model changes', function() { it('is called exactly once on model changes', function() {
var controller = createDirective().controller;
sandbox.spy(controller, 'render');
assert.notCalled(controller.render); assert.notCalled(controller.render);
annotation['delete'] = true; annotation['delete'] = true;
$scope.$digest(); $scope.$digest();
...@@ -606,17 +719,20 @@ describe('annotation', function() { ...@@ -606,17 +719,20 @@ describe('annotation', function() {
}); });
it('provides a document title', function() { it('provides a document title', function() {
var controller = createDirective().controller;
controller.render(); controller.render();
assert.equal(controller.document.title, 'A special document'); assert.equal(controller.document.title, 'A special document');
}); });
it('uses the first title when there are more than one', function() { it('uses the first title when there are more than one', function() {
var controller = createDirective().controller;
annotation.document.title = ['first title', 'second title']; annotation.document.title = ['first title', 'second title'];
controller.render(); controller.render();
assert.equal(controller.document.title, 'first title'); assert.equal(controller.document.title, 'first title');
}); });
it('truncates long titles', function() { it('truncates long titles', function() {
var controller = createDirective().controller;
annotation.document.title = 'A very very very long title that really\nshouldn\'t be found on a page on the internet.'; annotation.document.title = 'A very very very long title that really\nshouldn\'t be found on a page on the internet.';
controller.render(); controller.render();
assert.equal( assert.equal(
...@@ -624,16 +740,19 @@ describe('annotation', function() { ...@@ -624,16 +740,19 @@ describe('annotation', function() {
}); });
it('provides a document uri', function() { it('provides a document uri', function() {
var controller = createDirective().controller;
controller.render(); controller.render();
assert.equal(controller.document.uri, 'http://example.com'); assert.equal(controller.document.uri, 'http://example.com');
}); });
it('provides an extracted domain from the uri', function() { it('provides an extracted domain from the uri', function() {
var controller = createDirective().controller;
controller.render(); controller.render();
assert.equal(controller.document.domain, 'example.com'); assert.equal(controller.document.domain, 'example.com');
}); });
it('uses the domain for the title if the title is not present', function() { it('uses the domain for the title if the title is not present', function() {
var controller = createDirective().controller;
delete annotation.document.title; delete annotation.document.title;
controller.render(); controller.render();
assert.equal(controller.document.title, 'example.com'); assert.equal(controller.document.title, 'example.com');
...@@ -642,6 +761,7 @@ describe('annotation', function() { ...@@ -642,6 +761,7 @@ describe('annotation', function() {
it( it(
'still sets the uri correctly if the annotation has no document', 'still sets the uri correctly if the annotation has no document',
function() { function() {
var controller = createDirective().controller;
delete annotation.document; delete annotation.document;
controller.render(); controller.render();
assert(controller.document.uri === $scope.annotation.uri); assert(controller.document.uri === $scope.annotation.uri);
...@@ -651,6 +771,7 @@ describe('annotation', function() { ...@@ -651,6 +771,7 @@ describe('annotation', function() {
it( it(
'still sets the domain correctly if the annotation has no document', 'still sets the domain correctly if the annotation has no document',
function() { function() {
var controller = createDirective().controller;
delete annotation.document; delete annotation.document;
controller.render(); controller.render();
assert(controller.document.domain === 'example.com'); assert(controller.document.domain === 'example.com');
...@@ -660,6 +781,7 @@ describe('annotation', function() { ...@@ -660,6 +781,7 @@ describe('annotation', function() {
it( it(
'uses the domain for the title when the annotation has no document', 'uses the domain for the title when the annotation has no document',
function() { function() {
var controller = createDirective().controller;
delete annotation.document; delete annotation.document;
controller.render(); controller.render();
assert(controller.document.title === 'example.com'); assert(controller.document.title === 'example.com');
...@@ -680,19 +802,22 @@ describe('annotation', function() { ...@@ -680,19 +802,22 @@ describe('annotation', function() {
}); });
it('is not updated for unsaved annotations', function() { it('is not updated for unsaved annotations', function() {
annotation.updated = null;
var controller = createDirective().controller;
// Unsaved annotations don't have an updated time yet so a timestamp // Unsaved annotations don't have an updated time yet so a timestamp
// string can't be computed for them. // string can't be computed for them.
annotation.updated = null;
$scope.$digest(); $scope.$digest();
assert.equal(controller.timestamp, null); assert.equal(controller.timestamp, null);
}); });
it('is updated on first digest', function() { it('is updated on first digest', function() {
var controller = createDirective().controller;
$scope.$digest(); $scope.$digest();
assert.equal(controller.timestamp, 'a while ago'); assert.equal(controller.timestamp, 'a while ago');
}); });
it('is updated after a timeout', function() { it('is updated after a timeout', function() {
var controller = createDirective().controller;
fakeTime.nextFuzzyUpdate.returns(10); fakeTime.nextFuzzyUpdate.returns(10);
fakeTime.toFuzzyString.returns('ages ago'); fakeTime.toFuzzyString.returns('ages ago');
$scope.$digest(); $scope.$digest();
...@@ -702,6 +827,7 @@ describe('annotation', function() { ...@@ -702,6 +827,7 @@ describe('annotation', function() {
}); });
it('is no longer updated after the scope is destroyed', function() { it('is no longer updated after the scope is destroyed', function() {
var controller = createDirective().controller;
$scope.$digest(); $scope.$digest();
$scope.$destroy(); $scope.$destroy();
$timeout.flush(); $timeout.flush();
...@@ -710,17 +836,16 @@ describe('annotation', function() { ...@@ -710,17 +836,16 @@ describe('annotation', function() {
}); });
describe('share', function() { describe('share', function() {
var dialog;
beforeEach(function() {
dialog = $element.find('.share-dialog-wrapper');
});
it('sets and unsets the open class on the share wrapper', function() { it('sets and unsets the open class on the share wrapper', function() {
var components = createDirective();
var controller = components.controller;
var element = components.element;
var scope = components.scope;
var dialog = element.find('.share-dialog-wrapper');
dialog.find('button').click(); dialog.find('button').click();
isolateScope.$digest(); scope.$digest();
assert.ok(dialog.hasClass('open')); assert.ok(dialog.hasClass('open'));
$document.click(); documentService().click();
assert.notOk(dialog.hasClass('open')); assert.notOk(dialog.hasClass('open'));
}); });
}); });
...@@ -728,7 +853,6 @@ describe('annotation', function() { ...@@ -728,7 +853,6 @@ describe('annotation', function() {
describe('deleteAnnotation() method', function() { describe('deleteAnnotation() method', function() {
beforeEach(function() { beforeEach(function() {
createDirective();
fakeAnnotationMapper.deleteAnnotation = sandbox.stub(); fakeAnnotationMapper.deleteAnnotation = sandbox.stub();
fakeFlash.error = sandbox.stub(); fakeFlash.error = sandbox.stub();
}); });
...@@ -736,6 +860,7 @@ describe('annotation', function() { ...@@ -736,6 +860,7 @@ describe('annotation', function() {
it( it(
'calls annotationMapper.delete() if the delete is confirmed', 'calls annotationMapper.delete() if the delete is confirmed',
function(done) { function(done) {
var controller = createDirective().controller;
sandbox.stub($window, 'confirm').returns(true); sandbox.stub($window, 'confirm').returns(true);
fakeAnnotationMapper.deleteAnnotation.returns($q.resolve()); fakeAnnotationMapper.deleteAnnotation.returns($q.resolve());
controller['delete']().then(function() { controller['delete']().then(function() {
...@@ -749,6 +874,7 @@ describe('annotation', function() { ...@@ -749,6 +874,7 @@ describe('annotation', function() {
it( it(
'doesn\'t call annotationMapper.delete() if the delete is cancelled', 'doesn\'t call annotationMapper.delete() if the delete is cancelled',
function() { function() {
var controller = createDirective().controller;
sandbox.stub($window, 'confirm').returns(false); sandbox.stub($window, 'confirm').returns(false);
assert(fakeAnnotationMapper.deleteAnnotation.notCalled); assert(fakeAnnotationMapper.deleteAnnotation.notCalled);
} }
...@@ -757,6 +883,7 @@ describe('annotation', function() { ...@@ -757,6 +883,7 @@ describe('annotation', function() {
it( it(
'flashes a generic error if the server cannot be reached', 'flashes a generic error if the server cannot be reached',
function(done) { function(done) {
var controller = createDirective().controller;
sandbox.stub($window, 'confirm').returns(true); sandbox.stub($window, 'confirm').returns(true);
fakeAnnotationMapper.deleteAnnotation.returns($q.reject({ fakeAnnotationMapper.deleteAnnotation.returns($q.reject({
status: 0 status: 0
...@@ -771,6 +898,7 @@ describe('annotation', function() { ...@@ -771,6 +898,7 @@ describe('annotation', function() {
); );
it('flashes an error if the delete fails on the server', function(done) { it('flashes an error if the delete fails on the server', function(done) {
var controller = createDirective().controller;
sandbox.stub($window, 'confirm').returns(true); sandbox.stub($window, 'confirm').returns(true);
fakeAnnotationMapper.deleteAnnotation.returns($q.reject({ fakeAnnotationMapper.deleteAnnotation.returns($q.reject({
status: 500, status: 500,
...@@ -786,6 +914,7 @@ describe('annotation', function() { ...@@ -786,6 +914,7 @@ describe('annotation', function() {
}); });
it('doesn\'t flash an error if the delete succeeds', function(done) { it('doesn\'t flash an error if the delete succeeds', function(done) {
var controller = createDirective().controller;
sandbox.stub($window, 'confirm').returns(true); sandbox.stub($window, 'confirm').returns(true);
fakeAnnotationMapper.deleteAnnotation.returns($q.resolve()); fakeAnnotationMapper.deleteAnnotation.returns($q.resolve());
controller['delete']().then(function() { controller['delete']().then(function() {
...@@ -798,15 +927,20 @@ describe('annotation', function() { ...@@ -798,15 +927,20 @@ describe('annotation', function() {
describe('saving a new annotation', function() { describe('saving a new annotation', function() {
beforeEach(function() { beforeEach(function() {
createDirective();
fakeFlash.error = sandbox.stub(); fakeFlash.error = sandbox.stub();
controller.action = 'create';
annotation.$create = sandbox.stub(); annotation.$create = sandbox.stub();
}); });
function controllerWithActionCreate() {
var controller = createDirective().controller;
controller.action = 'create';
return controller;
}
it( it(
'emits annotationCreated when saving an annotation succeeds', 'emits annotationCreated when saving an annotation succeeds',
function(done) { function(done) {
var controller = controllerWithActionCreate();
sandbox.spy($rootScope, '$emit'); sandbox.spy($rootScope, '$emit');
annotation.$create.returns(Promise.resolve()); annotation.$create.returns(Promise.resolve());
controller.save().then(function() { controller.save().then(function() {
...@@ -819,6 +953,7 @@ describe('annotation', function() { ...@@ -819,6 +953,7 @@ describe('annotation', function() {
it( it(
'flashes a generic error if the server can\'t be reached', 'flashes a generic error if the server can\'t be reached',
function(done) { function(done) {
var controller = controllerWithActionCreate();
annotation.$create.returns(Promise.reject({ annotation.$create.returns(Promise.reject({
status: 0 status: 0
})); }));
...@@ -833,6 +968,7 @@ describe('annotation', function() { ...@@ -833,6 +968,7 @@ describe('annotation', function() {
it( it(
'flashes an error if saving the annotation fails on the server', 'flashes an error if saving the annotation fails on the server',
function(done) { function(done) {
var controller = controllerWithActionCreate();
annotation.$create.returns(Promise.reject({ annotation.$create.returns(Promise.reject({
status: 500, status: 500,
statusText: 'Server Error', statusText: 'Server Error',
...@@ -849,6 +985,7 @@ describe('annotation', function() { ...@@ -849,6 +985,7 @@ describe('annotation', function() {
it( it(
'doesn\'t flash an error when saving an annotation succeeds', 'doesn\'t flash an error when saving an annotation succeeds',
function() { function() {
var controller = controllerWithActionCreate();
annotation.$create.returns(Promise.resolve()); annotation.$create.returns(Promise.resolve());
controller.save(); controller.save();
assert(fakeFlash.error.notCalled); assert(fakeFlash.error.notCalled);
...@@ -858,15 +995,20 @@ describe('annotation', function() { ...@@ -858,15 +995,20 @@ describe('annotation', function() {
describe('saving an edited an annotation', function() { describe('saving an edited an annotation', function() {
beforeEach(function() { beforeEach(function() {
createDirective();
fakeFlash.error = sandbox.stub(); fakeFlash.error = sandbox.stub();
controller.action = 'edit';
annotation.$update = sandbox.stub(); annotation.$update = sandbox.stub();
}); });
function controllerWithActionEdit() {
var controller = createDirective().controller;
controller.action = 'edit';
return controller;
}
it( it(
'flashes a generic error if the server cannot be reached', 'flashes a generic error if the server cannot be reached',
function(done) { function(done) {
var controller = controllerWithActionEdit();
annotation.$update.returns(Promise.reject({ annotation.$update.returns(Promise.reject({
status: 0 status: 0
})); }));
...@@ -881,6 +1023,7 @@ describe('annotation', function() { ...@@ -881,6 +1023,7 @@ describe('annotation', function() {
it( it(
'flashes an error if saving the annotation fails on the server', 'flashes an error if saving the annotation fails on the server',
function(done) { function(done) {
var controller = controllerWithActionEdit();
annotation.$update.returns(Promise.reject({ annotation.$update.returns(Promise.reject({
status: 500, status: 500,
statusText: 'Server Error', statusText: 'Server Error',
...@@ -897,6 +1040,7 @@ describe('annotation', function() { ...@@ -897,6 +1040,7 @@ describe('annotation', function() {
it( it(
'doesn\'t flash an error if saving the annotation succeeds', 'doesn\'t flash an error if saving the annotation succeeds',
function() { function() {
var controller = controllerWithActionEdit();
annotation.$update.returns(Promise.resolve()); annotation.$update.returns(Promise.resolve());
controller.save(); controller.save();
assert(fakeFlash.error.notCalled); assert(fakeFlash.error.notCalled);
...@@ -906,7 +1050,7 @@ describe('annotation', function() { ...@@ -906,7 +1050,7 @@ describe('annotation', function() {
describe('drafts', function() { describe('drafts', function() {
it('creates a draft when editing an annotation', function() { it('creates a draft when editing an annotation', function() {
createDirective(); var controller = createDirective().controller;
controller.edit(); controller.edit();
assert.calledWith(fakeDrafts.update, annotation); assert.calledWith(fakeDrafts.update, annotation);
}); });
...@@ -918,7 +1062,7 @@ describe('annotation', function() { ...@@ -918,7 +1062,7 @@ describe('annotation', function() {
// "changes" object that aren't actually set on the annotation. In this // "changes" object that aren't actually set on the annotation. In this
// case, both permissions and tags are null so shouldn't be saved in // case, both permissions and tags are null so shouldn't be saved in
// the draft. // the draft.
createDirective(); var controller = createDirective().controller;
annotation.permissions = null; annotation.permissions = null;
annotation.text = 'Hello!'; annotation.text = 'Hello!';
annotation.tags = null; annotation.tags = null;
...@@ -938,7 +1082,7 @@ describe('annotation', function() { ...@@ -938,7 +1082,7 @@ describe('annotation', function() {
], ],
text: 'unsaved-text' text: 'unsaved-text'
}); });
createDirective(); var controller = createDirective().controller;
assert.isTrue(controller.editing()); assert.isTrue(controller.editing());
}); });
...@@ -947,7 +1091,7 @@ describe('annotation', function() { ...@@ -947,7 +1091,7 @@ describe('annotation', function() {
tags: ['unsaved-tag'], tags: ['unsaved-tag'],
text: 'unsaved-text' text: 'unsaved-text'
}); });
createDirective(); var controller = createDirective().controller;
assert.deepEqual(controller.annotation.tags, [ assert.deepEqual(controller.annotation.tags, [
{ {
text: 'unsaved-tag' text: 'unsaved-tag'
...@@ -957,7 +1101,7 @@ describe('annotation', function() { ...@@ -957,7 +1101,7 @@ describe('annotation', function() {
}); });
it('removes the draft when changes are discarded', function() { it('removes the draft when changes are discarded', function() {
createDirective(); var controller = createDirective().controller;
controller.edit(); controller.edit();
controller.revert(); controller.revert();
assert.calledWith(fakeDrafts.remove, annotation); assert.calledWith(fakeDrafts.remove, annotation);
...@@ -965,7 +1109,7 @@ describe('annotation', function() { ...@@ -965,7 +1109,7 @@ describe('annotation', function() {
it('removes the draft when changes are saved', function() { it('removes the draft when changes are saved', function() {
annotation.$update = sandbox.stub().returns(Promise.resolve()); annotation.$update = sandbox.stub().returns(Promise.resolve());
createDirective(); var controller = createDirective().controller;
controller.edit(); controller.edit();
controller.save(); controller.save();
...@@ -981,7 +1125,7 @@ describe('annotation', function() { ...@@ -981,7 +1125,7 @@ describe('annotation', function() {
describe('when the focused group changes', function() { describe('when the focused group changes', function() {
it('updates the current draft', function() { it('updates the current draft', function() {
createDirective(); var controller = createDirective().controller;
controller.edit(); controller.edit();
controller.annotation.text = 'unsaved-text'; controller.annotation.text = 'unsaved-text';
controller.annotation.tags = []; controller.annotation.tags = [];
...@@ -999,7 +1143,7 @@ describe('annotation', function() { ...@@ -999,7 +1143,7 @@ describe('annotation', function() {
}); });
it('should not create a new draft', function() { it('should not create a new draft', function() {
createDirective(); var controller = createDirective().controller;
controller.edit(); controller.edit();
fakeDrafts.update = sinon.stub(); fakeDrafts.update = sinon.stub();
fakeDrafts.get = sinon.stub().returns(null); fakeDrafts.get = sinon.stub().returns(null);
...@@ -1095,9 +1239,9 @@ describe('annotation', function() { ...@@ -1095,9 +1239,9 @@ describe('annotation', function() {
annotation.permissions.read, ['acct:bill@localhost'], annotation.permissions.read, ['acct:bill@localhost'],
'The annotation should still be private'); 'The annotation should still be private');
}); });
}); });
describe('AnnotationController', function() { describe('AnnotationController', function() {
before(function() { before(function() {
angular.module('h', []) angular.module('h', [])
.directive('annotation', require('../annotation').directive); .directive('annotation', require('../annotation').directive);
...@@ -1107,15 +1251,6 @@ describe('AnnotationController', function() { ...@@ -1107,15 +1251,6 @@ describe('AnnotationController', function() {
beforeEach(module('h.templates')); beforeEach(module('h.templates'));
/** Return Angular's $compile service. */
function getCompileService() {
var $compile;
inject(function(_$compile_) {
$compile = _$compile_;
});
return $compile;
}
/** Return Angular's $rootScope. */ /** Return Angular's $rootScope. */
function getRootScope() { function getRootScope() {
var $rootScope; var $rootScope;
...@@ -1227,7 +1362,7 @@ describe('AnnotationController', function() { ...@@ -1227,7 +1362,7 @@ describe('AnnotationController', function() {
$provide.value('localStorage', locals.localStorage); $provide.value('localStorage', locals.localStorage);
}); });
locals.element = angular.element('<div annotation="annotation">'); locals.element = angular.element('<div annotation="annotation">');
var compiledElement = getCompileService()(locals.element); var compiledElement = compileService()(locals.element);
locals.$rootScope = getRootScope(); locals.$rootScope = getRootScope();
locals.parentScope = locals.$rootScope.$new(); locals.parentScope = locals.$rootScope.$new();
locals.parentScope.annotation = args.annotation || {}; locals.parentScope.annotation = args.annotation || {};
...@@ -1452,101 +1587,5 @@ describe('AnnotationController', function() { ...@@ -1452,101 +1587,5 @@ describe('AnnotationController', function() {
controller.revert(); controller.revert();
assert.equal(controller.annotation.text, void 0); assert.equal(controller.annotation.text, void 0);
}); });
});
describe('validate()', function() {
var validate = require('../annotation').validate;
it('returns undefined if value is not an object', function() {
var i;
var values = [2, 'foo', true, null];
for (i = 0; i < values.length; i++) {
assert.equal(validate(values[i]), undefined);
}
});
it(
'returns the length if the value contains a non-empty tags array',
function() {
assert.equal(
validate({
tags: ['foo', 'bar'],
permissions: {
read: ['group:__world__']
},
target: [1, 2, 3]
}),
2);
}
);
it(
'returns the length if the value contains a non-empty text string',
function() {
assert.equal(
validate({
text: 'foobar',
permissions: {
read: ['group:__world__']
},
target: [1, 2, 3]
}),
6);
}
);
it('returns true for private highlights', function() {
assert.equal(
validate({
permissions: {
read: ['acct:seanh@hypothes.is']
},
target: [1, 2, 3]
}),
true);
});
it('returns true for group highlights', function() {
assert.equal(
validate({
permissions: {
read: ['group:foo']
},
target: [1, 2, 3]
}),
true);
});
it('returns false for public highlights', function() {
assert.equal(
validate({
text: void 0,
tags: void 0,
permissions: {
read: ['group:__world__']
},
target: [1, 2, 3]
}),
false);
});
it('handles values with no permissions', function() {
assert.equal(
validate({
permissions: void 0,
target: [1, 2, 3]
}),
true);
});
it('handles permissions objects with no read', function() {
assert.equal(
validate({
permissions: {
read: void 0
},
target: [1, 2, 3]
}),
true);
}); });
}); });
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