UNPKG

4.13 kBPlain TextView Raw
1import {
2 RequestIdleCallback as IdleCallback,
3 WindowWithRequestIdleCallback,
4} from '@shopify/async';
5
6export 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 // eslint-disable-next-line no-console
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 // We need to do it this way so that frames that queue other frames
94 // don't get removed
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}