@khanacademy/wonder-blocks-timing 2.0.2 → 2.0.3
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.
- package/dist/es/index.js +56 -2
- package/dist/index.js +80 -11
- package/package.json +3 -3
- package/src/hooks/__tests__/use-timeout.test.js +336 -0
- package/src/hooks/use-timeout.js +70 -0
- package/src/hooks/use-timeout.stories.mdx +152 -0
- package/src/index.js +1 -0
- package/src/util/__tests__/animation-frame.test.js +6 -8
- package/src/util/__tests__/interval.test.js +31 -14
- package/src/util/__tests__/timeout.test.js +18 -8
package/dist/es/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import _extends from '@babel/runtime/helpers/extends';
|
|
2
|
-
import { Component, forwardRef, createElement } from 'react';
|
|
2
|
+
import { Component, forwardRef, createElement, useState, useRef, useEffect, useCallback } from 'react';
|
|
3
3
|
|
|
4
4
|
const SchedulePolicy = {
|
|
5
5
|
Immediately: "schedule-immediately",
|
|
@@ -430,4 +430,58 @@ function withActionScheduler(WrappedComponent) {
|
|
|
430
430
|
}))));
|
|
431
431
|
}
|
|
432
432
|
|
|
433
|
-
|
|
433
|
+
function useTimeout(action, timeoutMs, options) {
|
|
434
|
+
var _options$schedulePoli;
|
|
435
|
+
|
|
436
|
+
const schedulePolicy = (_options$schedulePoli = options == null ? void 0 : options.schedulePolicy) != null ? _options$schedulePoli : SchedulePolicy.Immediately;
|
|
437
|
+
const [isSet, setIsSet] = useState(schedulePolicy === SchedulePolicy.Immediately);
|
|
438
|
+
const actionRef = useRef(action);
|
|
439
|
+
const mountedRef = useRef(false);
|
|
440
|
+
useEffect(() => {
|
|
441
|
+
mountedRef.current = true;
|
|
442
|
+
return () => {
|
|
443
|
+
mountedRef.current = false;
|
|
444
|
+
};
|
|
445
|
+
}, []);
|
|
446
|
+
useEffect(() => {
|
|
447
|
+
actionRef.current = action;
|
|
448
|
+
}, [action]);
|
|
449
|
+
const clear = useCallback(policy => {
|
|
450
|
+
if ((policy != null ? policy : options == null ? void 0 : options.clearPolicy) === ClearPolicy.Resolve) {
|
|
451
|
+
actionRef.current();
|
|
452
|
+
} // This will cause the useEffect below to re-run
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
setIsSet(false);
|
|
456
|
+
}, [options == null ? void 0 : options.clearPolicy]);
|
|
457
|
+
const set = useCallback(() => {
|
|
458
|
+
if (isSet) {
|
|
459
|
+
clear();
|
|
460
|
+
} // This will cause the useEffect below to re-run
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
setIsSet(true);
|
|
464
|
+
}, [clear, isSet]);
|
|
465
|
+
useEffect(() => {
|
|
466
|
+
if (isSet && mountedRef.current) {
|
|
467
|
+
const timeout = window.setTimeout(() => {
|
|
468
|
+
actionRef.current();
|
|
469
|
+
setIsSet(false);
|
|
470
|
+
}, timeoutMs);
|
|
471
|
+
return () => {
|
|
472
|
+
window.clearTimeout(timeout);
|
|
473
|
+
|
|
474
|
+
if (!mountedRef.current) {
|
|
475
|
+
clear();
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
}, [clear, isSet, timeoutMs]);
|
|
480
|
+
return {
|
|
481
|
+
isSet,
|
|
482
|
+
set,
|
|
483
|
+
clear
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
export { ClearPolicy, SchedulePolicy, useTimeout, withActionScheduler };
|
package/dist/index.js
CHANGED
|
@@ -82,7 +82,7 @@ module.exports =
|
|
|
82
82
|
/******/
|
|
83
83
|
/******/
|
|
84
84
|
/******/ // Load entry module and return exports
|
|
85
|
-
/******/ return __webpack_require__(__webpack_require__.s =
|
|
85
|
+
/******/ return __webpack_require__(__webpack_require__.s = 9);
|
|
86
86
|
/******/ })
|
|
87
87
|
/************************************************************************/
|
|
88
88
|
/******/ ([
|
|
@@ -115,7 +115,7 @@ module.exports = require("react");
|
|
|
115
115
|
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return withActionScheduler; });
|
|
116
116
|
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
|
|
117
117
|
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
|
|
118
|
-
/* harmony import */ var _action_scheduler_provider_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(
|
|
118
|
+
/* harmony import */ var _action_scheduler_provider_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4);
|
|
119
119
|
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
|
120
120
|
|
|
121
121
|
|
|
@@ -141,11 +141,76 @@ function withActionScheduler(WrappedComponent) {
|
|
|
141
141
|
/* 3 */
|
|
142
142
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
143
143
|
|
|
144
|
+
"use strict";
|
|
145
|
+
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return useTimeout; });
|
|
146
|
+
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
|
|
147
|
+
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
|
|
148
|
+
/* harmony import */ var _util_policies_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(0);
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
function useTimeout(action, timeoutMs, options) {
|
|
152
|
+
var _options$schedulePoli;
|
|
153
|
+
|
|
154
|
+
const schedulePolicy = (_options$schedulePoli = options == null ? void 0 : options.schedulePolicy) != null ? _options$schedulePoli : _util_policies_js__WEBPACK_IMPORTED_MODULE_1__[/* SchedulePolicy */ "b"].Immediately;
|
|
155
|
+
const [isSet, setIsSet] = Object(react__WEBPACK_IMPORTED_MODULE_0__["useState"])(schedulePolicy === _util_policies_js__WEBPACK_IMPORTED_MODULE_1__[/* SchedulePolicy */ "b"].Immediately);
|
|
156
|
+
const actionRef = Object(react__WEBPACK_IMPORTED_MODULE_0__["useRef"])(action);
|
|
157
|
+
const mountedRef = Object(react__WEBPACK_IMPORTED_MODULE_0__["useRef"])(false);
|
|
158
|
+
Object(react__WEBPACK_IMPORTED_MODULE_0__["useEffect"])(() => {
|
|
159
|
+
mountedRef.current = true;
|
|
160
|
+
return () => {
|
|
161
|
+
mountedRef.current = false;
|
|
162
|
+
};
|
|
163
|
+
}, []);
|
|
164
|
+
Object(react__WEBPACK_IMPORTED_MODULE_0__["useEffect"])(() => {
|
|
165
|
+
actionRef.current = action;
|
|
166
|
+
}, [action]);
|
|
167
|
+
const clear = Object(react__WEBPACK_IMPORTED_MODULE_0__["useCallback"])(policy => {
|
|
168
|
+
if ((policy != null ? policy : options == null ? void 0 : options.clearPolicy) === _util_policies_js__WEBPACK_IMPORTED_MODULE_1__[/* ClearPolicy */ "a"].Resolve) {
|
|
169
|
+
actionRef.current();
|
|
170
|
+
} // This will cause the useEffect below to re-run
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
setIsSet(false);
|
|
174
|
+
}, [options == null ? void 0 : options.clearPolicy]);
|
|
175
|
+
const set = Object(react__WEBPACK_IMPORTED_MODULE_0__["useCallback"])(() => {
|
|
176
|
+
if (isSet) {
|
|
177
|
+
clear();
|
|
178
|
+
} // This will cause the useEffect below to re-run
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
setIsSet(true);
|
|
182
|
+
}, [clear, isSet]);
|
|
183
|
+
Object(react__WEBPACK_IMPORTED_MODULE_0__["useEffect"])(() => {
|
|
184
|
+
if (isSet && mountedRef.current) {
|
|
185
|
+
const timeout = window.setTimeout(() => {
|
|
186
|
+
actionRef.current();
|
|
187
|
+
setIsSet(false);
|
|
188
|
+
}, timeoutMs);
|
|
189
|
+
return () => {
|
|
190
|
+
window.clearTimeout(timeout);
|
|
191
|
+
|
|
192
|
+
if (!mountedRef.current) {
|
|
193
|
+
clear();
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
}, [clear, isSet, timeoutMs]);
|
|
198
|
+
return {
|
|
199
|
+
isSet,
|
|
200
|
+
set,
|
|
201
|
+
clear
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/***/ }),
|
|
206
|
+
/* 4 */
|
|
207
|
+
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
208
|
+
|
|
144
209
|
"use strict";
|
|
145
210
|
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return ActionSchedulerProvider; });
|
|
146
211
|
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
|
|
147
212
|
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
|
|
148
|
-
/* harmony import */ var _util_action_scheduler_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(
|
|
213
|
+
/* harmony import */ var _util_action_scheduler_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(5);
|
|
149
214
|
|
|
150
215
|
|
|
151
216
|
|
|
@@ -179,14 +244,14 @@ class ActionSchedulerProvider extends react__WEBPACK_IMPORTED_MODULE_0__["Compon
|
|
|
179
244
|
}
|
|
180
245
|
|
|
181
246
|
/***/ }),
|
|
182
|
-
/*
|
|
247
|
+
/* 5 */
|
|
183
248
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
184
249
|
|
|
185
250
|
"use strict";
|
|
186
251
|
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return ActionScheduler; });
|
|
187
|
-
/* harmony import */ var _timeout_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
|
|
188
|
-
/* harmony import */ var _interval_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(
|
|
189
|
-
/* harmony import */ var _animation_frame_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(
|
|
252
|
+
/* harmony import */ var _timeout_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6);
|
|
253
|
+
/* harmony import */ var _interval_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(7);
|
|
254
|
+
/* harmony import */ var _animation_frame_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(8);
|
|
190
255
|
|
|
191
256
|
|
|
192
257
|
|
|
@@ -267,7 +332,7 @@ ActionScheduler.NoopAction = {
|
|
|
267
332
|
};
|
|
268
333
|
|
|
269
334
|
/***/ }),
|
|
270
|
-
/*
|
|
335
|
+
/* 6 */
|
|
271
336
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
272
337
|
|
|
273
338
|
"use strict";
|
|
@@ -378,7 +443,7 @@ class Timeout {
|
|
|
378
443
|
}
|
|
379
444
|
|
|
380
445
|
/***/ }),
|
|
381
|
-
/*
|
|
446
|
+
/* 7 */
|
|
382
447
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
383
448
|
|
|
384
449
|
"use strict";
|
|
@@ -487,7 +552,7 @@ class Interval {
|
|
|
487
552
|
}
|
|
488
553
|
|
|
489
554
|
/***/ }),
|
|
490
|
-
/*
|
|
555
|
+
/* 8 */
|
|
491
556
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
492
557
|
|
|
493
558
|
"use strict";
|
|
@@ -594,7 +659,7 @@ class AnimationFrame {
|
|
|
594
659
|
}
|
|
595
660
|
|
|
596
661
|
/***/ }),
|
|
597
|
-
/*
|
|
662
|
+
/* 9 */
|
|
598
663
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
599
664
|
|
|
600
665
|
"use strict";
|
|
@@ -607,6 +672,10 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
607
672
|
/* harmony import */ var _components_with_action_scheduler_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2);
|
|
608
673
|
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "withActionScheduler", function() { return _components_with_action_scheduler_js__WEBPACK_IMPORTED_MODULE_1__["a"]; });
|
|
609
674
|
|
|
675
|
+
/* harmony import */ var _hooks_use_timeout_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(3);
|
|
676
|
+
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "useTimeout", function() { return _hooks_use_timeout_js__WEBPACK_IMPORTED_MODULE_2__["a"]; });
|
|
677
|
+
|
|
678
|
+
|
|
610
679
|
|
|
611
680
|
|
|
612
681
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@khanacademy/wonder-blocks-timing",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "2.0.
|
|
4
|
+
"version": "2.0.3",
|
|
5
5
|
"design": "v1",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
"react": "16.14.0"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
|
-
"wb-dev-build-settings": "^0.
|
|
20
|
+
"wb-dev-build-settings": "^0.2.0"
|
|
21
21
|
},
|
|
22
22
|
"author": "",
|
|
23
23
|
"license": "MIT",
|
|
24
|
-
"gitHead": "
|
|
24
|
+
"gitHead": "9ebea88533e702011165072f090a377e02fa3f0f"
|
|
25
25
|
}
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import {renderHook, act} from "@testing-library/react-hooks";
|
|
3
|
+
import {SchedulePolicy, ClearPolicy} from "../../util/policies.js";
|
|
4
|
+
|
|
5
|
+
import {useTimeout} from "../use-timeout.js";
|
|
6
|
+
|
|
7
|
+
describe("useTimeout", () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
jest.useFakeTimers();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("should return an ITimeout", () => {
|
|
13
|
+
// Arrange
|
|
14
|
+
const {result} = renderHook(() => useTimeout(() => {}, 1000));
|
|
15
|
+
|
|
16
|
+
// Act
|
|
17
|
+
|
|
18
|
+
// Assert
|
|
19
|
+
expect(result.current).toEqual(
|
|
20
|
+
expect.objectContaining({
|
|
21
|
+
clear: expect.any(Function),
|
|
22
|
+
set: expect.any(Function),
|
|
23
|
+
isSet: expect.any(Boolean),
|
|
24
|
+
}),
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should default to being immediately set", () => {
|
|
29
|
+
// Arrange
|
|
30
|
+
const {result} = renderHook(() => useTimeout(() => {}, 1000));
|
|
31
|
+
|
|
32
|
+
// Act
|
|
33
|
+
|
|
34
|
+
// Assert
|
|
35
|
+
expect(result.current.isSet).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("SchedulePolicies.Immediately", () => {
|
|
39
|
+
it("should call the action after the timeout expires", () => {
|
|
40
|
+
// Arrange
|
|
41
|
+
const action = jest.fn();
|
|
42
|
+
renderHook(() => useTimeout(action, 1000));
|
|
43
|
+
|
|
44
|
+
// Act
|
|
45
|
+
act(() => {
|
|
46
|
+
jest.advanceTimersByTime(1000);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Assert
|
|
50
|
+
expect(action).toHaveBeenCalled();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should update isSet to false after the timeout expires", () => {
|
|
54
|
+
// Arrange
|
|
55
|
+
const action = jest.fn();
|
|
56
|
+
const {result} = renderHook(() => useTimeout(action, 1000));
|
|
57
|
+
|
|
58
|
+
// Act
|
|
59
|
+
act(() => {
|
|
60
|
+
jest.advanceTimersByTime(1000);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Assert
|
|
64
|
+
expect(result.current.isSet).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("should call the action again if 'set' is called after the action was called", () => {
|
|
68
|
+
// Arrange
|
|
69
|
+
const action = jest.fn();
|
|
70
|
+
const {result} = renderHook(() => useTimeout(action, 1000));
|
|
71
|
+
|
|
72
|
+
// Act
|
|
73
|
+
act(() => {
|
|
74
|
+
jest.advanceTimersByTime(1001);
|
|
75
|
+
result.current.set();
|
|
76
|
+
jest.advanceTimersByTime(1001);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Assert
|
|
80
|
+
expect(action).toHaveBeenCalledTimes(2);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("should restart the timeout if timeoutMs gets updated", () => {
|
|
84
|
+
// Arrange
|
|
85
|
+
const action = jest.fn();
|
|
86
|
+
const {rerender} = renderHook(
|
|
87
|
+
({timeoutMs}) => useTimeout(action, timeoutMs),
|
|
88
|
+
{
|
|
89
|
+
initialProps: {timeoutMs: 1000},
|
|
90
|
+
},
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// Act
|
|
94
|
+
act(() => {
|
|
95
|
+
jest.advanceTimersByTime(900);
|
|
96
|
+
});
|
|
97
|
+
rerender({timeoutMs: 500});
|
|
98
|
+
act(() => {
|
|
99
|
+
jest.advanceTimersByTime(100);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Assert
|
|
103
|
+
expect(action).not.toHaveBeenCalled();
|
|
104
|
+
act(() => jest.advanceTimersByTime(500));
|
|
105
|
+
expect(action).toHaveBeenCalled();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("should should timeout after the new timeoutMs if it gets updated", () => {
|
|
109
|
+
// Arrange
|
|
110
|
+
const action = jest.fn();
|
|
111
|
+
const {rerender} = renderHook(
|
|
112
|
+
({timeoutMs}) => useTimeout(action, timeoutMs),
|
|
113
|
+
{
|
|
114
|
+
initialProps: {timeoutMs: 1000},
|
|
115
|
+
},
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// Act
|
|
119
|
+
rerender({timeoutMs: 500});
|
|
120
|
+
act(() => {
|
|
121
|
+
jest.advanceTimersByTime(500);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Assert
|
|
125
|
+
expect(action).toHaveBeenCalled();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("should call the new action after re-rendering with a new action", () => {
|
|
129
|
+
// Arrange
|
|
130
|
+
const action1 = jest.fn();
|
|
131
|
+
const action2 = jest.fn();
|
|
132
|
+
const {rerender} = renderHook(
|
|
133
|
+
({action}) => useTimeout(action, 1000),
|
|
134
|
+
{
|
|
135
|
+
initialProps: {action: action1},
|
|
136
|
+
},
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
// Act
|
|
140
|
+
rerender({action: action2});
|
|
141
|
+
act(() => {
|
|
142
|
+
jest.advanceTimersByTime(1000);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Assert
|
|
146
|
+
expect(action2).toHaveBeenCalled();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("should not call the original action after re-rendering with a new action", () => {
|
|
150
|
+
// Arrange
|
|
151
|
+
const action1 = jest.fn();
|
|
152
|
+
const action2 = jest.fn();
|
|
153
|
+
const {rerender} = renderHook(
|
|
154
|
+
({action}) => useTimeout(action, 1000),
|
|
155
|
+
{
|
|
156
|
+
initialProps: {action: action1},
|
|
157
|
+
},
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
// Act
|
|
161
|
+
rerender({action: action2});
|
|
162
|
+
act(() => {
|
|
163
|
+
jest.advanceTimersByTime(1000);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Assert
|
|
167
|
+
expect(action1).not.toHaveBeenCalled();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("should not call the action if the timeout is cleared", () => {
|
|
171
|
+
// Arrange
|
|
172
|
+
const action = jest.fn();
|
|
173
|
+
const {result} = renderHook(() => useTimeout(action, 1000));
|
|
174
|
+
|
|
175
|
+
// Act
|
|
176
|
+
act(() => {
|
|
177
|
+
result.current.clear();
|
|
178
|
+
jest.advanceTimersByTime(1000);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Assert
|
|
182
|
+
expect(action).not.toHaveBeenCalled();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("should call the action when the timeout is cleared when passing ClearPolicies.Resolve to clear()", () => {
|
|
186
|
+
// Arrange
|
|
187
|
+
const action = jest.fn();
|
|
188
|
+
const {result} = renderHook(() => useTimeout(action, 1000));
|
|
189
|
+
|
|
190
|
+
// Act
|
|
191
|
+
act(() => {
|
|
192
|
+
result.current.clear(ClearPolicy.Resolve);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Assert
|
|
196
|
+
expect(action).toHaveBeenCalled();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("should call the action when the timeout is cleared when using ClearPolicies.Resolve in options", () => {
|
|
200
|
+
// Arrange
|
|
201
|
+
const action = jest.fn();
|
|
202
|
+
const {result} = renderHook(() =>
|
|
203
|
+
useTimeout(action, 1000, {clearPolicy: ClearPolicy.Resolve}),
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
// Act
|
|
207
|
+
act(() => {
|
|
208
|
+
result.current.clear();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Assert
|
|
212
|
+
expect(action).toHaveBeenCalled();
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("should call the action on unmount when using ClearPolicies.Resolve in options", () => {
|
|
216
|
+
// Arrange
|
|
217
|
+
const action = jest.fn();
|
|
218
|
+
const {unmount} = renderHook(() =>
|
|
219
|
+
useTimeout(action, 1000, {clearPolicy: ClearPolicy.Resolve}),
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
// Act
|
|
223
|
+
unmount();
|
|
224
|
+
|
|
225
|
+
// Assert
|
|
226
|
+
expect(action).toHaveBeenCalled();
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("should not call the action on unmount when using the default options", () => {
|
|
230
|
+
// Arrange
|
|
231
|
+
const action = jest.fn();
|
|
232
|
+
const {unmount} = renderHook(() => useTimeout(action, 1000));
|
|
233
|
+
|
|
234
|
+
// Act
|
|
235
|
+
unmount();
|
|
236
|
+
|
|
237
|
+
// Assert
|
|
238
|
+
expect(action).not.toHaveBeenCalled();
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
describe("SchedulePolicies.OnDemand", () => {
|
|
243
|
+
it("should not set the timer on creation", () => {
|
|
244
|
+
// Arrange
|
|
245
|
+
const {result} = renderHook(() =>
|
|
246
|
+
useTimeout(() => {}, 1000, {
|
|
247
|
+
schedulePolicy: SchedulePolicy.OnDemand,
|
|
248
|
+
}),
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
// Act
|
|
252
|
+
|
|
253
|
+
// Assert
|
|
254
|
+
expect(result.current.isSet).toBe(false);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("should not call action after timeoutMs if the timer hasn't been set", () => {
|
|
258
|
+
// Arrange
|
|
259
|
+
const action = jest.fn();
|
|
260
|
+
renderHook(() =>
|
|
261
|
+
useTimeout(action, 1000, {
|
|
262
|
+
schedulePolicy: SchedulePolicy.OnDemand,
|
|
263
|
+
}),
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
// Act
|
|
267
|
+
act(() => {
|
|
268
|
+
jest.advanceTimersByTime(1000);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// Assert
|
|
272
|
+
expect(action).not.toHaveBeenCalled();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it("should call action after timeoutMs if the timer has been set", () => {
|
|
276
|
+
// Arrange
|
|
277
|
+
const action = jest.fn();
|
|
278
|
+
const {result} = renderHook(() =>
|
|
279
|
+
useTimeout(action, 1000, {
|
|
280
|
+
schedulePolicy: SchedulePolicy.OnDemand,
|
|
281
|
+
}),
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
// Act
|
|
285
|
+
act(() => {
|
|
286
|
+
result.current.set();
|
|
287
|
+
jest.advanceTimersByTime(1000);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
// Assert
|
|
291
|
+
expect(action).toHaveBeenCalled();
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it("should reset the timer after calling set() again", () => {
|
|
295
|
+
// Arrange
|
|
296
|
+
const action = jest.fn();
|
|
297
|
+
const {result} = renderHook(() =>
|
|
298
|
+
useTimeout(action, 1000, {
|
|
299
|
+
schedulePolicy: SchedulePolicy.OnDemand,
|
|
300
|
+
}),
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
// Act
|
|
304
|
+
act(() => {
|
|
305
|
+
result.current.set();
|
|
306
|
+
jest.advanceTimersByTime(500);
|
|
307
|
+
result.current.set();
|
|
308
|
+
jest.advanceTimersByTime(500);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// Assert
|
|
312
|
+
expect(action).not.toHaveBeenCalled();
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it("should call the action after calling set() again", () => {
|
|
316
|
+
// Arrange
|
|
317
|
+
const action = jest.fn();
|
|
318
|
+
const {result} = renderHook(() =>
|
|
319
|
+
useTimeout(action, 1000, {
|
|
320
|
+
schedulePolicy: SchedulePolicy.OnDemand,
|
|
321
|
+
}),
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
// Act
|
|
325
|
+
act(() => {
|
|
326
|
+
result.current.set();
|
|
327
|
+
jest.advanceTimersByTime(500);
|
|
328
|
+
result.current.set();
|
|
329
|
+
jest.advanceTimersByTime(1000);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// Assert
|
|
333
|
+
expect(action).toHaveBeenCalled();
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import {useEffect, useState, useCallback, useRef} from "react";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
SchedulePolicy as SchedulePolicies,
|
|
6
|
+
ClearPolicy as ClearPolicies,
|
|
7
|
+
} from "../util/policies.js";
|
|
8
|
+
import type {ITimeout, ClearPolicy, Options} from "../util/types.js";
|
|
9
|
+
|
|
10
|
+
export function useTimeout(
|
|
11
|
+
action: () => mixed,
|
|
12
|
+
timeoutMs: number,
|
|
13
|
+
options?: Options,
|
|
14
|
+
): ITimeout {
|
|
15
|
+
const schedulePolicy =
|
|
16
|
+
options?.schedulePolicy ?? SchedulePolicies.Immediately;
|
|
17
|
+
const [isSet, setIsSet] = useState(
|
|
18
|
+
schedulePolicy === SchedulePolicies.Immediately,
|
|
19
|
+
);
|
|
20
|
+
const actionRef = useRef(action);
|
|
21
|
+
const mountedRef = useRef(false);
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
mountedRef.current = true;
|
|
25
|
+
return () => {
|
|
26
|
+
mountedRef.current = false;
|
|
27
|
+
};
|
|
28
|
+
}, []);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
actionRef.current = action;
|
|
32
|
+
}, [action]);
|
|
33
|
+
|
|
34
|
+
const clear = useCallback(
|
|
35
|
+
(policy?: ClearPolicy) => {
|
|
36
|
+
if ((policy ?? options?.clearPolicy) === ClearPolicies.Resolve) {
|
|
37
|
+
actionRef.current();
|
|
38
|
+
}
|
|
39
|
+
// This will cause the useEffect below to re-run
|
|
40
|
+
setIsSet(false);
|
|
41
|
+
},
|
|
42
|
+
[options?.clearPolicy],
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const set = useCallback(() => {
|
|
46
|
+
if (isSet) {
|
|
47
|
+
clear();
|
|
48
|
+
}
|
|
49
|
+
// This will cause the useEffect below to re-run
|
|
50
|
+
setIsSet(true);
|
|
51
|
+
}, [clear, isSet]);
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (isSet && mountedRef.current) {
|
|
55
|
+
const timeout = window.setTimeout(() => {
|
|
56
|
+
actionRef.current();
|
|
57
|
+
setIsSet(false);
|
|
58
|
+
}, timeoutMs);
|
|
59
|
+
|
|
60
|
+
return () => {
|
|
61
|
+
window.clearTimeout(timeout);
|
|
62
|
+
if (!mountedRef.current) {
|
|
63
|
+
clear();
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}, [clear, isSet, timeoutMs]);
|
|
68
|
+
|
|
69
|
+
return {isSet, set, clear};
|
|
70
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import {Meta, Story, Source, Canvas} from "@storybook/addon-docs";
|
|
2
|
+
|
|
3
|
+
import {Body, HeadingSmall} from "@khanacademy/wonder-blocks-typography";
|
|
4
|
+
import {View} from "@khanacademy/wonder-blocks-core";
|
|
5
|
+
import Button from "@khanacademy/wonder-blocks-button";
|
|
6
|
+
|
|
7
|
+
import {ClearPolicy, SchedulePolicy} from "../util/policies.js";
|
|
8
|
+
import {useTimeout} from "./use-timeout.js";
|
|
9
|
+
|
|
10
|
+
<Meta
|
|
11
|
+
title="Timing/useTimeout"
|
|
12
|
+
parameters={{
|
|
13
|
+
chromatic: {
|
|
14
|
+
disableSnapshot: true,
|
|
15
|
+
},
|
|
16
|
+
}}
|
|
17
|
+
/>
|
|
18
|
+
|
|
19
|
+
# `useTimeout`
|
|
20
|
+
|
|
21
|
+
`useTimeout` is a hook that provides a convenient API for setting and clearing
|
|
22
|
+
a timeout. It is defined as follows:
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
function useTimeout(
|
|
26
|
+
action: () => mixed,
|
|
27
|
+
timeoutMs: number,
|
|
28
|
+
options?: {|
|
|
29
|
+
schedulePolicy?: "schedule-immediately" | "schedule-on-demand",
|
|
30
|
+
clearPolicy?: "resolve-on-clear" | "cancel-on-clear",
|
|
31
|
+
|},
|
|
32
|
+
): ITimeout;
|
|
33
|
+
|
|
34
|
+
interface ITimeout {
|
|
35
|
+
get isSet(): boolean;
|
|
36
|
+
set(): void;
|
|
37
|
+
clear(policy?: ClearPolicy): void;
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
By default the timeout will be set immediately up creation. The `options` parameter can
|
|
42
|
+
be used to control when when the timeout is schedule and whether or not `action` should be
|
|
43
|
+
called when the timeout is cleared.
|
|
44
|
+
|
|
45
|
+
Notes:
|
|
46
|
+
|
|
47
|
+
- Because `clear` takes a param, it's import that you don't pass it directly to an event handler,
|
|
48
|
+
e.g. `<Button onClick={clear} />` will not work as expected.
|
|
49
|
+
- Calling `set` after the timeout has expired will restart the timeout.
|
|
50
|
+
- Updating the second paramter, `timeoutMs`, will also restart the timeout.
|
|
51
|
+
- When the component using this hooks is unmounted, the timeout will automatically be cleared.
|
|
52
|
+
- Calling `set` after the timeout is set but before it expires means that the timeout will be
|
|
53
|
+
reset and will call `action`, `timeoutMs` after the most recent call to `set` was made.
|
|
54
|
+
|
|
55
|
+
export const Immediately = () => {
|
|
56
|
+
const [callCount, setCallCount] = React.useState(0);
|
|
57
|
+
const callback = React.useCallback(() => {
|
|
58
|
+
setCallCount((callCount) => callCount + 1);
|
|
59
|
+
}, []);
|
|
60
|
+
const {isSet, set, clear} = useTimeout(callback, 1000);
|
|
61
|
+
return (
|
|
62
|
+
<View>
|
|
63
|
+
<View>isSet = {isSet.toString()}</View>
|
|
64
|
+
<View>callCount = {callCount}</View>
|
|
65
|
+
<View style={{flexDirection: "row"}}>
|
|
66
|
+
<Button onClick={set}>Set timeout</Button>
|
|
67
|
+
<Button onClick={clear}>Clear timeout</Button>
|
|
68
|
+
</View>
|
|
69
|
+
</View>
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
<Canvas>
|
|
74
|
+
<Story name="Immediately">
|
|
75
|
+
<Immediately />
|
|
76
|
+
</Story>
|
|
77
|
+
</Canvas>
|
|
78
|
+
|
|
79
|
+
```jsx
|
|
80
|
+
const Immediately = () => {
|
|
81
|
+
const [callCount, setCallCount] = React.useState(0);
|
|
82
|
+
const callback = React.useCallback(() => {
|
|
83
|
+
setCallCount((callCount) => callCount + 1);
|
|
84
|
+
}, []);
|
|
85
|
+
const {isSet, set, clear} = useTimeout(callback, 1000);
|
|
86
|
+
return (
|
|
87
|
+
<View>
|
|
88
|
+
<View>isSet = {isSet.toString()}</View>
|
|
89
|
+
<View>callCount = {callCount}</View>
|
|
90
|
+
<View style={{flexDirection: "row"}}>
|
|
91
|
+
<Button onClick={() => set()}>Set timeout</Button>
|
|
92
|
+
<Button onClick={() => clear()}>Clear timeout</Button>
|
|
93
|
+
</View>
|
|
94
|
+
</View>
|
|
95
|
+
);
|
|
96
|
+
};
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
export const OnDemandAndResolveOnClear = () => {
|
|
100
|
+
const [callCount, setCallCount] = React.useState(0);
|
|
101
|
+
const callback = React.useCallback(() => {
|
|
102
|
+
console.log("action called");
|
|
103
|
+
setCallCount((callCount) => callCount + 1);
|
|
104
|
+
}, []);
|
|
105
|
+
const {isSet, set, clear} = useTimeout(callback, 1000, {
|
|
106
|
+
clearPolicy: ClearPolicy.Resolve,
|
|
107
|
+
schedulePolicy: SchedulePolicy.OnDemand,
|
|
108
|
+
});
|
|
109
|
+
return (
|
|
110
|
+
<View>
|
|
111
|
+
<View>isSet = {isSet.toString()}</View>
|
|
112
|
+
<View>callCount = {callCount}</View>
|
|
113
|
+
<View style={{flexDirection: "row"}}>
|
|
114
|
+
<Button onClick={() => set()}>Set timeout</Button>
|
|
115
|
+
<Button onClick={() => clear()}>Clear timeout</Button>
|
|
116
|
+
</View>
|
|
117
|
+
</View>
|
|
118
|
+
);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
<Canvas>
|
|
122
|
+
<Story name="OnDemandAndResolveOnClear">
|
|
123
|
+
<OnDemandAndResolveOnClear />
|
|
124
|
+
</Story>
|
|
125
|
+
</Canvas>
|
|
126
|
+
|
|
127
|
+
```jsx
|
|
128
|
+
const OnDemandAndResolveOnClear = () => {
|
|
129
|
+
const [callCount, setCallCount] = React.useState(0);
|
|
130
|
+
const callback = React.useCallback(() => {
|
|
131
|
+
setCallCount((callCount) => callCount + 1);
|
|
132
|
+
}, []);
|
|
133
|
+
const {isSet, set, clear} = useTimeout(
|
|
134
|
+
callback,
|
|
135
|
+
1000,
|
|
136
|
+
{
|
|
137
|
+
clearPolicy: ClearPolicy.Resolve,
|
|
138
|
+
schedulePolicy: SchedulePolicy.OnDemand,
|
|
139
|
+
},
|
|
140
|
+
);
|
|
141
|
+
return (
|
|
142
|
+
<View>
|
|
143
|
+
<View>isSet = {isSet.toString()}</View>
|
|
144
|
+
<View>callCount = {callCount}</View>
|
|
145
|
+
<View style={{flexDirection: "row"}}>
|
|
146
|
+
<Button onClick={() => set()}>Set timeout</Button>
|
|
147
|
+
<Button onClick={() => clear()}>Clear timeout</Button>
|
|
148
|
+
</View>
|
|
149
|
+
</View>
|
|
150
|
+
);
|
|
151
|
+
};
|
|
152
|
+
```
|
package/src/index.js
CHANGED
|
@@ -9,14 +9,12 @@ describe("AnimationFrame", () => {
|
|
|
9
9
|
// Jest doesn't fake out the animation frame API, so we're going to do
|
|
10
10
|
// it here and map it to timeouts, that way we can use the fake timer
|
|
11
11
|
// API to test our animation frame things.
|
|
12
|
-
jest.spyOn(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"cancelAnimationFrame",
|
|
19
|
-
).mockImplementation((id, ...args) => clearTimeout(id));
|
|
12
|
+
jest.spyOn(global, "requestAnimationFrame").mockImplementation(
|
|
13
|
+
(fn, ...args) => setTimeout(fn, 0),
|
|
14
|
+
);
|
|
15
|
+
jest.spyOn(global, "cancelAnimationFrame").mockImplementation(
|
|
16
|
+
(id, ...args) => clearTimeout(id),
|
|
17
|
+
);
|
|
20
18
|
});
|
|
21
19
|
|
|
22
20
|
afterEach(() => {
|
|
@@ -7,6 +7,10 @@ describe("Interval", () => {
|
|
|
7
7
|
jest.useFakeTimers();
|
|
8
8
|
});
|
|
9
9
|
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
jest.restoreAllMocks();
|
|
12
|
+
});
|
|
13
|
+
|
|
10
14
|
describe("constructor", () => {
|
|
11
15
|
it("creates instance", () => {
|
|
12
16
|
// Arrange
|
|
@@ -44,13 +48,14 @@ describe("Interval", () => {
|
|
|
44
48
|
|
|
45
49
|
it("sets an interval when schedule policy is SchedulePolicy.Immediately", () => {
|
|
46
50
|
// Arrange
|
|
51
|
+
const intervalSpy = jest.spyOn(global, "setInterval");
|
|
47
52
|
|
|
48
53
|
// Act
|
|
49
54
|
// eslint-disable-next-line no-new
|
|
50
55
|
new Interval(() => {}, 1000, SchedulePolicy.Immediately);
|
|
51
56
|
|
|
52
57
|
// Assert
|
|
53
|
-
expect(
|
|
58
|
+
expect(intervalSpy).toHaveBeenCalledTimes(1);
|
|
54
59
|
});
|
|
55
60
|
});
|
|
56
61
|
|
|
@@ -99,13 +104,18 @@ describe("Interval", () => {
|
|
|
99
104
|
describe("#set", () => {
|
|
100
105
|
it("should call setInterval", () => {
|
|
101
106
|
// Arrange
|
|
102
|
-
const
|
|
107
|
+
const intervalSpy = jest.spyOn(global, "setInterval");
|
|
108
|
+
const interval = new Interval(
|
|
109
|
+
() => {},
|
|
110
|
+
500,
|
|
111
|
+
SchedulePolicy.OnDemand,
|
|
112
|
+
);
|
|
103
113
|
|
|
104
114
|
// Act
|
|
105
115
|
interval.set();
|
|
106
116
|
|
|
107
117
|
// Assert
|
|
108
|
-
expect(
|
|
118
|
+
expect(intervalSpy).toHaveBeenNthCalledWith(
|
|
109
119
|
1,
|
|
110
120
|
expect.any(Function),
|
|
111
121
|
500,
|
|
@@ -114,12 +124,15 @@ describe("Interval", () => {
|
|
|
114
124
|
|
|
115
125
|
it("should invoke setInterval to call the given action", () => {
|
|
116
126
|
// Arrange
|
|
127
|
+
const intervalSpy = jest.spyOn(global, "setInterval");
|
|
117
128
|
const action = jest.fn();
|
|
118
|
-
const interval = new Interval(
|
|
129
|
+
const interval = new Interval(
|
|
130
|
+
() => action(),
|
|
131
|
+
500,
|
|
132
|
+
SchedulePolicy.OnDemand,
|
|
133
|
+
);
|
|
119
134
|
interval.set();
|
|
120
|
-
|
|
121
|
-
// $FlowFixMe[prop-missing]
|
|
122
|
-
const scheduledAction = setInterval.mock.calls[0][0];
|
|
135
|
+
const scheduledAction = intervalSpy.mock.calls[0][0];
|
|
123
136
|
|
|
124
137
|
// Act
|
|
125
138
|
scheduledAction();
|
|
@@ -131,12 +144,16 @@ describe("Interval", () => {
|
|
|
131
144
|
it("should clear the active interval", () => {
|
|
132
145
|
// Arrange
|
|
133
146
|
const action = jest.fn();
|
|
134
|
-
const interval = new Interval(
|
|
147
|
+
const interval = new Interval(
|
|
148
|
+
() => action(),
|
|
149
|
+
500,
|
|
150
|
+
SchedulePolicy.OnDemand,
|
|
151
|
+
);
|
|
135
152
|
interval.set();
|
|
136
153
|
|
|
137
154
|
// Act
|
|
138
155
|
interval.set();
|
|
139
|
-
jest.
|
|
156
|
+
jest.advanceTimersByTime(501);
|
|
140
157
|
|
|
141
158
|
// Assert
|
|
142
159
|
expect(action).toHaveBeenCalledTimes(1);
|
|
@@ -149,7 +166,7 @@ describe("Interval", () => {
|
|
|
149
166
|
interval.set();
|
|
150
167
|
|
|
151
168
|
// Act
|
|
152
|
-
jest.
|
|
169
|
+
jest.advanceTimersByTime(1501);
|
|
153
170
|
|
|
154
171
|
// Assert
|
|
155
172
|
expect(action).toHaveBeenCalledTimes(3);
|
|
@@ -165,7 +182,7 @@ describe("Interval", () => {
|
|
|
165
182
|
|
|
166
183
|
// Act
|
|
167
184
|
interval.clear();
|
|
168
|
-
jest.
|
|
185
|
+
jest.advanceTimersByTime(501);
|
|
169
186
|
|
|
170
187
|
// Assert
|
|
171
188
|
expect(action).not.toHaveBeenCalled();
|
|
@@ -179,7 +196,7 @@ describe("Interval", () => {
|
|
|
179
196
|
|
|
180
197
|
// Act
|
|
181
198
|
interval.clear(ClearPolicy.Resolve);
|
|
182
|
-
jest.
|
|
199
|
+
jest.advanceTimersByTime(501);
|
|
183
200
|
|
|
184
201
|
// Assert
|
|
185
202
|
expect(action).toHaveBeenCalledTimes(1);
|
|
@@ -196,7 +213,7 @@ describe("Interval", () => {
|
|
|
196
213
|
|
|
197
214
|
// Act
|
|
198
215
|
interval.clear(ClearPolicy.Cancel);
|
|
199
|
-
jest.
|
|
216
|
+
jest.advanceTimersByTime(501);
|
|
200
217
|
|
|
201
218
|
// Assert
|
|
202
219
|
expect(action).not.toHaveBeenCalled();
|
|
@@ -209,7 +226,7 @@ describe("Interval", () => {
|
|
|
209
226
|
|
|
210
227
|
// Act
|
|
211
228
|
interval.clear(ClearPolicy.Resolve);
|
|
212
|
-
jest.
|
|
229
|
+
jest.advanceTimersByTime(501);
|
|
213
230
|
|
|
214
231
|
// Assert
|
|
215
232
|
expect(action).not.toHaveBeenCalled();
|
|
@@ -7,6 +7,10 @@ describe("Timeout", () => {
|
|
|
7
7
|
jest.useFakeTimers();
|
|
8
8
|
});
|
|
9
9
|
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
jest.restoreAllMocks();
|
|
12
|
+
});
|
|
13
|
+
|
|
10
14
|
describe("constructor", () => {
|
|
11
15
|
it("creates instance", () => {
|
|
12
16
|
// Arrange
|
|
@@ -44,13 +48,14 @@ describe("Timeout", () => {
|
|
|
44
48
|
|
|
45
49
|
it("sets a timeout when schedule policy is SchedulePolicy.Immediately", () => {
|
|
46
50
|
// Arrange
|
|
51
|
+
const timeoutSpy = jest.spyOn(global, "setTimeout");
|
|
47
52
|
|
|
48
53
|
// Act
|
|
49
54
|
// eslint-disable-next-line no-new
|
|
50
55
|
new Timeout(() => {}, 0, SchedulePolicy.Immediately);
|
|
51
56
|
|
|
52
57
|
// Assert
|
|
53
|
-
expect(
|
|
58
|
+
expect(timeoutSpy).toHaveBeenCalledTimes(1);
|
|
54
59
|
});
|
|
55
60
|
});
|
|
56
61
|
|
|
@@ -95,13 +100,14 @@ describe("Timeout", () => {
|
|
|
95
100
|
describe("#set", () => {
|
|
96
101
|
it("should call setTimeout", () => {
|
|
97
102
|
// Arrange
|
|
103
|
+
const timeoutSpy = jest.spyOn(global, "setTimeout");
|
|
98
104
|
const timeout = new Timeout(() => {}, 500, SchedulePolicy.OnDemand);
|
|
99
105
|
|
|
100
106
|
// Act
|
|
101
107
|
timeout.set();
|
|
102
108
|
|
|
103
109
|
// Assert
|
|
104
|
-
expect(
|
|
110
|
+
expect(timeoutSpy).toHaveBeenNthCalledWith(
|
|
105
111
|
1,
|
|
106
112
|
expect.any(Function),
|
|
107
113
|
500,
|
|
@@ -110,12 +116,15 @@ describe("Timeout", () => {
|
|
|
110
116
|
|
|
111
117
|
it("should invoke setTimeout to call the given action", () => {
|
|
112
118
|
// Arrange
|
|
119
|
+
const timeoutSpy = jest.spyOn(global, "setTimeout");
|
|
113
120
|
const action = jest.fn();
|
|
114
|
-
const timeout = new Timeout(
|
|
121
|
+
const timeout = new Timeout(
|
|
122
|
+
() => action(),
|
|
123
|
+
500,
|
|
124
|
+
SchedulePolicy.OnDemand,
|
|
125
|
+
);
|
|
115
126
|
timeout.set();
|
|
116
|
-
|
|
117
|
-
// $FlowFixMe[prop-missing]
|
|
118
|
-
const scheduledAction = setTimeout.mock.calls[0][0];
|
|
127
|
+
const scheduledAction = timeoutSpy.mock.calls[0][0];
|
|
119
128
|
|
|
120
129
|
// Act
|
|
121
130
|
scheduledAction();
|
|
@@ -140,16 +149,17 @@ describe("Timeout", () => {
|
|
|
140
149
|
|
|
141
150
|
it("should set the timeout again if it has already executed", () => {
|
|
142
151
|
// Arrange
|
|
152
|
+
const timeoutSpy = jest.spyOn(global, "setTimeout");
|
|
143
153
|
const action = jest.fn();
|
|
144
154
|
const timeout = new Timeout(action, 500, SchedulePolicy.OnDemand);
|
|
145
155
|
timeout.set();
|
|
146
|
-
jest.
|
|
156
|
+
jest.runAllTimers();
|
|
147
157
|
|
|
148
158
|
// Act
|
|
149
159
|
timeout.set();
|
|
150
160
|
|
|
151
161
|
// Assert
|
|
152
|
-
expect(
|
|
162
|
+
expect(timeoutSpy).toHaveBeenCalledTimes(2);
|
|
153
163
|
});
|
|
154
164
|
});
|
|
155
165
|
|