@lookit/record 4.0.0 → 5.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.
@@ -0,0 +1,129 @@
1
+ import { promiseWithTimeout } from "./utils";
2
+
3
+ let consoleLogSpy: jest.SpyInstance<
4
+ void,
5
+ [message?: unknown, ...optionalParams: unknown[]],
6
+ unknown
7
+ >;
8
+ let consoleWarnSpy: jest.SpyInstance<
9
+ void,
10
+ [message?: unknown, ...optionalParams: unknown[]],
11
+ unknown
12
+ >;
13
+ let consoleErrorSpy: jest.SpyInstance<
14
+ void,
15
+ [message?: unknown, ...optionalParams: unknown[]],
16
+ unknown
17
+ >;
18
+
19
+ beforeEach(() => {
20
+ jest.useRealTimers();
21
+ // Hide the console output during tests. Tests can still assert on these spies to check console calls.
22
+ consoleLogSpy = jest.spyOn(console, "log").mockImplementation(() => {});
23
+ consoleWarnSpy = jest.spyOn(console, "warn").mockImplementation(() => {});
24
+ consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(() => {});
25
+ });
26
+
27
+ afterEach(() => {
28
+ jest.restoreAllMocks();
29
+ jest.clearAllMocks();
30
+
31
+ consoleLogSpy.mockRestore();
32
+ consoleWarnSpy.mockRestore();
33
+ consoleErrorSpy.mockRestore();
34
+ });
35
+
36
+ test("Promise with timeout: promise wins", async () => {
37
+ // clears the timeout handle
38
+ const promise = Promise.resolve("url");
39
+ const timeout_handler = jest.fn();
40
+
41
+ const promiseRace = promiseWithTimeout(
42
+ promise,
43
+ "promiseId",
44
+ 10,
45
+ timeout_handler,
46
+ );
47
+
48
+ // returns the promise race
49
+ expect(promiseRace).toBeInstanceOf(Promise);
50
+
51
+ await promiseRace;
52
+
53
+ expect(promise).resolves;
54
+ expect(timeout_handler).not.toHaveBeenCalled();
55
+ expect(consoleLogSpy).toHaveBeenCalledWith("Upload for promiseId completed.");
56
+ });
57
+
58
+ test("Promise with timeout: promise wins and no timeout callback", async () => {
59
+ // clears the timeout handle
60
+ const promise = Promise.resolve("url");
61
+
62
+ const promiseRace = promiseWithTimeout(promise, "promiseId", 10);
63
+
64
+ // returns the promise race
65
+ expect(promiseRace).toBeInstanceOf(Promise);
66
+
67
+ await promiseRace;
68
+
69
+ expect(promise).resolves;
70
+ expect(consoleLogSpy).toHaveBeenCalledWith("Upload for promiseId completed.");
71
+ });
72
+
73
+ test("Promise with timeout: timeout wins", async () => {
74
+ jest.useFakeTimers();
75
+
76
+ // promise we're waiting for never resolves
77
+ const promise = new Promise<void>(() => {});
78
+
79
+ const timeout_handler = jest.fn();
80
+
81
+ const promiseRace = promiseWithTimeout(
82
+ promise,
83
+ "promiseId",
84
+ 10,
85
+ timeout_handler,
86
+ );
87
+ // attach a catch that swallows the error, to prevent a Node unhandled rejection error
88
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
89
+ promiseRace.catch((err) => {});
90
+
91
+ // returns the promise race
92
+ expect(promiseRace).toBeInstanceOf(Promise);
93
+
94
+ // advance fake timers so that the timeout triggers
95
+ await jest.advanceTimersByTimeAsync(11);
96
+
97
+ // timeout wins
98
+ await expect(promiseRace).resolves.toBe("timeout");
99
+
100
+ // flush microtask queue (where the timeout handler and promise rejection occur)
101
+ await Promise.resolve();
102
+
103
+ // calls the timeout handler
104
+ expect(timeout_handler).toHaveBeenCalledTimes(1);
105
+ });
106
+
107
+ test("Promise with timeout: timeout wins without callback", async () => {
108
+ jest.useFakeTimers();
109
+
110
+ // promise we're waiting for never resolves
111
+ const promise = new Promise<void>(() => {});
112
+
113
+ const promiseRace = promiseWithTimeout(promise, "promiseId", 10);
114
+ // attach a catch that swallows the error, to prevent a Node unhandled rejection error
115
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
116
+ promiseRace.catch((err) => {});
117
+
118
+ // returns the promise race
119
+ expect(promiseRace).toBeInstanceOf(Promise);
120
+
121
+ // advance fake timers so that the timeout triggers
122
+ await jest.advanceTimersByTimeAsync(11);
123
+
124
+ // flush microtask queue (where the timeout handler and promise rejection occur)
125
+ await Promise.resolve();
126
+
127
+ // timeout wins
128
+ await expect(promiseRace).resolves.toBe("timeout");
129
+ });
package/src/utils.ts ADDED
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Helper function for setting up a timeout on a promise.
3
+ *
4
+ * @param promise - Promise to be raced with the timeout.
5
+ * @param promiseId - String with an identifier for the promise being raced
6
+ * against the timeout.
7
+ * @param timeoutMs - Timeout duration, in milliseconds.
8
+ * @param onTimeoutCleanup - Callback function that should fire if the timeout
9
+ * duration is reached.
10
+ * @returns The first promise that is resolved, either the promise that we're
11
+ * actually awaiting (awaitPromise) or the timeout.
12
+ */
13
+ export const promiseWithTimeout = <T>(
14
+ promise: Promise<T>,
15
+ promiseId: string,
16
+ timeoutMs: number,
17
+ onTimeoutCleanup?: () => void,
18
+ ): Promise<T | string> => {
19
+ let timeoutHandle: ReturnType<typeof setTimeout>;
20
+
21
+ const timeout = new Promise<T | string>((resolve) => {
22
+ timeoutHandle = setTimeout(() => {
23
+ onTimeoutCleanup?.();
24
+ resolve("timeout");
25
+ }, timeoutMs);
26
+ });
27
+
28
+ // Return *immediately* a race promise.
29
+ // No async/await — so this function synchronously produces the final promise.
30
+ return Promise.race([promise, timeout]).then(
31
+ (value) => {
32
+ if (value == "timeout") {
33
+ console.log(`Upload for ${promiseId} timed out.`);
34
+ } else {
35
+ console.log(`Upload for ${promiseId} completed.`);
36
+ clearTimeout(timeoutHandle);
37
+ }
38
+ return value;
39
+ },
40
+ (err) => {
41
+ clearTimeout(timeoutHandle);
42
+ throw err;
43
+ },
44
+ );
45
+ };