Adding mapbox-gl branch
This commit is contained in:
@@ -0,0 +1,335 @@
|
||||
// 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.
|
||||
|
||||
/**
|
||||
* @fileoverview Topic-based publish/subscribe channel implementation.
|
||||
*
|
||||
* @author attila@google.com (Attila Bodis)
|
||||
*/
|
||||
|
||||
goog.provide('goog.pubsub.PubSub');
|
||||
|
||||
goog.require('goog.Disposable');
|
||||
goog.require('goog.array');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Topic-based publish/subscribe channel. Maintains a map of topics to
|
||||
* subscriptions. When a message is published to a topic, all functions
|
||||
* subscribed to that topic are invoked in the order they were added.
|
||||
* Uncaught errors abort publishing.
|
||||
*
|
||||
* Topics may be identified by any nonempty string, <strong>except</strong>
|
||||
* strings corresponding to native Object properties, e.g. "constructor",
|
||||
* "toString", "hasOwnProperty", etc.
|
||||
*
|
||||
* @constructor
|
||||
* @extends {goog.Disposable}
|
||||
*/
|
||||
goog.pubsub.PubSub = function() {
|
||||
goog.Disposable.call(this);
|
||||
this.subscriptions_ = [];
|
||||
this.topics_ = {};
|
||||
};
|
||||
goog.inherits(goog.pubsub.PubSub, goog.Disposable);
|
||||
|
||||
|
||||
/**
|
||||
* Sparse array of subscriptions. Each subscription is represented by a tuple
|
||||
* comprising a topic identifier, a function, and an optional context object.
|
||||
* Each tuple occupies three consecutive positions in the array, with the topic
|
||||
* identifier at index n, the function at index (n + 1), the context object at
|
||||
* index (n + 2), the next topic at index (n + 3), etc. (This representation
|
||||
* minimizes the number of object allocations and has been shown to be faster
|
||||
* than an array of objects with three key-value pairs or three parallel arrays,
|
||||
* especially on IE.) Once a subscription is removed via {@link #unsubscribe}
|
||||
* or {@link #unsubscribeByKey}, the three corresponding array elements are
|
||||
* deleted, and never reused. This means the total number of subscriptions
|
||||
* during the lifetime of the pubsub channel is limited by the maximum length
|
||||
* of a JavaScript array to (2^32 - 1) / 3 = 1,431,655,765 subscriptions, which
|
||||
* should suffice for most applications.
|
||||
*
|
||||
* @type {!Array<?>}
|
||||
* @private
|
||||
*/
|
||||
goog.pubsub.PubSub.prototype.subscriptions_;
|
||||
|
||||
|
||||
/**
|
||||
* The next available subscription key. Internally, this is an index into the
|
||||
* sparse array of subscriptions.
|
||||
*
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.pubsub.PubSub.prototype.key_ = 1;
|
||||
|
||||
|
||||
/**
|
||||
* Map of topics to arrays of subscription keys.
|
||||
*
|
||||
* @type {!Object<!Array<number>>}
|
||||
* @private
|
||||
*/
|
||||
goog.pubsub.PubSub.prototype.topics_;
|
||||
|
||||
|
||||
/**
|
||||
* Array of subscription keys pending removal once publishing is done.
|
||||
*
|
||||
* @type {Array<number>}
|
||||
* @private
|
||||
*/
|
||||
goog.pubsub.PubSub.prototype.pendingKeys_;
|
||||
|
||||
|
||||
/**
|
||||
* Lock to prevent the removal of subscriptions during publishing. Incremented
|
||||
* at the beginning of {@link #publish}, and decremented at the end.
|
||||
*
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.pubsub.PubSub.prototype.publishDepth_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Subscribes a function to a topic. The function is invoked as a method on
|
||||
* the given {@code opt_context} object, or in the global scope if no context
|
||||
* is specified. Subscribing the same function to the same topic multiple
|
||||
* times will result in multiple function invocations while publishing.
|
||||
* Returns a subscription key that can be used to unsubscribe the function from
|
||||
* the topic via {@link #unsubscribeByKey}.
|
||||
*
|
||||
* @param {string} topic Topic to subscribe to.
|
||||
* @param {Function} fn Function to be invoked when a message is published to
|
||||
* the given topic.
|
||||
* @param {Object=} opt_context Object in whose context the function is to be
|
||||
* called (the global scope if none).
|
||||
* @return {number} Subscription key.
|
||||
*/
|
||||
goog.pubsub.PubSub.prototype.subscribe = function(topic, fn, opt_context) {
|
||||
var keys = this.topics_[topic];
|
||||
if (!keys) {
|
||||
// First subscription to this topic; initialize subscription key array.
|
||||
keys = this.topics_[topic] = [];
|
||||
}
|
||||
|
||||
// Push the tuple representing the subscription onto the subscription array.
|
||||
var key = this.key_;
|
||||
this.subscriptions_[key] = topic;
|
||||
this.subscriptions_[key + 1] = fn;
|
||||
this.subscriptions_[key + 2] = opt_context;
|
||||
this.key_ = key + 3;
|
||||
|
||||
// Push the subscription key onto the list of subscriptions for the topic.
|
||||
keys.push(key);
|
||||
|
||||
// Return the subscription key.
|
||||
return key;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Subscribes a single-use function to a topic. The function is invoked as a
|
||||
* method on the given {@code opt_context} object, or in the global scope if
|
||||
* no context is specified, and is then unsubscribed. Returns a subscription
|
||||
* key that can be used to unsubscribe the function from the topic via
|
||||
* {@link #unsubscribeByKey}.
|
||||
*
|
||||
* @param {string} topic Topic to subscribe to.
|
||||
* @param {Function} fn Function to be invoked once and then unsubscribed when
|
||||
* a message is published to the given topic.
|
||||
* @param {Object=} opt_context Object in whose context the function is to be
|
||||
* called (the global scope if none).
|
||||
* @return {number} Subscription key.
|
||||
*/
|
||||
goog.pubsub.PubSub.prototype.subscribeOnce = function(topic, fn, opt_context) {
|
||||
// Behold the power of lexical closures!
|
||||
var key = this.subscribe(topic, function(var_args) {
|
||||
fn.apply(opt_context, arguments);
|
||||
this.unsubscribeByKey(key);
|
||||
}, this);
|
||||
return key;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Unsubscribes a function from a topic. Only deletes the first match found.
|
||||
* Returns a Boolean indicating whether a subscription was removed.
|
||||
*
|
||||
* @param {string} topic Topic to unsubscribe from.
|
||||
* @param {Function} fn Function to unsubscribe.
|
||||
* @param {Object=} opt_context Object in whose context the function was to be
|
||||
* called (the global scope if none).
|
||||
* @return {boolean} Whether a matching subscription was removed.
|
||||
*/
|
||||
goog.pubsub.PubSub.prototype.unsubscribe = function(topic, fn, opt_context) {
|
||||
var keys = this.topics_[topic];
|
||||
if (keys) {
|
||||
// Find the subscription key for the given combination of topic, function,
|
||||
// and context object.
|
||||
var subscriptions = this.subscriptions_;
|
||||
var key = goog.array.find(keys, function(k) {
|
||||
return subscriptions[k + 1] == fn && subscriptions[k + 2] == opt_context;
|
||||
});
|
||||
// Zero is not a valid key.
|
||||
if (key) {
|
||||
return this.unsubscribeByKey(/** @type {number} */ (key));
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes a subscription based on the key returned by {@link #subscribe}.
|
||||
* No-op if no matching subscription is found. Returns a Boolean indicating
|
||||
* whether a subscription was removed.
|
||||
*
|
||||
* @param {number} key Subscription key.
|
||||
* @return {boolean} Whether a matching subscription was removed.
|
||||
*/
|
||||
goog.pubsub.PubSub.prototype.unsubscribeByKey = function(key) {
|
||||
if (this.publishDepth_ != 0) {
|
||||
// Defer removal until after publishing is complete.
|
||||
if (!this.pendingKeys_) {
|
||||
this.pendingKeys_ = [];
|
||||
}
|
||||
this.pendingKeys_.push(key);
|
||||
return false;
|
||||
}
|
||||
|
||||
var topic = this.subscriptions_[key];
|
||||
if (topic) {
|
||||
// Subscription tuple found.
|
||||
var keys = this.topics_[topic];
|
||||
if (keys) {
|
||||
goog.array.remove(keys, key);
|
||||
}
|
||||
delete this.subscriptions_[key];
|
||||
delete this.subscriptions_[key + 1];
|
||||
delete this.subscriptions_[key + 2];
|
||||
}
|
||||
|
||||
return !!topic;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Publishes a message to a topic. Calls functions subscribed to the topic in
|
||||
* the order in which they were added, passing all arguments along. If any of
|
||||
* the functions throws an uncaught error, publishing is aborted.
|
||||
*
|
||||
* @param {string} topic Topic to publish to.
|
||||
* @param {...*} var_args Arguments that are applied to each subscription
|
||||
* function.
|
||||
* @return {boolean} Whether any subscriptions were called.
|
||||
*/
|
||||
goog.pubsub.PubSub.prototype.publish = function(topic, var_args) {
|
||||
var keys = this.topics_[topic];
|
||||
if (keys) {
|
||||
// We must lock subscriptions and remove them at the end, so we don't
|
||||
// adversely affect the performance of the common case by cloning the key
|
||||
// array.
|
||||
this.publishDepth_++;
|
||||
|
||||
// Copy var_args to a new array so they can be passed to subscribers.
|
||||
// Note that we can't use Array.slice or goog.array.toArray for this for
|
||||
// performance reasons. Using those with the arguments object will cause
|
||||
// deoptimization.
|
||||
var args = new Array(arguments.length - 1);
|
||||
for (var i = 1, len = arguments.length; i < len; i++) {
|
||||
args[i - 1] = arguments[i];
|
||||
}
|
||||
|
||||
// For each key in the list of subscription keys for the topic, apply the
|
||||
// function to the arguments in the appropriate context. The length of the
|
||||
// array mush be fixed during the iteration, since subscribers may add new
|
||||
// subscribers during publishing.
|
||||
for (var i = 0, len = keys.length; i < len; i++) {
|
||||
var key = keys[i];
|
||||
this.subscriptions_[key + 1].apply(this.subscriptions_[key + 2], args);
|
||||
}
|
||||
|
||||
// Unlock subscriptions.
|
||||
this.publishDepth_--;
|
||||
|
||||
if (this.pendingKeys_ && this.publishDepth_ == 0) {
|
||||
var pendingKey;
|
||||
while ((pendingKey = this.pendingKeys_.pop())) {
|
||||
this.unsubscribeByKey(pendingKey);
|
||||
}
|
||||
}
|
||||
|
||||
// At least one subscriber was called.
|
||||
return i != 0;
|
||||
}
|
||||
|
||||
// No subscribers were found.
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears the subscription list for a topic, or all topics if unspecified.
|
||||
* @param {string=} opt_topic Topic to clear (all topics if unspecified).
|
||||
*/
|
||||
goog.pubsub.PubSub.prototype.clear = function(opt_topic) {
|
||||
if (opt_topic) {
|
||||
var keys = this.topics_[opt_topic];
|
||||
if (keys) {
|
||||
goog.array.forEach(keys, this.unsubscribeByKey, this);
|
||||
delete this.topics_[opt_topic];
|
||||
}
|
||||
} else {
|
||||
this.subscriptions_.length = 0;
|
||||
this.topics_ = {};
|
||||
// We don't reset key_ on purpose, because we want subscription keys to be
|
||||
// unique throughout the lifetime of the application. Reusing subscription
|
||||
// keys could lead to subtle errors in client code.
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of subscriptions to the given topic (or all topics if
|
||||
* unspecified).
|
||||
* @param {string=} opt_topic The topic (all topics if unspecified).
|
||||
* @return {number} Number of subscriptions to the topic.
|
||||
*/
|
||||
goog.pubsub.PubSub.prototype.getCount = function(opt_topic) {
|
||||
if (opt_topic) {
|
||||
var keys = this.topics_[opt_topic];
|
||||
return keys ? keys.length : 0;
|
||||
}
|
||||
|
||||
var count = 0;
|
||||
for (var topic in this.topics_) {
|
||||
count += this.getCount(topic);
|
||||
}
|
||||
|
||||
return count;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.pubsub.PubSub.prototype.disposeInternal = function() {
|
||||
goog.pubsub.PubSub.superClass_.disposeInternal.call(this);
|
||||
delete this.subscriptions_;
|
||||
delete this.topics_;
|
||||
delete this.pendingKeys_;
|
||||
};
|
||||
@@ -0,0 +1,290 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2009 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
Author: attila@google.com (Attila Bodis)
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>Closure Performance Tests - goog.pubsub.PubSub</title>
|
||||
<link rel="stylesheet" href="../testing/performancetable.css" />
|
||||
<script src="../base.js"></script>
|
||||
<script>
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.EventTarget');
|
||||
goog.require('goog.pubsub.PubSub');
|
||||
goog.require('goog.testing.PerformanceTable');
|
||||
goog.require('goog.testing.PerformanceTimer');
|
||||
goog.require('goog.testing.jsunit');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>goog.pubsub.PubSub Performance Tests</h1>
|
||||
<p>
|
||||
<b>User-agent:</b> <script>document.write(navigator.userAgent);</script>
|
||||
</p>
|
||||
<p>
|
||||
Compares the performance of the event system (<code>goog.events.*</code>)
|
||||
with the <code>goog.pubsub.PubSub</code> class.
|
||||
</p>
|
||||
<p>
|
||||
The baseline test creates 1000 event targets and 1000 objects that handle
|
||||
events dispatched by the event targets, and has each event target dispatch
|
||||
2 events 5 times each.
|
||||
</p>
|
||||
<p>
|
||||
The single-<code>PubSub</code> test creates 1000 publishers, 1000
|
||||
subscribers, and a single pubsub channel. Each subscriber subscribes to
|
||||
topics on the same pubsub channel. Each publisher publishes 5 messages to
|
||||
2 topics each via the pubsub channel.
|
||||
</p>
|
||||
<p>
|
||||
The multi-<code>PubSub</code> test creates 1000 publishers that are
|
||||
subclasses of <code>goog.pubsub.PubSub</code> and 1000 subscribers. Each
|
||||
subscriber subscribes to its own publisher. Each publisher publishes 5
|
||||
messages to 2 topics each via its own pubsub channel.
|
||||
</p>
|
||||
<div id="perfTable"></div>
|
||||
<hr>
|
||||
<script>
|
||||
var targets, publishers, pubsubs, handlers;
|
||||
|
||||
// Number of objects to test per run.
|
||||
var SAMPLES_PER_RUN = 1000;
|
||||
|
||||
// The performance table & performance timer.
|
||||
var table, timer;
|
||||
|
||||
// Event/topic identifiers.
|
||||
var ACTION = 'action';
|
||||
var CHANGE = 'change';
|
||||
|
||||
// Number of times handlers have been called.
|
||||
var actionCount = 0;
|
||||
var changeCount = 0;
|
||||
|
||||
// Generic event handler class.
|
||||
function Handler() {
|
||||
}
|
||||
Handler.prototype.handleAction = function() {
|
||||
actionCount++;
|
||||
};
|
||||
Handler.prototype.handleChange = function() {
|
||||
changeCount++;
|
||||
};
|
||||
|
||||
// Generic publisher class that uses a global pubsub channel.
|
||||
function Publisher(pubsub, id) {
|
||||
this.pubsub = pubsub;
|
||||
this.id = id;
|
||||
}
|
||||
Publisher.prototype.publish = function(topic) {
|
||||
this.pubsub.publish(this.id + '.' + topic);
|
||||
};
|
||||
|
||||
// PubSub subclass; allows clients to subscribe and uses itself to publish.
|
||||
function PubSub() {
|
||||
goog.pubsub.PubSub.call(this);
|
||||
}
|
||||
goog.inherits(PubSub, goog.pubsub.PubSub);
|
||||
|
||||
// EventTarget subclass; uses goog.events.* to dispatch events.
|
||||
function Target() {
|
||||
goog.events.EventTarget.call(this);
|
||||
}
|
||||
goog.inherits(Target, goog.events.EventTarget);
|
||||
Target.prototype.fireEvent = function(type) {
|
||||
this.dispatchEvent(type);
|
||||
};
|
||||
|
||||
function initHandlers(count) {
|
||||
for (var i = 0; i < count; i++) {
|
||||
handlers[i] = new Handler();
|
||||
}
|
||||
}
|
||||
|
||||
function initPublishers(pubsub, count) {
|
||||
for (var i = 0; i < count; i++) {
|
||||
publishers[i] = new Publisher(pubsub, i);
|
||||
}
|
||||
}
|
||||
|
||||
function initPubSubs(count) {
|
||||
for (var i = 0; i < count; i++) {
|
||||
pubsubs[i] = new PubSub();
|
||||
}
|
||||
}
|
||||
|
||||
function initTargets(count) {
|
||||
for (var i = 0; i < count; i++) {
|
||||
targets[i] = new Target();
|
||||
}
|
||||
}
|
||||
|
||||
function createEventListeners(count) {
|
||||
initHandlers(count);
|
||||
initTargets(count);
|
||||
for (var i = 0; i < count; i++) {
|
||||
goog.events.listen(targets[i], ACTION, Handler.prototype.handleAction,
|
||||
false, handlers[i]);
|
||||
goog.events.listen(targets[i], CHANGE, Handler.prototype.handleChange,
|
||||
false, handlers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function createGlobalSubscriptions(pubsub, count) {
|
||||
initHandlers(count);
|
||||
initPublishers(pubsub, count);
|
||||
for (var i = 0; i < count; i++) {
|
||||
pubsub.subscribe(i + '.' + ACTION, Handler.prototype.handleAction,
|
||||
handlers[i]);
|
||||
pubsub.subscribe(i + '.' + CHANGE, Handler.prototype.handleChange,
|
||||
handlers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function createSubscriptions(count) {
|
||||
initHandlers(count);
|
||||
initPubSubs(count);
|
||||
for (var i = 0; i < count; i++) {
|
||||
pubsubs[i].subscribe(ACTION, Handler.prototype.handleAction,
|
||||
handlers[i]);
|
||||
pubsubs[i].subscribe(CHANGE, Handler.prototype.handleChange,
|
||||
handlers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function dispatchEvents(count) {
|
||||
for (var i = 0; i < count; i++) {
|
||||
for (var j = 0; j < 5; j++) {
|
||||
targets[i].fireEvent(ACTION);
|
||||
targets[i].fireEvent(CHANGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function publishGlobalMessages(count) {
|
||||
for (var i = 0; i < count; i++) {
|
||||
for (var j = 0; j < 5; j++) {
|
||||
publishers[i].publish(ACTION);
|
||||
publishers[i].publish(CHANGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function publishMessages(count) {
|
||||
for (var i = 0; i < count; i++) {
|
||||
for (var j = 0; j < 5; j++) {
|
||||
pubsubs[i].publish(ACTION);
|
||||
pubsubs[i].publish(CHANGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setUpPage() {
|
||||
timer = new goog.testing.PerformanceTimer();
|
||||
timer.setNumSamples(10);
|
||||
timer.setTimeoutInterval(9000);
|
||||
timer.setDiscardOutliers(true);
|
||||
table = new goog.testing.PerformanceTable(
|
||||
goog.dom.getElement('perfTable'), timer);
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
actionCount = 0;
|
||||
changeCount = 0;
|
||||
handlers = [];
|
||||
publishers = [];
|
||||
pubsubs = [];
|
||||
targets = [];
|
||||
}
|
||||
|
||||
function testCreateEventListeners() {
|
||||
table.run(goog.partial(createEventListeners, SAMPLES_PER_RUN),
|
||||
'1A: Create event listeners');
|
||||
assertEquals(0, actionCount);
|
||||
assertEquals(0, changeCount);
|
||||
}
|
||||
|
||||
function testCreateGlobalSubscriptions() {
|
||||
var pubsub = new goog.pubsub.PubSub();
|
||||
table.run(
|
||||
goog.partial(createGlobalSubscriptions, pubsub, SAMPLES_PER_RUN),
|
||||
'1B: Create global subscriptions');
|
||||
assertEquals(SAMPLES_PER_RUN * timer.getNumSamples() * 2,
|
||||
pubsub.getCount());
|
||||
assertEquals(0, actionCount);
|
||||
assertEquals(0, changeCount);
|
||||
pubsub.dispose();
|
||||
}
|
||||
|
||||
function testCreateSubscripions() {
|
||||
table.run(goog.partial(createSubscriptions, SAMPLES_PER_RUN),
|
||||
'1C: Create subscriptions');
|
||||
assertEquals(0, actionCount);
|
||||
assertEquals(0, changeCount);
|
||||
}
|
||||
|
||||
function testDispatchEvents() {
|
||||
createEventListeners(SAMPLES_PER_RUN);
|
||||
table.run(goog.partial(dispatchEvents, SAMPLES_PER_RUN),
|
||||
'2A: Dispatch events');
|
||||
assertEquals(SAMPLES_PER_RUN * timer.getNumSamples() * 5, actionCount);
|
||||
assertEquals(SAMPLES_PER_RUN * timer.getNumSamples() * 5, changeCount);
|
||||
}
|
||||
|
||||
function testPublishGlobalMessages() {
|
||||
var pubsub = new goog.pubsub.PubSub();
|
||||
createGlobalSubscriptions(pubsub, SAMPLES_PER_RUN);
|
||||
table.run(
|
||||
goog.partial(publishGlobalMessages, SAMPLES_PER_RUN),
|
||||
'2B: Publish global messages');
|
||||
assertEquals(SAMPLES_PER_RUN * timer.getNumSamples() * 5, actionCount);
|
||||
assertEquals(SAMPLES_PER_RUN * timer.getNumSamples() * 5, changeCount);
|
||||
pubsub.dispose();
|
||||
}
|
||||
|
||||
function testPublishMessages() {
|
||||
createSubscriptions(SAMPLES_PER_RUN);
|
||||
table.run(goog.partial(publishMessages, SAMPLES_PER_RUN),
|
||||
'2C: Publish messages');
|
||||
assertEquals(SAMPLES_PER_RUN * timer.getNumSamples() * 5, actionCount);
|
||||
assertEquals(SAMPLES_PER_RUN * timer.getNumSamples() * 5, changeCount);
|
||||
}
|
||||
|
||||
function testEvents() {
|
||||
table.run(function() {
|
||||
createEventListeners(SAMPLES_PER_RUN);
|
||||
dispatchEvents(SAMPLES_PER_RUN);
|
||||
}, '3A: Events');
|
||||
assertEquals(SAMPLES_PER_RUN * timer.getNumSamples() * 5, actionCount);
|
||||
assertEquals(SAMPLES_PER_RUN * timer.getNumSamples() * 5, changeCount);
|
||||
}
|
||||
|
||||
function testSinglePubSub() {
|
||||
table.run(function() {
|
||||
var pubsub = new goog.pubsub.PubSub();
|
||||
createGlobalSubscriptions(pubsub, SAMPLES_PER_RUN);
|
||||
publishGlobalMessages(SAMPLES_PER_RUN);
|
||||
pubsub.dispose();
|
||||
}, '3B: Single PubSub');
|
||||
assertEquals(SAMPLES_PER_RUN * timer.getNumSamples() * 5, actionCount);
|
||||
assertEquals(SAMPLES_PER_RUN * timer.getNumSamples() * 5, changeCount);
|
||||
}
|
||||
|
||||
function testMultiPubSub() {
|
||||
table.run(function() {
|
||||
createSubscriptions(SAMPLES_PER_RUN);
|
||||
publishMessages(SAMPLES_PER_RUN);
|
||||
}, '3C: Multi PubSub');
|
||||
assertEquals(SAMPLES_PER_RUN * timer.getNumSamples() * 5, actionCount);
|
||||
assertEquals(SAMPLES_PER_RUN * timer.getNumSamples() * 5, changeCount);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2007 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
Author: attila@google.com (Attila Bodis)
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.pubsub.PubSub
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.pubsub.PubSubTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,631 @@
|
||||
// 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());
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// 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.pubsub.TopicId');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A templated class that is used to register {@code goog.pubsub.PubSub}
|
||||
* subscribers.
|
||||
*
|
||||
* Typical usage for a publisher:
|
||||
* <code>
|
||||
* /** @type {!goog.pubsub.TopicId<!zorg.State>}
|
||||
* zorg.TopicId.STATE_CHANGE = new goog.pubsub.TopicId(
|
||||
* goog.events.getUniqueId('state-change'));
|
||||
*
|
||||
* // Compiler enforces that these types are correct.
|
||||
* pubSub.publish(zorg.TopicId.STATE_CHANGE, zorg.State.STARTED);
|
||||
* </code>
|
||||
*
|
||||
* Typical usage for a subscriber:
|
||||
* <code>
|
||||
* // Compiler enforces the callback parameter type.
|
||||
* pubSub.subscribe(zorg.TopicId.STATE_CHANGE, function(state) {
|
||||
* if (state == zorg.State.STARTED) {
|
||||
* // Handle STARTED state.
|
||||
* }
|
||||
* });
|
||||
* </code>
|
||||
*
|
||||
* @param {string} topicId
|
||||
* @template PAYLOAD
|
||||
* @constructor
|
||||
* @final
|
||||
* @struct
|
||||
*/
|
||||
goog.pubsub.TopicId = function(topicId) {
|
||||
/**
|
||||
* @const
|
||||
* @private
|
||||
*/
|
||||
this.topicId_ = topicId;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.pubsub.TopicId.prototype.toString = function() {
|
||||
return this.topicId_;
|
||||
};
|
||||
@@ -0,0 +1,126 @@
|
||||
// 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.pubsub.TypedPubSub');
|
||||
|
||||
goog.require('goog.Disposable');
|
||||
goog.require('goog.pubsub.PubSub');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This object is a temporary shim that provides goog.pubsub.TopicId support
|
||||
* for goog.pubsub.PubSub. See b/12477087 for more info.
|
||||
* @constructor
|
||||
* @extends {goog.Disposable}
|
||||
*/
|
||||
goog.pubsub.TypedPubSub = function() {
|
||||
goog.pubsub.TypedPubSub.base(this, 'constructor');
|
||||
|
||||
this.pubSub_ = new goog.pubsub.PubSub();
|
||||
this.registerDisposable(this.pubSub_);
|
||||
};
|
||||
goog.inherits(goog.pubsub.TypedPubSub, goog.Disposable);
|
||||
|
||||
|
||||
/**
|
||||
* See {@code goog.pubsub.PubSub.subscribe}.
|
||||
* @param {!goog.pubsub.TopicId<PAYLOAD>} topic Topic to subscribe to.
|
||||
* @param {function(this:CONTEXT, PAYLOAD)} fn Function to be invoked when a
|
||||
* message is published to the given topic.
|
||||
* @param {CONTEXT=} opt_context Object in whose context the function is to be
|
||||
* called (the global scope if none).
|
||||
* @return {number} Subscription key.
|
||||
* @template PAYLOAD, CONTEXT
|
||||
*/
|
||||
goog.pubsub.TypedPubSub.prototype.subscribe = function(topic, fn, opt_context) {
|
||||
return this.pubSub_.subscribe(topic.toString(), fn, opt_context);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* See {@code goog.pubsub.PubSub.subscribeOnce}.
|
||||
* @param {!goog.pubsub.TopicId<PAYLOAD>} topic Topic to subscribe to.
|
||||
* @param {function(this:CONTEXT, PAYLOAD)} fn Function to be invoked once and
|
||||
* then unsubscribed when a message is published to the given topic.
|
||||
* @param {CONTEXT=} opt_context Object in whose context the function is to be
|
||||
* called (the global scope if none).
|
||||
* @return {number} Subscription key.
|
||||
* @template PAYLOAD, CONTEXT
|
||||
*/
|
||||
goog.pubsub.TypedPubSub.prototype.subscribeOnce = function(
|
||||
topic, fn, opt_context) {
|
||||
return this.pubSub_.subscribeOnce(topic.toString(), fn, opt_context);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* See {@code goog.pubsub.PubSub.unsubscribe}.
|
||||
* @param {!goog.pubsub.TopicId<PAYLOAD>} topic Topic to unsubscribe from.
|
||||
* @param {function(this:CONTEXT, PAYLOAD)} fn Function to unsubscribe.
|
||||
* @param {CONTEXT=} opt_context Object in whose context the function was to be
|
||||
* called (the global scope if none).
|
||||
* @return {boolean} Whether a matching subscription was removed.
|
||||
* @template PAYLOAD, CONTEXT
|
||||
*/
|
||||
goog.pubsub.TypedPubSub.prototype.unsubscribe = function(
|
||||
topic, fn, opt_context) {
|
||||
return this.pubSub_.unsubscribe(topic.toString(), fn, opt_context);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* See {@code goog.pubsub.PubSub.unsubscribeByKey}.
|
||||
* @param {number} key Subscription key.
|
||||
* @return {boolean} Whether a matching subscription was removed.
|
||||
*/
|
||||
goog.pubsub.TypedPubSub.prototype.unsubscribeByKey = function(key) {
|
||||
return this.pubSub_.unsubscribeByKey(key);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* See {@code goog.pubsub.PubSub.publish}.
|
||||
* @param {!goog.pubsub.TopicId<PAYLOAD>} topic Topic to publish to.
|
||||
* @param {PAYLOAD} payload Payload passed to each subscription function.
|
||||
* @return {boolean} Whether any subscriptions were called.
|
||||
* @template PAYLOAD
|
||||
*/
|
||||
goog.pubsub.TypedPubSub.prototype.publish = function(topic, payload) {
|
||||
return this.pubSub_.publish(topic.toString(), payload);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* See {@code goog.pubsub.PubSub.clear}.
|
||||
* @param {!goog.pubsub.TopicId<PAYLOAD>=} opt_topic Topic to clear (all topics
|
||||
* if unspecified).
|
||||
* @template PAYLOAD
|
||||
*/
|
||||
goog.pubsub.TypedPubSub.prototype.clear = function(opt_topic) {
|
||||
this.pubSub_.clear(goog.isDef(opt_topic) ? opt_topic.toString() : undefined);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* See {@code goog.pubsub.PubSub.getCount}.
|
||||
* @param {!goog.pubsub.TopicId<PAYLOAD>=} opt_topic The topic (all topics if
|
||||
* unspecified).
|
||||
* @return {number} Number of subscriptions to the topic.
|
||||
* @template PAYLOAD
|
||||
*/
|
||||
goog.pubsub.TypedPubSub.prototype.getCount = function(opt_topic) {
|
||||
return this.pubSub_.getCount(
|
||||
goog.isDef(opt_topic) ? opt_topic.toString() : undefined);
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2014 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.pubsub.TypedPubSub
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.pubsub.TypedPubSubTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,663 @@
|
||||
// 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.pubsub.TypedPubSubTest');
|
||||
goog.setTestOnly('goog.pubsub.TypedPubSubTest');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.pubsub.TopicId');
|
||||
goog.require('goog.pubsub.TypedPubSub');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
var pubsub;
|
||||
|
||||
function setUp() {
|
||||
pubsub = new goog.pubsub.TypedPubSub();
|
||||
}
|
||||
|
||||
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.TypedPubSub);
|
||||
}
|
||||
|
||||
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() {
|
||||
}
|
||||
|
||||
/** const */ var FOO = new goog.pubsub.TopicId('foo');
|
||||
/** const */ var BAR = new goog.pubsub.TopicId('bar');
|
||||
/** const */ var BAZ = new goog.pubsub.TopicId('baz');
|
||||
|
||||
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 = {};
|
||||
|
||||
/** const */ var TOPIC_X = new goog.pubsub.TopicId('X');
|
||||
|
||||
assertEquals('Topic "X" must not have any subscribers', 0,
|
||||
pubsub.getCount(TOPIC_X));
|
||||
|
||||
pubsub.subscribe(TOPIC_X, foo, contextA);
|
||||
assertEquals('Topic "X" must have 1 subscriber', 1,
|
||||
pubsub.getCount(TOPIC_X));
|
||||
|
||||
pubsub.subscribe(TOPIC_X, bar);
|
||||
assertEquals('Topic "X" must have 2 subscribers', 2,
|
||||
pubsub.getCount(TOPIC_X));
|
||||
|
||||
pubsub.subscribe(TOPIC_X, bar, contextB);
|
||||
assertEquals('Topic "X" must have 3 subscribers', 3,
|
||||
pubsub.getCount(TOPIC_X));
|
||||
|
||||
assertFalse('Unknown function/context combination return false',
|
||||
pubsub.unsubscribe(TOPIC_X, foo, contextB));
|
||||
|
||||
assertTrue(pubsub.unsubscribe(TOPIC_X, foo, contextA));
|
||||
assertEquals('Topic "X" must have 2 subscribers', 2,
|
||||
pubsub.getCount(TOPIC_X));
|
||||
|
||||
assertTrue(pubsub.unsubscribe(TOPIC_X, bar));
|
||||
assertEquals('Topic "X" must have 1 subscriber', 1,
|
||||
pubsub.getCount(TOPIC_X));
|
||||
|
||||
assertTrue(pubsub.unsubscribe(TOPIC_X, bar, contextB));
|
||||
assertEquals('Topic "X" must have no subscribers', 0,
|
||||
pubsub.getCount(TOPIC_X));
|
||||
}
|
||||
|
||||
function testSubscribeOnce() {
|
||||
var called, context;
|
||||
|
||||
/** @const */ SOME_TOPIC = new goog.pubsub.TopicId('someTopic');
|
||||
|
||||
called = false;
|
||||
pubsub.subscribeOnce(SOME_TOPIC, function() {
|
||||
called = true;
|
||||
});
|
||||
assertEquals('Topic must have one subscriber', 1,
|
||||
pubsub.getCount(SOME_TOPIC));
|
||||
assertFalse('Subscriber must not have been called yet', called);
|
||||
|
||||
pubsub.publish(SOME_TOPIC);
|
||||
assertEquals('Topic must have no subscribers', 0,
|
||||
pubsub.getCount(SOME_TOPIC));
|
||||
assertTrue('Subscriber must have been called', called);
|
||||
|
||||
context = {called: false};
|
||||
pubsub.subscribeOnce(SOME_TOPIC, function() {
|
||||
this.called = true;
|
||||
}, context);
|
||||
assertEquals('Topic must have one subscriber', 1,
|
||||
pubsub.getCount(SOME_TOPIC));
|
||||
assertFalse('Subscriber must not have been called yet', context.called);
|
||||
|
||||
pubsub.publish(SOME_TOPIC);
|
||||
assertEquals('Topic must have no subscribers', 0,
|
||||
pubsub.getCount(SOME_TOPIC));
|
||||
assertTrue('Subscriber must have been called', context.called);
|
||||
|
||||
context = {called: false, value: 0};
|
||||
pubsub.subscribeOnce(SOME_TOPIC, function(value) {
|
||||
this.called = true;
|
||||
this.value = value;
|
||||
}, context);
|
||||
assertEquals('Topic must have one subscriber', 1,
|
||||
pubsub.getCount(SOME_TOPIC));
|
||||
assertFalse('Subscriber must not have been called yet', context.called);
|
||||
assertEquals('Value must have expected value', 0, context.value);
|
||||
|
||||
pubsub.publish(SOME_TOPIC, 17);
|
||||
assertEquals('Topic must have no subscribers', 0,
|
||||
pubsub.getCount(SOME_TOPIC));
|
||||
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};
|
||||
|
||||
/** @const */ SOME_TOPIC = new goog.pubsub.TopicId('someTopic');
|
||||
|
||||
function subscriber(value) {
|
||||
this.called = true;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
pubsub.subscribeOnce(SOME_TOPIC, goog.bind(subscriber, context));
|
||||
assertEquals('Topic must have one subscriber', 1,
|
||||
pubsub.getCount(SOME_TOPIC));
|
||||
assertFalse('Subscriber must not have been called yet', context.called);
|
||||
assertEquals('Value must have expected value', 0, context.value);
|
||||
|
||||
pubsub.publish(SOME_TOPIC, 17);
|
||||
assertEquals('Topic must have no subscribers', 0,
|
||||
pubsub.getCount(SOME_TOPIC));
|
||||
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;
|
||||
|
||||
/** @const */ SOME_TOPIC = new goog.pubsub.TopicId('someTopic');
|
||||
|
||||
function subscriber(hasBeenCalled, newValue) {
|
||||
called = hasBeenCalled;
|
||||
value = newValue;
|
||||
}
|
||||
|
||||
pubsub.subscribeOnce(SOME_TOPIC, goog.partial(subscriber, true));
|
||||
assertEquals('Topic must have one subscriber', 1,
|
||||
pubsub.getCount(SOME_TOPIC));
|
||||
assertFalse('Subscriber must not have been called yet', called);
|
||||
assertEquals('Value must have expected value', 0, value);
|
||||
|
||||
pubsub.publish(SOME_TOPIC, 17);
|
||||
assertEquals('Topic must have no subscribers', 0,
|
||||
pubsub.getCount(SOME_TOPIC));
|
||||
assertTrue('Subscriber must have been called', called);
|
||||
assertEquals('Value must have been updated', 17, value);
|
||||
}
|
||||
|
||||
function testSelfResubscribe() {
|
||||
var value = null;
|
||||
|
||||
/** @const */ SOME_TOPIC = new goog.pubsub.TopicId('someTopic');
|
||||
|
||||
function resubscribe(iteration, newValue) {
|
||||
pubsub.subscribeOnce(SOME_TOPIC,
|
||||
goog.partial(resubscribe, iteration + 1));
|
||||
value = newValue + ':' + iteration;
|
||||
}
|
||||
|
||||
pubsub.subscribeOnce(SOME_TOPIC, goog.partial(resubscribe, 0));
|
||||
assertEquals('Topic must have 1 subscriber', 1,
|
||||
pubsub.getCount(SOME_TOPIC));
|
||||
assertNull('Value must be null', value);
|
||||
|
||||
pubsub.publish(SOME_TOPIC, 'foo');
|
||||
assertEquals('Topic must have 1 subscriber', 1,
|
||||
pubsub.getCount(SOME_TOPIC));
|
||||
assertEquals('Value be as expected', 'foo:0', value);
|
||||
|
||||
pubsub.publish(SOME_TOPIC, 'bar');
|
||||
assertEquals('Topic must have 1 subscriber', 1,
|
||||
pubsub.getCount(SOME_TOPIC));
|
||||
assertEquals('Value be as expected', 'bar:1', value);
|
||||
|
||||
pubsub.publish(SOME_TOPIC, 'baz');
|
||||
assertEquals('Topic must have 1 subscriber', 1,
|
||||
pubsub.getCount(SOME_TOPIC));
|
||||
assertEquals('Value be as expected', 'baz:2', value);
|
||||
}
|
||||
|
||||
function testUnsubscribeByKey() {
|
||||
var key1, key2, key3;
|
||||
|
||||
/** const */ var TOPIC_X = new goog.pubsub.TopicId('X');
|
||||
/** const */ var TOPIC_Y = new goog.pubsub.TopicId('Y');
|
||||
|
||||
key1 = pubsub.subscribe(TOPIC_X, function() {});
|
||||
key2 = pubsub.subscribe(TOPIC_Y, function() {});
|
||||
|
||||
assertEquals('Topic "X" must have 1 subscriber', 1,
|
||||
pubsub.getCount(TOPIC_X));
|
||||
assertEquals('Topic "Y" must have 1 subscriber', 1,
|
||||
pubsub.getCount(TOPIC_Y));
|
||||
assertNotEquals('Subscription keys must be distinct', key1, key2);
|
||||
|
||||
pubsub.unsubscribeByKey(key1);
|
||||
assertEquals('Topic "X" must have no subscribers', 0,
|
||||
pubsub.getCount(TOPIC_X));
|
||||
assertEquals('Topic "Y" must have 1 subscriber', 1,
|
||||
pubsub.getCount(TOPIC_Y));
|
||||
|
||||
key3 = pubsub.subscribe(TOPIC_X, function() {});
|
||||
assertEquals('Topic "X" must have 1 subscriber', 1,
|
||||
pubsub.getCount(TOPIC_X));
|
||||
assertEquals('Topic "Y" must have 1 subscriber', 1,
|
||||
pubsub.getCount(TOPIC_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(TOPIC_X));
|
||||
assertEquals('Topic "Y" must have 1 subscriber', 1,
|
||||
pubsub.getCount(TOPIC_Y));
|
||||
|
||||
pubsub.unsubscribeByKey(key2);
|
||||
assertEquals('Topic "X" must have 1 subscriber', 1,
|
||||
pubsub.getCount(TOPIC_X));
|
||||
assertEquals('Topic "Y" must have no subscribers', 0,
|
||||
pubsub.getCount(TOPIC_Y));
|
||||
|
||||
pubsub.unsubscribeByKey(key3);
|
||||
assertEquals('Topic "X" must have no subscribers', 0,
|
||||
pubsub.getCount(TOPIC_X));
|
||||
assertEquals('Topic "Y" must have no subscribers', 0,
|
||||
pubsub.getCount(TOPIC_Y));
|
||||
}
|
||||
|
||||
function testSubscribeUnsubscribeMultiple() {
|
||||
function foo() {
|
||||
}
|
||||
function bar() {
|
||||
}
|
||||
|
||||
var context = {};
|
||||
|
||||
/** const */ var TOPIC_X = new goog.pubsub.TopicId('X');
|
||||
/** const */ var TOPIC_Y = new goog.pubsub.TopicId('Y');
|
||||
/** const */ var TOPIC_Z = new goog.pubsub.TopicId('Z');
|
||||
|
||||
assertEquals('Pubsub channel must not have any subscribers', 0,
|
||||
pubsub.getCount());
|
||||
|
||||
assertEquals('Topic "X" must not have any subscribers', 0,
|
||||
pubsub.getCount(TOPIC_X));
|
||||
assertEquals('Topic "Y" must not have any subscribers', 0,
|
||||
pubsub.getCount(TOPIC_Y));
|
||||
assertEquals('Topic "Z" must not have any subscribers', 0,
|
||||
pubsub.getCount(TOPIC_Z));
|
||||
|
||||
goog.array.forEach([TOPIC_X, TOPIC_Y, TOPIC_Z], function(topic) {
|
||||
pubsub.subscribe(topic, foo);
|
||||
});
|
||||
assertEquals('Topic "X" must have 1 subscriber', 1,
|
||||
pubsub.getCount(TOPIC_X));
|
||||
assertEquals('Topic "Y" must have 1 subscriber', 1,
|
||||
pubsub.getCount(TOPIC_Y));
|
||||
assertEquals('Topic "Z" must have 1 subscriber', 1,
|
||||
pubsub.getCount(TOPIC_Z));
|
||||
|
||||
goog.array.forEach([TOPIC_X, TOPIC_Y, TOPIC_Z], function(topic) {
|
||||
pubsub.subscribe(topic, bar, context);
|
||||
});
|
||||
assertEquals('Topic "X" must have 2 subscribers', 2,
|
||||
pubsub.getCount(TOPIC_X));
|
||||
assertEquals('Topic "Y" must have 2 subscribers', 2,
|
||||
pubsub.getCount(TOPIC_Y));
|
||||
assertEquals('Topic "Z" must have 2 subscribers', 2,
|
||||
pubsub.getCount(TOPIC_Z));
|
||||
|
||||
assertEquals('Pubsub channel must have a total of 6 subscribers', 6,
|
||||
pubsub.getCount());
|
||||
|
||||
goog.array.forEach([TOPIC_X, TOPIC_Y, TOPIC_Z], function(topic) {
|
||||
pubsub.unsubscribe(topic, foo);
|
||||
});
|
||||
assertEquals('Topic "X" must have 1 subscriber', 1,
|
||||
pubsub.getCount(TOPIC_X));
|
||||
assertEquals('Topic "Y" must have 1 subscriber', 1,
|
||||
pubsub.getCount(TOPIC_Y));
|
||||
assertEquals('Topic "Z" must have 1 subscriber', 1,
|
||||
pubsub.getCount(TOPIC_Z));
|
||||
|
||||
goog.array.forEach([TOPIC_X, TOPIC_Y, TOPIC_Z], function(topic) {
|
||||
pubsub.unsubscribe(topic, bar, context);
|
||||
});
|
||||
assertEquals('Topic "X" must not have any subscribers', 0,
|
||||
pubsub.getCount(TOPIC_X));
|
||||
assertEquals('Topic "Y" must not have any subscribers', 0,
|
||||
pubsub.getCount(TOPIC_Y));
|
||||
assertEquals('Topic "Z" must not have any subscribers', 0,
|
||||
pubsub.getCount(TOPIC_Z));
|
||||
|
||||
assertEquals('Pubsub channel must not have any subscribers', 0,
|
||||
pubsub.getCount());
|
||||
}
|
||||
|
||||
function testPublish() {
|
||||
var context = {};
|
||||
var fooCalled = false;
|
||||
var barCalled = false;
|
||||
|
||||
/** @const */ SOME_TOPIC = new goog.pubsub.TopicId('someTopic');
|
||||
|
||||
function foo(record) {
|
||||
fooCalled = true;
|
||||
assertEquals('x must have expected value', 'x', record.x);
|
||||
assertEquals('y must have expected value', 'y', record.y);
|
||||
}
|
||||
|
||||
function bar(record) {
|
||||
barCalled = true;
|
||||
assertEquals('Context must have expected value', context, this);
|
||||
assertEquals('x must have expected value', 'x', record.x);
|
||||
assertEquals('y must have expected value', 'y', record.y);
|
||||
}
|
||||
|
||||
pubsub.subscribe(SOME_TOPIC, foo);
|
||||
pubsub.subscribe(SOME_TOPIC, bar, context);
|
||||
|
||||
assertTrue(pubsub.publish(SOME_TOPIC, {x: 'x', y: 'y'}));
|
||||
assertTrue('foo() must have been called', fooCalled);
|
||||
assertTrue('bar() must have been called', barCalled);
|
||||
|
||||
fooCalled = false;
|
||||
barCalled = false;
|
||||
assertTrue(pubsub.unsubscribe(SOME_TOPIC, foo));
|
||||
|
||||
assertTrue(pubsub.publish(SOME_TOPIC, {x: 'x', y: '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(SOME_TOPIC, {x: 'x', y: '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;
|
||||
}
|
||||
|
||||
/** @const */ SOME_TOPIC = new goog.pubsub.TopicId('someTopic');
|
||||
|
||||
assertFalse('Publishing to nonexistent topic must return false',
|
||||
pubsub.publish(SOME_TOPIC));
|
||||
|
||||
pubsub.subscribe(SOME_TOPIC, foo);
|
||||
assertTrue('Publishing to topic with subscriber must return true',
|
||||
pubsub.publish(SOME_TOPIC));
|
||||
assertTrue('Foo must have been called', fooCalled);
|
||||
|
||||
pubsub.unsubscribe(SOME_TOPIC, foo);
|
||||
fooCalled = false;
|
||||
assertFalse('Publishing to topic without subscribers must return false',
|
||||
pubsub.publish(SOME_TOPIC));
|
||||
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;
|
||||
|
||||
/** @const */ SOME_TOPIC = new goog.pubsub.TopicId('someTopic');
|
||||
|
||||
pubsub.subscribe(SOME_TOPIC, function() {
|
||||
pubsub.subscribe(SOME_TOPIC, function() {
|
||||
secondCalled = true;
|
||||
});
|
||||
firstCalled = true;
|
||||
});
|
||||
assertEquals('Topic must have one subscriber', 1,
|
||||
pubsub.getCount(SOME_TOPIC));
|
||||
assertFalse('No subscriber must have been called yet',
|
||||
firstCalled || secondCalled);
|
||||
|
||||
pubsub.publish(SOME_TOPIC);
|
||||
assertEquals('Topic must have two subscribers', 2,
|
||||
pubsub.getCount(SOME_TOPIC));
|
||||
assertTrue('The first subscriber must have been called',
|
||||
firstCalled);
|
||||
assertFalse('The second subscriber must not have been called yet',
|
||||
secondCalled);
|
||||
|
||||
pubsub.publish(SOME_TOPIC);
|
||||
assertEquals('Topic must have three subscribers', 3,
|
||||
pubsub.getCount(SOME_TOPIC));
|
||||
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;
|
||||
|
||||
/** const */ var TOPIC_X = new goog.pubsub.TopicId('X');
|
||||
|
||||
function first() {
|
||||
assertFalse('unsubscribe() must return false during publishing',
|
||||
pubsub.unsubscribe(TOPIC_X, second));
|
||||
assertEquals('Topic "X" must still have 3 subscribers', 3,
|
||||
pubsub.getCount(TOPIC_X));
|
||||
firstCalled = true;
|
||||
}
|
||||
pubsub.subscribe(TOPIC_X, first);
|
||||
|
||||
function second() {
|
||||
assertEquals('Topic "X" must still have 3 subscribers', 3,
|
||||
pubsub.getCount(TOPIC_X));
|
||||
secondCalled = true;
|
||||
}
|
||||
pubsub.subscribe(TOPIC_X, second);
|
||||
|
||||
function third() {
|
||||
assertFalse('unsubscribe() must return false during publishing',
|
||||
pubsub.unsubscribe(TOPIC_X, first));
|
||||
assertEquals('Topic "X" must still have 3 subscribers', 3,
|
||||
pubsub.getCount(TOPIC_X));
|
||||
thirdCalled = true;
|
||||
}
|
||||
pubsub.subscribe(TOPIC_X, third);
|
||||
|
||||
assertEquals('Topic "X" must have 3 subscribers', 3,
|
||||
pubsub.getCount(TOPIC_X));
|
||||
assertFalse('No subscribers must have been called yet',
|
||||
firstCalled || secondCalled || thirdCalled);
|
||||
|
||||
assertTrue(pubsub.publish(TOPIC_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(TOPIC_X));
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/** @const */ SOME_TOPIC = new goog.pubsub.TopicId('someTopic');
|
||||
|
||||
function selfDestruct() {
|
||||
assertFalse('unsubscribe() must return false during publishing',
|
||||
pubsub.unsubscribe(SOME_TOPIC, arguments.callee));
|
||||
assertEquals('Topic must still have 1 subscriber', 1,
|
||||
pubsub.getCount(SOME_TOPIC));
|
||||
selfDestructCalled = true;
|
||||
}
|
||||
|
||||
pubsub.subscribe(SOME_TOPIC, selfDestruct);
|
||||
assertEquals('Topic must have 1 subscriber', 1,
|
||||
pubsub.getCount(SOME_TOPIC));
|
||||
assertFalse('selfDestruct() must not have been called yet',
|
||||
selfDestructCalled);
|
||||
|
||||
pubsub.publish(SOME_TOPIC);
|
||||
assertTrue('selfDestruct() must have been called', selfDestructCalled);
|
||||
assertEquals('Topic must have no subscribers after publishing', 0,
|
||||
pubsub.getCount(SOME_TOPIC));
|
||||
}
|
||||
|
||||
function testPublishReturnValue() {
|
||||
/** @const */ SOME_TOPIC = new goog.pubsub.TopicId('someTopic');
|
||||
pubsub.subscribe(SOME_TOPIC, function() {
|
||||
pubsub.unsubscribe(SOME_TOPIC, arguments.callee);
|
||||
});
|
||||
assertTrue('publish() must return true even if the only subscriber ' +
|
||||
'removes itself during publishing', pubsub.publish(SOME_TOPIC));
|
||||
}
|
||||
|
||||
function testNestedPublish() {
|
||||
var x1 = false;
|
||||
var x2 = false;
|
||||
var y1 = false;
|
||||
var y2 = false;
|
||||
|
||||
/** @const */ TOPIC_X = new goog.pubsub.TopicId('X');
|
||||
/** @const */ TOPIC_Y = new goog.pubsub.TopicId('Y');
|
||||
|
||||
pubsub.subscribe(TOPIC_X, function() {
|
||||
pubsub.publish(TOPIC_Y);
|
||||
pubsub.unsubscribe(TOPIC_X, arguments.callee);
|
||||
x1 = true;
|
||||
});
|
||||
|
||||
pubsub.subscribe(TOPIC_X, function() {
|
||||
x2 = true;
|
||||
});
|
||||
|
||||
pubsub.subscribe(TOPIC_Y, function() {
|
||||
pubsub.unsubscribe(TOPIC_Y, arguments.callee);
|
||||
y1 = true;
|
||||
});
|
||||
|
||||
pubsub.subscribe(TOPIC_Y, function() {
|
||||
y2 = true;
|
||||
});
|
||||
|
||||
pubsub.publish(TOPIC_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() {
|
||||
}
|
||||
|
||||
var topics = [
|
||||
new goog.pubsub.TopicId('W'),
|
||||
new goog.pubsub.TopicId('X'),
|
||||
new goog.pubsub.TopicId('Y'),
|
||||
new goog.pubsub.TopicId('Z')
|
||||
];
|
||||
|
||||
goog.array.forEach(topics, function(topic) {
|
||||
pubsub.subscribe(topic, fn);
|
||||
});
|
||||
assertEquals('Pubsub channel must have 4 subscribers', 4,
|
||||
pubsub.getCount());
|
||||
|
||||
pubsub.clear(topics[0]);
|
||||
assertEquals('Pubsub channel must have 3 subscribers', 3,
|
||||
pubsub.getCount());
|
||||
|
||||
pubsub.clear(topics[1]);
|
||||
pubsub.clear(topics[2]);
|
||||
assertEquals('Pubsub channel must have 1 subscriber', 1,
|
||||
pubsub.getCount());
|
||||
|
||||
pubsub.clear();
|
||||
assertEquals('Pubsub channel must have no subscribers', 0,
|
||||
pubsub.getCount());
|
||||
}
|
||||
Reference in New Issue
Block a user