blob: a63005b93974e112243647b965764d88d0c053df [file] [log] [blame]
// Copyright 2014 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.labs.pubsub.BroadcastPubSubTest');
goog.setTestOnly('goog.labs.pubsub.BroadcastPubSubTest');
goog.require('goog.array');
goog.require('goog.debug.Logger');
goog.require('goog.json');
goog.require('goog.labs.pubsub.BroadcastPubSub');
goog.require('goog.storage.Storage');
goog.require('goog.structs.Map');
goog.require('goog.testing.MockClock');
goog.require('goog.testing.MockControl');
goog.require('goog.testing.events');
goog.require('goog.testing.events.Event');
goog.require('goog.testing.jsunit');
goog.require('goog.testing.mockmatchers');
goog.require('goog.testing.mockmatchers.ArgumentMatcher');
goog.require('goog.testing.recordFunction');
goog.require('goog.userAgent');
/** @type {goog.labs.pubsub.BroadcastPubSub} */
var broadcastPubSub;
/** @type {goog.testing.MockControl} */
var mockControl;
/** @type {goog.testing.MockClock} */
var mockClock;
/** @type {goog.testing.MockInterface} */
var mockStorage;
/** @type {goog.testing.MockInterface} */
var mockStorageCtor;
/** @type {goog.structs.Map} */
var mockHtml5LocalStorage;
/** @type {goog.testing.MockInterface} */
var mockHTML5LocalStorageCtor;
/** @const {boolean} */
var isIe8 = goog.userAgent.IE && goog.userAgent.DOCUMENT_MODE == 8;
/**
* Sends a remote storage event with special handling for IE8. With IE8 an
* event is pushed to the event queue stored in local storage as a result of
* behaviour by the mockHtml5LocalStorage instanciated when using IE8 an event
* is automatically generated in the local browser context. For other browsers
* this simply creates a new browser event.
* @param {{'args': !Array<string>, 'timestamp': number}} data Value stored
* in localStorage which generated the remote event.
*/
function remoteStorageEvent(data) {
if (!isIe8) {
var event = new goog.testing.events.Event('storage', window);
event.key = goog.labs.pubsub.BroadcastPubSub.STORAGE_KEY_;
event.newValue = goog.json.serialize(data);
goog.testing.events.fireBrowserEvent(event);
} else {
var uniqueKey =
goog.labs.pubsub.BroadcastPubSub.IE8_EVENTS_KEY_PREFIX_ +
'1234567890';
var ie8Events = mockHtml5LocalStorage.get(uniqueKey);
if (goog.isDefAndNotNull(ie8Events)) {
ie8Events = goog.json.parse(ie8Events);
// Events should never overlap in IE8 mode.
if (ie8Events.length > 0 &&
ie8Events[ie8Events.length - 1]['timestamp'] >=
data['timestamp']) {
data['timestamp'] =
ie8Events[ie8Events.length - 1]['timestamp'] +
goog.labs.pubsub.BroadcastPubSub.
IE8_TIMESTAMP_UNIQUE_OFFSET_MS_;
}
} else {
ie8Events = [];
}
ie8Events.push(data);
// This will cause an event.
mockHtml5LocalStorage.set(uniqueKey, goog.json.serialize(ie8Events));
}
}
function setUp() {
mockControl = new goog.testing.MockControl();
mockClock = new goog.testing.MockClock(true);
// Time should never be 0...
mockClock.tick();
/** @suppress {missingRequire} */
mockHTML5LocalStorageCtor = mockControl.createConstructorMock(
goog.storage.mechanism, 'HTML5LocalStorage');
mockHtml5LocalStorage = new goog.structs.Map();
// The builtin localStorage returns null instead of undefined.
var originalGetFn = goog.bind(mockHtml5LocalStorage.get,
mockHtml5LocalStorage);
mockHtml5LocalStorage.get = function(key) {
var value = originalGetFn(key);
if (!goog.isDef(value)) {
return null;
}
return value;
};
mockHtml5LocalStorage.key = function(idx) {
return mockHtml5LocalStorage.getKeys()[idx];
};
mockHtml5LocalStorage.isAvailable = function() {
return true;
};
// IE has problems. IE9+ still dispatches storage events locally. IE8 also
// doesn't include the key/value information. So for IE, everytime we get a
// "set" on localStorage we simulate for the appropriate browser.
if (goog.userAgent.IE) {
var target = isIe8 ? document : window;
var originalSetFn = goog.bind(mockHtml5LocalStorage.set,
mockHtml5LocalStorage);
mockHtml5LocalStorage.set = function(key, value) {
originalSetFn(key, value);
var event = new goog.testing.events.Event('storage', target);
if (!isIe8) {
event.key = key;
event.newValue = value;
}
goog.testing.events.fireBrowserEvent(event);
};
}
}
function tearDown() {
mockControl.$tearDown();
mockClock.dispose();
broadcastPubSub = undefined;
}
function testConstructor() {
mockHTML5LocalStorageCtor().$returns(mockHtml5LocalStorage);
mockControl.$replayAll();
broadcastPubSub = new goog.labs.pubsub.BroadcastPubSub();
broadcastPubSub.logger_.setLevel(goog.debug.Logger.Level.OFF);
assertNotNullNorUndefined(
'BroadcastChannel instance must not be null', broadcastPubSub);
assertTrue('BroadcastChannel instance must have the expected type',
broadcastPubSub instanceof goog.labs.pubsub.BroadcastPubSub);
assertArrayEquals(
goog.labs.pubsub.BroadcastPubSub.instances_, [broadcastPubSub]);
broadcastPubSub.dispose();
mockControl.$verifyAll();
assertNotNullNorUndefined(
'Storage should not be undefined or null in broadcastPubSub.',
broadcastPubSub.storage_);
assertArrayEquals(goog.labs.pubsub.BroadcastPubSub.instances_, []);
}
function testConstructor_noLocalStorage() {
mockHTML5LocalStorageCtor().$returns({isAvailable: function() {
return false;
}});
mockControl.$replayAll();
broadcastPubSub = new goog.labs.pubsub.BroadcastPubSub();
broadcastPubSub.logger_.setLevel(goog.debug.Logger.Level.OFF);
assertNotNullNorUndefined(
'BroadcastChannel instance must not be null', broadcastPubSub);
assertTrue('BroadcastChannel instance must have the expected type',
broadcastPubSub instanceof goog.labs.pubsub.BroadcastPubSub);
assertArrayEquals(
goog.labs.pubsub.BroadcastPubSub.instances_, [broadcastPubSub]);
broadcastPubSub.dispose();
mockControl.$verifyAll();
assertNull(
'Storage should be null in broadcastPubSub.', broadcastPubSub.storage_);
assertArrayEquals(goog.labs.pubsub.BroadcastPubSub.instances_, []);
}
/** Verify we cleanup after ourselves. */
function testDispose() {
mockHTML5LocalStorageCtor().$returns(mockHtml5LocalStorage);
mockHTML5LocalStorageCtor().$returns(mockHtml5LocalStorage);
var mockStorage = mockControl.createLooseMock(goog.storage.Storage);
var mockStorageCtor = mockControl.createConstructorMock(
goog.storage, 'Storage');
mockStorageCtor(mockHtml5LocalStorage).$returns(mockStorage);
mockStorageCtor(mockHtml5LocalStorage).$returns(mockStorage);
if (isIe8) {
mockStorage.remove(
goog.labs.pubsub.BroadcastPubSub.IE8_EVENTS_KEY_);
}
mockControl.$replayAll();
broadcastPubSub = new goog.labs.pubsub.BroadcastPubSub();
broadcastPubSub.logger_.setLevel(goog.debug.Logger.Level.OFF);
var broadcastPubSubExtra = new goog.labs.pubsub.BroadcastPubSub();
assertArrayEquals(goog.labs.pubsub.BroadcastPubSub.instances_,
[broadcastPubSub, broadcastPubSubExtra]);
assertFalse('BroadcastChannel extra instance must not have been disposed of',
broadcastPubSubExtra.isDisposed());
broadcastPubSubExtra.dispose();
assertTrue('BroadcastChannel extra instance must have been disposed of',
broadcastPubSubExtra.isDisposed());
assertFalse('BroadcastChannel instance must not have been disposed of',
broadcastPubSub.isDisposed());
assertArrayEquals(
goog.labs.pubsub.BroadcastPubSub.instances_, [broadcastPubSub]);
assertFalse('BroadcastChannel instance must not have been disposed of',
broadcastPubSub.isDisposed());
broadcastPubSub.dispose();
assertTrue('BroadcastChannel instance must have been disposed of',
broadcastPubSub.isDisposed());
assertArrayEquals(goog.labs.pubsub.BroadcastPubSub.instances_, []);
mockControl.$verifyAll();
}
/**
* Tests related to remote events that an instance of BroadcastChannel
* should handle.
*/
function testHandleRemoteEvent() {
mockHTML5LocalStorageCtor().$returns(mockHtml5LocalStorage);
var foo = mockControl.createFunctionMock();
foo('x', 'y').$times(2);
var context = {'foo': 'bar'};
var bar = goog.testing.recordFunction();
mockControl.$replayAll();
broadcastPubSub = new goog.labs.pubsub.BroadcastPubSub();
broadcastPubSub.logger_.setLevel(goog.debug.Logger.Level.OFF);
var eventData = {
'args': ['someTopic', 'x', 'y'],
'timestamp': goog.now()
};
broadcastPubSub.subscribe('someTopic', foo);
broadcastPubSub.subscribe('someTopic', bar, context);
remoteStorageEvent(eventData);
mockClock.tick();
assertEquals(1, bar.getCallCount());
assertEquals(context, bar.getLastCall().getThis());
assertArrayEquals(['x', 'y'], bar.getLastCall().getArguments());
broadcastPubSub.unsubscribe('someTopic', foo);
eventData['timestamp'] = goog.now();
remoteStorageEvent(eventData);
mockClock.tick();
assertEquals(2, bar.getCallCount());
assertEquals(context, bar.getLastCall().getThis());
assertArrayEquals(['x', 'y'], bar.getLastCall().getArguments());
broadcastPubSub.subscribe('someTopic', foo);
broadcastPubSub.unsubscribe('someTopic', bar, context);
eventData['timestamp'] = goog.now();
remoteStorageEvent(eventData);
mockClock.tick();
assertEquals(2, bar.getCallCount());
broadcastPubSub.dispose();
mockControl.$verifyAll();
}
function testHandleRemoteEventSubscribeOnce() {
mockHTML5LocalStorageCtor().$returns(mockHtml5LocalStorage);
var foo = mockControl.createFunctionMock();
foo('x', 'y');
mockControl.$replayAll();
broadcastPubSub = new goog.labs.pubsub.BroadcastPubSub();
broadcastPubSub.logger_.setLevel(goog.debug.Logger.Level.OFF);
broadcastPubSub.subscribeOnce('someTopic', foo);
assertEquals('BroadcastChannel must have one subscriber',
1, broadcastPubSub.getCount());
remoteStorageEvent({
'args': ['someTopic', 'x', 'y'],
'timestamp': goog.now()
});
mockClock.tick();
assertEquals(
'BroadcastChannel must have no subscribers after receiving the event',
0, broadcastPubSub.getCount());
broadcastPubSub.dispose();
mockControl.$verifyAll();
}
function testHandleQueuedRemoteEvents() {
mockHTML5LocalStorageCtor().$returns(mockHtml5LocalStorage);
var foo = mockControl.createFunctionMock();
var bar = mockControl.createFunctionMock();
foo('x', 'y');
bar('d', 'c');
mockControl.$replayAll();
broadcastPubSub = new goog.labs.pubsub.BroadcastPubSub();
broadcastPubSub.logger_.setLevel(goog.debug.Logger.Level.OFF);
broadcastPubSub.subscribe('fooTopic', foo);
broadcastPubSub.subscribe('barTopic', bar);
var eventData = {
'args': ['fooTopic', 'x', 'y'],
'timestamp': goog.now()
};
remoteStorageEvent(eventData);
var eventData = {
'args': ['barTopic', 'd', 'c'],
'timestamp': goog.now()
};
remoteStorageEvent(eventData);
mockClock.tick();
broadcastPubSub.dispose();
mockControl.$verifyAll();
}
function testHandleRemoteEventsUnsubscribe() {
mockHTML5LocalStorageCtor().$returns(mockHtml5LocalStorage);
var foo = mockControl.createFunctionMock();
var bar = mockControl.createFunctionMock();
foo('x', 'y').$does(function() {
broadcastPubSub.unsubscribe('barTopic', bar);
});
mockControl.$replayAll();
broadcastPubSub = new goog.labs.pubsub.BroadcastPubSub();
broadcastPubSub.logger_.setLevel(goog.debug.Logger.Level.OFF);
broadcastPubSub.subscribe('fooTopic', foo);
broadcastPubSub.subscribe('barTopic', bar);
var eventData = {
'args': ['fooTopic', 'x', 'y'],
'timestamp': goog.now()
};
remoteStorageEvent(eventData);
mockClock.tick();
var eventData = {
'args': ['barTopic', 'd', 'c'],
'timestamp': goog.now()
};
remoteStorageEvent(eventData);
mockClock.tick();
broadcastPubSub.dispose();
mockControl.$verifyAll();
}
function testHandleRemoteEventsCalledOnce() {
mockHTML5LocalStorageCtor().$returns(mockHtml5LocalStorage);
var foo = mockControl.createFunctionMock();
foo('x', 'y');
mockControl.$replayAll();
broadcastPubSub = new goog.labs.pubsub.BroadcastPubSub();
broadcastPubSub.logger_.setLevel(goog.debug.Logger.Level.OFF);
broadcastPubSub.subscribeOnce('someTopic', foo);
var eventData = {
'args': ['someTopic', 'x', 'y'],
'timestamp': goog.now()
};
remoteStorageEvent(eventData);
mockClock.tick();
var eventData = {
'args': ['someTopic', 'x', 'y'],
'timestamp': goog.now()
};
remoteStorageEvent(eventData);
mockClock.tick();
broadcastPubSub.dispose();
mockControl.$verifyAll();
}
function testHandleRemoteEventNestedPublish() {
mockHTML5LocalStorageCtor().$returns(mockHtml5LocalStorage);
var foo1 = mockControl.createFunctionMock();
foo1().$does(function() {
remoteStorageEvent({
'args': ['bar'],
'timestamp': goog.now()
});
});
var foo2 = mockControl.createFunctionMock();
foo2();
var bar1 = mockControl.createFunctionMock();
bar1().$does(function() {
broadcastPubSub.publish('baz');
});
var bar2 = mockControl.createFunctionMock();
bar2();
var baz1 = mockControl.createFunctionMock();
baz1();
var baz2 = mockControl.createFunctionMock();
baz2();
mockControl.$replayAll();
broadcastPubSub = new goog.labs.pubsub.BroadcastPubSub();
broadcastPubSub.logger_.setLevel(goog.debug.Logger.Level.OFF);
broadcastPubSub.subscribe('foo', foo1);
broadcastPubSub.subscribe('foo', foo2);
broadcastPubSub.subscribe('bar', bar1);
broadcastPubSub.subscribe('bar', bar2);
broadcastPubSub.subscribe('baz', baz1);
broadcastPubSub.subscribe('baz', baz2);
remoteStorageEvent({
'args': ['foo'],
'timestamp': goog.now()
});
mockClock.tick();
broadcastPubSub.dispose();
mockControl.$verifyAll();
}
/**
* Local publish that originated from another instance of BroadcastChannel
* in the same Javascript context.
*/
function testSecondInstancePublish() {
mockHTML5LocalStorageCtor().$returns(mockHtml5LocalStorage).$times(2);
var foo = mockControl.createFunctionMock();
foo('x', 'y');
var context = {'foo': 'bar'};
var bar = goog.testing.recordFunction();
mockControl.$replayAll();
broadcastPubSub = new goog.labs.pubsub.BroadcastPubSub();
broadcastPubSub.logger_.setLevel(goog.debug.Logger.Level.OFF);
broadcastPubSub.subscribe('someTopic', foo);
broadcastPubSub.subscribe('someTopic', bar, context);
var broadcastPubSub2 = new goog.labs.pubsub.BroadcastPubSub();
broadcastPubSub2.publish('someTopic', 'x', 'y');
mockClock.tick();
assertEquals(1, bar.getCallCount());
assertEquals(context, bar.getLastCall().getThis());
assertArrayEquals(['x', 'y'], bar.getLastCall().getArguments());
broadcastPubSub.dispose();
mockControl.$verifyAll();
}
function testSecondInstanceNestedPublish() {
mockHTML5LocalStorageCtor().$returns(mockHtml5LocalStorage).$times(2);
var foo = mockControl.createFunctionMock();
foo('m', 'n').$does(function() {
broadcastPubSub.publish('barTopic', 'd', 'c');
});
var bar = mockControl.createFunctionMock();
bar('d', 'c');
mockControl.$replayAll();
broadcastPubSub = new goog.labs.pubsub.BroadcastPubSub();
broadcastPubSub.logger_.setLevel(goog.debug.Logger.Level.OFF);
broadcastPubSub.subscribe('fooTopic', foo);
var broadcastPubSub2 = new goog.labs.pubsub.BroadcastPubSub();
broadcastPubSub2.subscribe('barTopic', bar);
broadcastPubSub2.publish('fooTopic', 'm', 'n');
mockClock.tick();
mockClock.tick();
broadcastPubSub.dispose();
mockControl.$verifyAll();
}
/**
* Validate the localStorage data is being set as we expect.
*/
function testLocalStorageData() {
var topic = 'someTopic';
var anotherTopic = 'anotherTopic';
var now = goog.now();
mockHTML5LocalStorageCtor().$returns(mockHtml5LocalStorage);
var mockStorage = mockControl.createLooseMock(goog.storage.Storage);
var mockStorageCtor = mockControl.createConstructorMock(
goog.storage, 'Storage');
mockStorageCtor(mockHtml5LocalStorage).$returns(mockStorage);
if (!isIe8) {
mockStorage.set(goog.labs.pubsub.BroadcastPubSub.STORAGE_KEY_,
{'args': [topic, '10'], 'timestamp': now});
mockStorage.remove(goog.labs.pubsub.BroadcastPubSub.STORAGE_KEY_);
mockStorage.set(goog.labs.pubsub.BroadcastPubSub.STORAGE_KEY_,
{'args': [anotherTopic, '13'], 'timestamp': now});
mockStorage.remove(goog.labs.pubsub.BroadcastPubSub.STORAGE_KEY_);
} else {
var firstEventArray = [
{'args': [topic, '10'], 'timestamp': now}
];
var secondEventArray = [
{'args': [topic, '10'], 'timestamp': now},
{'args': [anotherTopic, '13'], 'timestamp': now +
goog.labs.pubsub.BroadcastPubSub.
IE8_TIMESTAMP_UNIQUE_OFFSET_MS_}
];
mockStorage.get(goog.labs.pubsub.BroadcastPubSub.IE8_EVENTS_KEY_).
$returns(null);
mockStorage.set(goog.labs.pubsub.BroadcastPubSub.IE8_EVENTS_KEY_,
new goog.testing.mockmatchers.ArgumentMatcher(function(val) {
return goog.testing.mockmatchers.flexibleArrayMatcher(
firstEventArray, val);
}, 'First event array'));
// Make sure to clone or you're going to have a bad time.
mockStorage.get(goog.labs.pubsub.BroadcastPubSub.IE8_EVENTS_KEY_).
$returns(goog.array.clone(firstEventArray));
mockStorage.set(goog.labs.pubsub.BroadcastPubSub.IE8_EVENTS_KEY_,
new goog.testing.mockmatchers.ArgumentMatcher(function(val) {
return goog.testing.mockmatchers.flexibleArrayMatcher(
secondEventArray, val);
}, 'Second event array'));
mockStorage.remove(
goog.labs.pubsub.BroadcastPubSub.IE8_EVENTS_KEY_);
}
var fn = goog.testing.recordFunction();
fn('10');
mockControl.$replayAll();
broadcastPubSub = new goog.labs.pubsub.BroadcastPubSub();
broadcastPubSub.logger_.setLevel(goog.debug.Logger.Level.OFF);
broadcastPubSub.subscribe(topic, fn);
broadcastPubSub.publish(topic, '10');
broadcastPubSub.publish(anotherTopic, '13');
mockClock.tick();
broadcastPubSub.dispose();
mockControl.$verifyAll();
}
function testBrokenTimestamp() {
mockHTML5LocalStorageCtor().$returns(mockHtml5LocalStorage);
var fn = mockControl.createFunctionMock();
mockControl.$replayAll();
broadcastPubSub = new goog.labs.pubsub.BroadcastPubSub();
broadcastPubSub.logger_.setLevel(goog.debug.Logger.Level.OFF);
broadcastPubSub.subscribe('someTopic', fn);
remoteStorageEvent({
'args': 'WAT?',
'timestamp': 'wat?'
});
mockClock.tick();
broadcastPubSub.dispose();
mockControl.$verifyAll();
}
/** Test response to bad localStorage data. */
function testBrokenEvent() {
mockHTML5LocalStorageCtor().$returns(mockHtml5LocalStorage);
var fn = mockControl.createFunctionMock();
mockControl.$replayAll();
broadcastPubSub = new goog.labs.pubsub.BroadcastPubSub();
broadcastPubSub.logger_.setLevel(goog.debug.Logger.Level.OFF);
broadcastPubSub.subscribe('someTopic', fn);
if (!isIe8) {
var event = new goog.testing.events.Event('storage', window);
event.key = 'FooBarBaz';
event.newValue = goog.json.serialize({'keyby': 'word'});
goog.testing.events.fireBrowserEvent(event);
} else {
var uniqueKey =
goog.labs.pubsub.BroadcastPubSub.IE8_EVENTS_KEY_PREFIX_ +
'1234567890';
// This will cause an event.
mockHtml5LocalStorage.set(uniqueKey, 'Toothpaste!');
}
mockClock.tick();
broadcastPubSub.dispose();
mockControl.$verifyAll();
}
/**
* The following tests are duplicated from pubsub because they depend
* on functionality (mostly "publish") that has changed in BroadcastChannel.
*/
function testPublish() {
mockHTML5LocalStorageCtor().$returns(mockHtml5LocalStorage);
var foo = mockControl.createFunctionMock();
foo('x', 'y');
var context = {'foo': 'bar'};
var bar = goog.testing.recordFunction();
var baz = mockControl.createFunctionMock();
baz('d', 'c');
mockControl.$replayAll();
broadcastPubSub = new goog.labs.pubsub.BroadcastPubSub();
broadcastPubSub.logger_.setLevel(goog.debug.Logger.Level.OFF);
broadcastPubSub.subscribe('someTopic', foo);
broadcastPubSub.subscribe('someTopic', bar, context);
broadcastPubSub.subscribe('anotherTopic', baz, context);
broadcastPubSub.publish('someTopic', 'x', 'y');
mockClock.tick();
assertTrue(broadcastPubSub.unsubscribe('someTopic', foo));
broadcastPubSub.publish('anotherTopic', 'd', 'c');
broadcastPubSub.publish('someTopic', 'x', 'y');
mockClock.tick();
broadcastPubSub.subscribe('differentTopic', foo);
broadcastPubSub.publish('someTopic', 'x', 'y');
mockClock.tick();
assertEquals(3, bar.getCallCount());
goog.array.forEach(bar.getCalls(), function(call) {
assertArrayEquals(['x', 'y'], call.getArguments());
assertEquals(context, call.getThis());
});
broadcastPubSub.dispose();
mockControl.$verifyAll();
}
function testPublishEmptyTopic() {
mockHTML5LocalStorageCtor().$returns(mockHtml5LocalStorage);
var foo = mockControl.createFunctionMock();
foo();
mockControl.$replayAll();
broadcastPubSub = new goog.labs.pubsub.BroadcastPubSub();
broadcastPubSub.logger_.setLevel(goog.debug.Logger.Level.OFF);
broadcastPubSub.publish('someTopic');
mockClock.tick();
broadcastPubSub.subscribe('someTopic', foo);
broadcastPubSub.publish('someTopic');
mockClock.tick();
broadcastPubSub.unsubscribe('someTopic', foo);
broadcastPubSub.publish('someTopic');
mockClock.tick();
broadcastPubSub.dispose();
mockControl.$verifyAll();
}
function testSubscribeWhilePublishing() {
mockHTML5LocalStorageCtor().$returns(mockHtml5LocalStorage);
// It's OK for a subscriber to add a new subscriber to its own topic,
// but the newly added subscriber shouldn't be called until the next
// publish cycle.
var fn1 = mockControl.createFunctionMock();
var fn2 = mockControl.createFunctionMock();
fn1().$does(function() {
broadcastPubSub.subscribe('someTopic', fn2);
}).$times(2);
fn2();
mockControl.$replayAll();
broadcastPubSub = new goog.labs.pubsub.BroadcastPubSub();
broadcastPubSub.logger_.setLevel(goog.debug.Logger.Level.OFF);
broadcastPubSub.subscribe('someTopic', fn1);
assertEquals('Topic must have one subscriber', 1,
broadcastPubSub.getCount('someTopic'));
broadcastPubSub.publish('someTopic');
mockClock.tick();
assertEquals('Topic must have two subscribers', 2,
broadcastPubSub.getCount('someTopic'));
broadcastPubSub.publish('someTopic');
mockClock.tick();
assertEquals('Topic must have three subscribers', 3,
broadcastPubSub.getCount('someTopic'));
broadcastPubSub.dispose();
mockControl.$verifyAll();
}
function testUnsubscribeWhilePublishing() {
mockHTML5LocalStorageCtor().$returns(mockHtml5LocalStorage);
// It's OK for a subscriber to unsubscribe another subscriber from its
// own topic, but the subscriber in question won't actually be removed
// until after publishing is complete.
var fn1 = mockControl.createFunctionMock();
var fn2 = mockControl.createFunctionMock();
var fn3 = mockControl.createFunctionMock();
fn1().$does(function() {
assertFalse('unsubscribe() must return false during publishing',
broadcastPubSub.unsubscribe('X', fn2));
assertEquals('Topic "X" must still have 3 subscribers', 3,
broadcastPubSub.getCount('X'));
});
fn2().$does(function() {
assertEquals('Topic "X" must still have 3 subscribers', 3,
broadcastPubSub.getCount('X'));
});
fn3().$does(function() {
assertFalse('unsubscribe() must return false during publishing',
broadcastPubSub.unsubscribe('X', fn1));
assertEquals('Topic "X" must still have 3 subscribers', 3,
broadcastPubSub.getCount('X'));
});
mockControl.$replayAll();
broadcastPubSub = new goog.labs.pubsub.BroadcastPubSub();
broadcastPubSub.logger_.setLevel(goog.debug.Logger.Level.OFF);
broadcastPubSub.subscribe('X', fn1);
broadcastPubSub.subscribe('X', fn2);
broadcastPubSub.subscribe('X', fn3);
assertEquals('Topic "X" must have 3 subscribers', 3,
broadcastPubSub.getCount('X'));
broadcastPubSub.publish('X');
mockClock.tick();
assertEquals('Topic "X" must have 1 subscriber after publishing', 1,
broadcastPubSub.getCount('X'));
assertEquals(
'BroadcastChannel must not have any subscriptions pending removal',
0, broadcastPubSub.pubSub_.pendingKeys_.length);
broadcastPubSub.dispose();
mockControl.$verifyAll();
}
function testUnsubscribeSelfWhilePublishing() {
mockHTML5LocalStorageCtor().$returns(mockHtml5LocalStorage);
// It's OK for a subscriber to unsubscribe itself, but it won't actually
// be removed until after publishing is complete.
var fn = mockControl.createFunctionMock();
fn().$does(function() {
assertFalse('unsubscribe() must return false during publishing',
broadcastPubSub.unsubscribe('someTopic', fn));
assertEquals('Topic must still have 1 subscriber', 1,
broadcastPubSub.getCount('someTopic'));
});
mockControl.$replayAll();
broadcastPubSub = new goog.labs.pubsub.BroadcastPubSub();
broadcastPubSub.logger_.setLevel(goog.debug.Logger.Level.OFF);
broadcastPubSub.subscribe('someTopic', fn);
assertEquals('Topic must have 1 subscriber', 1,
broadcastPubSub.getCount('someTopic'));
broadcastPubSub.publish('someTopic');
mockClock.tick();
assertEquals('Topic must have no subscribers after publishing', 0,
broadcastPubSub.getCount('someTopic'));
assertEquals(
'BroadcastChannel must not have any subscriptions pending removal',
0, broadcastPubSub.pubSub_.pendingKeys_.length);
broadcastPubSub.dispose();
mockControl.$verifyAll();
}
function testNestedPublish() {
mockHTML5LocalStorageCtor().$returns(mockHtml5LocalStorage);
var xFn1 = mockControl.createFunctionMock();
xFn1().$does(function() {
broadcastPubSub.publish('Y');
broadcastPubSub.unsubscribe('X', arguments.callee);
});
var xFn2 = mockControl.createFunctionMock();
xFn2();
var yFn1 = mockControl.createFunctionMock();
yFn1().$does(function() {
broadcastPubSub.unsubscribe('Y', arguments.callee);
});
var yFn2 = mockControl.createFunctionMock();
yFn2();
mockControl.$replayAll();
broadcastPubSub = new goog.labs.pubsub.BroadcastPubSub();
broadcastPubSub.logger_.setLevel(goog.debug.Logger.Level.OFF);
broadcastPubSub.subscribe('X', xFn1);
broadcastPubSub.subscribe('X', xFn2);
broadcastPubSub.subscribe('Y', yFn1);
broadcastPubSub.subscribe('Y', yFn2);
broadcastPubSub.publish('X');
mockClock.tick();
broadcastPubSub.dispose();
mockControl.$verifyAll();
}
function testSubscribeOnce() {
mockHTML5LocalStorageCtor().$returns(mockHtml5LocalStorage);
var fn = goog.testing.recordFunction();
mockControl.$replayAll();
broadcastPubSub = new goog.labs.pubsub.BroadcastPubSub();
broadcastPubSub.logger_.setLevel(goog.debug.Logger.Level.OFF);
broadcastPubSub.subscribeOnce('someTopic', fn);
assertEquals('Topic must have one subscriber', 1,
broadcastPubSub.getCount('someTopic'));
broadcastPubSub.publish('someTopic');
mockClock.tick();
assertEquals('Topic must have no subscribers', 0,
broadcastPubSub.getCount('someTopic'));
var context = {'foo': 'bar'};
broadcastPubSub.subscribeOnce('someTopic', fn, context);
assertEquals('Topic must have one subscriber', 1,
broadcastPubSub.getCount('someTopic'));
assertEquals('Subscriber must not have been called yet',
1, fn.getCallCount());
broadcastPubSub.publish('someTopic');
mockClock.tick();
assertEquals('Topic must have no subscribers', 0,
broadcastPubSub.getCount('someTopic'));
assertEquals('Subscriber must have been called',
2, fn.getCallCount());
assertEquals(context, fn.getLastCall().getThis());
assertArrayEquals([], fn.getLastCall().getArguments());
context = {'foo': 'bar'};
broadcastPubSub.subscribeOnce('someTopic', fn, context);
assertEquals('Topic must have one subscriber', 1,
broadcastPubSub.getCount('someTopic'));
assertEquals('Subscriber must not have been called',
2, fn.getCallCount());
broadcastPubSub.publish('someTopic', '17');
mockClock.tick();
assertEquals('Topic must have no subscribers', 0,
broadcastPubSub.getCount('someTopic'));
assertEquals(context, fn.getLastCall().getThis());
assertEquals('Subscriber must have been called',
3, fn.getCallCount());
assertArrayEquals(['17'], fn.getLastCall().getArguments());
broadcastPubSub.dispose();
mockControl.$verifyAll();
}
function testSubscribeOnce_boundFn() {
mockHTML5LocalStorageCtor().$returns(mockHtml5LocalStorage);
var fn = goog.testing.recordFunction();
var context = {'foo': 'bar'};
mockControl.$replayAll();
broadcastPubSub = new goog.labs.pubsub.BroadcastPubSub();
broadcastPubSub.logger_.setLevel(goog.debug.Logger.Level.OFF);
broadcastPubSub.subscribeOnce('someTopic', goog.bind(fn, context));
assertEquals('Topic must have one subscriber', 1,
broadcastPubSub.getCount('someTopic'));
assertNull('Subscriber must not have been called yet',
fn.getLastCall());
broadcastPubSub.publish('someTopic', '17');
mockClock.tick();
assertEquals('Topic must have no subscribers', 0,
broadcastPubSub.getCount('someTopic'));
assertEquals('Subscriber must have been called', 1, fn.getCallCount());
assertEquals('Must receive correct argument.',
'17', fn.getLastCall().getArgument(0));
assertEquals('Must have appropriate context.',
context, fn.getLastCall().getThis());
broadcastPubSub.dispose();
mockControl.$verifyAll();
}
function testSubscribeOnce_partialFn() {
mockHTML5LocalStorageCtor().$returns(mockHtml5LocalStorage);
var fullFn = mockControl.createFunctionMock();
fullFn(true, '17');
mockControl.$replayAll();
broadcastPubSub = new goog.labs.pubsub.BroadcastPubSub();
broadcastPubSub.logger_.setLevel(goog.debug.Logger.Level.OFF);
broadcastPubSub.subscribeOnce('someTopic', goog.partial(fullFn, true));
assertEquals('Topic must have one subscriber', 1,
broadcastPubSub.getCount('someTopic'));
broadcastPubSub.publish('someTopic', '17');
mockClock.tick();
assertEquals('Topic must have no subscribers', 0,
broadcastPubSub.getCount('someTopic'));
broadcastPubSub.dispose();
mockControl.$verifyAll();
}
function testSelfResubscribe() {
mockHTML5LocalStorageCtor().$returns(mockHtml5LocalStorage);
var resubscribeFn = mockControl.createFunctionMock();
var resubscribe = function() {
broadcastPubSub.subscribeOnce('someTopic', resubscribeFn);
};
resubscribeFn('foo').$does(resubscribe);
resubscribeFn('bar').$does(resubscribe);
resubscribeFn('baz').$does(resubscribe);
mockControl.$replayAll();
broadcastPubSub = new goog.labs.pubsub.BroadcastPubSub();
broadcastPubSub.logger_.setLevel(goog.debug.Logger.Level.OFF);
broadcastPubSub.subscribeOnce('someTopic', resubscribeFn);
assertEquals('Topic must have 1 subscriber', 1,
broadcastPubSub.getCount('someTopic'));
broadcastPubSub.publish('someTopic', 'foo');
mockClock.tick();
assertEquals('Topic must have 1 subscriber', 1,
broadcastPubSub.getCount('someTopic'));
assertEquals('BroadcastChannel must not have any pending unsubscribe keys', 0,
broadcastPubSub.pubSub_.pendingKeys_.length);
broadcastPubSub.publish('someTopic', 'bar');
mockClock.tick();
assertEquals('Topic must have 1 subscriber', 1,
broadcastPubSub.getCount('someTopic'));
assertEquals('BroadcastChannel must not have any pending unsubscribe keys', 0,
broadcastPubSub.pubSub_.pendingKeys_.length);
broadcastPubSub.publish('someTopic', 'baz');
mockClock.tick();
assertEquals('Topic must have 1 subscriber', 1,
broadcastPubSub.getCount('someTopic'));
assertEquals('BroadcastChannel must not have any pending unsubscribe keys', 0,
broadcastPubSub.pubSub_.pendingKeys_.length);
broadcastPubSub.dispose();
mockControl.$verifyAll();
}
function testClear() {
mockHTML5LocalStorageCtor().$returns(mockHtml5LocalStorage);
var fn = mockControl.createFunctionMock();
mockControl.$replayAll();
broadcastPubSub = new goog.labs.pubsub.BroadcastPubSub();
broadcastPubSub.logger_.setLevel(goog.debug.Logger.Level.OFF);
goog.array.forEach(['V', 'W', 'X', 'Y', 'Z'], function(topic) {
broadcastPubSub.subscribe(topic, fn);
});
assertEquals('BroadcastChannel must have 5 subscribers', 5,
broadcastPubSub.getCount());
broadcastPubSub.clear('W');
assertEquals('BroadcastChannel must have 4 subscribers', 4,
broadcastPubSub.getCount());
goog.array.forEach(['X', 'Y'], function(topic) {
broadcastPubSub.clear(topic);
});
assertEquals('BroadcastChannel must have 2 subscriber', 2,
broadcastPubSub.getCount());
broadcastPubSub.clear();
assertEquals('BroadcastChannel must have no subscribers', 0,
broadcastPubSub.getCount());
broadcastPubSub.dispose();
mockControl.$verifyAll();
}