@jspsych-contrib/plugin-trail-making 0.1.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 +36 -0
- package/dist/index.browser.js +409 -0
- package/dist/index.browser.js.map +1 -0
- package/dist/index.browser.min.js +2 -0
- package/dist/index.browser.min.js.map +1 -0
- package/dist/index.cjs +408 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +341 -0
- package/dist/index.js +406 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -0
- package/src/index.spec.ts +521 -0
- package/src/index.ts +541 -0
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
import { startTimeline } from "@jspsych/test-utils";
|
|
2
|
+
|
|
3
|
+
import jsPsychTrailMaking from ".";
|
|
4
|
+
|
|
5
|
+
jest.useFakeTimers();
|
|
6
|
+
|
|
7
|
+
describe("trail-making plugin", () => {
|
|
8
|
+
// Store original methods for restoration
|
|
9
|
+
let originalGetContext: typeof HTMLCanvasElement.prototype.getContext;
|
|
10
|
+
let mockCtx: any;
|
|
11
|
+
let arcCalls: Array<{ x: number; y: number; radius: number; fillStyle?: string }>;
|
|
12
|
+
let fillTextCalls: Array<{ text: string; x: number; y: number }>;
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
arcCalls = [];
|
|
16
|
+
fillTextCalls = [];
|
|
17
|
+
|
|
18
|
+
// Create mock context that tracks calls
|
|
19
|
+
mockCtx = {
|
|
20
|
+
fillStyle: "",
|
|
21
|
+
strokeStyle: "",
|
|
22
|
+
lineWidth: 0,
|
|
23
|
+
font: "",
|
|
24
|
+
textAlign: "",
|
|
25
|
+
textBaseline: "",
|
|
26
|
+
fillRect: jest.fn(),
|
|
27
|
+
beginPath: jest.fn(),
|
|
28
|
+
moveTo: jest.fn(),
|
|
29
|
+
lineTo: jest.fn(),
|
|
30
|
+
arc: jest.fn((x: number, y: number, radius: number) => {
|
|
31
|
+
arcCalls.push({ x, y, radius, fillStyle: mockCtx.fillStyle });
|
|
32
|
+
}),
|
|
33
|
+
stroke: jest.fn(),
|
|
34
|
+
fill: jest.fn(),
|
|
35
|
+
fillText: jest.fn((text: string, x: number, y: number) => {
|
|
36
|
+
fillTextCalls.push({ text, x, y });
|
|
37
|
+
}),
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Mock getContext to return our mock
|
|
41
|
+
originalGetContext = HTMLCanvasElement.prototype.getContext;
|
|
42
|
+
HTMLCanvasElement.prototype.getContext = jest.fn(() => mockCtx) as any;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
HTMLCanvasElement.prototype.getContext = originalGetContext;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("should load with default parameters", async () => {
|
|
50
|
+
const { displayElement } = await startTimeline([
|
|
51
|
+
{
|
|
52
|
+
type: jsPsychTrailMaking,
|
|
53
|
+
},
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
// Check that canvas is created
|
|
57
|
+
const canvas = displayElement.querySelector("canvas");
|
|
58
|
+
expect(canvas).not.toBeNull();
|
|
59
|
+
expect(canvas?.width).toBe(600);
|
|
60
|
+
expect(canvas?.height).toBe(600);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("should accept custom canvas dimensions", async () => {
|
|
64
|
+
const { displayElement } = await startTimeline([
|
|
65
|
+
{
|
|
66
|
+
type: jsPsychTrailMaking,
|
|
67
|
+
canvas_width: 800,
|
|
68
|
+
canvas_height: 500,
|
|
69
|
+
},
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
const canvas = displayElement.querySelector("canvas");
|
|
73
|
+
expect(canvas?.width).toBe(800);
|
|
74
|
+
expect(canvas?.height).toBe(500);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("should display a prompt when provided", async () => {
|
|
78
|
+
const { displayElement } = await startTimeline([
|
|
79
|
+
{
|
|
80
|
+
type: jsPsychTrailMaking,
|
|
81
|
+
prompt: "<p>Connect the circles in order</p>",
|
|
82
|
+
},
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
expect(displayElement.innerHTML).toContain("Connect the circles in order");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("should draw targets with specified target_radius", async () => {
|
|
89
|
+
arcCalls = [];
|
|
90
|
+
|
|
91
|
+
await startTimeline([
|
|
92
|
+
{
|
|
93
|
+
type: jsPsychTrailMaking,
|
|
94
|
+
target_radius: 35,
|
|
95
|
+
num_targets: 5,
|
|
96
|
+
},
|
|
97
|
+
]);
|
|
98
|
+
|
|
99
|
+
// Filter arc calls for target circles (radius should be 35)
|
|
100
|
+
const targetCalls = arcCalls.filter((call) => call.radius === 35);
|
|
101
|
+
// Should have 5 targets drawn
|
|
102
|
+
expect(targetCalls.length).toBe(5);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("should draw correct number of targets for test_type A", async () => {
|
|
106
|
+
fillTextCalls = [];
|
|
107
|
+
|
|
108
|
+
await startTimeline([
|
|
109
|
+
{
|
|
110
|
+
type: jsPsychTrailMaking,
|
|
111
|
+
test_type: "A",
|
|
112
|
+
num_targets: 8,
|
|
113
|
+
},
|
|
114
|
+
]);
|
|
115
|
+
|
|
116
|
+
// Should have labels "1" through "8"
|
|
117
|
+
const labels = fillTextCalls.map((call) => call.text);
|
|
118
|
+
for (let i = 1; i <= 8; i++) {
|
|
119
|
+
expect(labels).toContain(i.toString());
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("should draw alternating numbers and letters for test_type B", async () => {
|
|
124
|
+
fillTextCalls = [];
|
|
125
|
+
|
|
126
|
+
await startTimeline([
|
|
127
|
+
{
|
|
128
|
+
type: jsPsychTrailMaking,
|
|
129
|
+
test_type: "B",
|
|
130
|
+
num_targets: 6,
|
|
131
|
+
},
|
|
132
|
+
]);
|
|
133
|
+
|
|
134
|
+
// Should have labels 1, A, 2, B, 3, C
|
|
135
|
+
const labels = fillTextCalls.map((call) => call.text);
|
|
136
|
+
expect(labels).toContain("1");
|
|
137
|
+
expect(labels).toContain("A");
|
|
138
|
+
expect(labels).toContain("2");
|
|
139
|
+
expect(labels).toContain("B");
|
|
140
|
+
expect(labels).toContain("3");
|
|
141
|
+
expect(labels).toContain("C");
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("should use custom targets when provided", async () => {
|
|
145
|
+
fillTextCalls = [];
|
|
146
|
+
|
|
147
|
+
const customTargets = [
|
|
148
|
+
{ x: 100, y: 100, label: "1" },
|
|
149
|
+
{ x: 200, y: 200, label: "2" },
|
|
150
|
+
{ x: 300, y: 300, label: "3" },
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
await startTimeline([
|
|
154
|
+
{
|
|
155
|
+
type: jsPsychTrailMaking,
|
|
156
|
+
targets: customTargets,
|
|
157
|
+
},
|
|
158
|
+
]);
|
|
159
|
+
|
|
160
|
+
// Should draw labels at specified positions
|
|
161
|
+
const labels = fillTextCalls.map((call) => call.text);
|
|
162
|
+
expect(labels).toContain("1");
|
|
163
|
+
expect(labels).toContain("2");
|
|
164
|
+
expect(labels).toContain("3");
|
|
165
|
+
|
|
166
|
+
// Check that targets are drawn at correct positions
|
|
167
|
+
const label1Call = fillTextCalls.find((call) => call.text === "1");
|
|
168
|
+
expect(label1Call?.x).toBe(100);
|
|
169
|
+
expect(label1Call?.y).toBe(100);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("should complete when all targets are clicked in order", async () => {
|
|
173
|
+
const customTargets = [
|
|
174
|
+
{ x: 100, y: 100, label: "1" },
|
|
175
|
+
{ x: 200, y: 200, label: "2" },
|
|
176
|
+
{ x: 300, y: 300, label: "3" },
|
|
177
|
+
];
|
|
178
|
+
|
|
179
|
+
const { expectFinished, getData, displayElement } = await startTimeline([
|
|
180
|
+
{
|
|
181
|
+
type: jsPsychTrailMaking,
|
|
182
|
+
targets: customTargets,
|
|
183
|
+
test_type: "A",
|
|
184
|
+
},
|
|
185
|
+
]);
|
|
186
|
+
|
|
187
|
+
const canvas = displayElement.querySelector("canvas")!;
|
|
188
|
+
|
|
189
|
+
// Simulate clicking targets in correct order
|
|
190
|
+
for (const target of customTargets) {
|
|
191
|
+
const clickEvent = new MouseEvent("click", {
|
|
192
|
+
clientX: target.x,
|
|
193
|
+
clientY: target.y,
|
|
194
|
+
bubbles: true,
|
|
195
|
+
});
|
|
196
|
+
// Need to mock getBoundingClientRect
|
|
197
|
+
canvas.getBoundingClientRect = jest.fn(() => ({
|
|
198
|
+
left: 0,
|
|
199
|
+
top: 0,
|
|
200
|
+
right: 600,
|
|
201
|
+
bottom: 600,
|
|
202
|
+
width: 600,
|
|
203
|
+
height: 600,
|
|
204
|
+
x: 0,
|
|
205
|
+
y: 0,
|
|
206
|
+
toJSON: () => {},
|
|
207
|
+
}));
|
|
208
|
+
canvas.dispatchEvent(clickEvent);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
await expectFinished();
|
|
212
|
+
|
|
213
|
+
const data = getData().values()[0];
|
|
214
|
+
expect(data.test_type).toBe("A");
|
|
215
|
+
expect(data.num_errors).toBe(0);
|
|
216
|
+
expect(data.completion_time).toBeGreaterThanOrEqual(0);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("should track errors for incorrect clicks", async () => {
|
|
220
|
+
const customTargets = [
|
|
221
|
+
{ x: 100, y: 100, label: "1" },
|
|
222
|
+
{ x: 200, y: 200, label: "2" },
|
|
223
|
+
{ x: 300, y: 300, label: "3" },
|
|
224
|
+
];
|
|
225
|
+
|
|
226
|
+
const { expectFinished, getData, displayElement } = await startTimeline([
|
|
227
|
+
{
|
|
228
|
+
type: jsPsychTrailMaking,
|
|
229
|
+
targets: customTargets,
|
|
230
|
+
test_type: "A",
|
|
231
|
+
error_duration: 100,
|
|
232
|
+
},
|
|
233
|
+
]);
|
|
234
|
+
|
|
235
|
+
const canvas = displayElement.querySelector("canvas")!;
|
|
236
|
+
canvas.getBoundingClientRect = jest.fn(() => ({
|
|
237
|
+
left: 0,
|
|
238
|
+
top: 0,
|
|
239
|
+
right: 600,
|
|
240
|
+
bottom: 600,
|
|
241
|
+
width: 600,
|
|
242
|
+
height: 600,
|
|
243
|
+
x: 0,
|
|
244
|
+
y: 0,
|
|
245
|
+
toJSON: () => {},
|
|
246
|
+
}));
|
|
247
|
+
|
|
248
|
+
// Click target 2 first (wrong order)
|
|
249
|
+
canvas.dispatchEvent(
|
|
250
|
+
new MouseEvent("click", {
|
|
251
|
+
clientX: 200,
|
|
252
|
+
clientY: 200,
|
|
253
|
+
bubbles: true,
|
|
254
|
+
})
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
// Wait for error duration
|
|
258
|
+
jest.advanceTimersByTime(100);
|
|
259
|
+
|
|
260
|
+
// Now click in correct order
|
|
261
|
+
canvas.dispatchEvent(
|
|
262
|
+
new MouseEvent("click", {
|
|
263
|
+
clientX: 100,
|
|
264
|
+
clientY: 100,
|
|
265
|
+
bubbles: true,
|
|
266
|
+
})
|
|
267
|
+
);
|
|
268
|
+
canvas.dispatchEvent(
|
|
269
|
+
new MouseEvent("click", {
|
|
270
|
+
clientX: 200,
|
|
271
|
+
clientY: 200,
|
|
272
|
+
bubbles: true,
|
|
273
|
+
})
|
|
274
|
+
);
|
|
275
|
+
canvas.dispatchEvent(
|
|
276
|
+
new MouseEvent("click", {
|
|
277
|
+
clientX: 300,
|
|
278
|
+
clientY: 300,
|
|
279
|
+
bubbles: true,
|
|
280
|
+
})
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
await expectFinished();
|
|
284
|
+
|
|
285
|
+
const data = getData().values()[0];
|
|
286
|
+
expect(data.num_errors).toBe(1);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it("should use seed for reproducible layouts", async () => {
|
|
290
|
+
const seed = 42;
|
|
291
|
+
|
|
292
|
+
const { displayElement: display1 } = await startTimeline([
|
|
293
|
+
{
|
|
294
|
+
type: jsPsychTrailMaking,
|
|
295
|
+
num_targets: 5,
|
|
296
|
+
seed: seed,
|
|
297
|
+
},
|
|
298
|
+
]);
|
|
299
|
+
|
|
300
|
+
// The canvas should be created with the seeded layout
|
|
301
|
+
const canvas1 = display1.querySelector("canvas");
|
|
302
|
+
expect(canvas1).not.toBeNull();
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it("should record inter-click times between correct clicks", async () => {
|
|
306
|
+
const customTargets = [
|
|
307
|
+
{ x: 100, y: 100, label: "1" },
|
|
308
|
+
{ x: 200, y: 200, label: "2" },
|
|
309
|
+
{ x: 300, y: 300, label: "3" },
|
|
310
|
+
];
|
|
311
|
+
|
|
312
|
+
const { expectFinished, getData, displayElement } = await startTimeline([
|
|
313
|
+
{
|
|
314
|
+
type: jsPsychTrailMaking,
|
|
315
|
+
targets: customTargets,
|
|
316
|
+
},
|
|
317
|
+
]);
|
|
318
|
+
|
|
319
|
+
const canvas = displayElement.querySelector("canvas")!;
|
|
320
|
+
canvas.getBoundingClientRect = jest.fn(() => ({
|
|
321
|
+
left: 0,
|
|
322
|
+
top: 0,
|
|
323
|
+
right: 600,
|
|
324
|
+
bottom: 600,
|
|
325
|
+
width: 600,
|
|
326
|
+
height: 600,
|
|
327
|
+
x: 0,
|
|
328
|
+
y: 0,
|
|
329
|
+
toJSON: () => {},
|
|
330
|
+
}));
|
|
331
|
+
|
|
332
|
+
// Click with time delays
|
|
333
|
+
canvas.dispatchEvent(new MouseEvent("click", { clientX: 100, clientY: 100, bubbles: true }));
|
|
334
|
+
jest.advanceTimersByTime(500);
|
|
335
|
+
canvas.dispatchEvent(new MouseEvent("click", { clientX: 200, clientY: 200, bubbles: true }));
|
|
336
|
+
jest.advanceTimersByTime(300);
|
|
337
|
+
canvas.dispatchEvent(new MouseEvent("click", { clientX: 300, clientY: 300, bubbles: true }));
|
|
338
|
+
|
|
339
|
+
await expectFinished();
|
|
340
|
+
|
|
341
|
+
const data = getData().values()[0];
|
|
342
|
+
expect(data.inter_click_times.length).toBe(2);
|
|
343
|
+
// First inter-click time should be around 500ms
|
|
344
|
+
expect(data.inter_click_times[0]).toBeGreaterThanOrEqual(500);
|
|
345
|
+
// Second should be around 300ms
|
|
346
|
+
expect(data.inter_click_times[1]).toBeGreaterThanOrEqual(300);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it("should calculate total path distance correctly", async () => {
|
|
350
|
+
const customTargets = [
|
|
351
|
+
{ x: 0, y: 0, label: "1" },
|
|
352
|
+
{ x: 100, y: 0, label: "2" },
|
|
353
|
+
{ x: 100, y: 100, label: "3" },
|
|
354
|
+
];
|
|
355
|
+
|
|
356
|
+
const { expectFinished, getData, displayElement } = await startTimeline([
|
|
357
|
+
{
|
|
358
|
+
type: jsPsychTrailMaking,
|
|
359
|
+
targets: customTargets,
|
|
360
|
+
target_radius: 50, // Large radius to ensure hits
|
|
361
|
+
},
|
|
362
|
+
]);
|
|
363
|
+
|
|
364
|
+
const canvas = displayElement.querySelector("canvas")!;
|
|
365
|
+
canvas.getBoundingClientRect = jest.fn(() => ({
|
|
366
|
+
left: 0,
|
|
367
|
+
top: 0,
|
|
368
|
+
right: 600,
|
|
369
|
+
bottom: 600,
|
|
370
|
+
width: 600,
|
|
371
|
+
height: 600,
|
|
372
|
+
x: 0,
|
|
373
|
+
y: 0,
|
|
374
|
+
toJSON: () => {},
|
|
375
|
+
}));
|
|
376
|
+
|
|
377
|
+
canvas.dispatchEvent(new MouseEvent("click", { clientX: 0, clientY: 0, bubbles: true }));
|
|
378
|
+
canvas.dispatchEvent(new MouseEvent("click", { clientX: 100, clientY: 0, bubbles: true }));
|
|
379
|
+
canvas.dispatchEvent(new MouseEvent("click", { clientX: 100, clientY: 100, bubbles: true }));
|
|
380
|
+
|
|
381
|
+
await expectFinished();
|
|
382
|
+
|
|
383
|
+
const data = getData().values()[0];
|
|
384
|
+
// Distance should be 100 + 100 = 200
|
|
385
|
+
expect(data.total_path_distance).toBe(200);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it("should record click details with correct/incorrect status", async () => {
|
|
389
|
+
const customTargets = [
|
|
390
|
+
{ x: 100, y: 100, label: "1" },
|
|
391
|
+
{ x: 200, y: 200, label: "2" },
|
|
392
|
+
];
|
|
393
|
+
|
|
394
|
+
const { expectFinished, getData, displayElement } = await startTimeline([
|
|
395
|
+
{
|
|
396
|
+
type: jsPsychTrailMaking,
|
|
397
|
+
targets: customTargets,
|
|
398
|
+
},
|
|
399
|
+
]);
|
|
400
|
+
|
|
401
|
+
const canvas = displayElement.querySelector("canvas")!;
|
|
402
|
+
canvas.getBoundingClientRect = jest.fn(() => ({
|
|
403
|
+
left: 0,
|
|
404
|
+
top: 0,
|
|
405
|
+
right: 600,
|
|
406
|
+
bottom: 600,
|
|
407
|
+
width: 600,
|
|
408
|
+
height: 600,
|
|
409
|
+
x: 0,
|
|
410
|
+
y: 0,
|
|
411
|
+
toJSON: () => {},
|
|
412
|
+
}));
|
|
413
|
+
|
|
414
|
+
canvas.dispatchEvent(new MouseEvent("click", { clientX: 100, clientY: 100, bubbles: true }));
|
|
415
|
+
canvas.dispatchEvent(new MouseEvent("click", { clientX: 200, clientY: 200, bubbles: true }));
|
|
416
|
+
|
|
417
|
+
await expectFinished();
|
|
418
|
+
|
|
419
|
+
const data = getData().values()[0];
|
|
420
|
+
expect(data.clicks.length).toBe(2);
|
|
421
|
+
expect(data.clicks[0].correct).toBe(true);
|
|
422
|
+
expect(data.clicks[0].label).toBe("1");
|
|
423
|
+
expect(data.clicks[1].correct).toBe(true);
|
|
424
|
+
expect(data.clicks[1].label).toBe("2");
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it("should export targets in data", async () => {
|
|
428
|
+
const customTargets = [
|
|
429
|
+
{ x: 100, y: 100, label: "1" },
|
|
430
|
+
{ x: 200, y: 200, label: "2" },
|
|
431
|
+
];
|
|
432
|
+
|
|
433
|
+
const { expectFinished, getData, displayElement } = await startTimeline([
|
|
434
|
+
{
|
|
435
|
+
type: jsPsychTrailMaking,
|
|
436
|
+
targets: customTargets,
|
|
437
|
+
},
|
|
438
|
+
]);
|
|
439
|
+
|
|
440
|
+
const canvas = displayElement.querySelector("canvas")!;
|
|
441
|
+
canvas.getBoundingClientRect = jest.fn(() => ({
|
|
442
|
+
left: 0,
|
|
443
|
+
top: 0,
|
|
444
|
+
right: 600,
|
|
445
|
+
bottom: 600,
|
|
446
|
+
width: 600,
|
|
447
|
+
height: 600,
|
|
448
|
+
x: 0,
|
|
449
|
+
y: 0,
|
|
450
|
+
toJSON: () => {},
|
|
451
|
+
}));
|
|
452
|
+
|
|
453
|
+
canvas.dispatchEvent(new MouseEvent("click", { clientX: 100, clientY: 100, bubbles: true }));
|
|
454
|
+
canvas.dispatchEvent(new MouseEvent("click", { clientX: 200, clientY: 200, bubbles: true }));
|
|
455
|
+
|
|
456
|
+
await expectFinished();
|
|
457
|
+
|
|
458
|
+
const data = getData().values()[0];
|
|
459
|
+
expect(data.targets).toEqual(customTargets);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
it("should use custom target colors", async () => {
|
|
463
|
+
arcCalls = [];
|
|
464
|
+
|
|
465
|
+
await startTimeline([
|
|
466
|
+
{
|
|
467
|
+
type: jsPsychTrailMaking,
|
|
468
|
+
target_color: "#ff0000",
|
|
469
|
+
visited_color: "#00ff00",
|
|
470
|
+
num_targets: 3,
|
|
471
|
+
},
|
|
472
|
+
]);
|
|
473
|
+
|
|
474
|
+
// Verify that target circles are drawn with the specified color
|
|
475
|
+
const targetColorCalls = arcCalls.filter((call) => call.fillStyle === "#ff0000");
|
|
476
|
+
expect(targetColorCalls.length).toBeGreaterThan(0);
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
it("should respond to touch events", async () => {
|
|
480
|
+
const customTargets = [
|
|
481
|
+
{ x: 100, y: 100, label: "1" },
|
|
482
|
+
{ x: 200, y: 200, label: "2" },
|
|
483
|
+
];
|
|
484
|
+
|
|
485
|
+
const { expectFinished, displayElement } = await startTimeline([
|
|
486
|
+
{
|
|
487
|
+
type: jsPsychTrailMaking,
|
|
488
|
+
targets: customTargets,
|
|
489
|
+
},
|
|
490
|
+
]);
|
|
491
|
+
|
|
492
|
+
const canvas = displayElement.querySelector("canvas")!;
|
|
493
|
+
canvas.getBoundingClientRect = jest.fn(() => ({
|
|
494
|
+
left: 0,
|
|
495
|
+
top: 0,
|
|
496
|
+
right: 600,
|
|
497
|
+
bottom: 600,
|
|
498
|
+
width: 600,
|
|
499
|
+
height: 600,
|
|
500
|
+
x: 0,
|
|
501
|
+
y: 0,
|
|
502
|
+
toJSON: () => {},
|
|
503
|
+
}));
|
|
504
|
+
|
|
505
|
+
// Create mock touch events using TouchEvent with mocked changedTouches
|
|
506
|
+
const createTouchEvent = (clientX: number, clientY: number) => {
|
|
507
|
+
// Create TouchEvent with empty arrays, then override changedTouches
|
|
508
|
+
const touchEvent = new TouchEvent("touchend", { bubbles: true });
|
|
509
|
+
Object.defineProperty(touchEvent, "changedTouches", {
|
|
510
|
+
value: [{ clientX, clientY }],
|
|
511
|
+
writable: false,
|
|
512
|
+
});
|
|
513
|
+
return touchEvent;
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
canvas.dispatchEvent(createTouchEvent(100, 100));
|
|
517
|
+
canvas.dispatchEvent(createTouchEvent(200, 200));
|
|
518
|
+
|
|
519
|
+
await expectFinished();
|
|
520
|
+
});
|
|
521
|
+
});
|