Commit 7e9557f8 authored by Aron Carroll's avatar Aron Carroll

Add unit tests for the CrossFrameDiscovery component

parent b23e16ed
......@@ -21,19 +21,21 @@
# server.stopDiscovery();
# }
class CrossFrameDiscovery
@defaults:
# Origins allowed to communicate on the channel
origin: '*'
# Origins allowed to communicate on the channel
server: false
# When this is true, this bridge will act as a server and, similar to DHCP,
# offer to connect to bridges in other frames it discovers.
server: false
# When this is true, this bridge will act as a server and, similar to DHCP,
# offer to connect to bridges in other frames it discovers.
origin: '*'
onDiscovery: null
requestInProgress: false
# Accepts a target window and an object of options. The window provided will
# act as a starting point for discovering other windows.
constructor: (@target, options={}) ->
@server = options.server or CrossFrameDiscovery.defaults.server
@origin = options.origin or CrossFrameDiscovery.defaults.origin
@server = options.server if options.server
@origin = options.origin if options.origin
startDiscovery: (onDiscovery) ->
if @onDiscovery
......@@ -119,7 +121,7 @@ class CrossFrameDiscovery
token = ':' + ('' + Math.random()).replace(/\D/g, '')
reply = 'ack' + token
discovered = true
else if messageType is 'offer'
else if messageType is 'offer' or messageType is 'ack'
throw new Error("""
A second CrossFrameDiscovery server has been detected at #{origin}.
This is unsupported and will cause unexpected behaviour.""")
......@@ -127,7 +129,7 @@ class CrossFrameDiscovery
if messageType is 'offer'
# The server joined the party, or replied to our discovery message.
# Request it to set up a channel if we did not already do so.
unless @requestInProgress?
unless @requestInProgress
@requestInProgress = true # prevent creating two channels
reply = 'request'
else if messageType is 'ack'
......
This diff is collapsed.
......@@ -3,39 +3,186 @@ sinon.assert.expose assert, prefix: null
describe 'CrossFrameDiscovery', ->
sandbox = sinon.sandbox.create()
fakeWindow = null
fakeTopWindow = null
fakeFrameWindow = null
createDiscovery = null
beforeEach module('h')
beforeEach inject (CrossFrameDiscovery) ->
fakeWindow = {
createDiscovery = (win, options) ->
new CrossFrameDiscovery(win, options)
createWindow = ->
top: null
addEventListener: sandbox.stub()
removeEventListener: sandbox.stub()
}
createBridge = (options) ->
new CrossFrameDiscovery(fakeWindow, options)
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'
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)', ->
it 'sends out a "discovery" message to every frame'
it 'does not send the message to itself'
it 'sends an "ack" on receiving a "request"'
it 'calls the discovery callback on receiving "request"'
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)', ->
it 'sends out a discovery message to every frame'
it 'does not send the message to itself'
it 'sends a "request" in response to an "offer"'
it 'does not respond to an "offer" if a "request" is already in progress'
it 'allows responding to a "request" once a previous "request" has completed'
it 'calls the discovery callback on receiving an "ack"'
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'
it 'allows startDiscovery to be called with a new handler'
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()
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