Commit 8f4cc121 authored by Nick Stenning's avatar Nick Stenning

Fix missing PDF highlights

fda63b4 unfortunately broke highlighting in PDFs, because the PDF
anchoring code first finds the text in the document, and then uses a
TextPositionSelector and the HTML anchoring code to select a range in
the rendered page for highlighting.

This commit fixes that issue by moving the TextQuoteSelector check up a
level into `Guest#anchor`.

I've added two tests for this in `guest-test.coffee` -- one tests that
annotations where the target contains no TextQuoteSelector are marked as
orphans, and the second tests that the low-level anchoring code is never
called in this case.

I have also removed the integration test, on the basis that I don't
think integration testing failure cases is a good habit to get into.

Fixes #3530.
parent 7b4fc59d
...@@ -50,11 +50,6 @@ exports.anchor = (root, selectors, options = {}) -> ...@@ -50,11 +50,6 @@ exports.anchor = (root, selectors, options = {}) ->
when 'RangeSelector' when 'RangeSelector'
range = selector range = selector
# Assert that there is a quote selector, since otherwise we cannot validate
# the rest of the annotation's selectors.
if not quote?
return Promise.reject(new Error('quote selector not found'))
# Assert the quote matches the stored quote, if applicable # Assert the quote matches the stored quote, if applicable
maybeAssertQuote = (range) -> maybeAssertQuote = (range) ->
if quote?.exact? and range.toString() != quote.exact if quote?.exact? and range.toString() != quote.exact
......
...@@ -187,6 +187,15 @@ module.exports = class Guest extends Annotator ...@@ -187,6 +187,15 @@ module.exports = class Guest extends Annotator
annotation.target ?= [] annotation.target ?= []
locate = (target) -> locate = (target) ->
# Check that the anchor has a TextQuoteSelector -- without a
# TextQuoteSelector we have no basis on which to verify that we have
# reanchored correctly and so we shouldn't even try.
#
# Returning an anchor without a range will result in this annotation being
# treated as an orphan (assuming no other targets anchor).
if not (target.selector ? []).some((s) => s.type == 'TextQuoteSelector')
return Promise.resolve({annotation, target})
# Find a target using the anchoring module. # Find a target using the anchoring module.
options = { options = {
cache: self.anchoringCache cache: self.anchoringCache
......
...@@ -341,7 +341,11 @@ describe 'Guest', -> ...@@ -341,7 +341,11 @@ describe 'Guest', ->
it "doesn't mark an annotation in which the target anchors as an orphan", -> it "doesn't mark an annotation in which the target anchors as an orphan", ->
guest = createGuest() guest = createGuest()
annotation = {target: [{selector: []}]} annotation = {
target: [
{selector: [{type: 'TextQuoteSelector', exact: 'hello'}]},
]
}
sandbox.stub(anchoring, 'anchor').returns(Promise.resolve(range)) sandbox.stub(anchoring, 'anchor').returns(Promise.resolve(range))
guest.anchor(annotation).then -> guest.anchor(annotation).then ->
...@@ -349,7 +353,12 @@ describe 'Guest', -> ...@@ -349,7 +353,12 @@ describe 'Guest', ->
it "doesn't mark an annotation in which at least one target anchors as an orphan", -> it "doesn't mark an annotation in which at least one target anchors as an orphan", ->
guest = createGuest() guest = createGuest()
annotation = {target: [{selector: []}, {selector: []}]} annotation = {
target: [
{selector: [{type: 'TextQuoteSelector', exact: 'notinhere'}]},
{selector: [{type: 'TextQuoteSelector', exact: 'hello'}]},
]
}
sandbox.stub(anchoring, 'anchor') sandbox.stub(anchoring, 'anchor')
.onFirstCall().returns(Promise.reject()) .onFirstCall().returns(Promise.reject())
.onSecondCall().returns(Promise.resolve(range)) .onSecondCall().returns(Promise.resolve(range))
...@@ -359,7 +368,11 @@ describe 'Guest', -> ...@@ -359,7 +368,11 @@ describe 'Guest', ->
it "marks an annotation in which the target fails to anchor as an orphan", -> it "marks an annotation in which the target fails to anchor as an orphan", ->
guest = createGuest() guest = createGuest()
annotation = target: [{selector: []}] annotation = {
target: [
{selector: [{type: 'TextQuoteSelector', exact: 'notinhere'}]},
]
}
sandbox.stub(anchoring, 'anchor').returns(Promise.reject()) sandbox.stub(anchoring, 'anchor').returns(Promise.reject())
guest.anchor(annotation).then -> guest.anchor(annotation).then ->
...@@ -367,12 +380,43 @@ describe 'Guest', -> ...@@ -367,12 +380,43 @@ describe 'Guest', ->
it "marks an annotation in which all (suitable) targets fail to anchor as an orphan", -> it "marks an annotation in which all (suitable) targets fail to anchor as an orphan", ->
guest = createGuest() guest = createGuest()
annotation = target: [{selector: []}, {selector: []}] annotation = {
target: [
{selector: [{type: 'TextQuoteSelector', exact: 'notinhere'}]},
{selector: [{type: 'TextQuoteSelector', exact: 'neitherami'}]},
]
}
sandbox.stub(anchoring, 'anchor').returns(Promise.reject()) sandbox.stub(anchoring, 'anchor').returns(Promise.reject())
guest.anchor(annotation).then -> guest.anchor(annotation).then ->
assert.isTrue(annotation.$orphan) assert.isTrue(annotation.$orphan)
it "marks an annotation where the target has no TextQuoteSelectors as an orphan", ->
guest = createGuest()
annotation = {
target: [
{selector: [{type: 'TextPositionSelector', start: 0, end: 5}]},
]
}
# This shouldn't be called, but if it is, we successfully anchor so that
# this test is guaranteed to fail.
sandbox.stub(anchoring, 'anchor').returns(Promise.resolve(range))
guest.anchor(annotation).then ->
assert.isTrue(annotation.$orphan)
it "does not attempt to anchor targets which have no TextQuoteSelector", ->
guest = createGuest()
annotation = {
target: [
{selector: [{type: 'TextPositionSelector', start: 0, end: 5}]},
]
}
sandbox.spy(anchoring, 'anchor')
guest.anchor(annotation).then ->
assert.notCalled(anchoring.anchor)
it 'updates the cross frame and bucket bar plugins', (done) -> it 'updates the cross frame and bucket bar plugins', (done) ->
guest = createGuest() guest = createGuest()
guest.plugins.CrossFrame = guest.plugins.CrossFrame =
...@@ -390,7 +434,7 @@ describe 'Guest', -> ...@@ -390,7 +434,7 @@ describe 'Guest', ->
highlights = [document.createElement('span')] highlights = [document.createElement('span')]
sandbox.stub(anchoring, 'anchor').returns(Promise.resolve(range)) sandbox.stub(anchoring, 'anchor').returns(Promise.resolve(range))
sandbox.stub(highlighter, 'highlightRange').returns(highlights) sandbox.stub(highlighter, 'highlightRange').returns(highlights)
target = [{selector: []}] target = {selector: [{type: 'TextQuoteSelector', exact: 'hello'}]}
guest.anchor({target: [target]}).then (anchors) -> guest.anchor({target: [target]}).then (anchors) ->
assert.equal(anchors.length, 1) assert.equal(anchors.length, 1)
.then(done, done) .then(done, done)
...@@ -400,7 +444,7 @@ describe 'Guest', -> ...@@ -400,7 +444,7 @@ describe 'Guest', ->
highlights = [document.createElement('span')] highlights = [document.createElement('span')]
sandbox.stub(anchoring, 'anchor').returns(Promise.resolve(range)) sandbox.stub(anchoring, 'anchor').returns(Promise.resolve(range))
sandbox.stub(highlighter, 'highlightRange').returns(highlights) sandbox.stub(highlighter, 'highlightRange').returns(highlights)
target = [{selector: []}] target = {selector: [{type: 'TextQuoteSelector', exact: 'hello'}]}
annotation = {target: [target]} annotation = {target: [target]}
guest.anchor(annotation).then -> guest.anchor(annotation).then ->
assert.equal(guest.anchors.length, 1) assert.equal(guest.anchors.length, 1)
...@@ -426,7 +470,7 @@ describe 'Guest', -> ...@@ -426,7 +470,7 @@ describe 'Guest', ->
it 'does not reanchor targets that are already anchored', (done) -> it 'does not reanchor targets that are already anchored', (done) ->
guest = createGuest() guest = createGuest()
annotation = target: [{selector: "test"}] annotation = target: [{selector: [{type: 'TextQuoteSelector', exact: 'hello'}]}]
stub = sandbox.stub(anchoring, 'anchor').returns(Promise.resolve(range)) stub = sandbox.stub(anchoring, 'anchor').returns(Promise.resolve(range))
guest.anchor(annotation).then -> guest.anchor(annotation).then ->
guest.anchor(annotation).then -> guest.anchor(annotation).then ->
......
...@@ -99,22 +99,4 @@ describe('anchoring', function () { ...@@ -99,22 +99,4 @@ describe('anchoring', function () {
], ],
expectFail: true, expectFail: true,
}]); }]);
unroll('should silently fail when an annotation does not have a quote selector', function (testCase) {
var annotation = testCase.annotation;
var anchored = guest.anchor(annotation);
return anchored.then(function (result) {
assert.deepEqual(result, []);
});
}, [{
annotation: [{
target: [{
selector: [{
type: 'FragmentSelector',
value: 't=30,60'
}]
}]
}],
}]);
}); });
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