@lookit/record 4.1.0 → 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.
- package/README.md +158 -15
- package/dist/index.browser.js +212 -43
- package/dist/index.browser.js.map +1 -1
- package/dist/index.browser.min.js +16 -16
- package/dist/index.browser.min.js.map +1 -1
- package/dist/index.cjs +211 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +113 -9
- package/dist/index.js +211 -42
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/consentVideo.spec.ts +9 -0
- package/src/consentVideo.ts +24 -2
- package/src/errors.ts +28 -0
- package/src/index.spec.ts +497 -54
- package/src/recorder.spec.ts +669 -109
- package/src/recorder.ts +184 -36
- package/src/start.ts +45 -5
- package/src/stop.ts +29 -12
- package/src/trial.ts +42 -16
- package/src/types.ts +21 -0
- package/src/utils.spec.ts +129 -0
- package/src/utils.ts +45 -0
package/src/index.spec.ts
CHANGED
|
@@ -4,11 +4,28 @@ import { initJsPsych, PluginInfo, TrialType } from "jspsych";
|
|
|
4
4
|
import { ExistingRecordingError, NoSessionRecordingError } from "./errors";
|
|
5
5
|
import Rec from "./index";
|
|
6
6
|
import Recorder from "./recorder";
|
|
7
|
+
import type { StopResult } from "./types";
|
|
7
8
|
|
|
8
9
|
declare const window: LookitWindow;
|
|
9
10
|
|
|
10
11
|
let global_display_el: HTMLDivElement;
|
|
11
12
|
|
|
13
|
+
let consoleLogSpy: jest.SpyInstance<
|
|
14
|
+
void,
|
|
15
|
+
[message?: unknown, ...optionalParams: unknown[]],
|
|
16
|
+
unknown
|
|
17
|
+
>;
|
|
18
|
+
let consoleWarnSpy: jest.SpyInstance<
|
|
19
|
+
void,
|
|
20
|
+
[message?: unknown, ...optionalParams: unknown[]],
|
|
21
|
+
unknown
|
|
22
|
+
>;
|
|
23
|
+
let consoleErrorSpy: jest.SpyInstance<
|
|
24
|
+
void,
|
|
25
|
+
[message?: unknown, ...optionalParams: unknown[]],
|
|
26
|
+
unknown
|
|
27
|
+
>;
|
|
28
|
+
|
|
12
29
|
jest.mock("./recorder");
|
|
13
30
|
jest.mock("@lookit/data");
|
|
14
31
|
jest.mock("jspsych", () => ({
|
|
@@ -41,23 +58,34 @@ const setCHSValue = (chs = {}) => {
|
|
|
41
58
|
|
|
42
59
|
beforeEach(() => {
|
|
43
60
|
setCHSValue();
|
|
61
|
+
// Hide the console output during tests. Tests can still assert on these spies to check console calls.
|
|
62
|
+
consoleLogSpy = jest.spyOn(console, "log").mockImplementation(() => {});
|
|
63
|
+
consoleWarnSpy = jest.spyOn(console, "warn").mockImplementation(() => {});
|
|
64
|
+
consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(() => {});
|
|
44
65
|
});
|
|
45
66
|
|
|
46
67
|
afterEach(() => {
|
|
47
68
|
jest.restoreAllMocks();
|
|
48
69
|
jest.clearAllMocks();
|
|
70
|
+
|
|
71
|
+
consoleLogSpy.mockRestore();
|
|
72
|
+
consoleWarnSpy.mockRestore();
|
|
73
|
+
consoleErrorSpy.mockRestore();
|
|
49
74
|
});
|
|
50
75
|
|
|
51
|
-
test("Trial recording", () => {
|
|
76
|
+
test("Trial recording", async () => {
|
|
52
77
|
const mockRecStart = jest.spyOn(Recorder.prototype, "start");
|
|
53
|
-
const mockRecStop = jest.spyOn(Recorder.prototype, "stop")
|
|
78
|
+
const mockRecStop = jest.spyOn(Recorder.prototype, "stop").mockReturnValue({
|
|
79
|
+
stopped: Promise.resolve("url"),
|
|
80
|
+
uploaded: Promise.resolve(),
|
|
81
|
+
});
|
|
54
82
|
const jsPsych = initJsPsych();
|
|
55
83
|
const trialRec = new Rec.TrialRecordExtension(jsPsych);
|
|
56
84
|
const getCurrentPluginNameSpy = jest.spyOn(trialRec, "getCurrentPluginName");
|
|
57
85
|
|
|
58
86
|
trialRec.on_start();
|
|
59
87
|
trialRec.on_load();
|
|
60
|
-
trialRec.on_finish();
|
|
88
|
+
await trialRec.on_finish();
|
|
61
89
|
|
|
62
90
|
expect(Recorder).toHaveBeenCalledTimes(1);
|
|
63
91
|
expect(mockRecStart).toHaveBeenCalledTimes(1);
|
|
@@ -134,10 +162,18 @@ test("Trial recording start with wait_for_upload_message parameter", async () =>
|
|
|
134
162
|
|
|
135
163
|
test("Trial recording stop/finish with default uploading msg in English", async () => {
|
|
136
164
|
// control the recorder stop promise so that we can inspect the display before it resolves
|
|
137
|
-
let resolveStop!: () => void;
|
|
138
|
-
|
|
165
|
+
let resolveStop!: (value: string) => void;
|
|
166
|
+
let resolveUpload!: () => void;
|
|
167
|
+
const stopPromise = new Promise<string>((res) => {
|
|
168
|
+
resolveStop = res;
|
|
169
|
+
});
|
|
170
|
+
const uploadPromise = new Promise<void>((res) => {
|
|
171
|
+
resolveUpload = res;
|
|
172
|
+
});
|
|
139
173
|
|
|
140
|
-
jest
|
|
174
|
+
jest
|
|
175
|
+
.spyOn(Recorder.prototype, "stop")
|
|
176
|
+
.mockReturnValue({ stopped: stopPromise, uploaded: uploadPromise });
|
|
141
177
|
|
|
142
178
|
const jsPsych = initJsPsych();
|
|
143
179
|
const trialRec = new Rec.TrialRecordExtension(jsPsych);
|
|
@@ -160,14 +196,17 @@ test("Trial recording stop/finish with default uploading msg in English", async
|
|
|
160
196
|
} as TrialType<PluginInfo>),
|
|
161
197
|
);
|
|
162
198
|
expect(global_display_el.innerHTML).toBe(
|
|
163
|
-
"
|
|
199
|
+
`<div id="lookit-uploading-video-msg-container">
|
|
200
|
+
<div>uploading video, please wait...</div>
|
|
201
|
+
<div id="lookit-loader-container"><div class="loader"></div></div></div>`,
|
|
164
202
|
);
|
|
165
|
-
//expect(Recorder.prototype.stop).toHaveBeenCalledTimes(1);
|
|
166
203
|
|
|
167
204
|
// resolve the stop promise
|
|
168
|
-
resolveStop();
|
|
205
|
+
resolveStop("url");
|
|
169
206
|
await stopPromise;
|
|
170
|
-
|
|
207
|
+
// resolve the upload promise
|
|
208
|
+
resolveUpload();
|
|
209
|
+
await uploadPromise;
|
|
171
210
|
|
|
172
211
|
// check the display cleanup
|
|
173
212
|
expect(global_display_el.innerHTML).toBe("");
|
|
@@ -175,10 +214,18 @@ test("Trial recording stop/finish with default uploading msg in English", async
|
|
|
175
214
|
|
|
176
215
|
test("Trial recording stop/finish with different locale should display default uploading msg in specified language", async () => {
|
|
177
216
|
// control the recorder stop promise so that we can inspect the display before it resolves
|
|
178
|
-
let resolveStop!: () => void;
|
|
179
|
-
|
|
217
|
+
let resolveStop!: (value: string) => void;
|
|
218
|
+
let resolveUpload!: () => void;
|
|
219
|
+
const stopPromise = new Promise<string>((res) => {
|
|
220
|
+
resolveStop = res;
|
|
221
|
+
});
|
|
222
|
+
const uploadPromise = new Promise<void>((res) => {
|
|
223
|
+
resolveUpload = res;
|
|
224
|
+
});
|
|
180
225
|
|
|
181
|
-
jest
|
|
226
|
+
jest
|
|
227
|
+
.spyOn(Recorder.prototype, "stop")
|
|
228
|
+
.mockReturnValue({ stopped: stopPromise, uploaded: uploadPromise });
|
|
182
229
|
|
|
183
230
|
const jsPsych = initJsPsych();
|
|
184
231
|
const trialRec = new Rec.TrialRecordExtension(jsPsych);
|
|
@@ -201,14 +248,17 @@ test("Trial recording stop/finish with different locale should display default u
|
|
|
201
248
|
} as TrialType<PluginInfo>),
|
|
202
249
|
);
|
|
203
250
|
expect(global_display_el.innerHTML).toBe(
|
|
204
|
-
|
|
251
|
+
`<div id="lookit-uploading-video-msg-container">
|
|
252
|
+
<div>téléchargement video en cours, veuillez attendre...</div>
|
|
253
|
+
<div id="lookit-loader-container"><div class="loader"></div></div></div>`,
|
|
205
254
|
);
|
|
206
|
-
//expect(Recorder.prototype.stop).toHaveBeenCalledTimes(1);
|
|
207
255
|
|
|
208
256
|
// resolve the stop promise
|
|
209
|
-
resolveStop();
|
|
257
|
+
resolveStop("url");
|
|
210
258
|
await stopPromise;
|
|
211
|
-
|
|
259
|
+
// resolve the upload promise
|
|
260
|
+
resolveUpload();
|
|
261
|
+
await uploadPromise;
|
|
212
262
|
|
|
213
263
|
// check the display cleanup
|
|
214
264
|
expect(global_display_el.innerHTML).toBe("");
|
|
@@ -216,10 +266,18 @@ test("Trial recording stop/finish with different locale should display default u
|
|
|
216
266
|
|
|
217
267
|
test("Trial recording stop/finish with custom uploading message", async () => {
|
|
218
268
|
// control the recorder stop promise so that we can inspect the display before it resolves
|
|
219
|
-
let resolveStop!: () => void;
|
|
220
|
-
|
|
269
|
+
let resolveStop!: (value: string) => void;
|
|
270
|
+
let resolveUpload!: () => void;
|
|
271
|
+
const stopPromise = new Promise<string>((res) => {
|
|
272
|
+
resolveStop = res;
|
|
273
|
+
});
|
|
274
|
+
const uploadPromise = new Promise<void>((res) => {
|
|
275
|
+
resolveUpload = res;
|
|
276
|
+
});
|
|
221
277
|
|
|
222
|
-
jest
|
|
278
|
+
jest
|
|
279
|
+
.spyOn(Recorder.prototype, "stop")
|
|
280
|
+
.mockReturnValue({ stopped: stopPromise, uploaded: uploadPromise });
|
|
223
281
|
|
|
224
282
|
const jsPsych = initJsPsych();
|
|
225
283
|
const trialRec = new Rec.TrialRecordExtension(jsPsych);
|
|
@@ -235,25 +293,155 @@ test("Trial recording stop/finish with custom uploading message", async () => {
|
|
|
235
293
|
trialRec.on_finish();
|
|
236
294
|
|
|
237
295
|
expect(global_display_el.innerHTML).toBe("Wait!");
|
|
238
|
-
//expect(Recorder.prototype.stop).toHaveBeenCalledTimes(1);
|
|
239
296
|
|
|
240
297
|
// resolve the stop promise
|
|
241
|
-
resolveStop();
|
|
298
|
+
resolveStop("url");
|
|
242
299
|
await stopPromise;
|
|
243
|
-
|
|
300
|
+
resolveUpload();
|
|
301
|
+
await uploadPromise;
|
|
302
|
+
|
|
303
|
+
// check the display cleanup
|
|
304
|
+
expect(global_display_el.innerHTML).toBe("");
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test("Trial recording stop/finish timeout with default parameters", async () => {
|
|
308
|
+
// simulate a resolved stop promise and timeout upload promise
|
|
309
|
+
const stopPromise = new Promise<string>((res) => res("url"));
|
|
310
|
+
const uploadPromise = new Promise<string>((res) => res("timeout"));
|
|
311
|
+
|
|
312
|
+
const recStopSpy = jest
|
|
313
|
+
.spyOn(Recorder.prototype, "stop")
|
|
314
|
+
.mockReturnValue({ stopped: stopPromise, uploaded: uploadPromise });
|
|
315
|
+
|
|
316
|
+
const jsPsych = initJsPsych();
|
|
317
|
+
const trialRec = new Rec.TrialRecordExtension(jsPsych);
|
|
318
|
+
|
|
319
|
+
await trialRec.initialize();
|
|
320
|
+
trialRec.on_start();
|
|
321
|
+
trialRec.on_load();
|
|
322
|
+
|
|
323
|
+
await trialRec.on_finish();
|
|
324
|
+
|
|
325
|
+
// recorder.stop should be called with the default max upload duration
|
|
326
|
+
expect(recStopSpy).toHaveBeenCalledWith({
|
|
327
|
+
upload_timeout_ms: 10000,
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// check the display cleanup
|
|
331
|
+
expect(global_display_el.innerHTML).toBe("");
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
test("Trial recording stop/finish with max upload duration initialize parameter", async () => {
|
|
335
|
+
// simulate a resolved stop promise and timeout upload promise
|
|
336
|
+
const stopPromise = new Promise<string>((res) => res("url"));
|
|
337
|
+
const uploadPromise = new Promise<string>((res) => res("timeout"));
|
|
338
|
+
|
|
339
|
+
const recStopSpy = jest
|
|
340
|
+
.spyOn(Recorder.prototype, "stop")
|
|
341
|
+
.mockReturnValue({ stopped: stopPromise, uploaded: uploadPromise });
|
|
342
|
+
|
|
343
|
+
const jsPsych = initJsPsych();
|
|
344
|
+
const trialRec = new Rec.TrialRecordExtension(jsPsych);
|
|
345
|
+
|
|
346
|
+
const params = {
|
|
347
|
+
max_upload_seconds: 20,
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
await trialRec.initialize(params);
|
|
351
|
+
trialRec.on_start();
|
|
352
|
+
trialRec.on_load();
|
|
353
|
+
|
|
354
|
+
await trialRec.on_finish();
|
|
355
|
+
|
|
356
|
+
// recorder.stop should be called with 20 seconds as the max upload duration
|
|
357
|
+
expect(recStopSpy).toHaveBeenCalledWith({
|
|
358
|
+
upload_timeout_ms: 20000,
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// check the display cleanup
|
|
362
|
+
expect(global_display_el.innerHTML).toBe("");
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
test("Trial recording stop/finish with max upload duration start parameter", async () => {
|
|
366
|
+
// simulate a resolved stop promise and timeout upload promise
|
|
367
|
+
const stopPromise = new Promise<string>((res) => res("url"));
|
|
368
|
+
const uploadPromise = new Promise<string>((res) => res("timeout"));
|
|
369
|
+
|
|
370
|
+
const recStopSpy = jest
|
|
371
|
+
.spyOn(Recorder.prototype, "stop")
|
|
372
|
+
.mockReturnValue({ stopped: stopPromise, uploaded: uploadPromise });
|
|
373
|
+
|
|
374
|
+
const jsPsych = initJsPsych();
|
|
375
|
+
const trialRec = new Rec.TrialRecordExtension(jsPsych);
|
|
376
|
+
|
|
377
|
+
const initParams = {
|
|
378
|
+
max_upload_seconds: null,
|
|
379
|
+
};
|
|
380
|
+
const startParams = {
|
|
381
|
+
max_upload_seconds: 20,
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
await trialRec.initialize(initParams);
|
|
385
|
+
trialRec.on_start(startParams);
|
|
386
|
+
trialRec.on_load();
|
|
387
|
+
|
|
388
|
+
await trialRec.on_finish();
|
|
389
|
+
|
|
390
|
+
// recorder.stop should be called with 20 seconds as the max upload duration
|
|
391
|
+
expect(recStopSpy).toHaveBeenCalledWith({
|
|
392
|
+
upload_timeout_ms: 20000,
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
// check the display cleanup
|
|
396
|
+
expect(global_display_el.innerHTML).toBe("");
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
test("Trial recording stop/finish with null max upload duration", async () => {
|
|
400
|
+
// simulate a resolved stop promise and resolved upload promise
|
|
401
|
+
const stopPromise = new Promise<string>((res) => res("url"));
|
|
402
|
+
const uploadPromise = new Promise<void>((res) => res());
|
|
403
|
+
|
|
404
|
+
const recStopSpy = jest
|
|
405
|
+
.spyOn(Recorder.prototype, "stop")
|
|
406
|
+
.mockReturnValue({ stopped: stopPromise, uploaded: uploadPromise });
|
|
407
|
+
|
|
408
|
+
const jsPsych = initJsPsych();
|
|
409
|
+
const trialRec = new Rec.TrialRecordExtension(jsPsych);
|
|
410
|
+
|
|
411
|
+
const params = {
|
|
412
|
+
max_upload_seconds: null,
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
await trialRec.initialize();
|
|
416
|
+
trialRec.on_start(params);
|
|
417
|
+
trialRec.on_load();
|
|
418
|
+
|
|
419
|
+
await trialRec.on_finish();
|
|
420
|
+
|
|
421
|
+
// recorder.stop should be called with null as the max upload duration
|
|
422
|
+
expect(recStopSpy).toHaveBeenCalledWith({
|
|
423
|
+
upload_timeout_ms: null,
|
|
424
|
+
});
|
|
244
425
|
|
|
245
426
|
// check the display cleanup
|
|
246
427
|
expect(global_display_el.innerHTML).toBe("");
|
|
247
428
|
});
|
|
248
429
|
|
|
249
|
-
test("Trial recording
|
|
430
|
+
test("Trial recording stop with failure during stop", async () => {
|
|
250
431
|
// Create a controlled promise and capture the reject function
|
|
251
432
|
let rejectStop!: (err: unknown) => void;
|
|
252
|
-
const stopPromise = new Promise<
|
|
433
|
+
const stopPromise = new Promise<string>((_, reject) => {
|
|
253
434
|
rejectStop = reject;
|
|
254
435
|
});
|
|
436
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
437
|
+
let resolveUpload!: () => void;
|
|
438
|
+
const uploadPromise = new Promise<void>((res) => {
|
|
439
|
+
resolveUpload = res;
|
|
440
|
+
});
|
|
255
441
|
|
|
256
|
-
jest
|
|
442
|
+
jest
|
|
443
|
+
.spyOn(Recorder.prototype, "stop")
|
|
444
|
+
.mockReturnValue({ stopped: stopPromise, uploaded: uploadPromise });
|
|
257
445
|
|
|
258
446
|
const jsPsych = initJsPsych();
|
|
259
447
|
const trialRec = new Rec.TrialRecordExtension(jsPsych);
|
|
@@ -267,30 +455,103 @@ test("Trial recording rejection path (failure during upload)", async () => {
|
|
|
267
455
|
|
|
268
456
|
// Should show initial wait for upload message
|
|
269
457
|
expect(global_display_el.innerHTML).toBe(
|
|
270
|
-
"
|
|
458
|
+
`<div id="lookit-uploading-video-msg-container">
|
|
459
|
+
<div>uploading video, please wait...</div>
|
|
460
|
+
<div id="lookit-loader-container"><div class="loader"></div></div></div>`,
|
|
271
461
|
);
|
|
272
462
|
|
|
273
463
|
// Reject stop
|
|
274
|
-
rejectStop(new Error("
|
|
464
|
+
rejectStop(new Error("stop failed"));
|
|
465
|
+
|
|
466
|
+
// Wait for plugin's `.catch()` handler to run
|
|
467
|
+
await Promise.resolve();
|
|
468
|
+
|
|
469
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
470
|
+
"TrialRecordExtension: recorder stop/upload failed.",
|
|
471
|
+
Error("stop failed"),
|
|
472
|
+
);
|
|
275
473
|
|
|
276
474
|
// Wait for plugin's `.catch()` handler to run
|
|
277
475
|
await Promise.resolve();
|
|
278
476
|
|
|
279
477
|
// TO DO: modify the trial extension code to display translated error msg and/or researcher contact info
|
|
478
|
+
expect(global_display_el.innerHTML).toBe("");
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
test("Trial recording stop with failure during upload", async () => {
|
|
482
|
+
let resolveStop!: (value: string) => void;
|
|
483
|
+
const stopPromise = new Promise<string>((res) => {
|
|
484
|
+
resolveStop = res;
|
|
485
|
+
});
|
|
486
|
+
// Create a controlled promise and capture the reject function
|
|
487
|
+
let rejectUpload!: (err: unknown) => void;
|
|
488
|
+
const uploadPromise = new Promise<string>((_, reject) => {
|
|
489
|
+
rejectUpload = reject;
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
jest
|
|
493
|
+
.spyOn(Recorder.prototype, "stop")
|
|
494
|
+
.mockReturnValue({ stopped: stopPromise, uploaded: uploadPromise });
|
|
495
|
+
|
|
496
|
+
const jsPsych = initJsPsych();
|
|
497
|
+
const trialRec = new Rec.TrialRecordExtension(jsPsych);
|
|
498
|
+
|
|
499
|
+
await trialRec.initialize();
|
|
500
|
+
trialRec.on_start();
|
|
501
|
+
trialRec.on_load();
|
|
502
|
+
|
|
503
|
+
// call on_finish but don't await so that we can inspect before it resolves
|
|
504
|
+
trialRec.on_finish();
|
|
505
|
+
|
|
506
|
+
// Should show initial wait for upload message
|
|
280
507
|
expect(global_display_el.innerHTML).toBe(
|
|
281
|
-
"
|
|
508
|
+
`<div id="lookit-uploading-video-msg-container">
|
|
509
|
+
<div>uploading video, please wait...</div>
|
|
510
|
+
<div id="lookit-loader-container"><div class="loader"></div></div></div>`,
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
// Resolve stop
|
|
514
|
+
resolveStop("url");
|
|
515
|
+
// Reject upload
|
|
516
|
+
rejectUpload(new Error("upload failed"));
|
|
517
|
+
|
|
518
|
+
// Wait for plugin's `.catch()` handler to run and flush microtasks
|
|
519
|
+
await Promise.resolve();
|
|
520
|
+
await Promise.resolve();
|
|
521
|
+
|
|
522
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
523
|
+
"TrialRecordExtension: recorder stop/upload failed.",
|
|
524
|
+
Error("upload failed"),
|
|
282
525
|
);
|
|
526
|
+
|
|
527
|
+
// TO DO: modify the trial extension code to display translated error msg and/or researcher contact info
|
|
528
|
+
expect(global_display_el.innerHTML).toBe("");
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
test("Trial recording stop with no recorder", async () => {
|
|
532
|
+
const jsPsych = initJsPsych();
|
|
533
|
+
const trialRec = new Rec.TrialRecordExtension(jsPsych);
|
|
534
|
+
|
|
535
|
+
// no recorder - extension should clean up display and immediately resolve on_finish
|
|
536
|
+
await trialRec.on_finish();
|
|
537
|
+
expect(global_display_el.innerHTML).toBe("");
|
|
283
538
|
});
|
|
284
539
|
|
|
285
540
|
test("Start session recording", async () => {
|
|
286
541
|
const mockRecStart = jest.spyOn(Recorder.prototype, "start");
|
|
287
542
|
const jsPsych = initJsPsych();
|
|
288
543
|
const startRec = new Rec.StartRecordPlugin(jsPsych);
|
|
544
|
+
const display_element = jest
|
|
545
|
+
.fn()
|
|
546
|
+
.mockImplementation() as unknown as HTMLElement;
|
|
547
|
+
const trial = {
|
|
548
|
+
locale: "en-us",
|
|
549
|
+
} as unknown as TrialType<PluginInfo>;
|
|
289
550
|
|
|
290
551
|
// manual mock
|
|
291
552
|
mockRecStart.mockImplementation(jest.fn().mockReturnValue(Promise.resolve()));
|
|
292
553
|
|
|
293
|
-
await startRec.trial();
|
|
554
|
+
await startRec.trial(display_element, trial);
|
|
294
555
|
|
|
295
556
|
expect(jsPsych.finishTrial).toHaveBeenCalledTimes(1);
|
|
296
557
|
expect(() => {
|
|
@@ -298,6 +559,101 @@ test("Start session recording", async () => {
|
|
|
298
559
|
}).toThrow(ExistingRecordingError);
|
|
299
560
|
});
|
|
300
561
|
|
|
562
|
+
test("Start session recording with default wait for connection message", async () => {
|
|
563
|
+
let resolveMockStart!: () => void;
|
|
564
|
+
const startRecPromise = new Promise<void>((res) => {
|
|
565
|
+
resolveMockStart = res;
|
|
566
|
+
});
|
|
567
|
+
const mockRecStart = jest.spyOn(Recorder.prototype, "start");
|
|
568
|
+
mockRecStart.mockImplementation(jest.fn().mockReturnValue(startRecPromise));
|
|
569
|
+
|
|
570
|
+
const jsPsych = initJsPsych();
|
|
571
|
+
const startRec = new Rec.StartRecordPlugin(jsPsych);
|
|
572
|
+
const display_element = jest
|
|
573
|
+
.fn()
|
|
574
|
+
.mockImplementation() as unknown as HTMLElement;
|
|
575
|
+
const trial = { locale: "en-us" } as unknown as TrialType<PluginInfo>;
|
|
576
|
+
|
|
577
|
+
// call trial but don't await so that we can inspect display element
|
|
578
|
+
startRec.trial(display_element, trial);
|
|
579
|
+
expect(display_element.innerHTML).toBe(
|
|
580
|
+
`<div id="lookit-establishing-connection-msg">
|
|
581
|
+
<div>establishing video connection, please wait...</div>
|
|
582
|
+
<div id="lookit-loader-container"><div class="loader"></div></div></div>`,
|
|
583
|
+
);
|
|
584
|
+
|
|
585
|
+
// now resolve start promise and await
|
|
586
|
+
resolveMockStart();
|
|
587
|
+
await startRecPromise;
|
|
588
|
+
|
|
589
|
+
// clean up tasks should run
|
|
590
|
+
expect(display_element.innerHTML).toBe("");
|
|
591
|
+
expect(jsPsych.finishTrial).toHaveBeenCalledTimes(1);
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
test("Start session recording with translated connection message", async () => {
|
|
595
|
+
let resolveMockStart!: () => void;
|
|
596
|
+
const startRecPromise = new Promise<void>((res) => {
|
|
597
|
+
resolveMockStart = res;
|
|
598
|
+
});
|
|
599
|
+
const mockRecStart = jest.spyOn(Recorder.prototype, "start");
|
|
600
|
+
mockRecStart.mockImplementation(jest.fn().mockReturnValue(startRecPromise));
|
|
601
|
+
|
|
602
|
+
const jsPsych = initJsPsych();
|
|
603
|
+
const startRec = new Rec.StartRecordPlugin(jsPsych);
|
|
604
|
+
const display_element = jest
|
|
605
|
+
.fn()
|
|
606
|
+
.mockImplementation() as unknown as HTMLElement;
|
|
607
|
+
const trial = { locale: "fr" } as unknown as TrialType<PluginInfo>;
|
|
608
|
+
|
|
609
|
+
// call trial but don't await so that we can inspect display element
|
|
610
|
+
startRec.trial(display_element, trial);
|
|
611
|
+
expect(display_element.innerHTML).toBe(
|
|
612
|
+
`<div id="lookit-establishing-connection-msg">
|
|
613
|
+
<div>en attente de connection video, veuillez attendre...</div>
|
|
614
|
+
<div id="lookit-loader-container"><div class="loader"></div></div></div>`,
|
|
615
|
+
);
|
|
616
|
+
|
|
617
|
+
// now resolve start promise and await
|
|
618
|
+
resolveMockStart();
|
|
619
|
+
await startRecPromise;
|
|
620
|
+
|
|
621
|
+
// clean up tasks should run
|
|
622
|
+
expect(display_element.innerHTML).toBe("");
|
|
623
|
+
expect(jsPsych.finishTrial).toHaveBeenCalledTimes(1);
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
test("Start session recording with custom wait for connection message", async () => {
|
|
627
|
+
let resolveMockStart!: () => void;
|
|
628
|
+
const startRecPromise = new Promise<void>((res) => {
|
|
629
|
+
resolveMockStart = res;
|
|
630
|
+
});
|
|
631
|
+
const mockRecStart = jest.spyOn(Recorder.prototype, "start");
|
|
632
|
+
mockRecStart.mockImplementation(jest.fn().mockReturnValue(startRecPromise));
|
|
633
|
+
|
|
634
|
+
const jsPsych = initJsPsych();
|
|
635
|
+
const startRec = new Rec.StartRecordPlugin(jsPsych);
|
|
636
|
+
const display_element = jest
|
|
637
|
+
.fn()
|
|
638
|
+
.mockImplementation() as unknown as HTMLElement;
|
|
639
|
+
const trial = {
|
|
640
|
+
wait_for_connection_message: "Hello!",
|
|
641
|
+
locale: "de", // should be ignored
|
|
642
|
+
} as unknown as TrialType<PluginInfo>;
|
|
643
|
+
|
|
644
|
+
// call trial but don't await so that we can inspect display element
|
|
645
|
+
startRec.trial(display_element, trial);
|
|
646
|
+
expect(display_element.innerHTML).toBe("Hello!");
|
|
647
|
+
|
|
648
|
+
// now resolve start promise and await
|
|
649
|
+
resolveMockStart();
|
|
650
|
+
await startRecPromise;
|
|
651
|
+
|
|
652
|
+
// clean up tasks should run
|
|
653
|
+
expect(display_element.innerHTML).toBe("");
|
|
654
|
+
expect(jsPsych.finishTrial).toHaveBeenCalledTimes(1);
|
|
655
|
+
});
|
|
656
|
+
|
|
301
657
|
test("Stop session recording", async () => {
|
|
302
658
|
const mockRecStop = jest.spyOn(Recorder.prototype, "stop");
|
|
303
659
|
const jsPsych = initJsPsych();
|
|
@@ -311,7 +667,12 @@ test("Stop session recording", async () => {
|
|
|
311
667
|
.fn()
|
|
312
668
|
.mockImplementation() as unknown as HTMLElement;
|
|
313
669
|
|
|
314
|
-
mockRecStop.mockImplementation(
|
|
670
|
+
mockRecStop.mockImplementation(
|
|
671
|
+
(): StopResult => ({
|
|
672
|
+
stopped: Promise.resolve("mock-url"),
|
|
673
|
+
uploaded: Promise.resolve(),
|
|
674
|
+
}),
|
|
675
|
+
);
|
|
315
676
|
|
|
316
677
|
const trial = {
|
|
317
678
|
locale: "en-us",
|
|
@@ -332,10 +693,18 @@ test("Stop session recording", async () => {
|
|
|
332
693
|
|
|
333
694
|
test("Stop session recording should display default uploading msg in English", async () => {
|
|
334
695
|
// control the recorder stop promise so that we can inspect the display before it resolves
|
|
335
|
-
let resolveStop!: () => void;
|
|
336
|
-
|
|
696
|
+
let resolveStop!: (value: string) => void;
|
|
697
|
+
let resolveUpload!: () => void;
|
|
698
|
+
const stopPromise = new Promise<string>((res) => {
|
|
699
|
+
resolveStop = res;
|
|
700
|
+
});
|
|
701
|
+
const uploadPromise = new Promise<void>((res) => {
|
|
702
|
+
resolveUpload = res;
|
|
703
|
+
});
|
|
337
704
|
|
|
338
|
-
jest
|
|
705
|
+
jest
|
|
706
|
+
.spyOn(Recorder.prototype, "stop")
|
|
707
|
+
.mockReturnValue({ stopped: stopPromise, uploaded: uploadPromise });
|
|
339
708
|
|
|
340
709
|
const jsPsych = initJsPsych();
|
|
341
710
|
|
|
@@ -355,13 +724,24 @@ test("Stop session recording should display default uploading msg in English", a
|
|
|
355
724
|
// call trial but don't await so that we can inspect before it resolves
|
|
356
725
|
stop_rec_plugin.trial(display_element, trial);
|
|
357
726
|
|
|
358
|
-
|
|
727
|
+
const en_uploading_msg = chsTemplates.uploadingVideo(trial);
|
|
728
|
+
|
|
729
|
+
// check that en (default) is used
|
|
730
|
+
expect(en_uploading_msg).toBe(
|
|
731
|
+
`<div id="lookit-uploading-video-msg-container">
|
|
732
|
+
<div>uploading video, please wait...</div>
|
|
733
|
+
<div id="lookit-loader-container"><div class="loader"></div></div></div>`,
|
|
734
|
+
);
|
|
735
|
+
expect(display_element.innerHTML).toBe(en_uploading_msg);
|
|
359
736
|
expect(Recorder.prototype.stop).toHaveBeenCalledTimes(1);
|
|
360
737
|
|
|
361
|
-
// resolve the stop promise
|
|
362
|
-
resolveStop();
|
|
738
|
+
// resolve the stop promise and upload promise
|
|
739
|
+
resolveStop("url");
|
|
363
740
|
await stopPromise;
|
|
364
741
|
await Promise.resolve();
|
|
742
|
+
resolveUpload();
|
|
743
|
+
await uploadPromise;
|
|
744
|
+
await Promise.resolve();
|
|
365
745
|
|
|
366
746
|
// check the cleanup tasks after the trial method has resolved
|
|
367
747
|
expect(display_element.innerHTML).toBe("");
|
|
@@ -371,10 +751,18 @@ test("Stop session recording should display default uploading msg in English", a
|
|
|
371
751
|
|
|
372
752
|
test("Stop session recording with different locale should display default uploading msg in specified language", async () => {
|
|
373
753
|
// control the recorder stop promise so that we can inspect the display before it resolves
|
|
374
|
-
let resolveStop!: () => void;
|
|
375
|
-
|
|
754
|
+
let resolveStop!: (value: string) => void;
|
|
755
|
+
let resolveUpload!: () => void;
|
|
756
|
+
const stopPromise = new Promise<string>((res) => {
|
|
757
|
+
resolveStop = res;
|
|
758
|
+
});
|
|
759
|
+
const uploadPromise = new Promise<void>((res) => {
|
|
760
|
+
resolveUpload = res;
|
|
761
|
+
});
|
|
376
762
|
|
|
377
|
-
jest
|
|
763
|
+
jest
|
|
764
|
+
.spyOn(Recorder.prototype, "stop")
|
|
765
|
+
.mockReturnValue({ stopped: stopPromise, uploaded: uploadPromise });
|
|
378
766
|
|
|
379
767
|
const jsPsych = initJsPsych();
|
|
380
768
|
|
|
@@ -399,15 +787,20 @@ test("Stop session recording with different locale should display default upload
|
|
|
399
787
|
|
|
400
788
|
// check that fr translation is used
|
|
401
789
|
expect(fr_uploading_msg).toBe(
|
|
402
|
-
|
|
790
|
+
`<div id="lookit-uploading-video-msg-container">
|
|
791
|
+
<div>téléchargement video en cours, veuillez attendre...</div>
|
|
792
|
+
<div id="lookit-loader-container"><div class="loader"></div></div></div>`,
|
|
403
793
|
);
|
|
404
794
|
expect(display_element.innerHTML).toBe(fr_uploading_msg);
|
|
405
795
|
expect(Recorder.prototype.stop).toHaveBeenCalledTimes(1);
|
|
406
796
|
|
|
407
|
-
// resolve the stop promise
|
|
408
|
-
resolveStop();
|
|
797
|
+
// resolve the stop promise and upload promise
|
|
798
|
+
resolveStop("url");
|
|
409
799
|
await stopPromise;
|
|
410
800
|
await Promise.resolve();
|
|
801
|
+
resolveUpload();
|
|
802
|
+
await uploadPromise;
|
|
803
|
+
await Promise.resolve();
|
|
411
804
|
|
|
412
805
|
// check the cleanup tasks after the trial method has resolved
|
|
413
806
|
expect(display_element.innerHTML).toBe("");
|
|
@@ -417,10 +810,18 @@ test("Stop session recording with different locale should display default upload
|
|
|
417
810
|
|
|
418
811
|
test("Stop session recording with custom uploading message", async () => {
|
|
419
812
|
// control the recorder stop promise so that we can inspect the display before it resolves
|
|
420
|
-
let resolveStop!: () => void;
|
|
421
|
-
|
|
813
|
+
let resolveStop!: (value: string) => void;
|
|
814
|
+
let resolveUpload!: () => void;
|
|
815
|
+
const stopPromise = new Promise<string>((res) => {
|
|
816
|
+
resolveStop = res;
|
|
817
|
+
});
|
|
818
|
+
const uploadPromise = new Promise<void>((res) => {
|
|
819
|
+
resolveUpload = res;
|
|
820
|
+
});
|
|
422
821
|
|
|
423
|
-
jest
|
|
822
|
+
jest
|
|
823
|
+
.spyOn(Recorder.prototype, "stop")
|
|
824
|
+
.mockReturnValue({ stopped: stopPromise, uploaded: uploadPromise });
|
|
424
825
|
|
|
425
826
|
const jsPsych = initJsPsych();
|
|
426
827
|
setCHSValue({ sessionRecorder: new Recorder(jsPsych) });
|
|
@@ -439,9 +840,12 @@ test("Stop session recording with custom uploading message", async () => {
|
|
|
439
840
|
// check display before stop is resolved
|
|
440
841
|
expect(display_element.innerHTML).toBe("<p>Custom message…</p>");
|
|
441
842
|
|
|
442
|
-
resolveStop();
|
|
843
|
+
resolveStop("url");
|
|
443
844
|
await stopPromise;
|
|
444
845
|
await Promise.resolve();
|
|
846
|
+
resolveUpload();
|
|
847
|
+
await uploadPromise;
|
|
848
|
+
await Promise.resolve();
|
|
445
849
|
|
|
446
850
|
// check the cleanup tasks after the trial method has resolved
|
|
447
851
|
expect(display_element.innerHTML).toBe("");
|
|
@@ -449,14 +853,53 @@ test("Stop session recording with custom uploading message", async () => {
|
|
|
449
853
|
expect(window.chs.sessionRecorder).toBeNull();
|
|
450
854
|
});
|
|
451
855
|
|
|
452
|
-
test("
|
|
856
|
+
test("Session recording stop with null as max upload seconds (no upload timeout)", () => {
|
|
857
|
+
// simulate a resolved stop promise and upload promise
|
|
858
|
+
const stopPromise = new Promise<string>((res) => res("url"));
|
|
859
|
+
const uploadPromise = new Promise<void>((res) => res());
|
|
860
|
+
|
|
861
|
+
const recStopSpy = jest
|
|
862
|
+
.spyOn(Recorder.prototype, "stop")
|
|
863
|
+
.mockReturnValue({ stopped: stopPromise, uploaded: uploadPromise });
|
|
864
|
+
|
|
865
|
+
const jsPsych = initJsPsych();
|
|
866
|
+
setCHSValue({ sessionRecorder: new Recorder(jsPsych) });
|
|
867
|
+
|
|
868
|
+
const stop_rec_plugin = new Rec.StopRecordPlugin(jsPsych);
|
|
869
|
+
const display_element = document.createElement("div");
|
|
870
|
+
|
|
871
|
+
const trial = {
|
|
872
|
+
type: Rec.StopRecordPlugin.info.name,
|
|
873
|
+
locale: "en-us",
|
|
874
|
+
max_upload_seconds: null,
|
|
875
|
+
} as unknown as TrialType<PluginInfo>; // need to cast here because the "type" param is a string and should be a class
|
|
876
|
+
|
|
877
|
+
stop_rec_plugin.trial(display_element, trial);
|
|
878
|
+
|
|
879
|
+
// recorder.stop should be called with null as the max upload duration
|
|
880
|
+
expect(recStopSpy).toHaveBeenCalledWith({
|
|
881
|
+
upload_timeout_ms: null,
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
// check the display cleanup
|
|
885
|
+
expect(global_display_el.innerHTML).toBe("");
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
test("Stop recording stop with failure during upload", async () => {
|
|
453
889
|
// Create a controlled promise and capture the reject function
|
|
454
890
|
let rejectStop!: (err: unknown) => void;
|
|
455
|
-
const stopPromise = new Promise<
|
|
891
|
+
const stopPromise = new Promise<string>((_, reject) => {
|
|
456
892
|
rejectStop = reject;
|
|
457
893
|
});
|
|
894
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
895
|
+
let resolveUpload!: () => void;
|
|
896
|
+
const uploadPromise = new Promise<void>((res) => {
|
|
897
|
+
resolveUpload = res;
|
|
898
|
+
});
|
|
458
899
|
|
|
459
|
-
jest
|
|
900
|
+
jest
|
|
901
|
+
.spyOn(Recorder.prototype, "stop")
|
|
902
|
+
.mockReturnValue({ stopped: stopPromise, uploaded: uploadPromise });
|
|
460
903
|
|
|
461
904
|
const jsPsych = initJsPsych();
|
|
462
905
|
setCHSValue({ sessionRecorder: new Recorder(jsPsych) });
|
|
@@ -481,9 +924,9 @@ test("Stop recording rejection path (failure during upload)", async () => {
|
|
|
481
924
|
// Wait for plugin's `.catch()` handler to run
|
|
482
925
|
await Promise.resolve();
|
|
483
926
|
|
|
484
|
-
// Trial doesn't end and the cleanup tasks don't run.
|
|
485
927
|
// TO DO: modify the plugin code to display translated error msg and/or researcher contact info
|
|
486
|
-
expect(
|
|
487
|
-
|
|
488
|
-
|
|
928
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
929
|
+
"StopRecordPlugin: recorder stop/upload failed.",
|
|
930
|
+
Error("upload failed"),
|
|
931
|
+
);
|
|
489
932
|
});
|