View
@@ -0,0 +1,261 @@
assert = chai.assert
sinon.assert.expose assert, prefix: null
describe 'CrossFrameBridge', ->
sandbox = sinon.sandbox.create()
createBridge = null
createChannel = null
beforeEach module('h')
beforeEach inject (CrossFrameBridge) ->
createBridge = (options) ->
new CrossFrameBridge(options)
createChannel = ->
call: sandbox.stub()
bind: sandbox.stub()
unbind: sandbox.stub()
notify: sandbox.stub()
destroy: sandbox.stub()
sandbox.stub(Channel, 'build')
afterEach ->
sandbox.restore()
describe '.createChannel', ->
it 'creates a new channel with the provided options', ->
Channel.build.returns(createChannel())
bridge = createBridge()
bridge.createChannel('WINDOW', 'ORIGIN', 'TOKEN')
assert.called(Channel.build)
assert.calledWith(Channel.build, {
window: 'WINDOW'
origin: 'ORIGIN'
scope: 'crossFrameBridge:TOKEN'
onReady: sinon.match.func
})
it 'adds the channel to the .links property', ->
channel = createChannel()
Channel.build.returns(channel)
bridge = createBridge()
bridge.createChannel('WINDOW', 'ORIGIN', 'TOKEN')
assert.include(bridge.links, {channel: channel, window: 'WINDOW'})
it 'registers any existing listeners on the channel', ->
channel = createChannel()
Channel.build.returns(channel)
bridge = createBridge()
bridge.on('message1', sinon.spy())
bridge.on('message2', sinon.spy())
bridge.createChannel('WINDOW', 'ORIGIN', 'TOKEN')
assert.called(channel.bind)
assert.calledWith(channel.bind, 'message1', sinon.match.func)
assert.calledWith(channel.bind, 'message2', sinon.match.func)
it 'returns the newly created channel', ->
channel = createChannel()
Channel.build.returns(channel)
bridge = createBridge()
ret = bridge.createChannel('WINDOW', 'ORIGIN', 'TOKEN')
assert.equal(ret, channel)
describe '.call', ->
it 'forwards the call to every created channel', ->
channel = createChannel()
Channel.build.returns(channel)
bridge = createBridge()
bridge.createChannel('WINDOW', 'ORIGIN', 'TOKEN')
bridge.call({method: 'method1', params: 'params1'})
assert.called(channel.call)
message = channel.call.lastCall.args[0]
assert.equal(message.method, 'method1')
assert.equal(message.params, 'params1')
it 'provides a timeout', ->
channel = createChannel()
Channel.build.returns(channel)
bridge = createBridge()
bridge.createChannel('WINDOW', 'ORIGIN', 'TOKEN')
bridge.call({method: 'method1', params: 'params1'})
message = channel.call.lastCall.args[0]
assert.isNumber(message.timeout)
it 'calls options.callback when all channels return successfully', ->
channel1 = createChannel()
channel2 = createChannel()
channel1.call.yieldsTo('success', 'result1')
channel2.call.yieldsTo('success', 'result2')
callback = sandbox.stub()
bridge = createBridge()
Channel.build.returns(channel1)
bridge.createChannel('WINDOW', 'ORIGIN', 'TOKEN')
Channel.build.returns(channel2)
bridge.createChannel('WINDOW', 'ORIGIN', 'TOKEN')
bridge.call({method: 'method1', params: 'params1', callback: callback})
assert.called(callback)
assert.calledWith(callback, null, ['result1', 'result2'])
it 'calls options.callback with an error when one or more channels fail', ->
err = new Error('Uh oh')
channel1 = createChannel()
channel1.call.yieldsTo('error', err, 'A reason for the error')
channel2 = createChannel()
channel2.call.yieldsTo('success', 'result2')
callback = sandbox.stub()
bridge = createBridge()
Channel.build.returns(channel1)
bridge.createChannel('WINDOW', 'ORIGIN', 'TOKEN')
Channel.build.returns(channel2)
bridge.createChannel('WINDOW', 'ORIGIN', 'TOKEN')
bridge.call({method: 'method1', params: 'params1', callback: callback})
assert.called(callback)
assert.calledWith(callback, err)
it 'destroys the channel when a call fails', ->
channel = createChannel()
channel.call.yieldsTo('error', new Error(''), 'A reason for the error')
Channel.build.returns(channel)
bridge = createBridge()
bridge.createChannel('WINDOW', 'ORIGIN', 'TOKEN')
bridge.call({method: 'method1', params: 'params1', callback: sandbox.stub()})
assert.called(channel.destroy)
it 'no longer publishes to a channel that has had an errored response', ->
channel = createChannel()
channel.call.yieldsTo('error', new Error(''), 'A reason for the error')
Channel.build.returns(channel)
bridge = createBridge()
bridge.createChannel('WINDOW', 'ORIGIN', 'TOKEN')
bridge.call({method: 'method1', params: 'params1', callback: sandbox.stub()})
bridge.call({method: 'method1', params: 'params1', callback: sandbox.stub()})
assert.calledOnce(channel.call)
it 'treats a timeout as a success with no result', ->
channel = createChannel()
channel.call.yieldsTo('error', 'timeout_error', 'timeout')
Channel.build.returns(channel)
callback = sandbox.stub()
bridge = createBridge()
bridge.createChannel('WINDOW', 'ORIGIN', 'TOKEN')
bridge.call({method: 'method1', params: 'params1', callback: callback})
assert.called(callback)
assert.calledWith(callback, null, [null])
it 'returns a promise object', ->
channel = createChannel()
channel.call.yieldsTo('error', 'timeout_error', 'timeout')
Channel.build.returns(channel)
bridge = createBridge()
ret = bridge.call({method: 'method1', params: 'params1'})
assert.isFunction(ret.then)
describe '.notify', ->
it 'publishes the message on every created channel', ->
channel = createChannel()
message = {method: 'message1', params: 'params'}
Channel.build.returns(channel)
bridge = createBridge()
bridge.createChannel('WINDOW', 'ORIGIN', 'TOKEN')
bridge.notify(message)
assert.called(channel.notify)
assert.calledWith(channel.notify, message)
describe '.on', ->
it 'registers an event listener on all created channels', ->
channel = createChannel()
Channel.build.returns(channel)
bridge = createBridge()
bridge.createChannel('WINDOW', 'ORIGIN', 'TOKEN')
bridge.on('message1', sandbox.spy())
assert.called(channel.bind)
assert.calledWith(channel.bind, 'message1', sinon.match.func)
it 'only allows one message to be registered per method', ->
bridge = createBridge()
bridge.on('message1', sandbox.spy())
assert.throws ->
bridge.on('message1', sandbox.spy())
describe '.off', ->
it 'removes the event listener from the created channels', ->
channel = createChannel()
Channel.build.returns(channel)
bridge = createBridge()
bridge.createChannel('WINDOW', 'ORIGIN', 'TOKEN')
bridge.off('message1', sandbox.spy())
it 'ensures that the event is no longer bound when new channels are created', ->
channel1 = createChannel()
channel2 = createChannel()
Channel.build.returns(channel1)
bridge = createBridge()
bridge.createChannel('WINDOW', 'ORIGIN', 'TOKEN')
bridge.off('message1', sandbox.spy())
bridge.createChannel('WINDOW', 'ORIGIN', 'TOKEN')
assert.notCalled(channel2.bind)
describe '.onConnect', ->
it 'adds a callback that is called when a new channel is connected', ->
channel = createChannel()
Channel.build.returns(channel)
Channel.build.yieldsTo('onReady', channel)
callback = sandbox.stub()
bridge = createBridge()
bridge.onConnect(callback)
bridge.createChannel('WINDOW', 'ORIGIN', 'TOKEN')
assert.called(callback)
assert.calledWith(callback, channel)
it 'allows multiple callbacks to be registered', ->
channel = createChannel()
Channel.build.returns(channel)
Channel.build.yieldsTo('onReady', channel)
callback1 = sandbox.stub()
callback2 = sandbox.stub()
bridge = createBridge()
bridge.onConnect(callback1)
bridge.onConnect(callback2)
bridge.createChannel('WINDOW', 'ORIGIN', 'TOKEN')
assert.called(callback1)
assert.called(callback2)
View
@@ -0,0 +1,188 @@
assert = chai.assert
sinon.assert.expose assert, prefix: null
describe 'CrossFrameDiscovery', ->
sandbox = sinon.sandbox.create()
fakeTopWindow = null
fakeFrameWindow = null
createDiscovery = null
beforeEach module('h')
beforeEach inject (CrossFrameDiscovery) ->
createDiscovery = (win, options) ->
new CrossFrameDiscovery(win, options)
createWindow = ->
top: null
addEventListener: sandbox.stub()
removeEventListener: sandbox.stub()
postMessage: sandbox.stub()
length: 0
frames: []
fakeTopWindow = createWindow()
fakeTopWindow.top = fakeTopWindow
fakeFrameWindow = createWindow()
fakeFrameWindow.top = fakeTopWindow
fakeTopWindow.frames = [fakeFrameWindow]
afterEach ->
sandbox.restore()
describe 'startDiscovery', ->
it 'adds a "message" listener to the window object', ->
discovery = createDiscovery(fakeTopWindow)
discovery.startDiscovery(->)
assert.called(fakeTopWindow.addEventListener)
assert.calledWith(fakeTopWindow.addEventListener, 'message', sinon.match.func, false)
describe 'when acting as a server (options.server = true)', ->
server = null
beforeEach ->
server = createDiscovery(fakeFrameWindow, server: true)
it 'sends out a "offer" message to every frame', ->
server.startDiscovery(->)
assert.called(fakeTopWindow.postMessage)
assert.calledWith(fakeTopWindow.postMessage, '__cross_frame_dhcp_offer', '*')
it 'allows the origin to be provided', ->
server = createDiscovery(fakeFrameWindow, server: true, origin: 'foo')
server.startDiscovery(->)
assert.called(fakeTopWindow.postMessage)
assert.calledWith(fakeTopWindow.postMessage, '__cross_frame_dhcp_offer', 'foo')
it 'does not send the message to itself', ->
server.startDiscovery(->)
assert.notCalled(fakeFrameWindow.postMessage)
it 'sends an "ack" on receiving a "request"', ->
fakeFrameWindow.addEventListener.yields({
data: '__cross_frame_dhcp_request'
source: fakeTopWindow
origin: 'top'
})
server.startDiscovery(->)
assert.called(fakeTopWindow.postMessage)
matcher = sinon.match(/__cross_frame_dhcp_ack:\d+/)
assert.calledWith(fakeTopWindow.postMessage, matcher, 'top')
it 'calls the discovery callback on receiving "request"', ->
fakeFrameWindow.addEventListener.yields({
data: '__cross_frame_dhcp_request'
source: fakeTopWindow
origin: 'top'
})
handler = sandbox.stub()
server.startDiscovery(handler)
assert.called(handler)
assert.calledWith(handler, fakeTopWindow, 'top', sinon.match(/\d+/))
it 'raises an error if it recieves an event from another server', ->
fakeFrameWindow.addEventListener.yields({
data: '__cross_frame_dhcp_offer'
source: fakeTopWindow
origin: 'top'
})
handler = sandbox.stub()
assert.throws ->
server.startDiscovery(handler)
describe 'when acting as a client (options.client = false)', ->
client = null
beforeEach ->
client = createDiscovery(fakeTopWindow)
it 'sends out a discovery message to every frame', ->
client.startDiscovery(->)
assert.called(fakeFrameWindow.postMessage)
assert.calledWith(fakeFrameWindow.postMessage, '__cross_frame_dhcp_discovery', '*')
it 'does not send the message to itself', ->
client.startDiscovery(->)
assert.notCalled(fakeTopWindow.postMessage)
it 'sends a "request" in response to an "offer"', ->
fakeTopWindow.addEventListener.yields({
data: '__cross_frame_dhcp_offer'
source: fakeFrameWindow
origin: 'iframe'
})
client.startDiscovery(->)
assert.called(fakeFrameWindow.postMessage)
assert.calledWith(fakeFrameWindow.postMessage, '__cross_frame_dhcp_request', 'iframe')
it 'does not respond to an "offer" if a "request" is already in progress', ->
fakeTopWindow.addEventListener.yields({
data: '__cross_frame_dhcp_offer'
source: fakeFrameWindow
origin: 'iframe1'
})
fakeTopWindow.addEventListener.yields({
data: '__cross_frame_dhcp_offer'
source: fakeFrameWindow
origin: 'iframe2'
})
client.startDiscovery(->)
# Twice, once for discovery, once for offer.
assert.calledTwice(fakeFrameWindow.postMessage)
lastCall = fakeFrameWindow.postMessage.lastCall
assert(lastCall.notCalledWith(sinon.match.string, 'iframe2'))
it 'allows responding to a "request" once a previous "request" has completed', ->
fakeTopWindow.addEventListener.yields({
data: '__cross_frame_dhcp_offer'
source: fakeFrameWindow
origin: 'iframe1'
})
fakeTopWindow.addEventListener.yields({
data: '__cross_frame_dhcp_ack:1234'
source: fakeFrameWindow
origin: 'iframe1'
})
fakeTopWindow.addEventListener.yields({
data: '__cross_frame_dhcp_offer'
source: fakeFrameWindow
origin: 'iframe2'
})
client.startDiscovery(->)
assert.called(fakeFrameWindow.postMessage)
assert.calledWith(fakeFrameWindow.postMessage, '__cross_frame_dhcp_request', 'iframe2')
it 'calls the discovery callback on receiving an "ack"', ->
fakeTopWindow.addEventListener.yields({
data: '__cross_frame_dhcp_ack:1234'
source: fakeFrameWindow
origin: 'iframe'
})
callback = sandbox.stub()
client.startDiscovery(callback)
assert.called(callback)
assert.calledWith(callback, fakeFrameWindow, 'iframe', '1234')
describe 'stopDiscovery', ->
it 'removes the "message" listener from the window', ->
discovery = createDiscovery(fakeFrameWindow)
discovery.startDiscovery()
discovery.stopDiscovery()
handler = fakeFrameWindow.addEventListener.lastCall.args[1]
assert.called(fakeFrameWindow.removeEventListener)
assert.calledWith(fakeFrameWindow.removeEventListener, 'message', handler)
it 'allows startDiscovery to be called with a new handler', ->
discovery = createDiscovery(fakeFrameWindow)
discovery.startDiscovery()
discovery.stopDiscovery()
assert.doesNotThrow ->
discovery.startDiscovery()
View
@@ -0,0 +1,86 @@
assert = chai.assert
sinon.assert.expose assert, prefix: null
describe 'CrossFrameService', ->
sandbox = sinon.sandbox.create()
crossframe = null
$rootScope = null
$fakeDocument = null
$fakeWindow = null
fakeStore = null
fakeAnnotationUI = null
fakeCrossFrameDiscovery = null
fakeCrossFrameBridge = null
fakeAnnotationSync = null
fakeAnnotationUISync = null
beforeEach module('h')
beforeEach module ($provide) ->
$fakeDocument = {}
$fakeWindow = {}
fakeStore = {}
fakeAnnotationUI = {}
fakeCrossFrameDiscovery =
startDiscovery: sandbox.stub()
fakeCrossFrameBridge =
notify: sandbox.stub()
createChannel: sandbox.stub()
onConnect: sandbox.stub()
fakeAnnotationSync = {}
fakeAnnotationUISync = {}
$provide.value('$document', $fakeDocument)
$provide.value('$window', $fakeWindow)
$provide.value('store', fakeStore)
$provide.value('annotationUI', fakeAnnotationUI)
$provide.value('CrossFrameDiscovery',
sandbox.stub().returns(fakeCrossFrameDiscovery))
$provide.value('CrossFrameBridge',
sandbox.stub().returns(fakeCrossFrameBridge))
$provide.value('AnnotationSync',
sandbox.stub().returns(fakeAnnotationSync))
$provide.value('AnnotationUISync',
sandbox.stub().returns(fakeAnnotationUISync))
return # $provide returns a promise.
beforeEach inject (_$rootScope_, _crossframe_) ->
$rootScope = _$rootScope_
crossframe = _crossframe_
afterEach ->
sandbox.restore()
describe '.connect()', ->
it 'creates a new channel when the discovery module finds a frame', ->
fakeCrossFrameDiscovery.startDiscovery.yields('source', 'origin', 'token')
crossframe.connect()
assert.calledWith(fakeCrossFrameBridge.createChannel,
'source', 'origin', 'token')
it 'queries discovered frames for metadata', ->
info = {metadata: link: [{href: 'http://example.com'}]}
channel = {call: sandbox.stub().yieldsTo('success', info)}
fakeCrossFrameBridge.onConnect.yields(channel)
crossframe.connect()
assert.calledWith(channel.call, {
method: 'getDocumentInfo'
success: sinon.match.func
})
it 'updates the providers array', ->
info = {metadata: link: [{href: 'http://example.com'}]}
channel = {call: sandbox.stub().yieldsTo('success', info)}
fakeCrossFrameBridge.onConnect.yields(channel)
crossframe.connect()
assert.deepEqual(crossframe.providers, [
{channel: channel, entities: ['http://example.com']}
])
describe '.notify()', ->
it 'proxies the call to the bridge', ->
message = {method: 'foo', params: 'bar'}
crossframe.connect() # create the bridge.
crossframe.notify(message)
assert.calledOn(fakeCrossFrameBridge.notify, fakeCrossFrameBridge)
assert.calledWith(fakeCrossFrameBridge.notify, message)
View
@@ -6,23 +6,35 @@ describe 'h.directives.annotation', ->
$document = null
$scope = null
$timeout = null
annotator = null
annotation = null
createController = null
flash = null
fakeAuth = null
fakeStore = null
fakeUser = null
fakeAnnotationMapper = null
fakeAnnotationUI = null
beforeEach module('h')
beforeEach module('h.templates')
beforeEach module ($provide) ->
fakeAuth =
user: 'acct:bill@localhost'
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 = {}
$provide.value 'auth', fakeAuth
$provide.value 'store', fakeStore
$provide.value 'annotationMapper', fakeAnnotationMapper
$provide.value 'annotationUI', fakeAnnotationUI
return
beforeEach inject (_$compile_, $controller, _$document_, $rootScope, _$timeout_) ->
@@ -31,11 +43,6 @@ describe 'h.directives.annotation', ->
$timeout = _$timeout_
$scope = $rootScope.$new()
$scope.annotationGet = (locals) -> annotation
annotator = {
createAnnotation: sandbox.spy (data) -> data
plugins: {},
publish: sandbox.spy()
}
annotation =
id: 'deadbeef'
document:
@@ -48,15 +55,14 @@ describe 'h.directives.annotation', ->
createController = ->
$controller 'AnnotationController',
$scope: $scope
annotator: annotator
flash: flash
afterEach ->
sandbox.restore()
describe 'when the annotation is a highlight', ->
beforeEach ->
annotator.tool = 'highlight'
fakeAnnotationUI.tool = 'highlight'
annotation.$create = sinon.stub().returns
then: angular.noop
catch: angular.noop
@@ -91,36 +97,31 @@ describe 'h.directives.annotation', ->
destroy: ['acct:joe@localhost']
admin: ['acct:joe@localhost']
annotator.publish = sinon.spy (event, ann) ->
return unless event == 'beforeAnnotationCreated'
ann.permissions =
read: ['acct:bill@localhost']
update: ['acct:bill@localhost']
destroy: ['acct:bill@localhost']
admin: ['acct:bill@localhost']
it 'creates a new reply with the proper uri and references', ->
controller.reply()
match = sinon.match {references: [annotation.id], uri: annotation.uri}
assert.calledWith(annotator.createAnnotation, match)
assert.calledWith(fakeAnnotationMapper.createAnnotation, match)
it 'adds the world readable principal if the parent is public', ->
reply = {}
fakeAnnotationMapper.createAnnotation.returns(reply)
annotation.permissions.read.push('group:__world__')
controller.reply()
newAnnotation = annotator.createAnnotation.lastCall.args[0]
assert.include(newAnnotation.permissions.read, 'group:__world__')
assert.include(reply.permissions.read, 'group:__world__')
it 'does not add the world readable principal if the parent is private', ->
reply = {}
fakeAnnotationMapper.createAnnotation.returns(reply)
controller.reply()
newAnnotation = annotator.createAnnotation.lastCall.args[0]
assert.notInclude(newAnnotation.permissions.read, 'group:__world__')
assert.notInclude(reply.permissions.read, 'group:__world__')
it 'fills the other permissions too', ->
reply = {}
fakeAnnotationMapper.createAnnotation.returns(reply)
controller.reply()
newAnnotation = annotator.createAnnotation.lastCall.args[0]
assert.equal(newAnnotation.permissions.update[0], 'acct:bill@localhost')
assert.equal(newAnnotation.permissions.delete[0], 'acct:bill@localhost')
assert.equal(newAnnotation.permissions.admin[0], 'acct:bill@localhost')
assert.equal(reply.permissions.update[0], 'acct:bill@localhost')
assert.equal(reply.permissions.delete[0], 'acct:bill@localhost')
assert.equal(reply.permissions.admin[0], 'acct:bill@localhost')
describe '#render', ->
controller = null
View
@@ -2,13 +2,278 @@ assert = chai.assert
sinon.assert.expose(assert, prefix: '')
describe 'Annotator.Guest', ->
sandbox = sinon.sandbox.create()
fakeBridge = null
createGuest = (options) ->
element = document.createElement('div')
return new Annotator.Guest(element, options || {})
# Silence Annotator's sassy backchat
before -> sinon.stub(console, 'log')
after -> console.log.restore()
beforeEach -> sandbox.stub(console, 'log')
afterEach -> sandbox.restore()
beforeEach ->
fakeBridge =
onConnect: sandbox.stub()
on: sandbox.stub()
sandbox.stub(Annotator.Plugin, 'Bridge').returns(fakeBridge)
describe 'setting up the bridge', ->
it 'sets the scope for the cross frame bridge', ->
guest = createGuest()
options = Annotator.Plugin.Bridge.lastCall.args[1]
assert.equal(options.scope, 'annotator:bridge')
it 'provides an event bus for the annotation sync module', ->
guest = createGuest()
options = Annotator.Plugin.Bridge.lastCall.args[1]
assert.isFunction(options.on)
assert.isFunction(options.emit)
it 'provides a formatter for the annotation sync module', ->
guest = createGuest()
options = Annotator.Plugin.Bridge.lastCall.args[1]
assert.isFunction(options.formatter)
it 'publishes the "panelReady" event when a connection is established', ->
handler = sandbox.stub()
guest = createGuest()
guest.subscribe('panelReady', handler)
fakeBridge.onConnect.yield()
assert.called(handler)
describe 'the event bus .on method', ->
options = null
guest = null
beforeEach ->
guest = createGuest()
options = Annotator.Plugin.Bridge.lastCall.args[1]
it 'proxies the event into the annotator event system', ->
fooHandler = sandbox.stub()
barHandler = sandbox.stub()
options.on('foo', fooHandler)
options.on('bar', barHandler)
guest.publish('foo', ['1', '2'])
guest.publish('bar', ['1', '2'])
assert.calledWith(fooHandler, '1', '2')
assert.calledWith(barHandler, '1', '2')
describe 'the event bus .emit method', ->
options = null
guest = null
beforeEach ->
guest = createGuest()
options = Annotator.Plugin.Bridge.lastCall.args[1]
it 'calls deleteAnnotation when an annotationDeleted event is recieved', ->
ann = {id: 1, $$tag: 'tag1'}
sandbox.stub(guest, 'deleteAnnotation')
options.emit('annotationDeleted', ann)
assert.called(guest.deleteAnnotation)
assert.calledWith(guest.deleteAnnotation, ann)
it 'does not proxy the annotationDeleted event', ->
handler = sandbox.stub()
guest.subscribe('annotationDeleted', handler)
options.emit('annotationDeleted', {})
# Called only once by the deleteAnnotation() method.
assert.calledOnce(handler)
it 'calls loadAnnotations when an loadAnnotations event is recieved', ->
ann = {id: 1, $$tag: 'tag1'}
target = sandbox.stub(guest, 'loadAnnotations')
options.emit('loadAnnotations', [ann])
assert.called(target)
assert.calledWith(target, [ann])
it 'does not proxy the loadAnnotations event', ->
handler = sandbox.stub()
guest.subscribe('loadAnnotations', handler)
options.emit('loadAnnotations', [])
assert.notCalled(handler)
it 'proxies all other events into the annotator event system', ->
fooHandler = sandbox.stub()
barHandler = sandbox.stub()
guest.subscribe('foo', fooHandler)
guest.subscribe('bar', barHandler)
options.emit('foo', '1', '2')
options.emit('bar', '1', '2')
assert.calledWith(fooHandler, '1', '2')
assert.calledWith(barHandler, '1', '2')
describe 'the formatter', ->
options = null
guest = null
beforeEach ->
guest = createGuest()
guest.plugins.Document = {uri: -> 'http://example.com'}
options = Annotator.Plugin.Bridge.lastCall.args[1]
it 'applies a "uri" property to the formatted object', ->
ann = {$$tag: 'tag1'}
formatted = options.formatter(ann)
assert.equal(formatted.uri, 'http://example.com/')
it 'keeps an existing uri property', ->
ann = {$$tag: 'tag1', uri: 'http://example.com/foo'}
formatted = options.formatter(ann)
assert.equal(formatted.uri, 'http://example.com/foo')
it 'copies the properties from the provided annotation', ->
ann = {$$tag: 'tag1'}
formatted = options.formatter(ann)
assert.equal(formatted.$$tag, 'tag1')
it 'strips the "anchors" property', ->
ann = {$$tag: 'tag1', anchors: []}
formatted = options.formatter(ann)
assert.notProperty(formatted, 'anchors')
it 'clones the document.title array if present', ->
title = ['Page Title']
ann = {$$tag: 'tag1', document: {title: title}}
formatted = options.formatter(ann)
assert.notStrictEqual(title, formatted.document.title)
assert.deepEqual(title, formatted.document.title)
describe 'annotation UI events', ->
emitGuestEvent = (event, args...) ->
fn(args...) for [evt, fn] in fakeBridge.on.args when event == evt
describe 'on "onEditorHide" event', ->
it 'hides the editor', ->
target = sandbox.stub(Annotator.Guest.prototype, 'onEditorHide')
guest = createGuest()
emitGuestEvent('onEditorHide')
assert.called(target)
describe 'on "onEditorSubmit" event', ->
it 'sumbits the editor', ->
target = sandbox.stub(Annotator.Guest.prototype, 'onEditorSubmit')
guest = createGuest()
emitGuestEvent('onEditorSubmit')
assert.called(target)
describe 'on "focusAnnotations" event', ->
it 'focuses any annotations with a matching tag', ->
guest = createGuest()
highlights = [
{annotation: {$$tag: 'tag1'}, setFocused: sandbox.stub()}
{annotation: {$$tag: 'tag2'}, setFocused: sandbox.stub()}
]
sandbox.stub(guest.anchoring, 'getHighlights').returns(highlights)
emitGuestEvent('focusAnnotations', 'ctx', ['tag1'])
assert.called(highlights[0].setFocused)
assert.calledWith(highlights[0].setFocused, true)
it 'unfocuses any annotations without a matching tag', ->
guest = createGuest()
highlights = [
{annotation: {$$tag: 'tag1'}, setFocused: sandbox.stub()}
{annotation: {$$tag: 'tag2'}, setFocused: sandbox.stub()}
]
sandbox.stub(guest.anchoring, 'getHighlights').returns(highlights)
emitGuestEvent('focusAnnotations', 'ctx', ['tag1'])
assert.called(highlights[1].setFocused)
assert.calledWith(highlights[1].setFocused, false)
describe 'on "scrollToAnnotation" event', ->
it 'scrolls to the highLight with the matching tag', ->
guest = createGuest()
highlights = [
{annotation: {$$tag: 'tag1'}, scrollTo: sandbox.stub()}
]
sandbox.stub(guest.anchoring, 'getHighlights').returns(highlights)
emitGuestEvent('scrollToAnnotation', 'ctx', 'tag1')
assert.called(highlights[0].scrollTo)
describe 'on "getDocumentInfo" event', ->
guest = null
beforeEach ->
guest = createGuest()
guest.plugins.PDF =
uri: sandbox.stub().returns('http://example.com')
getMetaData: sandbox.stub()
it 'calls the callback with the href and pdf metadata', (done) ->
assertComplete = (payload) ->
try
assert.equal(payload.uri, 'http://example.com/')
assert.equal(payload.metadata, metadata)
done()
catch e
done(e)
ctx = {complete: assertComplete, delayReturn: sandbox.stub()}
metadata = {title: 'hi'}
promise = Promise.resolve(metadata)
guest.plugins.PDF.getMetaData.returns(promise)
emitGuestEvent('getDocumentInfo', ctx)
it 'calls the callback with the href and document metadata if pdf check fails', (done) ->
assertComplete = (payload) ->
try
assert.equal(payload.uri, 'http://example.com/')
assert.equal(payload.metadata, metadata)
done()
catch e
done(e)
ctx = {complete: assertComplete, delayReturn: sandbox.stub()}
metadata = {title: 'hi'}
guest.plugins.Document = {metadata: metadata}
promise = Promise.reject(new Error('Not a PDF document'))
guest.plugins.PDF.getMetaData.returns(promise)
emitGuestEvent('getDocumentInfo', ctx)
it 'notifies the channel that the return value is async', ->
delete guest.plugins.PDF
ctx = {complete: sandbox.stub(), delayReturn: sandbox.stub()}
emitGuestEvent('getDocumentInfo', ctx)
assert.calledWith(ctx.delayReturn, true)
describe 'on "setTool" event', ->
it 'updates the .tool property', ->
guest = createGuest()
emitGuestEvent('setTool', 'ctx', 'highlighter')
assert.equal(guest.tool, 'highlighter')
it 'publishes the "setTool" event', ->
handler = sandbox.stub()
guest = createGuest()
guest.subscribe('setTool', handler)
emitGuestEvent('setTool', 'ctx', 'highlighter')
assert.called(handler)
assert.calledWith(handler, 'highlighter')
describe 'on "setVisibleHighlights" event', ->
it 'publishes the "setVisibleHighlights" event', ->
handler = sandbox.stub()
guest = createGuest()
guest.subscribe('setTool', handler)
emitGuestEvent('setTool', 'ctx', 'highlighter')
assert.called(handler)
assert.calledWith(handler, 'highlighter')
describe 'onAdderMouseUp', ->
it 'it prevents the default browser action when triggered', () ->
View
@@ -2,13 +2,23 @@ assert = chai.assert
sinon.assert.expose(assert, prefix: '')
describe 'Annotator.Host', ->
sandbox = sinon.sandbox.create()
createHost = (options) ->
element = document.createElement('div')
return new Annotator.Host(element, options)
# Disable Annotator's ridiculous logging.
before -> sinon.stub(console, 'log')
after -> console.log.restore()
beforeEach ->
# Disable Annotator's ridiculous logging.
sandbox.stub(console, 'log')
fakeBridge =
onConnect: sandbox.stub()
on: sandbox.stub()
sandbox.stub(Annotator.Plugin, 'Bridge').returns(fakeBridge)
afterEach -> sandbox.restore()
describe 'options', ->
it 'enables highlighting when showHighlights option is provided', (done) ->
View

