1 | import {
|
2 | RequestIdleCallback as IdleCallback,
|
3 | WindowWithRequestIdleCallback,
|
4 | } from '@shopify/async';
|
5 |
|
6 | export default class RequestIdleCallback {
|
7 | private isUsingMockIdleCallback = false;
|
8 | private isMockingUnsupported = false;
|
9 | private queued: {
|
10 | [key: string]: IdleCallback;
|
11 | } = {};
|
12 |
|
13 | private originalRequestIdleCallback: any;
|
14 | private originalCancelIdleCallback: any;
|
15 | private currentIdleCallback = 0;
|
16 |
|
17 | mock() {
|
18 | if (this.isUsingMockIdleCallback) {
|
19 | throw new Error(
|
20 | 'requestIdleCallback is already mocked, but you tried to mock it again.',
|
21 | );
|
22 | }
|
23 |
|
24 | this.isUsingMockIdleCallback = true;
|
25 |
|
26 | const windowWithIdle: WindowWithRequestIdleCallback = window as any;
|
27 |
|
28 | this.originalRequestIdleCallback = windowWithIdle.requestIdleCallback;
|
29 | windowWithIdle.requestIdleCallback = this.requestIdleCallback;
|
30 |
|
31 | this.originalCancelIdleCallback = windowWithIdle.cancelIdleCallback;
|
32 | windowWithIdle.cancelIdleCallback = this.cancelIdleCallback;
|
33 | }
|
34 |
|
35 | mockAsUnsupported() {
|
36 | if (this.isUsingMockIdleCallback) {
|
37 | throw new Error(
|
38 | 'requestIdleCallback is already mocked, but you tried to mock it again.',
|
39 | );
|
40 | }
|
41 |
|
42 | this.isUsingMockIdleCallback = true;
|
43 | this.isMockingUnsupported = true;
|
44 |
|
45 | const windowWithIdle: WindowWithRequestIdleCallback = window as any;
|
46 |
|
47 | this.originalRequestIdleCallback = windowWithIdle.requestIdleCallback;
|
48 | delete windowWithIdle.requestIdleCallback;
|
49 |
|
50 | this.originalCancelIdleCallback = windowWithIdle.cancelIdleCallback;
|
51 | delete windowWithIdle.cancelIdleCallback;
|
52 | }
|
53 |
|
54 | restore() {
|
55 | if (!this.isUsingMockIdleCallback) {
|
56 | throw new Error(
|
57 | 'requestIdleCallback is already real, but you tried to restore it again.',
|
58 | );
|
59 | }
|
60 |
|
61 | if (Object.keys(this.queued).length > 0) {
|
62 |
|
63 | console.warn(
|
64 | 'You are restoring requestIdleCallback, but some idle callbacks have not been run. You can run requestIdleCallback.cancelIdleCallback() to clear them all and avoid this warning.',
|
65 | );
|
66 |
|
67 | this.cancelIdleCallbacks();
|
68 | }
|
69 |
|
70 | this.isUsingMockIdleCallback = false;
|
71 | this.isMockingUnsupported = false;
|
72 |
|
73 | if (this.originalRequestIdleCallback) {
|
74 | (window as any).requestIdleCallback = this.originalRequestIdleCallback;
|
75 | } else {
|
76 | delete (window as any).requestIdleCallback;
|
77 | }
|
78 |
|
79 | if (this.originalCancelIdleCallback) {
|
80 | (window as any).cancelIdleCallback = this.originalCancelIdleCallback;
|
81 | } else {
|
82 | delete (window as any).cancelIdleCallback;
|
83 | }
|
84 | }
|
85 |
|
86 | isMocked() {
|
87 | return this.isUsingMockIdleCallback;
|
88 | }
|
89 |
|
90 | runIdleCallbacks(timeRemaining = Infinity, didTimeout = false) {
|
91 | this.ensureIdleCallbackIsMock();
|
92 |
|
93 |
|
94 |
|
95 | Object.keys(this.queued).forEach((frame: any) => {
|
96 | const callback = this.queued[frame];
|
97 | delete this.queued[frame];
|
98 | callback({timeRemaining: () => timeRemaining, didTimeout});
|
99 | });
|
100 | }
|
101 |
|
102 | cancelIdleCallbacks() {
|
103 | this.ensureIdleCallbackIsMock();
|
104 |
|
105 | for (const id of Object.keys(this.queued)) {
|
106 | this.cancelIdleCallback(id);
|
107 | }
|
108 | }
|
109 |
|
110 | private requestIdleCallback = (
|
111 | callback: IdleCallback,
|
112 | ): ReturnType<WindowWithRequestIdleCallback['requestIdleCallback']> => {
|
113 | this.currentIdleCallback += 1;
|
114 | this.queued[this.currentIdleCallback] = callback;
|
115 | return this.currentIdleCallback;
|
116 | };
|
117 |
|
118 | private cancelIdleCallback = (
|
119 | callback: ReturnType<WindowWithRequestIdleCallback['requestIdleCallback']>,
|
120 | ) => {
|
121 | delete this.queued[callback];
|
122 | };
|
123 |
|
124 | private ensureIdleCallbackIsMock() {
|
125 | if (!this.isUsingMockIdleCallback) {
|
126 | throw new Error(
|
127 | 'You must call requestIdleCallback.mock() before interacting with the mock request- or cancel- IdleCallback methods.',
|
128 | );
|
129 | }
|
130 |
|
131 | if (this.isMockingUnsupported) {
|
132 | throw new Error(
|
133 | 'You have mocked requestIdleCallback as unsupported. Call requestIdleCallback.restore(), then requestIdleCallback.mock() if you want to simulate idle callbacks.',
|
134 | );
|
135 | }
|
136 | }
|
137 | }
|