1060 lines
33 KiB
JavaScript
1060 lines
33 KiB
JavaScript
// 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();
|
|
}
|