@webex/common 2.59.3-next.1 → 2.59.4

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 (80) hide show
  1. package/.eslintrc.js +6 -6
  2. package/README.md +42 -42
  3. package/babel.config.js +3 -3
  4. package/dist/base64.js +22 -22
  5. package/dist/base64.js.map +1 -1
  6. package/dist/browser-detection.js.map +1 -1
  7. package/dist/capped-debounce.js +12 -12
  8. package/dist/capped-debounce.js.map +1 -1
  9. package/dist/check-required.js +8 -8
  10. package/dist/check-required.js.map +1 -1
  11. package/dist/constants.js.map +1 -1
  12. package/dist/defer.js +13 -13
  13. package/dist/defer.js.map +1 -1
  14. package/dist/deprecated.js +5 -5
  15. package/dist/deprecated.js.map +1 -1
  16. package/dist/event-envelope.js +11 -11
  17. package/dist/event-envelope.js.map +1 -1
  18. package/dist/events.js +15 -15
  19. package/dist/events.js.map +1 -1
  20. package/dist/exception.js +13 -13
  21. package/dist/exception.js.map +1 -1
  22. package/dist/in-browser/browser.js +2 -2
  23. package/dist/in-browser/browser.js.map +1 -1
  24. package/dist/in-browser/index.js.map +1 -1
  25. package/dist/in-browser/node.js +2 -2
  26. package/dist/in-browser/node.js.map +1 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/isBuffer.js +6 -6
  29. package/dist/isBuffer.js.map +1 -1
  30. package/dist/make-state-datatype.js +14 -14
  31. package/dist/make-state-datatype.js.map +1 -1
  32. package/dist/one-flight.js +13 -13
  33. package/dist/one-flight.js.map +1 -1
  34. package/dist/patterns.js +30 -30
  35. package/dist/patterns.js.map +1 -1
  36. package/dist/resolve-with.js +19 -19
  37. package/dist/resolve-with.js.map +1 -1
  38. package/dist/retry.js +17 -17
  39. package/dist/retry.js.map +1 -1
  40. package/dist/tap.js +14 -14
  41. package/dist/tap.js.map +1 -1
  42. package/dist/template-container.js +51 -51
  43. package/dist/template-container.js.map +1 -1
  44. package/dist/uuid-utils.js +76 -76
  45. package/dist/uuid-utils.js.map +1 -1
  46. package/dist/while-in-flight.js +5 -5
  47. package/dist/while-in-flight.js.map +1 -1
  48. package/jest.config.js +3 -3
  49. package/package.json +11 -12
  50. package/process +1 -1
  51. package/src/base64.js +67 -67
  52. package/src/browser-detection.js +37 -37
  53. package/src/capped-debounce.js +65 -65
  54. package/src/check-required.js +18 -18
  55. package/src/constants.js +79 -79
  56. package/src/defer.js +24 -24
  57. package/src/deprecated.js +19 -19
  58. package/src/event-envelope.js +54 -54
  59. package/src/events.js +55 -55
  60. package/src/exception.js +45 -45
  61. package/src/in-browser/browser.js +5 -5
  62. package/src/in-browser/index.js +11 -11
  63. package/src/in-browser/node.js +5 -5
  64. package/src/index.js +44 -44
  65. package/src/isBuffer.js +12 -12
  66. package/src/make-state-datatype.js +91 -91
  67. package/src/one-flight.js +89 -89
  68. package/src/patterns.js +51 -51
  69. package/src/resolve-with.js +27 -27
  70. package/src/retry.js +124 -124
  71. package/src/tap.js +25 -25
  72. package/src/template-container.js +222 -222
  73. package/src/uuid-utils.js +189 -189
  74. package/src/while-in-flight.js +38 -38
  75. package/test/unit/spec/capped-debounce.js +103 -103
  76. package/test/unit/spec/common.js +42 -42
  77. package/test/unit/spec/exception.js +102 -102
  78. package/test/unit/spec/one-flight.js +211 -211
  79. package/test/unit/spec/template-container.js +81 -81
  80. package/test/unit/spec/while-in-flight.js +70 -70
