pubsub_test.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722
  1. // Copyright 2007 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. goog.provide('goog.pubsub.PubSubTest');
  15. goog.setTestOnly('goog.pubsub.PubSubTest');
  16. goog.require('goog.array');
  17. goog.require('goog.pubsub.PubSub');
  18. goog.require('goog.testing.MockClock');
  19. goog.require('goog.testing.jsunit');
  20. var pubsub;
  21. var asyncPubsub;
  22. var mockClock;
  23. function setUp() {
  24. pubsub = new goog.pubsub.PubSub();
  25. asyncPubsub = new goog.pubsub.PubSub(true);
  26. mockClock = new goog.testing.MockClock(true);
  27. }
  28. function tearDown() {
  29. mockClock.uninstall();
  30. asyncPubsub.dispose();
  31. pubsub.dispose();
  32. }
  33. function testConstructor() {
  34. assertNotNull('PubSub instance must not be null', pubsub);
  35. assertTrue(
  36. 'PubSub instance must have the expected type',
  37. pubsub instanceof goog.pubsub.PubSub);
  38. }
  39. function testDispose() {
  40. assertFalse(
  41. 'PubSub instance must not have been disposed of', pubsub.isDisposed());
  42. pubsub.dispose();
  43. assertTrue('PubSub instance must have been disposed of', pubsub.isDisposed());
  44. }
  45. function testSubscribeUnsubscribe() {
  46. function foo1() {}
  47. function bar1() {}
  48. function foo2() {}
  49. function bar2() {}
  50. assertEquals(
  51. 'Topic "foo" must not have any subscribers', 0, pubsub.getCount('foo'));
  52. assertEquals(
  53. 'Topic "bar" must not have any subscribers', 0, pubsub.getCount('bar'));
  54. pubsub.subscribe('foo', foo1);
  55. assertEquals('Topic "foo" must have 1 subscriber', 1, pubsub.getCount('foo'));
  56. assertEquals(
  57. 'Topic "bar" must not have any subscribers', 0, pubsub.getCount('bar'));
  58. pubsub.subscribe('bar', bar1);
  59. assertEquals('Topic "foo" must have 1 subscriber', 1, pubsub.getCount('foo'));
  60. assertEquals('Topic "bar" must have 1 subscriber', 1, pubsub.getCount('bar'));
  61. pubsub.subscribe('foo', foo2);
  62. assertEquals(
  63. 'Topic "foo" must have 2 subscribers', 2, pubsub.getCount('foo'));
  64. assertEquals('Topic "bar" must have 1 subscriber', 1, pubsub.getCount('bar'));
  65. pubsub.subscribe('bar', bar2);
  66. assertEquals(
  67. 'Topic "foo" must have 2 subscribers', 2, pubsub.getCount('foo'));
  68. assertEquals(
  69. 'Topic "bar" must have 2 subscribers', 2, pubsub.getCount('bar'));
  70. assertTrue(pubsub.unsubscribe('foo', foo1));
  71. assertEquals('Topic "foo" must have 1 subscriber', 1, pubsub.getCount('foo'));
  72. assertEquals(
  73. 'Topic "bar" must have 2 subscribers', 2, pubsub.getCount('bar'));
  74. assertTrue(pubsub.unsubscribe('foo', foo2));
  75. assertEquals(
  76. 'Topic "foo" must have no subscribers', 0, pubsub.getCount('foo'));
  77. assertEquals(
  78. 'Topic "bar" must have 2 subscribers', 2, pubsub.getCount('bar'));
  79. assertTrue(pubsub.unsubscribe('bar', bar1));
  80. assertEquals(
  81. 'Topic "foo" must have no subscribers', 0, pubsub.getCount('foo'));
  82. assertEquals('Topic "bar" must have 1 subscriber', 1, pubsub.getCount('bar'));
  83. assertTrue(pubsub.unsubscribe('bar', bar2));
  84. assertEquals(
  85. 'Topic "foo" must have no subscribers', 0, pubsub.getCount('foo'));
  86. assertEquals(
  87. 'Topic "bar" must have no subscribers', 0, pubsub.getCount('bar'));
  88. assertFalse(
  89. 'Unsubscribing a nonexistent topic must return false',
  90. pubsub.unsubscribe('baz', foo1));
  91. assertFalse(
  92. 'Unsubscribing a nonexistent function must return false',
  93. pubsub.unsubscribe('foo', function() {}));
  94. }
  95. function testSubscribeUnsubscribeWithContext() {
  96. function foo() {}
  97. function bar() {}
  98. var contextA = {};
  99. var contextB = {};
  100. assertEquals(
  101. 'Topic "X" must not have any subscribers', 0, pubsub.getCount('X'));
  102. pubsub.subscribe('X', foo, contextA);
  103. assertEquals('Topic "X" must have 1 subscriber', 1, pubsub.getCount('X'));
  104. pubsub.subscribe('X', bar);
  105. assertEquals('Topic "X" must have 2 subscribers', 2, pubsub.getCount('X'));
  106. pubsub.subscribe('X', bar, contextB);
  107. assertEquals('Topic "X" must have 3 subscribers', 3, pubsub.getCount('X'));
  108. assertFalse(
  109. 'Unknown function/context combination return false',
  110. pubsub.unsubscribe('X', foo, contextB));
  111. assertTrue(pubsub.unsubscribe('X', foo, contextA));
  112. assertEquals('Topic "X" must have 2 subscribers', 2, pubsub.getCount('X'));
  113. assertTrue(pubsub.unsubscribe('X', bar));
  114. assertEquals('Topic "X" must have 1 subscriber', 1, pubsub.getCount('X'));
  115. assertTrue(pubsub.unsubscribe('X', bar, contextB));
  116. assertEquals('Topic "X" must have no subscribers', 0, pubsub.getCount('X'));
  117. }
  118. function testSubscribeOnce() {
  119. var called, context;
  120. called = false;
  121. pubsub.subscribeOnce('someTopic', function() { called = true; });
  122. assertEquals(
  123. 'Topic must have one subscriber', 1, pubsub.getCount('someTopic'));
  124. assertFalse('Subscriber must not have been called yet', called);
  125. pubsub.publish('someTopic');
  126. assertEquals(
  127. 'Topic must have no subscribers', 0, pubsub.getCount('someTopic'));
  128. assertTrue('Subscriber must have been called', called);
  129. context = {called: false};
  130. pubsub.subscribeOnce(
  131. 'someTopic', function() { this.called = true; }, context);
  132. assertEquals(
  133. 'Topic must have one subscriber', 1, pubsub.getCount('someTopic'));
  134. assertFalse('Subscriber must not have been called yet', context.called);
  135. pubsub.publish('someTopic');
  136. assertEquals(
  137. 'Topic must have no subscribers', 0, pubsub.getCount('someTopic'));
  138. assertTrue('Subscriber must have been called', context.called);
  139. context = {called: false, value: 0};
  140. pubsub.subscribeOnce('someTopic', function(value) {
  141. this.called = true;
  142. this.value = value;
  143. }, context);
  144. assertEquals(
  145. 'Topic must have one subscriber', 1, pubsub.getCount('someTopic'));
  146. assertFalse('Subscriber must not have been called yet', context.called);
  147. assertEquals('Value must have expected value', 0, context.value);
  148. pubsub.publish('someTopic', 17);
  149. assertEquals(
  150. 'Topic must have no subscribers', 0, pubsub.getCount('someTopic'));
  151. assertTrue('Subscriber must have been called', context.called);
  152. assertEquals('Value must have been updated', 17, context.value);
  153. }
  154. function testAsyncSubscribeOnce() {
  155. var callCount = 0;
  156. asyncPubsub.subscribeOnce('someTopic', function() { callCount++; });
  157. assertEquals(
  158. 'Topic must have one subscriber', 1, asyncPubsub.getCount('someTopic'));
  159. mockClock.tick();
  160. assertEquals('Subscriber must not have been called yet', 0, callCount);
  161. asyncPubsub.publish('someTopic');
  162. asyncPubsub.publish('someTopic');
  163. mockClock.tick();
  164. assertEquals(
  165. 'Topic must have no subscribers', 0, asyncPubsub.getCount('someTopic'));
  166. assertEquals('Subscriber must have been called once', 1, callCount);
  167. }
  168. function testAsyncSubscribeOnceWithContext() {
  169. var context = {callCount: 0};
  170. asyncPubsub.subscribeOnce(
  171. 'someTopic', function() { this.callCount++; }, context);
  172. assertEquals(
  173. 'Topic must have one subscriber', 1, asyncPubsub.getCount('someTopic'));
  174. mockClock.tick();
  175. assertEquals(
  176. 'Subscriber must not have been called yet', 0, context.callCount);
  177. asyncPubsub.publish('someTopic');
  178. asyncPubsub.publish('someTopic');
  179. mockClock.tick();
  180. assertEquals(
  181. 'Topic must have no subscribers', 0, asyncPubsub.getCount('someTopic'));
  182. assertEquals('Subscriber must have been called once', 1, context.callCount);
  183. }
  184. function testAsyncSubscribeOnceWithContextAndValue() {
  185. var context = {callCount: 0, value: 0};
  186. asyncPubsub.subscribeOnce('someTopic', function(value) {
  187. this.callCount++;
  188. this.value = value;
  189. }, context);
  190. assertEquals(
  191. 'Topic must have one subscriber', 1, asyncPubsub.getCount('someTopic'));
  192. mockClock.tick();
  193. assertEquals(
  194. 'Subscriber must not have been called yet', 0, context.callCount);
  195. assertEquals('Value must have expected value', 0, context.value);
  196. asyncPubsub.publish('someTopic', 17);
  197. asyncPubsub.publish('someTopic', 42);
  198. mockClock.tick();
  199. assertEquals(
  200. 'Topic must have no subscribers', 0, asyncPubsub.getCount('someTopic'));
  201. assertEquals('Subscriber must have been called once', 1, context.callCount);
  202. assertEquals('Value must have been updated', 17, context.value);
  203. }
  204. function testSubscribeOnce_boundFn() {
  205. var context = {called: false, value: 0};
  206. function subscriber(value) {
  207. this.called = true;
  208. this.value = value;
  209. }
  210. pubsub.subscribeOnce('someTopic', goog.bind(subscriber, context));
  211. assertEquals(
  212. 'Topic must have one subscriber', 1, pubsub.getCount('someTopic'));
  213. assertFalse('Subscriber must not have been called yet', context.called);
  214. assertEquals('Value must have expected value', 0, context.value);
  215. pubsub.publish('someTopic', 17);
  216. assertEquals(
  217. 'Topic must have no subscribers', 0, pubsub.getCount('someTopic'));
  218. assertTrue('Subscriber must have been called', context.called);
  219. assertEquals('Value must have been updated', 17, context.value);
  220. }
  221. function testSubscribeOnce_partialFn() {
  222. var called = false;
  223. var value = 0;
  224. function subscriber(hasBeenCalled, newValue) {
  225. called = hasBeenCalled;
  226. value = newValue;
  227. }
  228. pubsub.subscribeOnce('someTopic', goog.partial(subscriber, true));
  229. assertEquals(
  230. 'Topic must have one subscriber', 1, pubsub.getCount('someTopic'));
  231. assertFalse('Subscriber must not have been called yet', called);
  232. assertEquals('Value must have expected value', 0, value);
  233. pubsub.publish('someTopic', 17);
  234. assertEquals(
  235. 'Topic must have no subscribers', 0, pubsub.getCount('someTopic'));
  236. assertTrue('Subscriber must have been called', called);
  237. assertEquals('Value must have been updated', 17, value);
  238. }
  239. function testSelfResubscribe() {
  240. var value = null;
  241. function resubscribe(iteration, newValue) {
  242. pubsub.subscribeOnce('someTopic', goog.partial(resubscribe, iteration + 1));
  243. value = newValue + ':' + iteration;
  244. }
  245. pubsub.subscribeOnce('someTopic', goog.partial(resubscribe, 0));
  246. assertEquals('Topic must have 1 subscriber', 1, pubsub.getCount('someTopic'));
  247. assertNull('Value must be null', value);
  248. pubsub.publish('someTopic', 'foo');
  249. assertEquals('Topic must have 1 subscriber', 1, pubsub.getCount('someTopic'));
  250. assertEquals(
  251. 'Pubsub must not have any pending unsubscribe keys', 0,
  252. pubsub.pendingKeys_.length);
  253. assertEquals('Value be as expected', 'foo:0', value);
  254. pubsub.publish('someTopic', 'bar');
  255. assertEquals('Topic must have 1 subscriber', 1, pubsub.getCount('someTopic'));
  256. assertEquals(
  257. 'Pubsub must not have any pending unsubscribe keys', 0,
  258. pubsub.pendingKeys_.length);
  259. assertEquals('Value be as expected', 'bar:1', value);
  260. pubsub.publish('someTopic', 'baz');
  261. assertEquals('Topic must have 1 subscriber', 1, pubsub.getCount('someTopic'));
  262. assertEquals(
  263. 'Pubsub must not have any pending unsubscribe keys', 0,
  264. pubsub.pendingKeys_.length);
  265. assertEquals('Value be as expected', 'baz:2', value);
  266. }
  267. function testUnsubscribeByKey() {
  268. var key1, key2, key3;
  269. key1 = pubsub.subscribe('X', function() {});
  270. key2 = pubsub.subscribe('Y', function() {});
  271. assertEquals('Topic "X" must have 1 subscriber', 1, pubsub.getCount('X'));
  272. assertEquals('Topic "Y" must have 1 subscriber', 1, pubsub.getCount('Y'));
  273. assertNotEquals('Subscription keys must be distinct', key1, key2);
  274. pubsub.unsubscribeByKey(key1);
  275. assertEquals('Topic "X" must have no subscribers', 0, pubsub.getCount('X'));
  276. assertEquals('Topic "Y" must have 1 subscriber', 1, pubsub.getCount('Y'));
  277. key3 = pubsub.subscribe('X', function() {});
  278. assertEquals('Topic "X" must have 1 subscriber', 1, pubsub.getCount('X'));
  279. assertEquals('Topic "Y" must have 1 subscriber', 1, pubsub.getCount('Y'));
  280. assertNotEquals('Subscription keys must be distinct', key1, key3);
  281. assertNotEquals('Subscription keys must be distinct', key2, key3);
  282. pubsub.unsubscribeByKey(key1); // Obsolete key; should be no-op.
  283. assertEquals('Topic "X" must have 1 subscriber', 1, pubsub.getCount('X'));
  284. assertEquals('Topic "Y" must have 1 subscriber', 1, pubsub.getCount('Y'));
  285. pubsub.unsubscribeByKey(key2);
  286. assertEquals('Topic "X" must have 1 subscriber', 1, pubsub.getCount('X'));
  287. assertEquals('Topic "Y" must have no subscribers', 0, pubsub.getCount('Y'));
  288. pubsub.unsubscribeByKey(key3);
  289. assertEquals('Topic "X" must have no subscribers', 0, pubsub.getCount('X'));
  290. assertEquals('Topic "Y" must have no subscribers', 0, pubsub.getCount('Y'));
  291. }
  292. function testSubscribeUnsubscribeMultiple() {
  293. function foo() {}
  294. function bar() {}
  295. var context = {};
  296. assertEquals(
  297. 'Pubsub channel must not have any subscribers', 0, pubsub.getCount());
  298. assertEquals(
  299. 'Topic "X" must not have any subscribers', 0, pubsub.getCount('X'));
  300. assertEquals(
  301. 'Topic "Y" must not have any subscribers', 0, pubsub.getCount('Y'));
  302. assertEquals(
  303. 'Topic "Z" must not have any subscribers', 0, pubsub.getCount('Z'));
  304. goog.array.forEach(
  305. ['X', 'Y', 'Z'], function(topic) { pubsub.subscribe(topic, foo); });
  306. assertEquals('Topic "X" must have 1 subscriber', 1, pubsub.getCount('X'));
  307. assertEquals('Topic "Y" must have 1 subscriber', 1, pubsub.getCount('Y'));
  308. assertEquals('Topic "Z" must have 1 subscriber', 1, pubsub.getCount('Z'));
  309. goog.array.forEach(['X', 'Y', 'Z'], function(topic) {
  310. pubsub.subscribe(topic, bar, context);
  311. });
  312. assertEquals('Topic "X" must have 2 subscribers', 2, pubsub.getCount('X'));
  313. assertEquals('Topic "Y" must have 2 subscribers', 2, pubsub.getCount('Y'));
  314. assertEquals('Topic "Z" must have 2 subscribers', 2, pubsub.getCount('Z'));
  315. assertEquals(
  316. 'Pubsub channel must have a total of 6 subscribers', 6,
  317. pubsub.getCount());
  318. goog.array.forEach(
  319. ['X', 'Y', 'Z'], function(topic) { pubsub.unsubscribe(topic, foo); });
  320. assertEquals('Topic "X" must have 1 subscriber', 1, pubsub.getCount('X'));
  321. assertEquals('Topic "Y" must have 1 subscriber', 1, pubsub.getCount('Y'));
  322. assertEquals('Topic "Z" must have 1 subscriber', 1, pubsub.getCount('Z'));
  323. goog.array.forEach(['X', 'Y', 'Z'], function(topic) {
  324. pubsub.unsubscribe(topic, bar, context);
  325. });
  326. assertEquals(
  327. 'Topic "X" must not have any subscribers', 0, pubsub.getCount('X'));
  328. assertEquals(
  329. 'Topic "Y" must not have any subscribers', 0, pubsub.getCount('Y'));
  330. assertEquals(
  331. 'Topic "Z" must not have any subscribers', 0, pubsub.getCount('Z'));
  332. assertEquals(
  333. 'Pubsub channel must not have any subscribers', 0, pubsub.getCount());
  334. }
  335. function testPublish() {
  336. var context = {};
  337. var fooCalled = false;
  338. var barCalled = false;
  339. function foo(x, y) {
  340. fooCalled = true;
  341. assertEquals('x must have expected value', 'x', x);
  342. assertEquals('y must have expected value', 'y', y);
  343. }
  344. function bar(x, y) {
  345. barCalled = true;
  346. assertEquals('Context must have expected value', context, this);
  347. assertEquals('x must have expected value', 'x', x);
  348. assertEquals('y must have expected value', 'y', y);
  349. }
  350. pubsub.subscribe('someTopic', foo);
  351. pubsub.subscribe('someTopic', bar, context);
  352. assertTrue(pubsub.publish('someTopic', 'x', 'y'));
  353. assertTrue('foo() must have been called', fooCalled);
  354. assertTrue('bar() must have been called', barCalled);
  355. fooCalled = false;
  356. barCalled = false;
  357. assertTrue(pubsub.unsubscribe('someTopic', foo));
  358. assertTrue(pubsub.publish('someTopic', 'x', 'y'));
  359. assertFalse('foo() must not have been called', fooCalled);
  360. assertTrue('bar() must have been called', barCalled);
  361. fooCalled = false;
  362. barCalled = false;
  363. pubsub.subscribe('differentTopic', foo);
  364. assertTrue(pubsub.publish('someTopic', 'x', 'y'));
  365. assertFalse('foo() must not have been called', fooCalled);
  366. assertTrue('bar() must have been called', barCalled);
  367. }
  368. function testAsyncPublish() {
  369. var context = {};
  370. var fooCallCount = 0;
  371. var barCallCount = 0;
  372. function foo(x, y) {
  373. fooCallCount++;
  374. assertEquals('x must have expected value', 'x', x);
  375. assertEquals('y must have expected value', 'y', y);
  376. }
  377. function bar(x, y) {
  378. barCallCount++;
  379. assertEquals('Context must have expected value', context, this);
  380. assertEquals('x must have expected value', 'x', x);
  381. assertEquals('y must have expected value', 'y', y);
  382. }
  383. asyncPubsub.subscribe('someTopic', foo);
  384. asyncPubsub.subscribe('someTopic', bar, context);
  385. assertTrue(asyncPubsub.publish('someTopic', 'x', 'y'));
  386. assertEquals('foo() must not have been called', 0, fooCallCount);
  387. assertEquals('bar() must not have been called', 0, barCallCount);
  388. mockClock.tick();
  389. assertEquals('foo() must have been called once', 1, fooCallCount);
  390. assertEquals('bar() must have been called once', 1, barCallCount);
  391. fooCallCount = 0;
  392. barCallCount = 0;
  393. assertTrue(asyncPubsub.unsubscribe('someTopic', foo));
  394. assertTrue(asyncPubsub.publish('someTopic', 'x', 'y'));
  395. assertEquals('foo() must not have been called', 0, fooCallCount);
  396. assertEquals('bar() must not have been called', 0, barCallCount);
  397. mockClock.tick();
  398. assertEquals('foo() must not have been called', 0, fooCallCount);
  399. assertEquals('bar() must have been called once', 1, barCallCount);
  400. fooCallCount = 0;
  401. barCallCount = 0;
  402. asyncPubsub.subscribe('differentTopic', foo);
  403. assertTrue(asyncPubsub.publish('someTopic', 'x', 'y'));
  404. assertEquals('foo() must not have been called', 0, fooCallCount);
  405. assertEquals('bar() must not have been called', 0, barCallCount);
  406. mockClock.tick();
  407. assertEquals('foo() must not have been called', 0, fooCallCount);
  408. assertEquals('bar() must have been called once', 1, barCallCount);
  409. }
  410. function testPublishEmptyTopic() {
  411. var fooCalled = false;
  412. function foo() { fooCalled = true; }
  413. assertFalse(
  414. 'Publishing to nonexistent topic must return false',
  415. pubsub.publish('someTopic'));
  416. pubsub.subscribe('someTopic', foo);
  417. assertTrue(
  418. 'Publishing to topic with subscriber must return true',
  419. pubsub.publish('someTopic'));
  420. assertTrue('Foo must have been called', fooCalled);
  421. pubsub.unsubscribe('someTopic', foo);
  422. fooCalled = false;
  423. assertFalse(
  424. 'Publishing to topic without subscribers must return false',
  425. pubsub.publish('someTopic'));
  426. assertFalse('Foo must nothave been called', fooCalled);
  427. }
  428. function testSubscribeWhilePublishing() {
  429. // It's OK for a subscriber to add a new subscriber to its own topic,
  430. // but the newly added subscriber shouldn't be called until the next
  431. // publish cycle.
  432. var firstCalled = false;
  433. var secondCalled = false;
  434. pubsub.subscribe('someTopic', function() {
  435. pubsub.subscribe('someTopic', function() { secondCalled = true; });
  436. firstCalled = true;
  437. });
  438. assertEquals(
  439. 'Topic must have one subscriber', 1, pubsub.getCount('someTopic'));
  440. assertFalse(
  441. 'No subscriber must have been called yet', firstCalled || secondCalled);
  442. pubsub.publish('someTopic');
  443. assertEquals(
  444. 'Topic must have two subscribers', 2, pubsub.getCount('someTopic'));
  445. assertTrue('The first subscriber must have been called', firstCalled);
  446. assertFalse(
  447. 'The second subscriber must not have been called yet', secondCalled);
  448. pubsub.publish('someTopic');
  449. assertEquals(
  450. 'Topic must have three subscribers', 3, pubsub.getCount('someTopic'));
  451. assertTrue('The first subscriber must have been called', firstCalled);
  452. assertTrue('The second subscriber must also have been called', secondCalled);
  453. }
  454. function testUnsubscribeWhilePublishing() {
  455. // It's OK for a subscriber to unsubscribe another subscriber from its
  456. // own topic, but the subscriber in question won't actually be removed
  457. // until after publishing is complete.
  458. var firstCalled = false;
  459. var secondCalled = false;
  460. var thirdCalled = false;
  461. function first() {
  462. assertTrue(
  463. 'unsubscribe() must return true when unsubscribing',
  464. pubsub.unsubscribe('X', second));
  465. assertEquals(
  466. 'Topic "X" must still have 3 subscribers', 3, pubsub.getCount('X'));
  467. firstCalled = true;
  468. }
  469. pubsub.subscribe('X', first);
  470. function second() { secondCalled = true; }
  471. pubsub.subscribe('X', second);
  472. function third() {
  473. assertTrue(
  474. 'unsubscribe() must return true when unsubscribing',
  475. pubsub.unsubscribe('X', first));
  476. assertEquals(
  477. 'Topic "X" must still have 3 subscribers', 3, pubsub.getCount('X'));
  478. thirdCalled = true;
  479. }
  480. pubsub.subscribe('X', third);
  481. assertEquals('Topic "X" must have 3 subscribers', 3, pubsub.getCount('X'));
  482. assertFalse(
  483. 'No subscribers must have been called yet',
  484. firstCalled || secondCalled || thirdCalled);
  485. assertTrue(pubsub.publish('X'));
  486. assertTrue('First function must have been called', firstCalled);
  487. assertFalse('Second function must not have been called', secondCalled);
  488. assertTrue('Third function must have been called', thirdCalled);
  489. assertEquals(
  490. 'Topic "X" must have 1 subscriber after publishing', 1,
  491. pubsub.getCount('X'));
  492. assertEquals(
  493. 'PubSub must not have any subscriptions pending removal', 0,
  494. pubsub.pendingKeys_.length);
  495. }
  496. function testUnsubscribeSelfWhilePublishing() {
  497. // It's OK for a subscriber to unsubscribe itself, but it won't actually
  498. // be removed until after publishing is complete.
  499. var selfDestructCalled = false;
  500. function selfDestruct() {
  501. assertTrue(
  502. 'unsubscribe() must return true when unsubscribing',
  503. pubsub.unsubscribe('someTopic', arguments.callee));
  504. assertEquals(
  505. 'Topic must still have 1 subscriber', 1, pubsub.getCount('someTopic'));
  506. selfDestructCalled = true;
  507. }
  508. pubsub.subscribe('someTopic', selfDestruct);
  509. assertEquals('Topic must have 1 subscriber', 1, pubsub.getCount('someTopic'));
  510. assertFalse(
  511. 'selfDestruct() must not have been called yet', selfDestructCalled);
  512. pubsub.publish('someTopic');
  513. assertTrue('selfDestruct() must have been called', selfDestructCalled);
  514. assertEquals(
  515. 'Topic must have no subscribers after publishing', 0,
  516. pubsub.getCount('someTopic'));
  517. assertEquals(
  518. 'PubSub must not have any subscriptions pending removal', 0,
  519. pubsub.pendingKeys_.length);
  520. }
  521. function testPublishReturnValue() {
  522. pubsub.subscribe(
  523. 'X', function() { pubsub.unsubscribe('X', arguments.callee); });
  524. assertTrue(
  525. 'publish() must return true even if the only subscriber ' +
  526. 'removes itself during publishing',
  527. pubsub.publish('X'));
  528. }
  529. function testNestedPublish() {
  530. var x1 = false;
  531. var x2 = false;
  532. var y1 = false;
  533. var y2 = false;
  534. pubsub.subscribe('X', function() {
  535. pubsub.publish('Y');
  536. pubsub.unsubscribe('X', arguments.callee);
  537. x1 = true;
  538. });
  539. pubsub.subscribe('X', function() { x2 = true; });
  540. pubsub.subscribe('Y', function() {
  541. pubsub.unsubscribe('Y', arguments.callee);
  542. y1 = true;
  543. });
  544. pubsub.subscribe('Y', function() { y2 = true; });
  545. pubsub.publish('X');
  546. assertTrue('x1 must be true', x1);
  547. assertTrue('x2 must be true', x2);
  548. assertTrue('y1 must be true', y1);
  549. assertTrue('y2 must be true', y2);
  550. }
  551. function testClear() {
  552. function fn() {}
  553. goog.array.forEach(
  554. ['W', 'X', 'Y', 'Z'], function(topic) { pubsub.subscribe(topic, fn); });
  555. assertEquals('Pubsub channel must have 4 subscribers', 4, pubsub.getCount());
  556. pubsub.clear('W');
  557. assertEquals('Pubsub channel must have 3 subscribers', 3, pubsub.getCount());
  558. goog.array.forEach(['X', 'Y'], function(topic) { pubsub.clear(topic); });
  559. assertEquals('Pubsub channel must have 1 subscriber', 1, pubsub.getCount());
  560. pubsub.clear();
  561. assertEquals('Pubsub channel must have no subscribers', 0, pubsub.getCount());
  562. }
  563. function testSubscriberExceptionUnlocksSubscriptions() {
  564. var key1 = pubsub.subscribe('X', function() {});
  565. pubsub.subscribe('X', function() {
  566. // Pushes "key1" onto queue to be unsubscribed after subscriptions are
  567. // processed.
  568. pubsub.unsubscribeByKey(key1);
  569. });
  570. pubsub.subscribe('X', function() { throw 'Oh no!'; });
  571. var key2 = pubsub.subscribe('X', function() {});
  572. assertThrows(function() { pubsub.publish('X'); });
  573. assertTrue(pubsub.unsubscribeByKey(key2));
  574. // "key1" should've been successfully removed already;
  575. assertFalse(pubsub.unsubscribeByKey(key1));
  576. }
  577. function testNestedSubscribeOnce() {
  578. var calls = 0;
  579. pubsub.subscribeOnce('X', function() { calls++; });
  580. pubsub.subscribe('Y', function() {
  581. pubsub.publish('X');
  582. pubsub.publish('X');
  583. });
  584. pubsub.publish('Y');
  585. assertEquals('X must be called once', 1, calls);
  586. }