1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069 |
- // 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, every time 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);
- 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();
- 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();
- 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() {
- assertTrue(
- 'unsubscribe() must return true when removing a topic',
- broadcastPubSub.unsubscribe('X', fn2));
- assertEquals(
- 'Topic "X" must still have 3 subscribers', 3,
- broadcastPubSub.getCount('X'));
- });
- fn2().$times(0);
- fn3().$does(function() {
- assertTrue(
- 'unsubscribe() must return true when removing a topic',
- 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() {
- assertTrue(
- 'unsubscribe() must return true when removing a topic',
- 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();
- }
- function testNestedSubscribeOnce() {
- mockHTML5LocalStorageCtor().$returns(mockHtml5LocalStorage);
- var x = mockControl.createFunctionMock();
- var y = mockControl.createFunctionMock();
- x().$times(1);
- y().$does(function() {
- broadcastPubSub.publish('X');
- broadcastPubSub.publish('X');
- });
- mockControl.$replayAll();
- broadcastPubSub = new goog.labs.pubsub.BroadcastPubSub();
- broadcastPubSub.subscribeOnce('X', x);
- broadcastPubSub.subscribe('Y', y);
- broadcastPubSub.publish('Y');
- mockClock.tick();
- broadcastPubSub.dispose();
- mockControl.$verifyAll();
- }
|