@angular-wave/angular.ts 0.0.26 → 0.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +1 -2
  2. package/dist/angular-ts.esm.js +1 -1
  3. package/dist/angular-ts.umd.js +1 -1
  4. package/e2e/unit.spec.ts +2 -1
  5. package/index.html +8 -9
  6. package/package.json +1 -1
  7. package/src/core/pubsub.js +329 -0
  8. package/src/router/globals.js +0 -5
  9. package/src/router/hooks/core-resolvables.js +12 -11
  10. package/src/router/hooks/ignored-transition.js +1 -1
  11. package/src/router/hooks/lazy-load.js +40 -41
  12. package/src/router/hooks/redirect-to.js +32 -29
  13. package/src/router/hooks/update-globals.js +1 -1
  14. package/src/router/hooks/url.js +33 -24
  15. package/src/router/hooks/views.js +21 -20
  16. package/src/router/params/param-factory.js +17 -0
  17. package/src/router/params/param-types.js +0 -3
  18. package/src/router/router.js +78 -62
  19. package/src/router/services.js +2 -4
  20. package/src/router/state/state-queue-manager.js +5 -7
  21. package/src/router/state/state-registry.js +8 -10
  22. package/src/router/state/state-service.js +34 -33
  23. package/src/router/transition/hook-builder.js +2 -2
  24. package/src/router/transition/transition-hook.js +3 -9
  25. package/src/router/transition/transition-service.js +12 -30
  26. package/src/router/transition/transition.js +28 -25
  27. package/src/router/url/url-config.js +1 -49
  28. package/src/router/url/url-matcher-factory.js +10 -51
  29. package/src/router/url/url-router.js +27 -17
  30. package/src/router/url/url-rule.js +9 -13
  31. package/src/router/url/url-rules.js +3 -7
  32. package/src/router/url/url-service.js +134 -97
  33. package/src/router/view/view.js +3 -3
  34. package/src/shared/hof.js +1 -1
  35. package/test/angular.spec.js +1 -0
  36. package/test/aria/aria.spec.js +2 -1
  37. package/test/core/interval.spec.js +1 -1
  38. package/test/core/pubsub.spec.js +314 -0
  39. package/test/directive/bind.spec.js +2 -1
  40. package/test/directive/boolean.spec.js +4 -2
  41. package/test/directive/change.spec.js +1 -1
  42. package/test/directive/class.spec.js +1 -0
  43. package/test/directive/click.spec.js +2 -1
  44. package/test/directive/cloak.spec.js +1 -2
  45. package/test/directive/{constoller.spec.js → controller.spec.js} +1 -0
  46. package/test/directive/element-style.spec.js +1 -0
  47. package/test/directive/event.spec.js +1 -1
  48. package/test/directive/href.spec.js +2 -1
  49. package/test/directive/init.spec.js +1 -0
  50. package/test/directive/input.spec.js +200 -285
  51. package/test/directive/list.spec.js +2 -1
  52. package/test/directive/model.spec.js +1 -0
  53. package/test/directive/non-bindable.spec.js +2 -1
  54. package/test/directive/script.spec.js +1 -0
  55. package/test/directive/scrset.spec.js +2 -1
  56. package/test/directive/show-hide.spec.js +1 -0
  57. package/test/directive/src.spec.js +2 -1
  58. package/test/directive/style.spec.js +1 -0
  59. package/test/directive/switch.spec.js +2 -1
  60. package/test/directive/validators.spec.js +1 -1
  61. package/test/router/state-directives.spec.js +72 -72
  62. package/test/router/state.spec.js +5 -5
  63. package/test/router/template-factory.spec.js +2 -2
  64. package/test/router/view-directive.spec.js +65 -65
  65. package/test/router/view-hook.spec.js +13 -13
  66. package/test/router/view-scroll.spec.js +3 -3
  67. package/test/router/view.spec.js +2 -2
  68. package/types/router/core/common/coreservices.d.ts +2 -3
  69. package/types/router/core/globals.d.ts +1 -4
  70. package/types/router/core/interface.d.ts +2 -8
  71. package/types/router/core/params/paramTypes.d.ts +0 -1
  72. package/types/router/core/router.d.ts +2 -3
  73. package/types/router/core/state/stateQueueManager.d.ts +1 -3
  74. package/types/router/core/state/stateRegistry.d.ts +0 -2
  75. package/types/router/core/state/stateService.d.ts +1 -2
  76. package/types/router/core/transition/interface.d.ts +3 -3
  77. package/types/router/core/transition/transitionService.d.ts +1 -2
  78. package/types/router/core/url/urlConfig.d.ts +1 -2
  79. package/types/router/core/url/urlRules.d.ts +1 -2
  80. package/types/router/core/url/urlService.d.ts +1 -2
  81. package/types/router/locationServices.d.ts +0 -1
  82. package/src/router/location-services.js +0 -67