package/src/uuid-utils.js CHANGED
@@ -1,189 +1,189 @@
1
- import {encode, decode} from './base64';
2
- import {
3
- SDK_EVENT,
4
- hydraTypes,
5
- INTERNAL_US_CLUSTER_NAME,
6
- INTERNAL_US_INTEGRATION_CLUSTER_NAME,
7
- } from './constants';
8
-
9
- const hydraBaseUrl = 'https://api.ciscospark.com/v1';
10
-
11
- const isRequired = () => {
12
- throw Error('parameter is required');
13
- };
14
-
15
- /**
16
- * Constructs a Hydra ID for a given UUID and type.
17
- *
18
- * @export
19
- * @param {string} type one of PEOPLE, TEAM, ROOM
20
- * @param {any} id identifying the "TYPE" object
21
- * @param {string} cluster containing the "TYPE" object
22
- * @returns {string}
23
- */
24
- export function constructHydraId(type = isRequired(), id = isRequired(), cluster = 'us') {
25
- if (!type.toUpperCase) {
26
- throw Error('"type" must be a string');
27
- }
28
-
29
- if (type === hydraTypes.PEOPLE || type === hydraTypes.ORGANIZATION) {
30
- // Cluster is always "us" for people and orgs
31
- return encode(`ciscospark://us/${type.toUpperCase()}/${id}`);
32
- }
33
-
34
- return encode(`ciscospark://${cluster}/${type.toUpperCase()}/${id}`);
35
- }
36
-
37
- /**
38
- * @typedef {Object} DeconstructedHydraId
39
- * @property {UUID} id identifying the object
40
- * @property {String} type of the object
41
- * @property {String} cluster containing the object
42
- */
43
-
44
- /**
45
- * Deconstructs a Hydra ID.
46
- *
47
- * @export
48
- * @param {String} id Hydra style id
49
- * @returns {DeconstructedHydraId} deconstructed id
50
- */
51
- export function deconstructHydraId(id) {
52
- const payload = decode(id).split('/');
53
-
54
- return {
55
- id: payload.pop(),
56
- type: payload.pop(),
57
- cluster: payload.pop(),
58
- };
59
- }
60
-
61
- /**
62
- * Constructs a Hydra ID for a message based on internal UUID
63
- *
64
- * @export
65
- * @param {any} uuid
66
- * @param {string} cluster containing the message
67
- * @returns {string}
68
- */
69
- export function buildHydraMessageId(uuid, cluster) {
70
- return constructHydraId(hydraTypes.MESSAGE, uuid, cluster);
71
- }
72
-
73
- /**
74
- * Constructs a Hydra ID for a person based on internal UUID
75
- *
76
- * @export
77
- * @param {any} uuid
78
- * @param {string} cluster containing the person
79
- * @returns {string}
80
- */
81
- export function buildHydraPersonId(uuid, cluster) {
82
- return constructHydraId(hydraTypes.PEOPLE, uuid, cluster);
83
- }
84
-
85
- /**
86
- * Constructs a Hydra ID for a room based on internal UUID
87
- *
88
- * @export
89
- * @param {any} uuid
90
- * @param {string} cluster containing the room
91
- * @returns {string}
92
- */
93
- export function buildHydraRoomId(uuid, cluster) {
94
- return constructHydraId(hydraTypes.ROOM, uuid, cluster);
95
- }
96
-
97
- /**
98
- * Constructs a Hydra ID for an organization based on internal UUID
99
- *
100
- * @export
101
- * @param {any} uuid
102
- * @param {string} cluster containing the organization
103
- * @returns {string}
104
- */
105
- export function buildHydraOrgId(uuid, cluster) {
106
- return constructHydraId(hydraTypes.ORGANIZATION, uuid, cluster);
107
- }
108
-
109
- /**
110
- * Constructs a Hydra ID for an membership based on an
111
- * internal UUID for the person, and the space
112
- *
113
- * @export
114
- * @param {any} personUUID
115
- * @param {any} spaceUUID
116
- * @param {string} cluster containing the membership
117
- * @returns {string}
118
- */
119
- export function buildHydraMembershipId(personUUID, spaceUUID, cluster) {
120
- return constructHydraId(hydraTypes.MEMBERSHIP, `${personUUID}:${spaceUUID}`, cluster);
121
- }
122
-
123
- /**
124
- * Returns a hydra cluster string based on a conversation url
125
- * @private
126
- * @memberof Messages
127
- * @param {Object} webex sdk instance
128
- * @param {String} conversationUrl url of space where activity took place
129
- * @returns {String} string suitable for UUID -> public ID encoding
130
- */
131
- export function getHydraClusterString(webex, conversationUrl) {
132
- const internalClusterString = webex.internal.services.getClusterId(conversationUrl);
133
-
134
- if (
135
- internalClusterString.startsWith(INTERNAL_US_CLUSTER_NAME) ||
136
- internalClusterString.startsWith(INTERNAL_US_INTEGRATION_CLUSTER_NAME)
137
- ) {
138
- // Original US cluster is simply 'us' for backwards compatibility
139
- return 'us';
140
- }
141
- const clusterParts = internalClusterString.split(':');
142
-
143
- if (clusterParts.length < 3) {
144
- throw Error(`Unable to determine cluster for convo: ${conversationUrl}`);
145
- }
146
-
147
- return `${clusterParts[0]}:${clusterParts[1]}:${clusterParts[2]}`;
148
- }
149
-
150
- /**
151
- * Returns a Hydra roomType based on conversation tags
152
- *
153
- * @export
154
- * @param {arra} tags
155
- * @returns {string}
156
- */
157
- export function getHydraRoomType(tags) {
158
- if (tags.includes(SDK_EVENT.INTERNAL.ACTIVITY_TAG.ONE_ON_ONE)) {
159
- return SDK_EVENT.EXTERNAL.SPACE_TYPE.DIRECT;
160
- }
161
-
162
- return SDK_EVENT.EXTERNAL.SPACE_TYPE.GROUP;
163
- }
164
-
165
- /**
166
- * Returns file URLs for the activity, adhering to Hydra details,
167
- * e.g., https://api.ciscospark.com/v1/contents/Y2lzY29zcGF...
168
- * @see https://developer.webex.com/docs/api/v1/messages/get-message-details
169
- * @param {Object} activity from mercury
170
- * @param {string} cluster containing the files
171
- * @returns {Array} file URLs
172
- */
173
- export function getHydraFiles(activity, cluster) {
174
- const hydraFiles = [];
175
- const {files} = activity.object;
176
-
177
- if (files) {
178
- const {items} = files;
179
-
180
- // Note: Generated ID is dependent on file order.
181
- for (let i = 0; i < items.length; i += 1) {
182
- const contentId = constructHydraId(hydraTypes.CONTENT, `${activity.id}/${i}`, cluster);
183
-
184
- hydraFiles.push(`${hydraBaseUrl}/contents/${contentId}`);
185
- }
186
- }
187
-
188
- return hydraFiles;
189
- }
1
+ import {encode, decode} from './base64';
2
+ import {
3
+ SDK_EVENT,
4
+ hydraTypes,
5
+ INTERNAL_US_CLUSTER_NAME,
6
+ INTERNAL_US_INTEGRATION_CLUSTER_NAME,
7
+ } from './constants';
8
+
9
+ const hydraBaseUrl = 'https://api.ciscospark.com/v1';
10
+
11
+ const isRequired = () => {
12
+ throw Error('parameter is required');
13
+ };
14
+
15
+ /**
16
+ * Constructs a Hydra ID for a given UUID and type.
17
+ *
18
+ * @export
19
+ * @param {string} type one of PEOPLE, TEAM, ROOM
20
+ * @param {any} id identifying the "TYPE" object
21
+ * @param {string} cluster containing the "TYPE" object
22
+ * @returns {string}
23
+ */
24
+ export function constructHydraId(type = isRequired(), id = isRequired(), cluster = 'us') {
25
+ if (!type.toUpperCase) {
26
+ throw Error('"type" must be a string');
27
+ }
28
+
29
+ if (type === hydraTypes.PEOPLE || type === hydraTypes.ORGANIZATION) {
30
+ // Cluster is always "us" for people and orgs
31
+ return encode(`ciscospark://us/${type.toUpperCase()}/${id}`);
32
+ }
33
+
34
+ return encode(`ciscospark://${cluster}/${type.toUpperCase()}/${id}`);
35
+ }
36
+
37
+ /**
38
+ * @typedef {Object} DeconstructedHydraId
39
+ * @property {UUID} id identifying the object
40
+ * @property {String} type of the object
41
+ * @property {String} cluster containing the object
42
+ */
43
+
44
+ /**
45
+ * Deconstructs a Hydra ID.
46
+ *
47
+ * @export
48
+ * @param {String} id Hydra style id
49
+ * @returns {DeconstructedHydraId} deconstructed id
50
+ */
51
+ export function deconstructHydraId(id) {
52
+ const payload = decode(id).split('/');
53
+
54
+ return {
55
+ id: payload.pop(),
56
+ type: payload.pop(),
57
+ cluster: payload.pop(),
58
+ };
59
+ }
60
+
61
+ /**
62
+ * Constructs a Hydra ID for a message based on internal UUID
63
+ *
64
+ * @export
65
+ * @param {any} uuid
66
+ * @param {string} cluster containing the message
67
+ * @returns {string}
68
+ */
69
+ export function buildHydraMessageId(uuid, cluster) {
70
+ return constructHydraId(hydraTypes.MESSAGE, uuid, cluster);
71
+ }
72
+
73
+ /**
74
+ * Constructs a Hydra ID for a person based on internal UUID
75
+ *
76
+ * @export
77
+ * @param {any} uuid
78
+ * @param {string} cluster containing the person
79
+ * @returns {string}
80
+ */
81
+ export function buildHydraPersonId(uuid, cluster) {
82
+ return constructHydraId(hydraTypes.PEOPLE, uuid, cluster);
83
+ }
84
+
85
+ /**
86
+ * Constructs a Hydra ID for a room based on internal UUID
87
+ *
88
+ * @export
89
+ * @param {any} uuid
90
+ * @param {string} cluster containing the room
91
+ * @returns {string}
92
+ */
93
+ export function buildHydraRoomId(uuid, cluster) {
94
+ return constructHydraId(hydraTypes.ROOM, uuid, cluster);
95
+ }
96
+
97
+ /**
98
+ * Constructs a Hydra ID for an organization based on internal UUID
99
+ *
100
+ * @export
101
+ * @param {any} uuid
102
+ * @param {string} cluster containing the organization
103
+ * @returns {string}
104
+ */
105
+ export function buildHydraOrgId(uuid, cluster) {
106
+ return constructHydraId(hydraTypes.ORGANIZATION, uuid, cluster);
107
+ }
108
+
109
+ /**
110
+ * Constructs a Hydra ID for an membership based on an
111
+ * internal UUID for the person, and the space
112
+ *
113
+ * @export
114
+ * @param {any} personUUID
115
+ * @param {any} spaceUUID
116
+ * @param {string} cluster containing the membership
117
+ * @returns {string}
118
+ */
119
+ export function buildHydraMembershipId(personUUID, spaceUUID, cluster) {
120
+ return constructHydraId(hydraTypes.MEMBERSHIP, `${personUUID}:${spaceUUID}`, cluster);
121
+ }
122
+
123
+ /**
124
+ * Returns a hydra cluster string based on a conversation url
125
+ * @private
126
+ * @memberof Messages
127
+ * @param {Object} webex sdk instance
128
+ * @param {String} conversationUrl url of space where activity took place
129
+ * @returns {String} string suitable for UUID -> public ID encoding
130
+ */
131
+ export function getHydraClusterString(webex, conversationUrl) {
132
+ const internalClusterString = webex.internal.services.getClusterId(conversationUrl);
133
+
134
+ if (
135
+ internalClusterString.startsWith(INTERNAL_US_CLUSTER_NAME) ||
136
+ internalClusterString.startsWith(INTERNAL_US_INTEGRATION_CLUSTER_NAME)
137
+ ) {
138
+ // Original US cluster is simply 'us' for backwards compatibility
139
+ return 'us';
140
+ }
141
+ const clusterParts = internalClusterString.split(':');
142
+
143
+ if (clusterParts.length < 3) {
144
+ throw Error(`Unable to determine cluster for convo: ${conversationUrl}`);
145
+ }
146
+
147
+ return `${clusterParts[0]}:${clusterParts[1]}:${clusterParts[2]}`;
148
+ }
149
+
150
+ /**
151
+ * Returns a Hydra roomType based on conversation tags
152
+ *
153
+ * @export
154
+ * @param {arra} tags
155
+ * @returns {string}
156
+ */
157
+ export function getHydraRoomType(tags) {
158
+ if (tags.includes(SDK_EVENT.INTERNAL.ACTIVITY_TAG.ONE_ON_ONE)) {
159
+ return SDK_EVENT.EXTERNAL.SPACE_TYPE.DIRECT;
160
+ }
161
+
162
+ return SDK_EVENT.EXTERNAL.SPACE_TYPE.GROUP;
163
+ }
164
+
165
+ /**
166
+ * Returns file URLs for the activity, adhering to Hydra details,
167
+ * e.g., https://api.ciscospark.com/v1/contents/Y2lzY29zcGF...
168
+ * @see https://developer.webex.com/docs/api/v1/messages/get-message-details
169
+ * @param {Object} activity from mercury
170
+ * @param {string} cluster containing the files
171
+ * @returns {Array} file URLs
172
+ */
173
+ export function getHydraFiles(activity, cluster) {
174
+ const hydraFiles = [];
175
+ const {files} = activity.object;
176
+
177
+ if (files) {
178
+ const {items} = files;
179
+
180
+ // Note: Generated ID is dependent on file order.
181
+ for (let i = 0; i < items.length; i += 1) {
182
+ const contentId = constructHydraId(hydraTypes.CONTENT, `${activity.id}/${i}`, cluster);
183
+
184
+ hydraFiles.push(`${hydraBaseUrl}/contents/${contentId}`);
185
+ }
186
+ }
187
+
188
+ return hydraFiles;
189
+ }
@@ -1,38 +1,38 @@
1
- /*!
2
- * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
- */
4
-
5
- /* eslint no-invalid-this: [0] */
6
-
7
- import {wrap} from 'lodash';
8
-
9
- import tap from './tap';
10
-
11
- /**
12
- * While the promise returned by the decorated is unfullfilled, sets, the
13
- * specified boolean on the target class to `true`
14
- * @param {string} param
15
- * @returns {Function}
16
- */
17
- export default function whileInFlight(param) {
18
- return function whileInFlightDecorator(target, name, descriptor) {
19
- descriptor.value = wrap(descriptor.value, function whileInFlightExecutor(fn, ...args) {
20
- return new Promise((resolve) => {
21
- this[param] = true;
22
- resolve(
23
- Reflect.apply(fn, this, args)
24
- .then(
25
- tap(() => {
26
- this[param] = false;
27
- })
28
- )
29
- .catch((reason) => {
30
- this[param] = false;
31
-
32
- return Promise.reject(reason);
33
- })
34
- );
35
- });
36
- });
37
- };
38
- }
1
+ /*!
2
+ * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
+ */
4
+
5
+ /* eslint no-invalid-this: [0] */
6
+
7
+ import {wrap} from 'lodash';
8
+
9
+ import tap from './tap';
10
+
11
+ /**
12
+ * While the promise returned by the decorated is unfullfilled, sets, the
13
+ * specified boolean on the target class to `true`
14
+ * @param {string} param
15
+ * @returns {Function}
16
+ */
17
+ export default function whileInFlight(param) {
18
+ return function whileInFlightDecorator(target, name, descriptor) {
19
+ descriptor.value = wrap(descriptor.value, function whileInFlightExecutor(fn, ...args) {
20
+ return new Promise((resolve) => {
21
+ this[param] = true;
22
+ resolve(
23
+ Reflect.apply(fn, this, args)
24
+ .then(
25
+ tap(() => {
26
+ this[param] = false;
27
+ })
28
+ )
29
+ .catch((reason) => {
30
+ this[param] = false;
31
+
32
+ return Promise.reject(reason);
33
+ })
34
+ );
35
+ });
36
+ });
37
+ };
38
+ }
@@ -1,103 +1,103 @@
1
- /*!
2
- * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
- */
4
-
5
- import {assert} from '@webex/test-helper-chai';
6
- import sinon from 'sinon';
7
- import {cappedDebounce} from '@webex/common';
8
- import FakeTimers from '@sinonjs/fake-timers';
9
-
10
- describe('common', () => {
11
- describe('cappedDebounce()', () => {
12
- let clock;
13
-
14
- beforeEach(() => {
15
- clock = FakeTimers.install({now: Date.now()});
16
- });
17
-
18
- afterEach(() => {
19
- clock.uninstall();
20
- });
21
-
22
- it('requires a function', () => {
23
- assert.throws(cappedDebounce, /`fn` must be a function/);
24
- });
25
-
26
- it('requires a `wait`', () => {
27
- assert.throws(cappedDebounce.bind(null, sinon.spy()), /`wait` is required/);
28
- });
29
-
30
- it('requires a `maxWait`', () => {
31
- assert.throws(cappedDebounce.bind(null, sinon.spy(), 5), /`options.maxWait` is required/);
32
- });
33
-
34
- it('requires a `maxCalls`', () => {
35
- assert.throws(
36
- cappedDebounce.bind(null, sinon.spy(), 5, {maxWait: 10}),
37
- /`options.maxCalls` is required/
38
- );
39
- });
40
-
41
- it('returns a function that will execute once it stops being invoked for `wait` ms', () => {
42
- const spy = sinon.spy();
43
- const fn = cappedDebounce(spy, 50, {maxWait: 1000, maxCalls: 10000});
44
-
45
- fn();
46
- fn();
47
- fn();
48
- fn();
49
- assert.notCalled(spy);
50
- clock.tick(20);
51
- assert.notCalled(spy);
52
- clock.tick(40);
53
- assert.calledOnce(spy);
54
- clock.tick(40);
55
- assert.calledOnce(spy);
56
- });
57
-
58
- it('returns a function that will execute once if it continues to be invoked for `wait` ms after `maxWait` ms', () => {
59
- const spy = sinon.spy();
60
- const fn = cappedDebounce(spy, 100, {maxWait: 130, maxCalls: 10000});
61
-
62
- fn();
63
- assert.notCalled(spy);
64
- clock.tick(50);
65
- fn();
66
- assert.notCalled(spy);
67
- clock.tick(50);
68
- fn();
69
- assert.notCalled(spy);
70
- clock.tick(50);
71
- fn();
72
- assert.calledOnce(spy);
73
- });
74
-
75
- it('returns a function that will execute once it has been invoked `maxCalls` times', () => {
76
- const spy = sinon.spy();
77
- const fn = cappedDebounce(spy, 50, {maxWait: 100, maxCalls: 4});
78
-
79
- fn();
80
- assert.notCalled(spy);
81
- fn();
82
- assert.notCalled(spy);
83
- fn();
84
- assert.notCalled(spy);
85
- fn();
86
- assert.called(spy);
87
- });
88
-
89
- it('returns a function that will execute once it has been invoked `maxCalls` times and executes again after `maxWait` ms', () => {
90
- const spy = sinon.spy();
91
- const fn = cappedDebounce(spy, 50, {maxWait: 100, maxCalls: 3});
92
-
93
- fn();
94
- fn();
95
- fn();
96
- assert.called(spy);
97
- fn();
98
- fn();
99
- clock.tick(150);
100
- assert.calledTwice(spy);
101
- });
102
- });
103
- });
1
+ /*!
2
+ * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
+ */
4
+
5
+ import {assert} from '@webex/test-helper-chai';
6
+ import sinon from 'sinon';
7
+ import {cappedDebounce} from '@webex/common';
8
+ import FakeTimers from '@sinonjs/fake-timers';
9
+
10
+ describe('common', () => {
11
+ describe('cappedDebounce()', () => {
12
+ let clock;
13
+
14
+ beforeEach(() => {
15
+ clock = FakeTimers.install({now: Date.now()});
16
+ });
17
+
18
+ afterEach(() => {
19
+ clock.uninstall();
20
+ });
21
+
22
+ it('requires a function', () => {
23
+ assert.throws(cappedDebounce, /`fn` must be a function/);
24
+ });
25
+
26
+ it('requires a `wait`', () => {
27
+ assert.throws(cappedDebounce.bind(null, sinon.spy()), /`wait` is required/);
28
+ });
29
+
30
+ it('requires a `maxWait`', () => {
31
+ assert.throws(cappedDebounce.bind(null, sinon.spy(), 5), /`options.maxWait` is required/);
32
+ });
33
+
34
+ it('requires a `maxCalls`', () => {
35
+ assert.throws(
36
+ cappedDebounce.bind(null, sinon.spy(), 5, {maxWait: 10}),
37
+ /`options.maxCalls` is required/
38
+ );
39
+ });
40
+
41
+ it('returns a function that will execute once it stops being invoked for `wait` ms', () => {
42
+ const spy = sinon.spy();
43
+ const fn = cappedDebounce(spy, 50, {maxWait: 1000, maxCalls: 10000});
44
+
45
+ fn();
46
+ fn();
47
+ fn();
48
+ fn();
49
+ assert.notCalled(spy);
50
+ clock.tick(20);
51
+ assert.notCalled(spy);
52
+ clock.tick(40);
53
+ assert.calledOnce(spy);
54
+ clock.tick(40);
55
+ assert.calledOnce(spy);
56
+ });
57
+
58
+ it('returns a function that will execute once if it continues to be invoked for `wait` ms after `maxWait` ms', () => {
59
+ const spy = sinon.spy();
60
+ const fn = cappedDebounce(spy, 100, {maxWait: 130, maxCalls: 10000});
61
+
62
+ fn();
63
+ assert.notCalled(spy);
64
+ clock.tick(50);
65
+ fn();
66
+ assert.notCalled(spy);
67
+ clock.tick(50);
68
+ fn();
69
+ assert.notCalled(spy);
70
+ clock.tick(50);
71
+ fn();
72
+ assert.calledOnce(spy);
73
+ });
74
+
75
+ it('returns a function that will execute once it has been invoked `maxCalls` times', () => {
76
+ const spy = sinon.spy();
77
+ const fn = cappedDebounce(spy, 50, {maxWait: 100, maxCalls: 4});
78
+
79
+ fn();
80
+ assert.notCalled(spy);
81
+ fn();
82
+ assert.notCalled(spy);
83
+ fn();
84
+ assert.notCalled(spy);
85
+ fn();
86
+ assert.called(spy);
87
+ });
88
+
89
+ it('returns a function that will execute once it has been invoked `maxCalls` times and executes again after `maxWait` ms', () => {
90
+ const spy = sinon.spy();
91
+ const fn = cappedDebounce(spy, 50, {maxWait: 100, maxCalls: 3});
92
+
93
+ fn();
94
+ fn();
95
+ fn();
96
+ assert.called(spy);
97
+ fn();
98
+ fn();
99
+ clock.tick(150);
100
+ assert.calledTwice(spy);
101
+ });
102
+ });
103
+ });