-
-
Notifications
You must be signed in to change notification settings - Fork 21
/
test.js
291 lines (221 loc) · 7.68 KB
/
test.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
const test = require('ava');
const throttle = require('./index.js');
const delay = async duration => new Promise(resolve => {
setTimeout(resolve, duration);
});
function counter() {
function count() {
count.callCount++;
}
count.callCount = 0;
return count;
}
test('throttled function is called at most once per interval', async t => {
const count = counter();
const wait = 100;
const total = 300;
const throttled = throttle(count, wait);
const interval = setInterval(throttled, 20);
await delay(total);
clearInterval(interval);
// Using floor since the first call happens immediately
const expectedCalls = 1 + Math.floor((total - wait) / wait);
t.is(count.callCount, expectedCalls, 'Should call function based on total time and wait interval');
});
test('throttled function executes final call after wait time', async t => {
const count = counter();
const wait = 100;
const throttled = throttle(count, wait);
throttled();
throttled();
t.is(count.callCount, 1, 'Should call once immediately');
await delay(wait + 10);
t.is(count.callCount, 2, 'Should call again after wait interval');
});
test('throttled function preserves last context', async t => {
let context;
const wait = 100;
const throttled = throttle(function () {
context = this; // eslint-disable-line unicorn/no-this-assignment
}, wait);
const foo = {};
const bar = {};
throttled.call(foo);
throttled.call(bar);
t.is(context, foo, 'Context should be first call context initially');
await delay(wait + 5);
t.is(context, bar, 'Context should be last call context after wait');
});
test('throttled function preserves last arguments', async t => {
let arguments_;
const wait = 100;
const throttled = throttle((...localArguments) => {
arguments_ = localArguments;
}, wait);
throttled(1);
throttled(2);
t.is(arguments_[0], 1, 'Arguments should be from first call initially');
await delay(wait + 5);
t.is(arguments_[0], 2, 'Arguments should be from last call after wait');
});
test('throttled function handles rapid succession calls', async t => {
const count = counter();
const wait = 50;
const throttled = throttle(count, wait);
throttled();
throttled();
throttled();
t.is(count.callCount, 1, 'Should call once immediately despite multiple rapid calls');
await delay(wait + 10);
t.is(count.callCount, 2, 'Should call again after wait interval');
});
test('throttled function responds to different arguments', async t => {
let lastArg;
const wait = 50;
const throttled = throttle(arg => {
lastArg = arg;
}, wait);
throttled(1);
throttled(2);
throttled(3);
t.is(lastArg, 1, 'Should capture first argument initially');
await delay(wait + 10);
t.is(lastArg, 3, 'Should capture last argument after wait interval');
});
test('throttled function handles repeated calls post-wait', async t => {
const count = counter();
const wait = 50;
const throttled = throttle(count, wait);
throttled();
await delay(wait + 10);
throttled();
t.is(count.callCount, 2, 'Should allow a call after wait period has elapsed');
});
test('throttled function does not call function within wait time', async t => {
const count = counter();
const wait = 100;
const throttled = throttle(count, wait);
throttled();
await delay(wait / 2);
throttled();
t.is(count.callCount, 1, 'Should not call function again within wait time');
});
test('throttled function with zero wait time calls function immediately each time', t => {
const count = counter();
const wait = 0;
const throttled = throttle(count, wait);
throttled();
throttled();
throttled();
t.is(count.callCount, 3, 'Should call function immediately on each invocation with zero wait time');
});
test('throttled function with large wait time delays subsequent calls appropriately', async t => {
const count = counter();
const wait = 1000; // 1 second
const throttled = throttle(count, wait);
throttled();
t.is(count.callCount, 1, 'Should call function immediately for the first time');
// Attempt a call before the wait time elapses
await delay(500);
throttled();
t.is(count.callCount, 1, 'Should not call function again before wait time elapses');
// Check after the wait time
await delay(600); // Total 1100ms
t.is(count.callCount, 2, 'Should call function again after wait time elapses');
});
test('throttled function handles calls from different contexts', async t => {
const wait = 100;
const throttled = throttle(function () {
this.callCount = (this.callCount ?? 0) + 1;
}, wait);
const objectA = {};
const objectB = {};
throttled.call(objectA);
throttled.call(objectB);
t.is(objectA.callCount, 1, 'Should call function with first context immediately');
t.is(objectB.callCount, undefined, 'Should not call function with second context immediately');
await delay(wait + 10);
t.is(objectB.callCount, 1, 'Should call function with second context after wait time');
});
test('throttled function allows immediate invocation after wait time from last call', async t => {
const count = counter();
const wait = 100;
const throttled = throttle(count, wait);
throttled();
await delay(wait + 10);
throttled();
t.is(count.callCount, 2, 'Should allow immediate invocation after wait time from last call');
});
test('throttled function handles rapid calls with short delays', async t => {
const count = counter();
const wait = 100;
const throttled = throttle(count, wait);
throttled();
await delay(30);
throttled();
await delay(30);
throttled();
t.is(count.callCount, 1, 'Should only call once despite rapid calls with short delays');
await delay(wait);
t.is(count.callCount, 2, 'Should call again after wait time');
});
test('throttled function with extremely short wait time behaves correctly', async t => {
const count = counter();
const wait = 1; // 1 millisecond
const throttled = throttle(count, wait);
throttled();
throttled();
throttled();
await delay(5); // Slightly longer than the wait time
t.true(count.callCount >= 1, 'Should call at least once with extremely short wait time');
});
test('simultaneous throttled functions with different wait times operate independently', async t => {
const count1 = counter();
const count2 = counter();
const wait1 = 50;
const wait2 = 150;
const throttled1 = throttle(count1, wait1);
const throttled2 = throttle(count2, wait2);
throttled1();
throttled2();
await delay(60); // Just over wait1, but under wait2
throttled1();
throttled2();
t.is(count1.callCount, 2, 'First throttled function should be called twice');
t.is(count2.callCount, 1, 'Second throttled function should be called once');
});
test('throttled functions with side effects only apply effects once per interval', async t => {
let sideEffectCounter = 0;
const incrementSideEffect = () => {
sideEffectCounter++;
};
const wait = 100;
const throttledIncrement = throttle(incrementSideEffect, wait);
throttledIncrement();
throttledIncrement();
throttledIncrement();
t.is(sideEffectCounter, 1, 'Side effect should only have occurred once');
await delay(wait + 10);
t.is(sideEffectCounter, 2, 'Side effect should occur again after wait time');
});
test('throttled function handles system time changes', async t => {
const count = counter();
const wait = 100;
const throttled = throttle(count, wait);
const originalNow = Date.now;
Date.now = () => originalNow() + 1000; // Simulate a time jump forward
throttled();
throttled();
Date.now = originalNow; // Reset Date.now to original
t.is(count.callCount, 1, 'Should respect throttling despite time change');
await delay(wait);
t.is(count.callCount, 2, 'Should allow a call after wait time');
});
test('parameter validation', t => {
t.throws(() => {
throttle(undefined, 0);
}, {instanceOf: TypeError});
/// t.throws(() => {
// throttle(() => {});
// }, {instanceOf: TypeError});
});