Commit 7e43bad0 authored by Kyle Keating's avatar Kyle Keating

Remove unroll and convert its use cases to use forEach instead

`unroll()` is not necessary to maintain as it didn't offer any value when writing tests over a simple `forEach()` call over a list of test parameter sets for a common testing function.
parent 380f6de3
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
const html = require('../html'); const html = require('../html');
const toResult = require('../../../shared/test/promise-util').toResult; const toResult = require('../../../shared/test/promise-util').toResult;
const unroll = require('../../../shared/test/util').unroll;
const fixture = require('./html-anchoring-fixture.html'); const fixture = require('./html-anchoring-fixture.html');
/** Return all text node children of `container`. */ /** Return all text node children of `container`. */
...@@ -320,7 +319,7 @@ describe('HTML anchoring', function() { ...@@ -320,7 +319,7 @@ describe('HTML anchoring', function() {
container.remove(); container.remove();
}); });
const testCases = rangeSpecs.map(function(data) { const testCases = rangeSpecs.map(data => {
return { return {
range: { range: {
startContainer: data[0], startContainer: data[0],
...@@ -333,9 +332,8 @@ describe('HTML anchoring', function() { ...@@ -333,9 +332,8 @@ describe('HTML anchoring', function() {
}; };
}); });
unroll( testCases.forEach(testCase => {
'describes and anchors "#description"', it(`describes and anchors ${testCase.description}`, () => {
function(testCase) {
// Resolve the range descriptor to a DOM Range, verify that the expected // Resolve the range descriptor to a DOM Range, verify that the expected
// text was selected. // text was selected.
const range = toRange(container, testCase.range); const range = toRange(container, testCase.range);
...@@ -373,9 +371,8 @@ describe('HTML anchoring', function() { ...@@ -373,9 +371,8 @@ describe('HTML anchoring', function() {
}); });
}); });
return Promise.all(anchored); return Promise.all(anchored);
}, });
testCases });
);
describe('When anchoring fails', function() { describe('When anchoring fails', function() {
const validQuoteSelector = { const validQuoteSelector = {
...@@ -436,9 +433,8 @@ describe('HTML anchoring', function() { ...@@ -436,9 +433,8 @@ describe('HTML anchoring', function() {
frame.remove(); frame.remove();
}); });
unroll( fixtures.forEach(fixture => {
'generates selectors which match the baseline (#name)', it(`generates selectors which match the baseline ${fixture.name}`, () => {
function(fixture) {
let fixtureHtml = fixture.html; let fixtureHtml = fixture.html;
const annotations = fixture.annotations.rows; const annotations = fixture.annotations.rows;
...@@ -471,9 +467,8 @@ describe('HTML anchoring', function() { ...@@ -471,9 +467,8 @@ describe('HTML anchoring', function() {
}); });
}); });
return Promise.all(annotationsChecked); Promise.all(annotationsChecked);
}, });
fixtures });
);
}); });
}); });
'use strict'; 'use strict';
const adder = require('../adder'); const adder = require('../adder');
const unroll = require('../../shared/test/util').unroll;
function rect(left, top, width, height) { function rect(left, top, width, height) {
return { left: left, top: top, width: width, height: height }; return { left: left, top: top, width: width, height: height };
...@@ -57,9 +56,15 @@ describe('annotator.adder', function() { ...@@ -57,9 +56,15 @@ describe('annotator.adder', function() {
} }
context('when Shadow DOM is supported', function() { context('when Shadow DOM is supported', function() {
unroll( [
'creates the adder DOM in a shadow root (using #attachFn)', {
function(testCase) { attachFn: 'createShadowRoot', // Shadow DOM v0 API
},
{
attachFn: 'attachShadow', // Shadow DOM v1 API
},
].forEach(testCase => {
it(`creates the adder DOM in a shadow root (using ${testCase.attachFn})`, () => {
const adderEl = document.createElement('div'); const adderEl = document.createElement('div');
let shadowEl; let shadowEl;
...@@ -83,16 +88,8 @@ describe('annotator.adder', function() { ...@@ -83,16 +88,8 @@ describe('annotator.adder', function() {
); );
adderEl.remove(); adderEl.remove();
}, });
[ });
{
attachFn: 'createShadowRoot', // Shadow DOM v0 API
},
{
attachFn: 'attachShadow', // Shadow DOM v1 API
},
]
);
}); });
describe('button handling', function() { describe('button handling', function() {
......
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
'use strict'; 'use strict';
const unroll = require('../../../shared/test/util').unroll;
const Guest = require('../../guest'); const Guest = require('../../guest');
function quoteSelector(quote) { function quoteSelector(quote) {
...@@ -71,9 +70,25 @@ describe('anchoring', function() { ...@@ -71,9 +70,25 @@ describe('anchoring', function() {
console.warn.restore(); console.warn.restore();
}); });
unroll( [
'should highlight #tag when annotations are loaded', {
function(testCase) { tag: 'a simple quote',
quotes: ["This has not been a scientist's war"],
},
{
// Known failure with nested annotations that are anchored via quotes
// or positions. See https://github.com/hypothesis/h/pull/3313 and
// https://github.com/hypothesis/h/issues/3278
tag: 'nested quotes',
quotes: [
"This has not been a scientist's war;" +
' it has been a war in which all have had a part',
"scientist's war",
],
expectFail: true,
},
].forEach(testCase => {
it(`should highlight ${testCase.tag} when annotations are loaded`, () => {
const normalize = function(quotes) { const normalize = function(quotes) {
return quotes.map(function(q) { return quotes.map(function(q) {
return simplifyWhitespace(q); return simplifyWhitespace(q);
...@@ -97,24 +112,6 @@ describe('anchoring', function() { ...@@ -97,24 +112,6 @@ describe('anchoring', function() {
normalize(testCase.quotes) normalize(testCase.quotes)
); );
}); });
}, });
[ });
{
tag: 'a simple quote',
quotes: ["This has not been a scientist's war"],
},
{
// Known failure with nested annotations that are anchored via quotes
// or positions. See https://github.com/hypothesis/h/pull/3313 and
// https://github.com/hypothesis/h/issues/3278
tag: 'nested quotes',
quotes: [
"This has not been a scientist's war;" +
' it has been a war in which all have had a part',
"scientist's war",
],
expectFail: true,
},
]
);
}); });
'use strict'; 'use strict';
const unroll = require('../../shared/test/util').unroll;
const observable = require('../util/observable'); const observable = require('../util/observable');
const selections = require('../selections'); const selections = require('../selections');
...@@ -61,15 +59,11 @@ describe('selections', function() { ...@@ -61,15 +59,11 @@ describe('selections', function() {
clock.restore(); clock.restore();
}); });
unroll( it('emits the selected range when mouseup occurs', function() {
'emits the selected range when #event occurs', fakeDocument.dispatchEvent({ type: 'mouseup' });
function(testCase) { clock.tick(20);
fakeDocument.dispatchEvent({ type: testCase.event }); assert.calledWith(onSelectionChanged, range);
clock.tick(testCase.delay); });
assert.calledWith(onSelectionChanged, range);
},
[{ event: 'mouseup', delay: 20 }]
);
it('emits an event if there is a selection at the initial subscription', function() { it('emits an event if there is a selection at the initial subscription', function() {
const onInitialSelection = sinon.stub(); const onInitialSelection = sinon.stub();
......
'use strict';
/**
* Helper for writing parameterized tests.
*
* This is a wrapper around the `it()` function for creating a Mocha test case
* which takes an array of fixture objects and calls it() once for each fixture,
* passing in the fixture object as an argument to the test function.
*
* Usage:
* unroll('should return #output with #input', function (fixture) {
* assert.equal(functionUnderTest(fixture.input), fixture.output);
* },[
* {input: 'foo', output: 'bar'}
* ]);
*
* Based on https://github.com/lawrencec/Unroll with the following changes:
*
* 1. Support for test functions that return promises
* 2. Mocha's `it()` is the only supported test function
* 3. Fixtures are objects rather than arrays
*
* @param {string} description - Description with optional '#key' placeholders
* which are replaced by the values of the corresponding key from each
* fixture object.
* @param {Function} testFn - Test function which can accept either `fixture`
* or `done, fixture` as arguments, where `done` is the callback for
* reporting completion of an async test and `fixture` is an object
* from the `fixtures` array.
* @param {Array<T>} fixtures - Array of fixture objects.
*/
function unroll(description, testFn, fixtures) {
fixtures.forEach(function(fixture) {
const caseDescription = Object.keys(fixture).reduce(function(desc, key) {
return desc.replace('#' + key, String(fixture[key]));
}, description);
it(caseDescription, function(done) {
if (testFn.length === 1) {
// Test case does not accept a 'done' callback argument, so we either
// call done() immediately if it returns a non-Promiselike object
// or when the Promise resolves otherwise
const result = testFn(fixture);
if (typeof result === 'object' && result.then) {
result.then(function() {
done();
}, done);
} else {
done();
}
} else {
// Test case accepts a 'done' callback argument and takes responsibility
// for calling it when the test completes.
testFn(done, fixture);
}
});
});
}
module.exports = {
unroll: unroll,
};
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
const { createElement } = require('preact'); const { createElement } = require('preact');
const { shallow } = require('enzyme'); const { shallow } = require('enzyme');
const unroll = require('../../../shared/test/util').unroll;
const fixtures = require('../../test/annotation-fixtures'); const fixtures = require('../../test/annotation-fixtures');
const AnnotationHeader = require('../annotation-header'); const AnnotationHeader = require('../annotation-header');
...@@ -36,30 +35,28 @@ describe('AnnotationHeader', () => { ...@@ -36,30 +35,28 @@ describe('AnnotationHeader', () => {
assert.equal(replyCollapseLink.prop('onClick'), fakeCallback); assert.equal(replyCollapseLink.prop('onClick'), fakeCallback);
}); });
unroll( [
'it should render the annotation reply count', {
testCase => { replyCount: 0,
expected: '0 replies',
},
{
replyCount: 1,
expected: '1 reply',
},
{
replyCount: 2,
expected: '2 replies',
},
].forEach(testCase => {
it(`it should render the annotation reply count (${testCase.replyCount})`, () => {
const wrapper = createAnnotationHeader({ const wrapper = createAnnotationHeader({
replyCount: testCase.replyCount, replyCount: testCase.replyCount,
}); });
const replyCollapseLink = wrapper.find('.annotation-link'); const replyCollapseLink = wrapper.find('.annotation-link');
assert.equal(replyCollapseLink.text(), testCase.expected); assert.equal(replyCollapseLink.text(), testCase.expected);
}, });
[ });
{
replyCount: 0,
expected: '0 replies',
},
{
replyCount: 1,
expected: '1 reply',
},
{
replyCount: 2,
expected: '2 replies',
},
]
);
}); });
describe('timestamps', () => { describe('timestamps', () => {
......
...@@ -4,13 +4,11 @@ const angular = require('angular'); ...@@ -4,13 +4,11 @@ const angular = require('angular');
const events = require('../../events'); const events = require('../../events');
const fixtures = require('../../test/annotation-fixtures'); const fixtures = require('../../test/annotation-fixtures');
const testUtil = require('../../../shared/test/util');
const util = require('../../directive/test/util'); const util = require('../../directive/test/util');
const annotationComponent = require('../annotation'); const annotationComponent = require('../annotation');
const inject = angular.mock.inject; const inject = angular.mock.inject;
const unroll = testUtil.unroll;
const draftFixtures = { const draftFixtures = {
shared: { text: 'draft', tags: [], isPrivate: false }, shared: { text: 'draft', tags: [], isPrivate: false },
...@@ -917,9 +915,39 @@ describe('annotation', function() { ...@@ -917,9 +915,39 @@ describe('annotation', function() {
}); });
describe('#shouldShowLicense', function() { describe('#shouldShowLicense', function() {
unroll( [
'returns #expected if #case_', {
function(testCase) { case_: 'the annotation is not being edited',
draft: null,
group: groupFixtures.open,
expected: false,
},
{
case_: 'the draft is private',
draft: draftFixtures.private,
group: groupFixtures.open,
expected: false,
},
{
case_: 'the group is private',
draft: draftFixtures.shared,
group: groupFixtures.private,
expected: false,
},
{
case_: 'the draft is shared and the group is open',
draft: draftFixtures.shared,
group: groupFixtures.open,
expected: true,
},
{
case_: 'the draft is shared and the group is restricted',
draft: draftFixtures.shared,
group: groupFixtures.restricted,
expected: true,
},
].forEach(testCase => {
it(`returns ${testCase.expected} if ${testCase.case_}`, () => {
const ann = fixtures.publicAnnotation(); const ann = fixtures.publicAnnotation();
ann.group = testCase.group.id; ann.group = testCase.group.id;
fakeStore.getDraft.returns(testCase.draft); fakeStore.getDraft.returns(testCase.draft);
...@@ -928,40 +956,8 @@ describe('annotation', function() { ...@@ -928,40 +956,8 @@ describe('annotation', function() {
const controller = createDirective(ann).controller; const controller = createDirective(ann).controller;
assert.equal(controller.shouldShowLicense(), testCase.expected); assert.equal(controller.shouldShowLicense(), testCase.expected);
}, });
[ });
{
case_: 'the annotation is not being edited',
draft: null,
group: groupFixtures.open,
expected: false,
},
{
case_: 'the draft is private',
draft: draftFixtures.private,
group: groupFixtures.open,
expected: false,
},
{
case_: 'the group is private',
draft: draftFixtures.shared,
group: groupFixtures.private,
expected: false,
},
{
case_: 'the draft is shared and the group is open',
draft: draftFixtures.shared,
group: groupFixtures.open,
expected: true,
},
{
case_: 'the draft is shared and the group is restricted',
draft: draftFixtures.shared,
group: groupFixtures.restricted,
expected: true,
},
]
);
}); });
describe('#authorize', function() { describe('#authorize', function() {
...@@ -1272,9 +1268,32 @@ describe('annotation', function() { ...@@ -1272,9 +1268,32 @@ describe('annotation', function() {
assert.equal(el[0].querySelector('blockquote').textContent, '<<-&->>'); assert.equal(el[0].querySelector('blockquote').textContent, '<<-&->>');
}); });
unroll( [
'renders hidden annotations with a custom text class (#context)', {
function(testCase) { context: 'for moderators',
ann: Object.assign(fixtures.moderatedAnnotation({ hidden: true }), {
// Content still present.
text: 'Some offensive content',
}),
textClass: {
'annotation-body is-hidden': true,
'has-content': true,
},
},
{
context: 'for non-moderators',
ann: Object.assign(fixtures.moderatedAnnotation({ hidden: true }), {
// Content filtered out by service.
tags: [],
text: '',
}),
textClass: {
'annotation-body is-hidden': true,
'has-content': false,
},
},
].forEach(testCase => {
it(`renders hidden annotations with a custom text class (${testCase.context})`, () => {
const el = createDirective(testCase.ann).element; const el = createDirective(testCase.ann).element;
assert.match( assert.match(
el.find('markdown-view').controller('markdownView'), el.find('markdown-view').controller('markdownView'),
...@@ -1282,33 +1301,8 @@ describe('annotation', function() { ...@@ -1282,33 +1301,8 @@ describe('annotation', function() {
textClass: testCase.textClass, textClass: testCase.textClass,
}) })
); );
}, });
[ });
{
context: 'for moderators',
ann: Object.assign(fixtures.moderatedAnnotation({ hidden: true }), {
// Content still present.
text: 'Some offensive content',
}),
textClass: {
'annotation-body is-hidden': true,
'has-content': true,
},
},
{
context: 'for non-moderators',
ann: Object.assign(fixtures.moderatedAnnotation({ hidden: true }), {
// Content filtered out by service.
tags: [],
text: '',
}),
textClass: {
'annotation-body is-hidden': true,
'has-content': false,
},
},
]
);
it('flags the annotation when the user clicks the "Flag" button', function() { it('flags the annotation when the user clicks the "Flag" button', function() {
fakeAnnotationMapper.flagAnnotation.returns(Promise.resolve()); fakeAnnotationMapper.flagAnnotation.returns(Promise.resolve());
......
...@@ -5,7 +5,6 @@ const { createElement } = require('preact'); ...@@ -5,7 +5,6 @@ const { createElement } = require('preact');
const ModerationBanner = require('../moderation-banner'); const ModerationBanner = require('../moderation-banner');
const fixtures = require('../../test/annotation-fixtures'); const fixtures = require('../../test/annotation-fixtures');
const unroll = require('../../../shared/test/util').unroll;
const moderatedAnnotation = fixtures.moderatedAnnotation; const moderatedAnnotation = fixtures.moderatedAnnotation;
...@@ -44,9 +43,40 @@ describe('ModerationBanner', () => { ...@@ -44,9 +43,40 @@ describe('ModerationBanner', () => {
ModerationBanner.$imports.$restore(); ModerationBanner.$imports.$restore();
}); });
unroll( [
'displays if user is a moderator and annotation is hidden or flagged', {
function(testCase) { // Not hidden or flagged and user is not a moderator
test: 'not hidden or flagged and user is not a moderator',
ann: fixtures.defaultAnnotation(),
expectVisible: false,
},
{
test: 'hidden, but user is not a moderator',
ann: {
...fixtures.defaultAnnotation(),
hidden: true,
},
expectVisible: false,
},
{
test: 'not hidden or flagged and user is a moderator',
ann: fixtures.moderatedAnnotation({ flagCount: 0, hidden: false }),
expectVisible: false,
},
{
test: 'flagged but not hidden and the user is a moderator',
ann: fixtures.moderatedAnnotation({ flagCount: 1, hidden: false }),
expectVisible: true,
},
{
// The client only allows moderators to hide flagged annotations but
// an unflagged annotation can still be hidden via the API.
test: 'hidden but not flagged and the user is a moderator',
ann: fixtures.moderatedAnnotation({ flagCount: 0, hidden: true }),
expectVisible: true,
},
].forEach(testCase => {
it(`displays if the annotation is ${testCase.test}`, () => {
const wrapper = createComponent({ const wrapper = createComponent({
annotation: testCase.ann, annotation: testCase.ann,
}); });
...@@ -55,39 +85,8 @@ describe('ModerationBanner', () => { ...@@ -55,39 +85,8 @@ describe('ModerationBanner', () => {
} else { } else {
assert.isFalse(wrapper.exists()); assert.isFalse(wrapper.exists());
} }
}, });
[ });
{
// Not hidden or flagged and user is not a moderator
ann: fixtures.defaultAnnotation(),
expectVisible: false,
},
{
// Hidden, but user is not a moderator
ann: {
...fixtures.defaultAnnotation(),
hidden: true,
},
expectVisible: false,
},
{
// Not hidden or flagged and user is a moderator
ann: fixtures.moderatedAnnotation({ flagCount: 0, hidden: false }),
expectVisible: false,
},
{
// Flagged but not hidden
ann: fixtures.moderatedAnnotation({ flagCount: 1, hidden: false }),
expectVisible: true,
},
{
// Hidden but not flagged. The client only allows moderators to hide flagged
// annotations but an unflagged annotation can still be hidden via the API.
ann: fixtures.moderatedAnnotation({ flagCount: 0, hidden: true }),
expectVisible: true,
},
]
);
it('displays the number of flags the annotation has received', function() { it('displays the number of flags the annotation has received', function() {
const ann = fixtures.moderatedAnnotation({ flagCount: 10 }); const ann = fixtures.moderatedAnnotation({ flagCount: 10 });
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
const { createElement } = require('preact'); const { createElement } = require('preact');
const { shallow } = require('enzyme'); const { shallow } = require('enzyme');
const unroll = require('../../../shared/test/util').unroll;
const ShareAnnotationsPanel = require('../share-annotations-panel'); const ShareAnnotationsPanel = require('../share-annotations-panel');
const SidebarPanel = require('../sidebar-panel'); const SidebarPanel = require('../sidebar-panel');
...@@ -95,9 +94,24 @@ describe('ShareAnnotationsPanel', () => { ...@@ -95,9 +94,24 @@ describe('ShareAnnotationsPanel', () => {
}); });
}); });
unroll( [
'it displays appropriate help text depending on group type', {
testCase => { groupType: 'private',
introPattern: /Use this link.*with other group members/,
visibilityPattern: /Annotations in the private group.*are only visible to group members/,
},
{
groupType: 'restricted',
introPattern: /Use this link to share these annotations with anyone/,
visibilityPattern: /Anyone using this link may view the annotations in the group/,
},
{
groupType: 'open',
introPattern: /Use this link to share these annotations with anyone/,
visibilityPattern: /Anyone using this link may view the annotations in the group/,
},
].forEach(testCase => {
it('it displays appropriate help text depending on group type', () => {
fakeStore.focusedGroup.returns({ fakeStore.focusedGroup.returns({
type: testCase.groupType, type: testCase.groupType,
name: 'Test Group', name: 'Test Group',
...@@ -115,25 +129,8 @@ describe('ShareAnnotationsPanel', () => { ...@@ -115,25 +129,8 @@ describe('ShareAnnotationsPanel', () => {
wrapper.find('.share-annotations-panel').text(), wrapper.find('.share-annotations-panel').text(),
testCase.visibilityPattern testCase.visibilityPattern
); );
}, });
[ });
{
groupType: 'private',
introPattern: /Use this link.*with other group members/,
visibilityPattern: /Annotations in the private group.*are only visible to group members/,
},
{
groupType: 'restricted',
introPattern: /Use this link to share these annotations with anyone/,
visibilityPattern: /Anyone using this link may view the annotations in the group/,
},
{
groupType: 'open',
introPattern: /Use this link to share these annotations with anyone/,
visibilityPattern: /Anyone using this link may view the annotations in the group/,
},
]
);
describe('web share link', () => { describe('web share link', () => {
it('displays web share link in readonly form input', () => { it('displays web share link in readonly form input', () => {
......
'use strict'; 'use strict';
const angular = require('angular'); const angular = require('angular');
const unroll = require('../../../shared/test/util').unroll;
describe('BrandingDirective', function() { describe('BrandingDirective', function() {
let $compile; let $compile;
...@@ -62,9 +61,8 @@ describe('BrandingDirective', function() { ...@@ -62,9 +61,8 @@ describe('BrandingDirective', function() {
assert.equal(el[0].style.backgroundColor, ''); assert.equal(el[0].style.backgroundColor, '');
}); });
unroll( require('./h-branding-fixtures').forEach(testCase => {
'applies branding to elements', it('applies branding to elements', () => {
function(testCase) {
applyBrandingSettings(testCase.settings); applyBrandingSettings(testCase.settings);
const el = makeElementWithAttrs(testCase.attrs); const el = makeElementWithAttrs(testCase.attrs);
...@@ -84,7 +82,6 @@ describe('BrandingDirective', function() { ...@@ -84,7 +82,6 @@ describe('BrandingDirective', function() {
testCase.expectedPropValue testCase.expectedPropValue
); );
} }
}, });
require('./h-branding-fixtures') });
);
}); });
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
const angular = require('angular'); const angular = require('angular');
const unroll = require('../../../shared/test/util').unroll;
const util = require('./util'); const util = require('./util');
function testComponent() { function testComponent() {
...@@ -35,22 +34,20 @@ describe('hOnTouch', function() { ...@@ -35,22 +34,20 @@ describe('hOnTouch', function() {
testEl = util.createDirective(document, 'test', {}); testEl = util.createDirective(document, 'test', {});
}); });
unroll( [
'calls the handler when activated with a "#event" event', {
function(testCase) { event: 'touchstart',
},
{
event: 'mousedown',
},
{
event: 'click',
},
].forEach(testCase => {
it(`calls the handler when activated with a "${testCase.event}" event`, () => {
util.sendEvent(testEl[0].querySelector('div'), testCase.event); util.sendEvent(testEl[0].querySelector('div'), testCase.event);
assert.equal(testEl.ctrl.tapCount, 1); assert.equal(testEl.ctrl.tapCount, 1);
}, });
[ });
{
event: 'touchstart',
},
{
event: 'mousedown',
},
{
event: 'click',
},
]
);
}); });
...@@ -5,7 +5,6 @@ const immutable = require('seamless-immutable'); ...@@ -5,7 +5,6 @@ const immutable = require('seamless-immutable');
const storeFactory = require('../index'); const storeFactory = require('../index');
const annotationFixtures = require('../../test/annotation-fixtures'); const annotationFixtures = require('../../test/annotation-fixtures');
const metadata = require('../../util/annotation-metadata'); const metadata = require('../../util/annotation-metadata');
const unroll = require('../../../shared/test/util').unroll;
const uiConstants = require('../../ui-constants'); const uiConstants = require('../../ui-constants');
const defaultAnnotation = annotationFixtures.defaultAnnotation; const defaultAnnotation = annotationFixtures.defaultAnnotation;
...@@ -360,14 +359,12 @@ describe('store', function() { ...@@ -360,14 +359,12 @@ describe('store', function() {
}); });
describe('#setShowHighlights()', function() { describe('#setShowHighlights()', function() {
unroll( [{ state: true }, { state: false }].forEach(testCase => {
'sets the visibleHighlights state flag to #state', it(`sets the visibleHighlights state flag to ${testCase.state}`, () => {
function(testCase) {
store.setShowHighlights(testCase.state); store.setShowHighlights(testCase.state);
assert.equal(store.getState().viewer.visibleHighlights, testCase.state); assert.equal(store.getState().viewer.visibleHighlights, testCase.state);
}, });
[{ state: true }, { state: false }] });
);
}); });
describe('#updatingAnchorStatus', function() { describe('#updatingAnchorStatus', function() {
......
...@@ -3,8 +3,6 @@ ...@@ -3,8 +3,6 @@
const angular = require('angular'); const angular = require('angular');
const immutable = require('seamless-immutable'); const immutable = require('seamless-immutable');
const unroll = require('../../../shared/test/util').unroll;
const fixtures = immutable({ const fixtures = immutable({
annotations: [ annotations: [
{ {
...@@ -87,9 +85,17 @@ describe('annotation threading', function() { ...@@ -87,9 +85,17 @@ describe('annotation threading', function() {
assert.equal(rootThread.thread(store.getState()).children[0].id, '2'); assert.equal(rootThread.thread(store.getState()).children[0].id, '2');
}); });
unroll( [
'should sort annotations by #mode', {
function(testCase) { sortKey: 'Oldest',
expectedOrder: ['1', '2'],
},
{
sortKey: 'Newest',
expectedOrder: ['2', '1'],
},
].forEach(testCase => {
it(`should sort annotations by ${testCase.mode}`, () => {
store.addAnnotations(fixtures.annotations); store.addAnnotations(fixtures.annotations);
store.setSortKey(testCase.sortKey); store.setSortKey(testCase.sortKey);
const actualOrder = rootThread const actualOrder = rootThread
...@@ -98,16 +104,6 @@ describe('annotation threading', function() { ...@@ -98,16 +104,6 @@ describe('annotation threading', function() {
return thread.annotation.id; return thread.annotation.id;
}); });
assert.deepEqual(actualOrder, testCase.expectedOrder); assert.deepEqual(actualOrder, testCase.expectedOrder);
}, });
[ });
{
sortKey: 'Oldest',
expectedOrder: ['1', '2'],
},
{
sortKey: 'Newest',
expectedOrder: ['2', '1'],
},
]
);
}); });
'use strict'; 'use strict';
const commands = require('../markdown-commands'); const commands = require('../markdown-commands');
const unroll = require('../../shared/test/util').unroll;
/** /**
* Convert a string containing '<sel>' and '</sel>' markers * Convert a string containing '<sel>' and '</sel>' markers
...@@ -84,7 +83,7 @@ describe('markdown commands', function() { ...@@ -84,7 +83,7 @@ describe('markdown commands', function() {
}); });
describe('block formatting', function() { describe('block formatting', function() {
const FIXTURES = [ [
{ {
tag: 'adds formatting to blocks', tag: 'adds formatting to blocks',
input: 'one\n<sel>two\nthree</sel>\nfour', input: 'one\n<sel>two\nthree</sel>\nfour',
...@@ -105,19 +104,15 @@ describe('markdown commands', function() { ...@@ -105,19 +104,15 @@ describe('markdown commands', function() {
input: '<sel></sel>', input: '<sel></sel>',
output: '> <sel></sel>', output: '> <sel></sel>',
}, },
]; ].forEach(fixture => {
it(fixture.tag, () => {
unroll(
'#tag',
function(fixture) {
const output = commands.toggleBlockStyle( const output = commands.toggleBlockStyle(
parseState(fixture.input), parseState(fixture.input),
'> ' '> '
); );
assert.equal(formatState(output), fixture.output); assert.equal(formatState(output), fixture.output);
}, });
FIXTURES });
);
}); });
describe('link formatting', function() { describe('link formatting', function() {
...@@ -125,35 +120,31 @@ describe('markdown commands', function() { ...@@ -125,35 +120,31 @@ describe('markdown commands', function() {
return commands.convertSelectionToLink(parseState(text), linkType); return commands.convertSelectionToLink(parseState(text), linkType);
}; };
unroll( [{ selection: 'two' }, { selection: 'jim:smith' }].forEach(testCase => {
'converts text to links', it('converts text to links', () => {
function(testCase) {
const sel = testCase.selection; const sel = testCase.selection;
const output = linkify('one <sel>' + sel + '</sel> three'); const output = linkify('one <sel>' + sel + '</sel> three');
assert.equal( assert.equal(
formatState(output), formatState(output),
'one [' + sel + '](<sel>http://insert-your-link-here.com</sel>) three' 'one [' + sel + '](<sel>http://insert-your-link-here.com</sel>) three'
); );
}, });
[{ selection: 'two' }, { selection: 'jim:smith' }] });
);
unroll( [
'converts URLs to links', { selection: 'http://foobar.com' },
function(testCase) { { selection: 'https://twitter.com/username' },
{ selection: ' http://example.com/url-with-a-leading-space' },
].forEach(testCase => {
it(`converts URLs to links`, () => {
const sel = testCase.selection; const sel = testCase.selection;
const output = linkify('one <sel>' + sel + '</sel> three'); const output = linkify('one <sel>' + sel + '</sel> three');
assert.equal( assert.equal(
formatState(output), formatState(output),
'one [<sel>Description</sel>](' + sel + ') three' 'one [<sel>Description</sel>](' + sel + ') three'
); );
}, });
[ });
{ selection: 'http://foobar.com' },
{ selection: 'https://twitter.com/username' },
{ selection: ' http://example.com/url-with-a-leading-space' },
]
);
it('converts URLs to image links', function() { it('converts URLs to image links', function() {
const output = linkify( const output = linkify(
......
'use strict'; 'use strict';
const util = require('../../shared/test/util');
const VirtualThreadList = require('../virtual-thread-list'); const VirtualThreadList = require('../virtual-thread-list');
const unroll = util.unroll;
describe('VirtualThreadList', function() { describe('VirtualThreadList', function() {
let lastState; let lastState;
let threadList; let threadList;
...@@ -105,9 +102,36 @@ describe('VirtualThreadList', function() { ...@@ -105,9 +102,36 @@ describe('VirtualThreadList', function() {
}); });
}); });
unroll( [
'generates expected state when #when', {
function(testCase) { when: 'scrollRoot is scrolled to top of list',
threads: 100,
scrollOffset: 0,
windowHeight: 300,
expectedVisibleThreads: idRange(0, 5),
expectedHeightAbove: 0,
expectedHeightBelow: 18800,
},
{
when: 'scrollRoot is scrolled to middle of list',
threads: 100,
scrollOffset: 2000,
windowHeight: 300,
expectedVisibleThreads: idRange(5, 15),
expectedHeightAbove: 1000,
expectedHeightBelow: 16800,
},
{
when: 'scrollRoot is scrolled to bottom of list',
threads: 100,
scrollOffset: 18800,
windowHeight: 300,
expectedVisibleThreads: idRange(89, 99),
expectedHeightAbove: 17800,
expectedHeightBelow: 0,
},
].forEach(testCase => {
it(`generates expected state when ${testCase.when}`, () => {
const thread = generateRootThread(testCase.threads); const thread = generateRootThread(testCase.threads);
fakeScrollRoot.scrollTop = testCase.scrollOffset; fakeScrollRoot.scrollTop = testCase.scrollOffset;
...@@ -134,37 +158,8 @@ describe('VirtualThreadList', function() { ...@@ -134,37 +158,8 @@ describe('VirtualThreadList', function() {
lastState.offscreenLowerHeight, lastState.offscreenLowerHeight,
testCase.expectedHeightBelow testCase.expectedHeightBelow
); );
}, });
[ });
{
when: 'scrollRoot is scrolled to top of list',
threads: 100,
scrollOffset: 0,
windowHeight: 300,
expectedVisibleThreads: idRange(0, 5),
expectedHeightAbove: 0,
expectedHeightBelow: 18800,
},
{
when: 'scrollRoot is scrolled to middle of list',
threads: 100,
scrollOffset: 2000,
windowHeight: 300,
expectedVisibleThreads: idRange(5, 15),
expectedHeightAbove: 1000,
expectedHeightBelow: 16800,
},
{
when: 'scrollRoot is scrolled to bottom of list',
threads: 100,
scrollOffset: 18800,
windowHeight: 300,
expectedVisibleThreads: idRange(89, 99),
expectedHeightAbove: 17800,
expectedHeightBelow: 0,
},
]
);
it('recalculates when a window.resize occurs', function() { it('recalculates when a window.resize occurs', function() {
lastState = null; lastState = null;
...@@ -184,9 +179,17 @@ describe('VirtualThreadList', function() { ...@@ -184,9 +179,17 @@ describe('VirtualThreadList', function() {
}); });
describe('#setThreadHeight', function() { describe('#setThreadHeight', function() {
unroll( [
'affects visible threads', {
function(testCase) { threadHeight: 1000,
expectedVisibleThreads: idRange(0, 1),
},
{
threadHeight: 300,
expectedVisibleThreads: idRange(0, 4),
},
].forEach(testCase => {
it('affects visible threads', () => {
const thread = generateRootThread(10); const thread = generateRootThread(10);
fakeWindow.innerHeight = 500; fakeWindow.innerHeight = 500;
fakeScrollRoot.scrollTop = 0; fakeScrollRoot.scrollTop = 0;
...@@ -198,18 +201,8 @@ describe('VirtualThreadList', function() { ...@@ -198,18 +201,8 @@ describe('VirtualThreadList', function() {
threadIDs(lastState.visibleThreads), threadIDs(lastState.visibleThreads),
testCase.expectedVisibleThreads testCase.expectedVisibleThreads
); );
}, });
[ });
{
threadHeight: 1000,
expectedVisibleThreads: idRange(0, 1),
},
{
threadHeight: 300,
expectedVisibleThreads: idRange(0, 4),
},
]
);
}); });
describe('#detach', function() { describe('#detach', function() {
...@@ -228,9 +221,24 @@ describe('VirtualThreadList', function() { ...@@ -228,9 +221,24 @@ describe('VirtualThreadList', function() {
}); });
describe('#yOffsetOf', function() { describe('#yOffsetOf', function() {
unroll( [
'returns #offset as the Y offset of the #nth thread', {
function(testCase) { nth: 'first',
index: 0,
offset: 0,
},
{
nth: 'second',
index: 1,
offset: 100,
},
{
nth: 'last',
index: 9,
offset: 900,
},
].forEach(testCase => {
it(`returns ${testCase.offset} as the Y offset of the ${testCase.nth} thread`, () => {
const thread = generateRootThread(10); const thread = generateRootThread(10);
threadList.setRootThread(thread); threadList.setRootThread(thread);
idRange(0, 10).forEach(function(id) { idRange(0, 10).forEach(function(id) {
...@@ -238,24 +246,7 @@ describe('VirtualThreadList', function() { ...@@ -238,24 +246,7 @@ describe('VirtualThreadList', function() {
}); });
const id = idRange(testCase.index, testCase.index)[0]; const id = idRange(testCase.index, testCase.index)[0];
assert.equal(threadList.yOffsetOf(id), testCase.offset); assert.equal(threadList.yOffsetOf(id), testCase.offset);
}, });
[ });
{
nth: 'first',
index: 0,
offset: 0,
},
{
nth: 'second',
index: 1,
offset: 100,
},
{
nth: 'last',
index: 9,
offset: 900,
},
]
);
}); });
}); });
...@@ -3,8 +3,6 @@ ...@@ -3,8 +3,6 @@
const annotationMetadata = require('../annotation-metadata'); const annotationMetadata = require('../annotation-metadata');
const fixtures = require('../../test/annotation-fixtures'); const fixtures = require('../../test/annotation-fixtures');
const unroll = require('../../../shared/test/util').unroll;
const documentMetadata = annotationMetadata.documentMetadata; const documentMetadata = annotationMetadata.documentMetadata;
const domainAndTitle = annotationMetadata.domainAndTitle; const domainAndTitle = annotationMetadata.domainAndTitle;
...@@ -288,23 +286,21 @@ describe('annotation-metadata', function() { ...@@ -288,23 +286,21 @@ describe('annotation-metadata', function() {
assert.isTrue(annotationMetadata.isPublic(fixtures.publicAnnotation())); assert.isTrue(annotationMetadata.isPublic(fixtures.publicAnnotation()));
}); });
unroll( [
'returns false if an annotation is not publicly readable', {
function(testCase) { read: ['acct:someemail@localhost'],
},
{
read: ['something invalid'],
},
].forEach(testCase => {
it('returns false if an annotation is not publicly readable', () => {
const annotation = Object.assign(fixtures.defaultAnnotation(), { const annotation = Object.assign(fixtures.defaultAnnotation(), {
permissions: testCase, permissions: testCase,
}); });
assert.isFalse(annotationMetadata.isPublic(annotation)); assert.isFalse(annotationMetadata.isPublic(annotation));
}, });
[ });
{
read: ['acct:someemail@localhost'],
},
{
read: ['something invalid'],
},
]
);
it('returns false if an annotation is missing permissions', function() { it('returns false if an annotation is missing permissions', function() {
assert.isFalse(annotationMetadata.isPublic(fixtures.defaultAnnotation())); assert.isFalse(annotationMetadata.isPublic(fixtures.defaultAnnotation()));
......
...@@ -3,38 +3,66 @@ ...@@ -3,38 +3,66 @@
const fixtures = require('../../test/annotation-fixtures'); const fixtures = require('../../test/annotation-fixtures');
const uiConstants = require('../../ui-constants'); const uiConstants = require('../../ui-constants');
const tabs = require('../tabs'); const tabs = require('../tabs');
const unroll = require('../../../shared/test/util').unroll;
describe('tabs', function() { describe('tabs', function() {
describe('tabForAnnotation', function() { describe('tabForAnnotation', function() {
unroll( [
'shows annotation in correct tab', {
function(testCase) { ann: fixtures.defaultAnnotation(),
expectedTab: uiConstants.TAB_ANNOTATIONS,
},
{
ann: fixtures.oldPageNote(),
expectedTab: uiConstants.TAB_NOTES,
},
{
ann: Object.assign(fixtures.defaultAnnotation(), { $orphan: true }),
expectedTab: uiConstants.TAB_ORPHANS,
},
].forEach(testCase => {
it(`shows annotation in correct tab (${testCase.expectedTab})`, () => {
const ann = testCase.ann; const ann = testCase.ann;
const expectedTab = testCase.expectedTab; const expectedTab = testCase.expectedTab;
assert.equal(tabs.tabForAnnotation(ann), expectedTab); assert.equal(tabs.tabForAnnotation(ann), expectedTab);
}, });
[ });
{
ann: fixtures.defaultAnnotation(),
expectedTab: uiConstants.TAB_ANNOTATIONS,
},
{
ann: fixtures.oldPageNote(),
expectedTab: uiConstants.TAB_NOTES,
},
{
ann: Object.assign(fixtures.defaultAnnotation(), { $orphan: true }),
expectedTab: uiConstants.TAB_ORPHANS,
},
]
);
}); });
describe('shouldShowInTab', function() { describe('shouldShowInTab', function() {
unroll( [
'returns true if the annotation should be shown', {
function(testCase) { // Anchoring in progress.
anchorTimeout: false,
orphan: undefined,
expectedTab: null,
},
{
// Anchoring succeeded.
anchorTimeout: false,
orphan: false,
expectedTab: uiConstants.TAB_ANNOTATIONS,
},
{
// Anchoring failed.
anchorTimeout: false,
orphan: true,
expectedTab: uiConstants.TAB_ORPHANS,
},
{
// Anchoring timed out.
anchorTimeout: true,
orphan: undefined,
expectedTab: uiConstants.TAB_ANNOTATIONS,
},
{
// Anchoring initially timed out but eventually
// failed.
anchorTimeout: true,
orphan: true,
expectedTab: uiConstants.TAB_ORPHANS,
},
].forEach(testCase => {
it('returns true if the annotation should be shown', () => {
const ann = fixtures.defaultAnnotation(); const ann = fixtures.defaultAnnotation();
ann.$anchorTimeout = testCase.anchorTimeout; ann.$anchorTimeout = testCase.anchorTimeout;
ann.$orphan = testCase.orphan; ann.$orphan = testCase.orphan;
...@@ -47,40 +75,7 @@ describe('tabs', function() { ...@@ -47,40 +75,7 @@ describe('tabs', function() {
tabs.shouldShowInTab(ann, uiConstants.TAB_ORPHANS), tabs.shouldShowInTab(ann, uiConstants.TAB_ORPHANS),
testCase.expectedTab === uiConstants.TAB_ORPHANS testCase.expectedTab === uiConstants.TAB_ORPHANS
); );
}, });
[ });
{
// Anchoring in progress.
anchorTimeout: false,
orphan: undefined,
expectedTab: null,
},
{
// Anchoring succeeded.
anchorTimeout: false,
orphan: false,
expectedTab: uiConstants.TAB_ANNOTATIONS,
},
{
// Anchoring failed.
anchorTimeout: false,
orphan: true,
expectedTab: uiConstants.TAB_ORPHANS,
},
{
// Anchoring timed out.
anchorTimeout: true,
orphan: undefined,
expectedTab: uiConstants.TAB_ANNOTATIONS,
},
{
// Anchoring initially timed out but eventually
// failed.
anchorTimeout: true,
orphan: true,
expectedTab: uiConstants.TAB_ORPHANS,
},
]
);
}); });
}); });
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