// Copyright 2007 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.pubsub.PubSubTest'); goog.setTestOnly('goog.pubsub.PubSubTest'); goog.require('goog.array'); goog.require('goog.pubsub.PubSub'); goog.require('goog.testing.jsunit'); var pubsub; function setUp() { pubsub = new goog.pubsub.PubSub(); } function tearDown() { pubsub.dispose(); } function testConstructor() { assertNotNull('PubSub instance must not be null', pubsub); assertTrue('PubSub instance must have the expected type', pubsub instanceof goog.pubsub.PubSub); } function testDispose() { assertFalse('PubSub instance must not have been disposed of', pubsub.isDisposed()); pubsub.dispose(); assertTrue('PubSub instance must have been disposed of', pubsub.isDisposed()); } function testSubscribeUnsubscribe() { function foo1() { } function bar1() { } function foo2() { } function bar2() { } assertEquals('Topic "foo" must not have any subscribers', 0, pubsub.getCount('foo')); assertEquals('Topic "bar" must not have any subscribers', 0, pubsub.getCount('bar')); pubsub.subscribe('foo', foo1); assertEquals('Topic "foo" must have 1 subscriber', 1, pubsub.getCount('foo')); assertEquals('Topic "bar" must not have any subscribers', 0, pubsub.getCount('bar')); pubsub.subscribe('bar', bar1); assertEquals('Topic "foo" must have 1 subscriber', 1, pubsub.getCount('foo')); assertEquals('Topic "bar" must have 1 subscriber', 1, pubsub.getCount('bar')); pubsub.subscribe('foo', foo2); assertEquals('Topic "foo" must have 2 subscribers', 2, pubsub.getCount('foo')); assertEquals('Topic "bar" must have 1 subscriber', 1, pubsub.getCount('bar')); pubsub.subscribe('bar', bar2); assertEquals('Topic "foo" must have 2 subscribers', 2, pubsub.getCount('foo')); assertEquals('Topic "bar" must have 2 subscribers', 2, pubsub.getCount('bar')); assertTrue(pubsub.unsubscribe('foo', foo1)); assertEquals('Topic "foo" must have 1 subscriber', 1, pubsub.getCount('foo')); assertEquals('Topic "bar" must have 2 subscribers', 2, pubsub.getCount('bar')); assertTrue(pubsub.unsubscribe('foo', foo2)); assertEquals('Topic "foo" must have no subscribers', 0, pubsub.getCount('foo')); assertEquals('Topic "bar" must have 2 subscribers', 2, pubsub.getCount('bar')); assertTrue(pubsub.unsubscribe('bar', bar1)); assertEquals('Topic "foo" must have no subscribers', 0, pubsub.getCount('foo')); assertEquals('Topic "bar" must have 1 subscriber', 1, pubsub.getCount('bar')); assertTrue(pubsub.unsubscribe('bar', bar2)); assertEquals('Topic "foo" must have no subscribers', 0, pubsub.getCount('foo')); assertEquals('Topic "bar" must have no subscribers', 0, pubsub.getCount('bar')); assertFalse('Unsubscribing a nonexistent topic must return false', pubsub.unsubscribe('baz', foo1)); assertFalse('Unsubscribing a nonexistent function must return false', pubsub.unsubscribe('foo', function() {})); } function testSubscribeUnsubscribeWithContext() { function foo() { } function bar() { } var contextA = {}; var contextB = {}; assertEquals('Topic "X" must not have any subscribers', 0, pubsub.getCount('X')); pubsub.subscribe('X', foo, contextA); assertEquals('Topic "X" must have 1 subscriber', 1, pubsub.getCount('X')); pubsub.subscribe('X', bar); assertEquals('Topic "X" must have 2 subscribers', 2, pubsub.getCount('X')); pubsub.subscribe('X', bar, contextB); assertEquals('Topic "X" must have 3 subscribers', 3, pubsub.getCount('X')); assertFalse('Unknown function/context combination return false', pubsub.unsubscribe('X', foo, contextB)); assertTrue(pubsub.unsubscribe('X', foo, contextA)); assertEquals('Topic "X" must have 2 subscribers', 2, pubsub.getCount('X')); assertTrue(pubsub.unsubscribe('X', bar)); assertEquals('Topic "X" must have 1 subscriber', 1, pubsub.getCount('X')); assertTrue(pubsub.unsubscribe('X', bar, contextB)); assertEquals('Topic "X" must have no subscribers', 0, pubsub.getCount('X')); } function testSubscribeOnce() { var called, context; called = false; pubsub.subscribeOnce('someTopic', function() { called = true; }); assertEquals('Topic must have one subscriber', 1, pubsub.getCount('someTopic')); assertFalse('Subscriber must not have been called yet', called); pubsub.publish('someTopic'); assertEquals('Topic must have no subscribers', 0, pubsub.getCount('someTopic')); assertTrue('Subscriber must have been called', called); context = {called: false}; pubsub.subscribeOnce('someTopic', function() { this.called = true; }, context); assertEquals('Topic must have one subscriber', 1, pubsub.getCount('someTopic')); assertFalse('Subscriber must not have been called yet', context.called); pubsub.publish('someTopic'); assertEquals('Topic must have no subscribers', 0, pubsub.getCount('someTopic')); assertTrue('Subscriber must have been called', context.called); context = {called: false, value: 0}; pubsub.subscribeOnce('someTopic', function(value) { this.called = true; this.value = value; }, context); assertEquals('Topic must have one subscriber', 1, pubsub.getCount('someTopic')); assertFalse('Subscriber must not have been called yet', context.called); assertEquals('Value must have expected value', 0, context.value); pubsub.publish('someTopic', 17); assertEquals('Topic must have no subscribers', 0, pubsub.getCount('someTopic')); assertTrue('Subscriber must have been called', context.called); assertEquals('Value must have been updated', 17, context.value); } function testSubscribeOnce_boundFn() { var context = {called: false, value: 0}; function subscriber(value) { this.called = true; this.value = value; } pubsub.subscribeOnce('someTopic', goog.bind(subscriber, context)); assertEquals('Topic must have one subscriber', 1, pubsub.getCount('someTopic')); assertFalse('Subscriber must not have been called yet', context.called); assertEquals('Value must have expected value', 0, context.value); pubsub.publish('someTopic', 17); assertEquals('Topic must have no subscribers', 0, pubsub.getCount('someTopic')); assertTrue('Subscriber must have been called', context.called); assertEquals('Value must have been updated', 17, context.value); } function testSubscribeOnce_partialFn() { var called = false; var value = 0; function subscriber(hasBeenCalled, newValue) { called = hasBeenCalled; value = newValue; } pubsub.subscribeOnce('someTopic', goog.partial(subscriber, true)); assertEquals('Topic must have one subscriber', 1, pubsub.getCount('someTopic')); assertFalse('Subscriber must not have been called yet', called); assertEquals('Value must have expected value', 0, value); pubsub.publish('someTopic', 17); assertEquals('Topic must have no subscribers', 0, pubsub.getCount('someTopic')); assertTrue('Subscriber must have been called', called); assertEquals('Value must have been updated', 17, value); } function testSelfResubscribe() { var value = null; function resubscribe(iteration, newValue) { pubsub.subscribeOnce('someTopic', goog.partial(resubscribe, iteration + 1)); value = newValue + ':' + iteration; } pubsub.subscribeOnce('someTopic', goog.partial(resubscribe, 0)); assertEquals('Topic must have 1 subscriber', 1, pubsub.getCount('someTopic')); assertNull('Value must be null', value); pubsub.publish('someTopic', 'foo'); assertEquals('Topic must have 1 subscriber', 1, pubsub.getCount('someTopic')); assertEquals('Pubsub must not have any pending unsubscribe keys', 0, pubsub.pendingKeys_.length); assertEquals('Value be as expected', 'foo:0', value); pubsub.publish('someTopic', 'bar'); assertEquals('Topic must have 1 subscriber', 1, pubsub.getCount('someTopic')); assertEquals('Pubsub must not have any pending unsubscribe keys', 0, pubsub.pendingKeys_.length); assertEquals('Value be as expected', 'bar:1', value); pubsub.publish('someTopic', 'baz'); assertEquals('Topic must have 1 subscriber', 1, pubsub.getCount('someTopic')); assertEquals('Pubsub must not have any pending unsubscribe keys', 0, pubsub.pendingKeys_.length); assertEquals('Value be as expected', 'baz:2', value); } function testUnsubscribeByKey() { var key1, key2, key3; key1 = pubsub.subscribe('X', function() {}); key2 = pubsub.subscribe('Y', function() {}); assertEquals('Topic "X" must have 1 subscriber', 1, pubsub.getCount('X')); assertEquals('Topic "Y" must have 1 subscriber', 1, pubsub.getCount('Y')); assertNotEquals('Subscription keys must be distinct', key1, key2); pubsub.unsubscribeByKey(key1); assertEquals('Topic "X" must have no subscribers', 0, pubsub.getCount('X')); assertEquals('Topic "Y" must have 1 subscriber', 1, pubsub.getCount('Y')); key3 = pubsub.subscribe('X', function() {}); assertEquals('Topic "X" must have 1 subscriber', 1, pubsub.getCount('X')); assertEquals('Topic "Y" must have 1 subscriber', 1, pubsub.getCount('Y')); assertNotEquals('Subscription keys must be distinct', key1, key3); assertNotEquals('Subscription keys must be distinct', key2, key3); pubsub.unsubscribeByKey(key1); // Obsolete key; should be no-op. assertEquals('Topic "X" must have 1 subscriber', 1, pubsub.getCount('X')); assertEquals('Topic "Y" must have 1 subscriber', 1, pubsub.getCount('Y')); pubsub.unsubscribeByKey(key2); assertEquals('Topic "X" must have 1 subscriber', 1, pubsub.getCount('X')); assertEquals('Topic "Y" must have no subscribers', 0, pubsub.getCount('Y')); pubsub.unsubscribeByKey(key3); assertEquals('Topic "X" must have no subscribers', 0, pubsub.getCount('X')); assertEquals('Topic "Y" must have no subscribers', 0, pubsub.getCount('Y')); } function testSubscribeUnsubscribeMultiple() { function foo() { } function bar() { } var context = {}; assertEquals('Pubsub channel must not have any subscribers', 0, pubsub.getCount()); assertEquals('Topic "X" must not have any subscribers', 0, pubsub.getCount('X')); assertEquals('Topic "Y" must not have any subscribers', 0, pubsub.getCount('Y')); assertEquals('Topic "Z" must not have any subscribers', 0, pubsub.getCount('Z')); goog.array.forEach(['X', 'Y', 'Z'], function(topic) { pubsub.subscribe(topic, foo); }); assertEquals('Topic "X" must have 1 subscriber', 1, pubsub.getCount('X')); assertEquals('Topic "Y" must have 1 subscriber', 1, pubsub.getCount('Y')); assertEquals('Topic "Z" must have 1 subscriber', 1, pubsub.getCount('Z')); goog.array.forEach(['X', 'Y', 'Z'], function(topic) { pubsub.subscribe(topic, bar, context); }); assertEquals('Topic "X" must have 2 subscribers', 2, pubsub.getCount('X')); assertEquals('Topic "Y" must have 2 subscribers', 2, pubsub.getCount('Y')); assertEquals('Topic "Z" must have 2 subscribers', 2, pubsub.getCount('Z')); assertEquals('Pubsub channel must have a total of 6 subscribers', 6, pubsub.getCount()); goog.array.forEach(['X', 'Y', 'Z'], function(topic) { pubsub.unsubscribe(topic, foo); }); assertEquals('Topic "X" must have 1 subscriber', 1, pubsub.getCount('X')); assertEquals('Topic "Y" must have 1 subscriber', 1, pubsub.getCount('Y')); assertEquals('Topic "Z" must have 1 subscriber', 1, pubsub.getCount('Z')); goog.array.forEach(['X', 'Y', 'Z'], function(topic) { pubsub.unsubscribe(topic, bar, context); }); assertEquals('Topic "X" must not have any subscribers', 0, pubsub.getCount('X')); assertEquals('Topic "Y" must not have any subscribers', 0, pubsub.getCount('Y')); assertEquals('Topic "Z" must not have any subscribers', 0, pubsub.getCount('Z')); assertEquals('Pubsub channel must not have any subscribers', 0, pubsub.getCount()); } function testPublish() { var context = {}; var fooCalled = false; var barCalled = false; function foo(x, y) { fooCalled = true; assertEquals('x must have expected value', 'x', x); assertEquals('y must have expected value', 'y', y); } function bar(x, y) { barCalled = true; assertEquals('Context must have expected value', context, this); assertEquals('x must have expected value', 'x', x); assertEquals('y must have expected value', 'y', y); } pubsub.subscribe('someTopic', foo); pubsub.subscribe('someTopic', bar, context); assertTrue(pubsub.publish('someTopic', 'x', 'y')); assertTrue('foo() must have been called', fooCalled); assertTrue('bar() must have been called', barCalled); fooCalled = false; barCalled = false; assertTrue(pubsub.unsubscribe('someTopic', foo)); assertTrue(pubsub.publish('someTopic', 'x', 'y')); assertFalse('foo() must not have been called', fooCalled); assertTrue('bar() must have been called', barCalled); fooCalled = false; barCalled = false; pubsub.subscribe('differentTopic', foo); assertTrue(pubsub.publish('someTopic', 'x', 'y')); assertFalse('foo() must not have been called', fooCalled); assertTrue('bar() must have been called', barCalled); } function testPublishEmptyTopic() { var fooCalled = false; function foo() { fooCalled = true; } assertFalse('Publishing to nonexistent topic must return false', pubsub.publish('someTopic')); pubsub.subscribe('someTopic', foo); assertTrue('Publishing to topic with subscriber must return true', pubsub.publish('someTopic')); assertTrue('Foo must have been called', fooCalled); pubsub.unsubscribe('someTopic', foo); fooCalled = false; assertFalse('Publishing to topic without subscribers must return false', pubsub.publish('someTopic')); assertFalse('Foo must nothave been called', fooCalled); } function testSubscribeWhilePublishing() { // 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 firstCalled = false; var secondCalled = false; pubsub.subscribe('someTopic', function() { pubsub.subscribe('someTopic', function() { secondCalled = true; }); firstCalled = true; }); assertEquals('Topic must have one subscriber', 1, pubsub.getCount('someTopic')); assertFalse('No subscriber must have been called yet', firstCalled || secondCalled); pubsub.publish('someTopic'); assertEquals('Topic must have two subscribers', 2, pubsub.getCount('someTopic')); assertTrue('The first subscriber must have been called', firstCalled); assertFalse('The second subscriber must not have been called yet', secondCalled); pubsub.publish('someTopic'); assertEquals('Topic must have three subscribers', 3, pubsub.getCount('someTopic')); assertTrue('The first subscriber must have been called', firstCalled); assertTrue('The second subscriber must also have been called', secondCalled); } function testUnsubscribeWhilePublishing() { // 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 firstCalled = false; var secondCalled = false; var thirdCalled = false; function first() { assertFalse('unsubscribe() must return false during publishing', pubsub.unsubscribe('X', second)); assertEquals('Topic "X" must still have 3 subscribers', 3, pubsub.getCount('X')); firstCalled = true; } pubsub.subscribe('X', first); function second() { assertEquals('Topic "X" must still have 3 subscribers', 3, pubsub.getCount('X')); secondCalled = true; } pubsub.subscribe('X', second); function third() { assertFalse('unsubscribe() must return false during publishing', pubsub.unsubscribe('X', first)); assertEquals('Topic "X" must still have 3 subscribers', 3, pubsub.getCount('X')); thirdCalled = true; } pubsub.subscribe('X', third); assertEquals('Topic "X" must have 3 subscribers', 3, pubsub.getCount('X')); assertFalse('No subscribers must have been called yet', firstCalled || secondCalled || thirdCalled); assertTrue(pubsub.publish('X')); assertTrue('First function must have been called', firstCalled); assertTrue('Second function must have been called', secondCalled); assertTrue('Third function must have been called', thirdCalled); assertEquals('Topic "X" must have 1 subscriber after publishing', 1, pubsub.getCount('X')); assertEquals('PubSub must not have any subscriptions pending removal', 0, pubsub.pendingKeys_.length); } function testUnsubscribeSelfWhilePublishing() { // It's OK for a subscriber to unsubscribe itself, but it won't actually // be removed until after publishing is complete. var selfDestructCalled = false; function selfDestruct() { assertFalse('unsubscribe() must return false during publishing', pubsub.unsubscribe('someTopic', arguments.callee)); assertEquals('Topic must still have 1 subscriber', 1, pubsub.getCount('someTopic')); selfDestructCalled = true; } pubsub.subscribe('someTopic', selfDestruct); assertEquals('Topic must have 1 subscriber', 1, pubsub.getCount('someTopic')); assertFalse('selfDestruct() must not have been called yet', selfDestructCalled); pubsub.publish('someTopic'); assertTrue('selfDestruct() must have been called', selfDestructCalled); assertEquals('Topic must have no subscribers after publishing', 0, pubsub.getCount('someTopic')); assertEquals('PubSub must not have any subscriptions pending removal', 0, pubsub.pendingKeys_.length); } function testPublishReturnValue() { pubsub.subscribe('X', function() { pubsub.unsubscribe('X', arguments.callee); }); assertTrue('publish() must return true even if the only subscriber ' + 'removes itself during publishing', pubsub.publish('X')); } function testNestedPublish() { var x1 = false; var x2 = false; var y1 = false; var y2 = false; pubsub.subscribe('X', function() { pubsub.publish('Y'); pubsub.unsubscribe('X', arguments.callee); x1 = true; }); pubsub.subscribe('X', function() { x2 = true; }); pubsub.subscribe('Y', function() { pubsub.unsubscribe('Y', arguments.callee); y1 = true; }); pubsub.subscribe('Y', function() { y2 = true; }); pubsub.publish('X'); assertTrue('x1 must be true', x1); assertTrue('x2 must be true', x2); assertTrue('y1 must be true', y1); assertTrue('y2 must be true', y2); } function testClear() { function fn() { } goog.array.forEach(['W', 'X', 'Y', 'Z'], function(topic) { pubsub.subscribe(topic, fn); }); assertEquals('Pubsub channel must have 4 subscribers', 4, pubsub.getCount()); pubsub.clear('W'); assertEquals('Pubsub channel must have 3 subscribers', 3, pubsub.getCount()); goog.array.forEach(['X', 'Y'], function(topic) { pubsub.clear(topic); }); assertEquals('Pubsub channel must have 1 subscriber', 1, pubsub.getCount()); pubsub.clear(); assertEquals('Pubsub channel must have no subscribers', 0, pubsub.getCount()); }