@khanacademy/wonder-blocks-timing 5.0.1 → 6.0.0

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.
@@ -1,234 +0,0 @@
1
- import Interval from "../interval";
2
- import {SchedulePolicy, ClearPolicy} from "../policies";
3
-
4
- describe("Interval", () => {
5
- beforeEach(() => {
6
- jest.useFakeTimers();
7
- });
8
-
9
- afterEach(() => {
10
- jest.restoreAllMocks();
11
- });
12
-
13
- describe("constructor", () => {
14
- it("creates instance", () => {
15
- // Arrange
16
-
17
- // Act
18
- const result = new Interval(() => {}, 1000);
19
-
20
- // Assert
21
- expect(result).toBeDefined();
22
- });
23
-
24
- it("throws if the action is not a function", () => {
25
- // Arrange
26
-
27
- // Act
28
- const underTest = () => new Interval(null as any, 1);
29
-
30
- // Assert
31
- expect(underTest).toThrowErrorMatchingInlineSnapshot(
32
- `"Action must be a function"`,
33
- );
34
- });
35
-
36
- it("throws if the period is less than 1", () => {
37
- // Arrange
38
-
39
- // Act
40
- const underTest = () => new Interval(() => {}, 0);
41
-
42
- // Assert
43
- expect(underTest).toThrowErrorMatchingInlineSnapshot(
44
- `"Interval period must be >= 1"`,
45
- );
46
- });
47
-
48
- it("sets an interval when schedule policy is SchedulePolicy.Immediately", () => {
49
- // Arrange
50
- const intervalSpy = jest.spyOn(global, "setInterval");
51
-
52
- // Act
53
- // eslint-disable-next-line no-new
54
- new Interval(() => {}, 1000, SchedulePolicy.Immediately);
55
-
56
- // Assert
57
- expect(intervalSpy).toHaveBeenCalledTimes(1);
58
- });
59
- });
60
-
61
- describe("isSet", () => {
62
- it("is false when the interval has not been set", () => {
63
- // Arrange
64
- const interval = new Interval(
65
- () => {},
66
- 1000,
67
- SchedulePolicy.OnDemand,
68
- );
69
-
70
- // Act
71
- const result = interval.isSet;
72
-
73
- // Assert
74
- expect(result).toBeFalsy();
75
- });
76
-
77
- it("is true when the interval is active", () => {
78
- // Arrange
79
- const interval = new Interval(() => {}, 1000);
80
- interval.set();
81
-
82
- // Act
83
- const result = interval.isSet;
84
-
85
- // Assert
86
- expect(result).toBeTruthy();
87
- });
88
-
89
- it("is false when the interval is cleared", () => {
90
- // Arrange
91
- const interval = new Interval(() => {}, 1000);
92
- interval.set();
93
- interval.clear();
94
-
95
- // Act
96
- const result = interval.isSet;
97
-
98
- // Assert
99
- expect(result).toBeFalsy();
100
- });
101
- });
102
-
103
- describe("#set", () => {
104
- it("should call setInterval", () => {
105
- // Arrange
106
- const intervalSpy = jest.spyOn(global, "setInterval");
107
- const interval = new Interval(
108
- () => {},
109
- 500,
110
- SchedulePolicy.OnDemand,
111
- );
112
-
113
- // Act
114
- interval.set();
115
-
116
- // Assert
117
- expect(intervalSpy).toHaveBeenNthCalledWith(
118
- 1,
119
- expect.any(Function),
120
- 500,
121
- );
122
- });
123
-
124
- it("should invoke setInterval to call the given action", () => {
125
- // Arrange
126
- const intervalSpy = jest.spyOn(global, "setInterval");
127
- const action = jest.fn();
128
- const interval = new Interval(
129
- () => action(),
130
- 500,
131
- SchedulePolicy.OnDemand,
132
- );
133
- interval.set();
134
- const scheduledAction = intervalSpy.mock.calls[0][0];
135
-
136
- // Act
137
- scheduledAction();
138
-
139
- // Assert
140
- expect(action).toHaveBeenCalledTimes(1);
141
- });
142
-
143
- it("should clear the active interval", () => {
144
- // Arrange
145
- const action = jest.fn();
146
- const interval = new Interval(
147
- () => action(),
148
- 500,
149
- SchedulePolicy.OnDemand,
150
- );
151
- interval.set();
152
-
153
- // Act
154
- interval.set();
155
- jest.advanceTimersByTime(501);
156
-
157
- // Assert
158
- expect(action).toHaveBeenCalledTimes(1);
159
- });
160
-
161
- it("should set an interval that stays active while not cleared", () => {
162
- // Arrange
163
- const action = jest.fn();
164
- const interval = new Interval(() => action(), 500);
165
- interval.set();
166
-
167
- // Act
168
- jest.advanceTimersByTime(1501);
169
-
170
- // Assert
171
- expect(action).toHaveBeenCalledTimes(3);
172
- });
173
- });
174
-
175
- describe("#clear", () => {
176
- it("should clear an active interval", () => {
177
- // Arrange
178
- const action = jest.fn();
179
- const interval = new Interval(action, 500);
180
- interval.set();
181
-
182
- // Act
183
- interval.clear();
184
- jest.advanceTimersByTime(501);
185
-
186
- // Assert
187
- expect(action).not.toHaveBeenCalled();
188
- });
189
-
190
- it("should invoke the action if clear policy is ClearPolicy.Resolve", () => {
191
- // Arrange
192
- const action = jest.fn();
193
- const interval = new Interval(action, 500);
194
- interval.set();
195
-
196
- // Act
197
- interval.clear(ClearPolicy.Resolve);
198
- jest.advanceTimersByTime(501);
199
-
200
- // Assert
201
- expect(action).toHaveBeenCalledTimes(1);
202
- });
203
-
204
- it("should not invoke the action if clear policy is ClearPolicy.Cancel", () => {
205
- // Arrange
206
- const action = jest.fn();
207
- const interval = new Interval(
208
- action,
209
- 500,
210
- SchedulePolicy.Immediately,
211
- );
212
-
213
- // Act
214
- interval.clear(ClearPolicy.Cancel);
215
- jest.advanceTimersByTime(501);
216
-
217
- // Assert
218
- expect(action).not.toHaveBeenCalled();
219
- });
220
-
221
- it("should not invoke the action if interval is inactive and clear policy is ClearPolicy.Resolve", () => {
222
- // Arrange
223
- const action = jest.fn();
224
- const interval = new Interval(action, 500, SchedulePolicy.OnDemand);
225
-
226
- // Act
227
- interval.clear(ClearPolicy.Resolve);
228
- jest.advanceTimersByTime(501);
229
-
230
- // Assert
231
- expect(action).not.toHaveBeenCalled();
232
- });
233
- });
234
- });
@@ -1,224 +0,0 @@
1
- import Timeout from "../timeout";
2
- import {SchedulePolicy, ClearPolicy} from "../policies";
3
-
4
- describe("Timeout", () => {
5
- beforeEach(() => {
6
- jest.useFakeTimers();
7
- });
8
-
9
- afterEach(() => {
10
- jest.restoreAllMocks();
11
- });
12
-
13
- describe("constructor", () => {
14
- it("creates instance", () => {
15
- // Arrange
16
-
17
- // Act
18
- const result = new Timeout(() => {}, 0);
19
-
20
- // Assert
21
- expect(result).toBeDefined();
22
- });
23
-
24
- it("throws if the action is not a function", () => {
25
- // Arrange
26
-
27
- // Act
28
- const underTest = () => new Timeout(null as any, 0);
29
-
30
- // Assert
31
- expect(underTest).toThrowErrorMatchingInlineSnapshot(
32
- `"Action must be a function"`,
33
- );
34
- });
35
-
36
- it("throws if the period is less than 0", () => {
37
- // Arrange
38
-
39
- // Act
40
- const underTest = () => new Timeout(() => {}, -1);
41
-
42
- // Assert
43
- expect(underTest).toThrowErrorMatchingInlineSnapshot(
44
- `"Timeout period must be >= 0"`,
45
- );
46
- });
47
-
48
- it("sets a timeout when schedule policy is SchedulePolicy.Immediately", () => {
49
- // Arrange
50
- const timeoutSpy = jest.spyOn(global, "setTimeout");
51
-
52
- // Act
53
- // eslint-disable-next-line no-new
54
- new Timeout(() => {}, 0, SchedulePolicy.Immediately);
55
-
56
- // Assert
57
- expect(timeoutSpy).toHaveBeenCalledTimes(1);
58
- });
59
- });
60
-
61
- describe("isSet", () => {
62
- it("is false when the timeout has not been set", () => {
63
- // Arrange
64
- const timeout = new Timeout(() => {}, 0, SchedulePolicy.OnDemand);
65
-
66
- // Act
67
- const result = timeout.isSet;
68
-
69
- // Assert
70
- expect(result).toBeFalsy();
71
- });
72
-
73
- it("is true when the timeout is pending", () => {
74
- // Arrange
75
- const timeout = new Timeout(() => {}, 0);
76
- timeout.set();
77
-
78
- // Act
79
- const result = timeout.isSet;
80
-
81
- // Assert
82
- expect(result).toBeTruthy();
83
- });
84
-
85
- it("is false when the timeout has executed", () => {
86
- // Arrange
87
- const timeout = new Timeout(() => {}, 0);
88
- timeout.set();
89
- jest.runOnlyPendingTimers();
90
-
91
- // Act
92
- const result = timeout.isSet;
93
-
94
- // Assert
95
- expect(result).toBeFalsy();
96
- });
97
- });
98
-
99
- describe("#set", () => {
100
- it("should call setTimeout", () => {
101
- // Arrange
102
- const timeoutSpy = jest.spyOn(global, "setTimeout");
103
- const timeout = new Timeout(() => {}, 500, SchedulePolicy.OnDemand);
104
-
105
- // Act
106
- timeout.set();
107
-
108
- // Assert
109
- expect(timeoutSpy).toHaveBeenNthCalledWith(
110
- 1,
111
- expect.any(Function),
112
- 500,
113
- );
114
- });
115
-
116
- it("should invoke setTimeout to call the given action", () => {
117
- // Arrange
118
- const timeoutSpy = jest.spyOn(global, "setTimeout");
119
- const action = jest.fn();
120
- const timeout = new Timeout(
121
- () => action(),
122
- 500,
123
- SchedulePolicy.OnDemand,
124
- );
125
- timeout.set();
126
- const scheduledAction = timeoutSpy.mock.calls[0][0];
127
-
128
- // Act
129
- scheduledAction();
130
-
131
- // Assert
132
- expect(action).toHaveBeenCalledTimes(1);
133
- });
134
-
135
- it("should clear any pending timeout", () => {
136
- // Arrange
137
- const action = jest.fn();
138
- const timeout = new Timeout(() => action(), 500);
139
- timeout.set();
140
-
141
- // Act
142
- timeout.set();
143
- jest.runOnlyPendingTimers();
144
-
145
- // Assert
146
- expect(action).toHaveBeenCalledTimes(1);
147
- });
148
-
149
- it("should set the timeout again if it has already executed", () => {
150
- // Arrange
151
- const timeoutSpy = jest.spyOn(global, "setTimeout");
152
- const action = jest.fn();
153
- const timeout = new Timeout(action, 500, SchedulePolicy.OnDemand);
154
- timeout.set();
155
- jest.runAllTimers();
156
-
157
- // Act
158
- timeout.set();
159
-
160
- // Assert
161
- expect(timeoutSpy).toHaveBeenCalledTimes(2);
162
- });
163
- });
164
-
165
- describe("#clear", () => {
166
- it("should clear a pending timout", () => {
167
- // Arrange
168
- const action = jest.fn();
169
- const timeout = new Timeout(action, 500);
170
- timeout.set();
171
-
172
- // Act
173
- timeout.clear();
174
- jest.runOnlyPendingTimers();
175
-
176
- // Assert
177
- expect(action).not.toHaveBeenCalled();
178
- });
179
-
180
- it("should invoke the action if clear policy is ClearPolicy.Resolve", () => {
181
- // Arrange
182
- const action = jest.fn();
183
- const timeout = new Timeout(action, 500);
184
- timeout.set();
185
-
186
- // Act
187
- timeout.clear(ClearPolicy.Resolve);
188
- jest.runOnlyPendingTimers();
189
-
190
- // Assert
191
- expect(action).toHaveBeenCalledTimes(1);
192
- });
193
-
194
- it("should not invoke the action if clear policy is ClearPolicy.Cancel", () => {
195
- // Arrange
196
- const action = jest.fn();
197
- const timeout = new Timeout(
198
- action,
199
- 500,
200
- SchedulePolicy.Immediately,
201
- );
202
-
203
- // Act
204
- timeout.clear(ClearPolicy.Cancel);
205
- jest.runOnlyPendingTimers();
206
-
207
- // Assert
208
- expect(action).not.toHaveBeenCalled();
209
- });
210
-
211
- it("should not invoke the action if timeout is not pending", () => {
212
- // Arrange
213
- const action = jest.fn();
214
- const timeout = new Timeout(action, 500, SchedulePolicy.OnDemand);
215
-
216
- // Act
217
- timeout.clear(ClearPolicy.Resolve);
218
- jest.runOnlyPendingTimers();
219
-
220
- // Assert
221
- expect(action).not.toHaveBeenCalled();
222
- });
223
- });
224
- });
@@ -1,89 +0,0 @@
1
- import Timeout from "./timeout";
2
- import Interval from "./interval";
3
- import AnimationFrame from "./animation-frame";
4
-
5
- import type {
6
- IAnimationFrame,
7
- IInterval,
8
- ITimeout,
9
- IScheduleActions,
10
- Options,
11
- } from "./types";
12
-
13
- /**
14
- * Implements the `IScheduleActions` API to provide timeout, interval, and
15
- * animation frame support. This is not intended for direct use, but instead
16
- * is to be used solely by the `ActionSchedulerProvider` to provide an
17
- * `IScheduleActions` instance.
18
- */
19
- export default class ActionScheduler implements IScheduleActions {
20
- _disabled = false;
21
- _registeredActions: Array<() => void> = [];
22
- static readonly NoopAction: ITimeout & IAnimationFrame & IInterval = {
23
- set: () => {},
24
- get isSet() {
25
- return false;
26
- },
27
- clear: () => {},
28
- };
29
-
30
- timeout(
31
- action: () => unknown,
32
- period: number,
33
- options?: Options,
34
- ): ITimeout {
35
- if (this._disabled) {
36
- return ActionScheduler.NoopAction;
37
- }
38
- const timeout = new Timeout(action, period, options?.schedulePolicy);
39
- this._registeredActions.push(() => timeout.clear(options?.clearPolicy));
40
- return timeout;
41
- }
42
-
43
- interval(
44
- action: () => unknown,
45
- period: number,
46
- options?: Options,
47
- ): IInterval {
48
- if (this._disabled) {
49
- return ActionScheduler.NoopAction;
50
- }
51
- const interval = new Interval(action, period, options?.schedulePolicy);
52
- this._registeredActions.push(() =>
53
- interval.clear(options?.clearPolicy),
54
- );
55
- return interval;
56
- }
57
-
58
- animationFrame(
59
- action: (arg1: DOMHighResTimeStamp) => void,
60
- options?: Options,
61
- ): IAnimationFrame {
62
- if (this._disabled) {
63
- return ActionScheduler.NoopAction;
64
- }
65
- const animationFrame = new AnimationFrame(
66
- action,
67
- options?.schedulePolicy,
68
- );
69
- this._registeredActions.push(() =>
70
- animationFrame.clear(options?.clearPolicy),
71
- );
72
- return animationFrame;
73
- }
74
-
75
- clearAll(): void {
76
- const registered = [...this._registeredActions];
77
- this._registeredActions = [];
78
- registered.forEach((clearFn) => clearFn());
79
- }
80
-
81
- /**
82
- * Prevents this scheduler from creating any additional actions.
83
- * This also clears any pending actions.
84
- */
85
- disable(): void {
86
- this._disabled = true;
87
- this.clearAll();
88
- }
89
- }
@@ -1,103 +0,0 @@
1
- import {SchedulePolicy, ClearPolicy} from "./policies";
2
-
3
- import type {IAnimationFrame} from "./types";
4
-
5
- /**
6
- * Encapsulates everything associated with calling requestAnimationFrame/
7
- * cancelAnimationFrame, and managing the lifecycle of that request, including
8
- * the ability to resolve or cancel a pending request action.
9
- *
10
- * @export
11
- * @class AnimationFrame
12
- * @implements {IAnimationFrame}
13
- */
14
- export default class AnimationFrame implements IAnimationFrame {
15
- _animationFrameId: number | null | undefined;
16
- _action: (time: DOMHighResTimeStamp) => unknown;
17
-
18
- /**
19
- * Creates an animation frame request that will invoke the given action.
20
- * The request is not made until set is called.
21
- *
22
- * @param action The action to be invoked.
23
- * @param [schedulePolicy] When SchedulePolicy.Immediately,
24
- * the interval is set immediately on instantiation; otherwise, `set` must be
25
- * called to set the interval.
26
- * Defaults to `SchedulePolicy.Immediately`.
27
- * @memberof AnimationFrame
28
- */
29
- constructor(
30
- action: (time: DOMHighResTimeStamp) => unknown,
31
- schedulePolicy: SchedulePolicy = SchedulePolicy.Immediately,
32
- ) {
33
- if (typeof action !== "function") {
34
- throw new Error("Action must be a function");
35
- }
36
-
37
- this._action = action;
38
-
39
- if (schedulePolicy === SchedulePolicy.Immediately) {
40
- this.set();
41
- }
42
- }
43
-
44
- /**
45
- * Determine if the request is pending or not.
46
- *
47
- * @returns {boolean} true if the request is pending, otherwise
48
- * false.
49
- * @memberof AnimationFrame
50
- */
51
- get isSet(): boolean {
52
- return this._animationFrameId != null;
53
- }
54
-
55
- /**
56
- * Make the animation frame request.
57
- *
58
- * If the request is pending, this cancels that pending request and
59
- * makes the request afresh. If the request is not pending, this
60
- * makes a new request.
61
- *
62
- * @memberof AnimationFrame
63
- */
64
- set(): void {
65
- if (this.isSet) {
66
- this.clear(ClearPolicy.Cancel);
67
- }
68
- this._animationFrameId = requestAnimationFrame((time) =>
69
- this.clear(ClearPolicy.Resolve, time),
70
- );
71
- }
72
-
73
- /**
74
- * Clear the pending request.
75
- *
76
- * If the request is pending, this cancels that pending request without
77
- * invoking the action. If no request is pending, this does nothing.
78
- *
79
- * @param [policy] When ClearPolicy.Resolve, if the request
80
- * was set when called, the request action is invoked after cancelling
81
- * the request; otherwise, the pending action is cancelled.
82
- * Defaults to `ClearPolicy.Cancel`.
83
- * @param [time] Timestamp to pass to the action when
84
- * ClearPolicy.Resolve is specified. Ignored when ClearPolicy.Cancel is
85
- * specified.
86
- *
87
- * @memberof AnimationFrame
88
- */
89
- clear(
90
- policy: ClearPolicy = ClearPolicy.Cancel,
91
- time?: DOMHighResTimeStamp,
92
- ): void {
93
- const animationFrameId = this._animationFrameId;
94
- this._animationFrameId = null;
95
- if (animationFrameId == null) {
96
- return;
97
- }
98
- cancelAnimationFrame(animationFrameId);
99
- if (policy === ClearPolicy.Resolve) {
100
- this._action(time || performance.now());
101
- }
102
- }
103
- }