@@ -1,21 +1,21 @@
1
- import { isString } from "../../shared/utils";
1
+ import { isDefined, isObject, isString } from "../../shared/utils";
2
2
  import { is, pattern } from "../../shared/hof";
3
3
  import { UrlRules } from "./url-rules";
4
4
  import { UrlConfig } from "./url-config";
5
5
  import { TargetState } from "../state/target-state";
6
+ import { removeFrom } from "../../shared/common";
7
+
6
8
  /**
7
9
  * API for URL management
8
10
  */
9
11
  export class UrlService {
10
12
  /**
11
- *
12
- * @param {import('../router').UIRouter} router
13
+ * @param {angular.ILocationProvider} $locationProvider
13
14
  */
14
- constructor(router) {
15
- /**
16
- * @type {import('../router').UIRouter}
17
- */
18
- this.router = router;
15
+ constructor($locationProvider, urlRuleFactory, stateService) {
16
+ this.stateService = stateService;
17
+
18
+ this.$locationProvider = $locationProvider;
19
19
 
20
20
  /** @type {boolean} */
21
21
  this.interceptDeferred = false;
@@ -25,69 +25,15 @@ export class UrlService {
25
25
  * See: [[UrlRules]] for details
26
26
  * @type {UrlRules}
27
27
  */
28
- this.rules = new UrlRules(this.router);
28
+ this.rules = new UrlRules(urlRuleFactory);
29
29
  /**
30
30
  * The nested [[UrlConfig]] API to configure the URL and retrieve URL information
31
31
  *
32
32
  * See: [[UrlConfig]] for details
33
33
  * @type {UrlConfig}
34
34
  */
35
- this.config = new UrlConfig(this.router);
36
- // Delegate these calls to the current LocationServices implementation
37
- /**
38
- * Gets the current url, or updates the url
39
- *
40
- * ### Getting the current URL
41
- *
42
- * When no arguments are passed, returns the current URL.
43
- * The URL is normalized using the internal [[path]]/[[search]]/[[hash]] values.
44
- *
45
- * For example, the URL may be stored in the hash ([[HashLocationServices]]) or
46
- * have a base HREF prepended ([[PushStateLocationServices]]).
47
- *
48
- * The raw URL in the browser might be:
49
- *
50
- * ```
51
- * http://mysite.com/somepath/index.html#/internal/path/123?param1=foo#anchor
52
- * ```
53
- *
54
- * or
55
- *
56
- * ```
57
- * http://mysite.com/basepath/internal/path/123?param1=foo#anchor
58
- * ```
59
- *
60
- * then this method returns:
61
- *
62
- * ```
63
- * /internal/path/123?param1=foo#anchor
64
- * ```
65
- *
66
- *
67
- * #### Example:
68
- * ```js
69
- * locationServices.url(); // "/some/path?query=value#anchor"
70
- * ```
71
- *
72
- * ### Updating the URL
73
- *
74
- * When `newurl` arguments is provided, changes the URL to reflect `newurl`
75
- *
76
- * #### Example:
77
- * ```js
78
- * locationServices.url("/some/path?query=value#anchor", true);
79
- * ```
80
- *
81
- * @param {string} newurl The new value for the URL.
82
- * This url should reflect only the new internal [[path]], [[search]], and [[hash]] values.
83
- * It should not include the protocol, site, port, or base path of an absolute HREF.
84
- * @param {boolean} replace When true, replaces the current history entry (instead of appending it) with this new url
85
- * @param {any} state The history's state object, i.e., pushState (if the LocationServices implementation supports it)
86
- *
87
- * @return the url (after potentially being processed)
88
- */
89
- this.url = (newurl, replace, state) =>
90
- this.router.locationService.url(newurl, replace, state);
35
+ this.config = new UrlConfig();
36
+
91
37
  /**
92
38
  * Gets the path part of the current url
93
39
  *
@@ -95,7 +41,7 @@ export class UrlService {
95
41
  *
96
42
  * @return the path portion of the url
97
43
  */
98
- this.path = () => this.router.locationService.path();
44
+ this.path = () => this.$location.path();
99
45
  /**
100
46
  * Gets the search part of the current url as an object
101
47
  *
@@ -103,7 +49,7 @@ export class UrlService {
103
49
  *
104
50
  * @return the search (query) portion of the url, as an object
105
51
  */
106
- this.search = () => this.router.locationService.search();
52
+ this.search = () => this.$location.search();
107
53
  /**
108
54
  * Gets the hash part of the current url
109
55
  *
@@ -111,30 +57,103 @@ export class UrlService {
111
57
  *
112
58
  * @return the hash (anchor) portion of the url
113
59
  */
114
- this.hash = () => this.router.locationService.hash();
115
- /**
116
- * @internal
117
- *
118
- * Registers a low level url change handler
119
- *
120
- * Note: Because this is a low level handler, it's not recommended for general use.
121
- *
122
- * #### Example:
123
- * ```js
124
- * let deregisterFn = locationServices.onChange((evt) => console.log("url change", evt));
125
- * ```
126
- *
127
- * @param callback a function that will be called when the url is changing
128
- * @return a function that de-registers the callback
129
- */
130
- this.onChange = (callback) =>
131
- this.router.locationService.onChange(callback);
60
+ this.hash = () => this.$location.hash();
61
+
62
+ this._urlListeners = [];
63
+ }
64
+
65
+ html5Mode() {
66
+ let html5Mode = this.$locationProvider.html5Mode();
67
+ html5Mode = isObject(html5Mode) ? html5Mode.enabled : html5Mode;
68
+ return html5Mode && typeof history !== "undefined";
132
69
  }
133
70
 
134
- dispose() {
135
- this.listen(false);
136
- this.rules.dispose();
71
+ baseHref() {
72
+ return (
73
+ this._baseHref ||
74
+ (this._baseHref = this.$browser.baseHref() || window.location.pathname)
75
+ );
137
76
  }
77
+
78
+ /**
79
+ * Gets the current url, or updates the url
80
+ *
81
+ * ### Getting the current URL
82
+ *
83
+ * When no arguments are passed, returns the current URL.
84
+ * The URL is normalized using the internal [[path]]/[[search]]/[[hash]] values.
85
+ *
86
+ * For example, the URL may be stored in the hash ([[HashLocationServices]]) or
87
+ * have a base HREF prepended ([[PushStateLocationServices]]).
88
+ *
89
+ * The raw URL in the browser might be:
90
+ *
91
+ * ```
92
+ * http://mysite.com/somepath/index.html#/internal/path/123?param1=foo#anchor
93
+ * ```
94
+ *
95
+ * or
96
+ *
97
+ * ```
98
+ * http://mysite.com/basepath/internal/path/123?param1=foo#anchor
99
+ * ```
100
+ *
101
+ * then this method returns:
102
+ *
103
+ * ```
104
+ * /internal/path/123?param1=foo#anchor
105
+ * ```
106
+ *
107
+ *
108
+ * #### Example:
109
+ * ```js
110
+ * locationServices.url(); // "/some/path?query=value#anchor"
111
+ * ```
112
+ *
113
+ * ### Updating the URL
114
+ *
115
+ * When `newurl` arguments is provided, changes the URL to reflect `newurl`
116
+ *
117
+ * #### Example:
118
+ * ```js
119
+ * locationServices.url("/some/path?query=value#anchor", true);
120
+ * ```
121
+ *
122
+ * @param {string} newUrl The new value for the URL.
123
+ * This url should reflect only the new internal [[path]], [[search]], and [[hash]] values.
124
+ * It should not include the protocol, site, port, or base path of an absolute HREF.
125
+ * @param {boolean} replace When true, replaces the current history entry (instead of appending it) with this new url
126
+ * @param {any} state The history's state object, i.e., pushState (if the LocationServices implementation supports it)
127
+ *
128
+ * @return the url (after potentially being processed)
129
+ */
130
+ url(newUrl, replace = false, state) {
131
+ if (isDefined(newUrl)) this.$location.url(newUrl);
132
+ if (replace) this.$location.replace();
133
+ if (state) this.$location.state(state);
134
+ return this.$location.url();
135
+ }
136
+
137
+ /**
138
+ * @internal
139
+ *
140
+ * Registers a low level url change handler
141
+ *
142
+ * Note: Because this is a low level handler, it's not recommended for general use.
143
+ *
144
+ * #### Example:
145
+ * ```js
146
+ * let deregisterFn = locationServices.onChange((evt) => console.log("url change", evt));
147
+ * ```
148
+ *
149
+ * @param callback a function that will be called when the url is changing
150
+ * @return a function that de-registers the callback
151
+ */
152
+ onChange(callback) {
153
+ this._urlListeners.push(callback);
154
+ return () => removeFrom(this._urlListeners)(callback);
155
+ }
156
+
138
157
  /**
139
158
  * Gets the current URL parts
140
159
  *
@@ -165,15 +184,18 @@ export class UrlService {
165
184
  */
166
185
  sync(evt) {
167
186
  if (evt && evt.defaultPrevented) return;
168
- const { urlService, stateService } = this.router;
187
+ const stateService = this.stateService;
169
188
  const url = {
170
- path: urlService.path(),
171
- search: urlService.search(),
172
- hash: urlService.hash(),
189
+ path: this.path(),
190
+ search: this.search(),
191
+ hash: this.hash(),
173
192
  };
193
+ /**
194
+ * @type {angular.MatchResult}
195
+ */
174
196
  const best = this.match(url);
175
197
  const applyResult = pattern([
176
- [isString, (newurl) => urlService.url(newurl, true)],
198
+ [isString, (newurl) => this.url(newurl, true)],
177
199
  [
178
200
  TargetState.isDef,
179
201
  (def) => stateService.go(def.state, def.params, def.options),
@@ -184,7 +206,8 @@ export class UrlService {
184
206
  stateService.go(target.state(), target.params(), target.options()),
185
207
  ],
186
208
  ]);
187
- applyResult(best && best.rule.handler(best.match, url, this.router));
209
+
210
+ applyResult(best && best.rule.handler(best.match, url));
188
211
  }
189
212
  /**
190
213
  * Starts or stops listening for URL changes
@@ -214,8 +237,7 @@ export class UrlService {
214
237
  delete this._stopListeningFn;
215
238
  } else {
216
239
  return (this._stopListeningFn =
217
- this._stopListeningFn ||
218
- this.router.urlService.onChange((evt) => this.sync(evt)));
240
+ this._stopListeningFn || this.onChange((evt) => this.sync(evt)));
219
241
  }
220
242
  }
221
243
  /**
@@ -251,13 +273,18 @@ export class UrlService {
251
273
  *
252
274
  * Given a URL (as a [[UrlParts]] object), check all rules and determine the best matching rule.
253
275
  * Return the result as a [[MatchResult]].
276
+ * @returns {angular.MatchResult}
254
277
  */
255
278
  match(url) {
256
279
  url = Object.assign({ path: "", search: {}, hash: "" }, url);
257
280
  const rules = this.rules.rules();
258
281
  // Checks a single rule. Returns { rule: rule, match: match, weight: weight } if it matched, or undefined
282
+ /**
283
+ *
284
+ * @param {import("./url-rule").BaseUrlRule} rule
285
+ */
259
286
  const checkRule = (rule) => {
260
- const match = rule.match(url, this.router);
287
+ const match = rule.match(url);
261
288
  return match && { match, rule, weight: rule.matchPriority(match) };
262
289
  };
263
290
  // The rules are pre-sorted.
@@ -275,4 +302,14 @@ export class UrlService {
275
302
  }
276
303
  return best;
277
304
  }
305
+
306
+ _runtimeServices($rootScope, $location, $browser) {
307
+ /** @type {angular.ILocationService} */
308
+ this.$location = $location;
309
+ this.$browser = $browser;
310
+ // Bind $locationChangeSuccess to the listeners registered in LocationService.onChange
311
+ $rootScope.$on("$locationChangeSuccess", (evt) =>
312
+ this._urlListeners.forEach((fn) => fn(evt)),
313
+ );
314
+ }
278
315
  }
@@ -26,9 +26,9 @@ import { trace } from "../common/trace";
26
26
  */
27
27
  export class ViewService {
28
28
  /**
29
- * @param {import('../router').UIRouter} router
29
+ * @param {number} $id
30
30
  */
31
- constructor(router) {
31
+ constructor($id) {
32
32
  this._uiViews = [];
33
33
  this._viewConfigs = [];
34
34
  this._viewConfigFactories = {};
@@ -37,7 +37,7 @@ export class ViewService {
37
37
  _rootViewContext: this._rootViewContext.bind(this),
38
38
  _viewConfigFactory: this._viewConfigFactory.bind(this),
39
39
  _registeredUIView: (id) =>
40
- find(this._uiViews, (view) => `${router.$id}.${view.id}` === id),
40
+ find(this._uiViews, (view) => `${$id}.${view.id}` === id),
41
41
  _registeredUIViews: () => this._uiViews,
42
42
  _activeViewConfigs: () => this._viewConfigs,
43
43
  _onSync: (listener) => {
package/src/shared/hof.js CHANGED
@@ -77,7 +77,7 @@ export function compose() {
77
77
  * let piped = pipe(f,g,h);
78
78
  * then, piped is: h(g(f(x)))
79
79
  */
80
- export function pipe(...funcs) {
80
+ export function pipe() {
81
81
  return compose.apply(null, [].slice.call(arguments).reverse());
82
82
  }
83
83
  /**
@@ -48,6 +48,7 @@ describe("angular", () => {
48
48
 
49
49
  afterEach(() => {
50
50
  dealoc(element);
51
+ jqLite.CACHE.clear();
51
52
  });
52
53
 
53
54
  describe("case", () => {
@@ -1,6 +1,6 @@
1
1
  import { createInjector } from "../../src/injector";
2
2
  import { publishExternalAPI } from "../../src/public";
3
- import { dealoc } from "../../src/jqLite";
3
+ import { dealoc, jqLite } from "../../src/jqLite";
4
4
 
5
5
  describe("$aria", () => {
6
6
  let scope;
@@ -17,6 +17,7 @@ describe("$aria", () => {
17
17
 
18
18
  afterEach(() => {
19
19
  dealoc(element);
20
+ jqLite.CACHE.clear();
20
21
  });
21
22
 
22
23
  describe("with `ngAriaDisable`", () => {
@@ -71,7 +71,7 @@ describe("$interval", () => {
71
71
 
72
72
  $interval(notifySpy, 1, 1, false);
73
73
 
74
- await wait(10);
74
+ await wait(100);
75
75
  expect(notifySpy).toHaveBeenCalled();
76
76
  expect(evalAsyncSpy).not.toHaveBeenCalled();
77
77
  expect(digestSpy).not.toHaveBeenCalled();
@@ -0,0 +1,314 @@
1
+ import { PubSub } from "../../src/core/pubsub";
2
+
3
+ describe("PubSub", function () {
4
+ let pubsub;
5
+ let asyncPubsub;
6
+
7
+ beforeEach(function () {
8
+ pubsub = new PubSub();
9
+ asyncPubsub = new PubSub(true);
10
+ });
11
+
12
+ afterEach(function () {
13
+ asyncPubsub.dispose();
14
+ pubsub.dispose();
15
+ });
16
+
17
+ it("should create a PubSub instance", function () {
18
+ expect(pubsub).not.toBeNull();
19
+ expect(pubsub instanceof PubSub).toBe(true);
20
+ });
21
+
22
+ it("should dispose of the PubSub instance", function () {
23
+ expect(pubsub.isDisposed()).toBe(false);
24
+ pubsub.dispose();
25
+ expect(pubsub.isDisposed()).toBe(true);
26
+ });
27
+
28
+ it("should subscribe and unsubscribe correctly", function () {
29
+ function foo1() {}
30
+ function bar1() {}
31
+ function foo2() {}
32
+ function bar2() {}
33
+
34
+ expect(pubsub.getCount("foo")).toBe(0);
35
+ expect(pubsub.getCount("bar")).toBe(0);
36
+
37
+ pubsub.subscribe("foo", foo1);
38
+ expect(pubsub.getCount("foo")).toBe(1);
39
+ expect(pubsub.getCount("bar")).toBe(0);
40
+
41
+ pubsub.subscribe("bar", bar1);
42
+ expect(pubsub.getCount("foo")).toBe(1);
43
+ expect(pubsub.getCount("bar")).toBe(1);
44
+
45
+ pubsub.subscribe("foo", foo2);
46
+ expect(pubsub.getCount("foo")).toBe(2);
47
+ expect(pubsub.getCount("bar")).toBe(1);
48
+
49
+ pubsub.subscribe("bar", bar2);
50
+ expect(pubsub.getCount("foo")).toBe(2);
51
+ expect(pubsub.getCount("bar")).toBe(2);
52
+
53
+ expect(pubsub.unsubscribe("foo", foo1)).toBe(true);
54
+ expect(pubsub.getCount("foo")).toBe(1);
55
+ expect(pubsub.getCount("bar")).toBe(2);
56
+
57
+ expect(pubsub.unsubscribe("foo", foo2)).toBe(true);
58
+ expect(pubsub.getCount("foo")).toBe(0);
59
+ expect(pubsub.getCount("bar")).toBe(2);
60
+
61
+ expect(pubsub.unsubscribe("bar", bar1)).toBe(true);
62
+ expect(pubsub.getCount("foo")).toBe(0);
63
+ expect(pubsub.getCount("bar")).toBe(1);
64
+
65
+ expect(pubsub.unsubscribe("bar", bar2)).toBe(true);
66
+ expect(pubsub.getCount("foo")).toBe(0);
67
+ expect(pubsub.getCount("bar")).toBe(0);
68
+
69
+ expect(pubsub.unsubscribe("baz", foo1)).toBe(false);
70
+ expect(pubsub.unsubscribe("foo", () => {})).toBe(false);
71
+ });
72
+
73
+ it("should subscribe and unsubscribe with context correctly", function () {
74
+ function foo() {}
75
+ function bar() {}
76
+
77
+ const contextA = {};
78
+ const contextB = {};
79
+
80
+ expect(pubsub.getCount("X")).toBe(0);
81
+
82
+ pubsub.subscribe("X", foo, contextA);
83
+ expect(pubsub.getCount("X")).toBe(1);
84
+
85
+ pubsub.subscribe("X", bar);
86
+ expect(pubsub.getCount("X")).toBe(2);
87
+
88
+ pubsub.subscribe("X", bar, contextB);
89
+ expect(pubsub.getCount("X")).toBe(3);
90
+
91
+ expect(pubsub.unsubscribe("X", foo, contextB)).toBe(false);
92
+
93
+ expect(pubsub.unsubscribe("X", foo, contextA)).toBe(true);
94
+ expect(pubsub.getCount("X")).toBe(2);
95
+
96
+ expect(pubsub.unsubscribe("X", bar)).toBe(true);
97
+ expect(pubsub.getCount("X")).toBe(1);
98
+
99
+ expect(pubsub.unsubscribe("X", bar, contextB)).toBe(true);
100
+ expect(pubsub.getCount("X")).toBe(0);
101
+ });
102
+
103
+ it("should subscribe once correctly", function () {
104
+ let called;
105
+ let context;
106
+
107
+ called = false;
108
+ pubsub.subscribeOnce("someTopic", () => {
109
+ called = true;
110
+ });
111
+ expect(pubsub.getCount("someTopic")).toBe(1);
112
+ expect(called).toBe(false);
113
+
114
+ pubsub.publish("someTopic");
115
+ expect(pubsub.getCount("someTopic")).toBe(0);
116
+ expect(called).toBe(true);
117
+
118
+ context = { called: false };
119
+ pubsub.subscribeOnce(
120
+ "someTopic",
121
+ function () {
122
+ this.called = true;
123
+ },
124
+ context,
125
+ );
126
+ expect(pubsub.getCount("someTopic")).toBe(1);
127
+ expect(context.called).toBe(false);
128
+
129
+ pubsub.publish("someTopic");
130
+ expect(pubsub.getCount("someTopic")).toBe(0);
131
+ expect(context.called).toBe(true);
132
+
133
+ context = { called: false, value: 0 };
134
+ pubsub.subscribeOnce(
135
+ "someTopic",
136
+ function (value) {
137
+ this.called = true;
138
+ this.value = value;
139
+ },
140
+ context,
141
+ );
142
+ expect(pubsub.getCount("someTopic")).toBe(1);
143
+ expect(context.called).toBe(false);
144
+ expect(context.value).toBe(0);
145
+
146
+ pubsub.publish("someTopic", 17);
147
+ expect(pubsub.getCount("someTopic")).toBe(0);
148
+ expect(context.called).toBe(true);
149
+ expect(context.value).toBe(17);
150
+ });
151
+
152
+ it("should async subscribe once correctly", function (done) {
153
+ let callCount = 0;
154
+ asyncPubsub.subscribeOnce("someTopic", () => {
155
+ callCount++;
156
+ });
157
+ expect(asyncPubsub.getCount("someTopic")).toBe(1);
158
+
159
+ asyncPubsub.publish("someTopic");
160
+ asyncPubsub.publish("someTopic");
161
+
162
+ setTimeout(() => {
163
+ expect(asyncPubsub.getCount("someTopic")).toBe(0);
164
+ expect(callCount).toBe(1);
165
+ done();
166
+ }, 0);
167
+ });
168
+
169
+ it("should async subscribe once with context correctly", function (done) {
170
+ const context = { callCount: 0 };
171
+ asyncPubsub.subscribeOnce(
172
+ "someTopic",
173
+ function () {
174
+ this.callCount++;
175
+ },
176
+ context,
177
+ );
178
+ expect(asyncPubsub.getCount("someTopic")).toBe(1);
179
+
180
+ asyncPubsub.publish("someTopic");
181
+ asyncPubsub.publish("someTopic");
182
+
183
+ setTimeout(() => {
184
+ expect(asyncPubsub.getCount("someTopic")).toBe(0);
185
+ expect(context.callCount).toBe(1);
186
+ done();
187
+ }, 0);
188
+ });
189
+
190
+ it("should async subscribe once with context and value correctly", function (done) {
191
+ const context = { callCount: 0, value: 0 };
192
+ asyncPubsub.subscribeOnce(
193
+ "someTopic",
194
+ function (value) {
195
+ this.callCount++;
196
+ this.value = value;
197
+ },
198
+ context,
199
+ );
200
+ expect(asyncPubsub.getCount("someTopic")).toBe(1);
201
+
202
+ asyncPubsub.publish("someTopic", 17);
203
+ asyncPubsub.publish("someTopic", 42);
204
+
205
+ setTimeout(() => {
206
+ expect(asyncPubsub.getCount("someTopic")).toBe(0);
207
+ expect(context.callCount).toBe(1);
208
+ expect(context.value).toBe(17);
209
+ done();
210
+ }, 0);
211
+ });
212
+
213
+ it("should subscribe once with bound function correctly", function () {
214
+ const context = { called: false, value: 0 };
215
+
216
+ function subscriber(value) {
217
+ this.called = true;
218
+ this.value = value;
219
+ }
220
+
221
+ pubsub.subscribeOnce("someTopic", subscriber.bind(context));
222
+ expect(pubsub.getCount("someTopic")).toBe(1);
223
+ expect(context.called).toBe(false);
224
+ expect(context.value).toBe(0);
225
+
226
+ pubsub.publish("someTopic", 17);
227
+ expect(pubsub.getCount("someTopic")).toBe(0);
228
+ expect(context.called).toBe(true);
229
+ expect(context.value).toBe(17);
230
+ });
231
+
232
+ it("should subscribe once with partial function correctly", function () {
233
+ let called = false;
234
+ let value = 0;
235
+
236
+ function subscriber(hasBeenCalled, newValue) {
237
+ called = hasBeenCalled;
238
+ value = newValue;
239
+ }
240
+
241
+ pubsub.subscribeOnce("someTopic", subscriber.bind(null, true));
242
+ expect(pubsub.getCount("someTopic")).toBe(1);
243
+ expect(called).toBe(false);
244
+ expect(value).toBe(0);
245
+
246
+ pubsub.publish("someTopic", 17);
247
+ expect(pubsub.getCount("someTopic")).toBe(0);
248
+ expect(called).toBe(true);
249
+ expect(value).toBe(17);
250
+ });
251
+
252
+ it("should handle self resubscribe correctly", function () {
253
+ let value = null;
254
+
255
+ function resubscribe(iteration, newValue) {
256
+ pubsub.subscribeOnce("someTopic", resubscribe.bind(null, iteration + 1));
257
+ value = `${newValue}:${iteration}`;
258
+ }
259
+
260
+ pubsub.subscribeOnce("someTopic", resubscribe.bind(null, 0));
261
+ expect(pubsub.getCount("someTopic")).toBe(1);
262
+ expect(value).toBeNull();
263
+
264
+ pubsub.publish("someTopic", "foo");
265
+ expect(pubsub.getCount("someTopic")).toBe(1);
266
+ expect(value).toBe("foo:0");
267
+
268
+ pubsub.publish("someTopic", "bar");
269
+ expect(pubsub.getCount("someTopic")).toBe(1);
270
+ expect(value).toBe("bar:1");
271
+
272
+ pubsub.publish("someTopic", "baz");
273
+ expect(pubsub.getCount("someTopic")).toBe(1);
274
+ expect(value).toBe("baz:2");
275
+ });
276
+
277
+ it("should handle async self resubscribe correctly", function (done) {
278
+ let value = null;
279
+
280
+ function resubscribe(iteration, newValue) {
281
+ asyncPubsub.subscribeOnce(
282
+ "someTopic",
283
+ resubscribe.bind(null, iteration + 1),
284
+ );
285
+ value = `${newValue}:${iteration}`;
286
+ }
287
+
288
+ asyncPubsub.subscribeOnce("someTopic", resubscribe.bind(null, 0));
289
+ expect(asyncPubsub.getCount("someTopic")).toBe(1);
290
+ expect(value).toBeNull();
291
+
292
+ asyncPubsub.publish("someTopic", "foo");
293
+
294
+ setTimeout(() => {
295
+ expect(asyncPubsub.getCount("someTopic")).toBe(1);
296
+ expect(value).toBe("foo:0");
297
+
298
+ asyncPubsub.publish("someTopic", "bar");
299
+
300
+ setTimeout(() => {
301
+ expect(asyncPubsub.getCount("someTopic")).toBe(1);
302
+ expect(value).toBe("bar:1");
303
+
304
+ asyncPubsub.publish("someTopic", "baz");
305
+
306
+ setTimeout(() => {
307
+ expect(asyncPubsub.getCount("someTopic")).toBe(1);
308
+ expect(value).toBe("baz:2");
309
+ done();
310
+ }, 0);
311
+ }, 0);
312
+ }, 0);
313
+ });
314
+ });