This file was deleted.

Oops, something went wrong.
View
@@ -3,13 +3,13 @@ sinon.assert.expose assert, prefix: null
sandbox = sinon.sandbox.create()
mockFlash = sandbox.spy()
mockDocumentHelpers = {absoluteURI: -> '/session'}
mockDocument = {prop: -> '/session'}
describe 'session', ->
beforeEach module('h.session')
beforeEach module ($provide, sessionProvider) ->
$provide.value 'documentHelpers', mockDocumentHelpers
$provide.value '$document', mockDocument
$provide.value 'flash', mockFlash
sessionProvider.actions =
login:
View
@@ -20,6 +20,7 @@ describe 'streamer', ->
beforeEach inject (_streamer_) ->
streamer = _streamer_
streamer.clientId = 'FAKE_CLIENT_ID'
afterEach ->
sandbox.restore()
View
@@ -0,0 +1,84 @@
assert = chai.assert
sinon.assert.expose(assert, prefix: '')
describe 'Threading', ->
instance = null
beforeEach module('h')
beforeEach inject (_threading_) ->
instance = _threading_
describe 'pruneEmpties', ->
it 'keeps public messages with no children', ->
threadA = mail.messageContainer(mail.message('subject a', 'a', []))
threadB = mail.messageContainer(mail.message('subject b', 'b', []))
threadC = mail.messageContainer(mail.message('subject c', 'c', []))
root = mail.messageContainer()
root.addChild(threadA)
root.addChild(threadB)
root.addChild(threadC)
instance.pruneEmpties(root)
assert.equal(root.children.length, 3)
it 'keeps public messages with public children', ->
threadA = mail.messageContainer(mail.message('subject a', 'a', []))
threadA1 = mail.messageContainer(mail.message('subject a1', 'a1', ['a']))
threadA2 = mail.messageContainer(mail.message('subject a2', 'a2', ['a']))
root = mail.messageContainer()
root.addChild(threadA)
threadA.addChild(threadA1)
threadA.addChild(threadA2)
instance.pruneEmpties(root)
assert.equal(root.children.length, 1)
it 'prunes private messages with no children', ->
threadA = mail.messageContainer()
threadB = mail.messageContainer()
threadC = mail.messageContainer()
root = mail.messageContainer()
root.addChild(threadA)
root.addChild(threadB)
root.addChild(threadC)
instance.pruneEmpties(root)
assert.equal(root.children.length, 0)
it 'keeps private messages with public children', ->
threadA = mail.messageContainer()
threadA1 = mail.messageContainer(mail.message('subject a1', 'a1', ['a']))
threadA2 = mail.messageContainer(mail.message('subject a2', 'a2', ['a']))
root = mail.messageContainer()
root.addChild(threadA)
threadA.addChild(threadA1)
threadA.addChild(threadA2)
instance.pruneEmpties(root)
assert.equal(root.children.length, 1)
it 'prunes private messages with private children', ->
threadA = mail.messageContainer()
threadA1 = mail.messageContainer()
threadA2 = mail.messageContainer()
root = mail.messageContainer()
root.addChild(threadA)
threadA.addChild(threadA1)
threadA.addChild(threadA2)
instance.pruneEmpties(root)
assert.equal(root.children.length, 0)