@prosopo/procaptcha-puzzle 2.13.3 → 2.14.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/.turbo/turbo-build$colon$cjs.log +6 -6
- package/.turbo/turbo-build$colon$tsc.log +19 -19
- package/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +41 -0
- package/dist/cjs/components/procaptchaPuzzle.cjs +22 -0
- package/dist/cjs/components/procaptchaWidget.cjs +225 -0
- package/dist/cjs/components/puzzleCanvas.cjs +450 -0
- package/dist/cjs/index.cjs +7 -2
- package/dist/components/procaptchaPuzzle.d.ts +7 -0
- package/dist/components/procaptchaPuzzle.d.ts.map +1 -0
- package/dist/components/procaptchaPuzzle.js +21 -0
- package/dist/components/procaptchaPuzzle.js.map +1 -0
- package/dist/components/procaptchaWidget.d.ts +7 -0
- package/dist/components/procaptchaWidget.d.ts.map +1 -0
- package/dist/components/procaptchaWidget.js +225 -0
- package/dist/components/procaptchaWidget.js.map +1 -0
- package/dist/components/{PuzzleCanvas.d.ts → puzzleCanvas.d.ts} +6 -4
- package/dist/components/puzzleCanvas.d.ts.map +1 -0
- package/dist/components/puzzleCanvas.js +450 -0
- package/dist/components/puzzleCanvas.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/tests/domHarness.d.ts +20 -0
- package/dist/tests/domHarness.d.ts.map +1 -0
- package/dist/tests/domHarness.js +56 -0
- package/dist/tests/domHarness.js.map +1 -0
- package/dist/tests/procaptchaPuzzle.test-d.js +13 -10
- package/dist/tests/procaptchaPuzzle.test-d.js.map +1 -1
- package/dist/tests/procaptchaPuzzle.unit.test.js +44 -33
- package/dist/tests/procaptchaPuzzle.unit.test.js.map +1 -1
- package/dist/tests/procaptchaWidget.unit.test.js +108 -175
- package/dist/tests/procaptchaWidget.unit.test.js.map +1 -1
- package/dist/tests/puzzleCanvas.unit.test.js +47 -80
- package/dist/tests/puzzleCanvas.unit.test.js.map +1 -1
- package/package.json +8 -12
- package/src/components/procaptchaPuzzle.ts +54 -0
- package/src/components/procaptchaWidget.ts +392 -0
- package/src/components/puzzleCanvas.ts +653 -0
- package/src/index.ts +3 -2
- package/src/tests/domHarness.ts +122 -0
- package/src/tests/procaptchaPuzzle.test-d.ts +33 -17
- package/src/tests/procaptchaPuzzle.unit.test.ts +59 -44
- package/src/tests/procaptchaWidget.unit.test.ts +145 -258
- package/src/tests/puzzleCanvas.unit.test.ts +76 -120
- package/tsconfig.cjs.json +1 -2
- package/tsconfig.json +1 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/vite.test.config.ts +3 -4
- package/dist/cjs/components/ProcaptchaPuzzle.cjs +0 -7
- package/dist/cjs/components/ProcaptchaWidget.cjs +0 -206
- package/dist/cjs/components/PuzzleCanvas.cjs +0 -476
- package/dist/components/ProcaptchaPuzzle.d.ts +0 -3
- package/dist/components/ProcaptchaPuzzle.d.ts.map +0 -1
- package/dist/components/ProcaptchaPuzzle.js +0 -7
- package/dist/components/ProcaptchaPuzzle.js.map +0 -1
- package/dist/components/ProcaptchaWidget.d.ts +0 -4
- package/dist/components/ProcaptchaWidget.d.ts.map +0 -1
- package/dist/components/ProcaptchaWidget.js +0 -207
- package/dist/components/ProcaptchaWidget.js.map +0 -1
- package/dist/components/PuzzleCanvas.d.ts.map +0 -1
- package/dist/components/PuzzleCanvas.js +0 -476
- package/dist/components/PuzzleCanvas.js.map +0 -1
- package/dist/tests/setup.d.ts +0 -5
- package/dist/tests/setup.d.ts.map +0 -1
- package/dist/tests/setup.js +0 -3
- package/dist/tests/setup.js.map +0 -1
- package/src/components/ProcaptchaPuzzle.tsx +0 -36
- package/src/components/ProcaptchaWidget.tsx +0 -320
- package/src/components/PuzzleCanvas.tsx +0 -638
- package/src/tests/setup.ts +0 -26
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
// See the License for the specific language governing permissions and
|
|
13
13
|
// limitations under the License.
|
|
14
14
|
|
|
15
|
+
import type { Translator } from "@prosopo/locale";
|
|
16
|
+
import type { Component } from "@prosopo/procaptcha-common";
|
|
15
17
|
import type { PuzzleEvent } from "@prosopo/types";
|
|
16
|
-
import {
|
|
17
|
-
import { type ReactElement, act, createElement } from "react";
|
|
18
|
-
import { type Root, createRoot } from "react-dom/client";
|
|
18
|
+
import { lightTheme } from "@prosopo/widget-skeleton";
|
|
19
19
|
import {
|
|
20
20
|
type Mock,
|
|
21
21
|
afterEach,
|
|
@@ -25,7 +25,10 @@ import {
|
|
|
25
25
|
test,
|
|
26
26
|
vi,
|
|
27
27
|
} from "vitest";
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
type PuzzleCanvasProps,
|
|
30
|
+
mountPuzzleCanvas,
|
|
31
|
+
} from "../components/puzzleCanvas.js";
|
|
29
32
|
|
|
30
33
|
/**
|
|
31
34
|
* The canvas is the only piece of the puzzle flow the user actually touches:
|
|
@@ -34,73 +37,68 @@ import { PuzzleCanvas } from "../components/PuzzleCanvas.js";
|
|
|
34
37
|
* against a real render rather than calling the handlers directly.
|
|
35
38
|
*/
|
|
36
39
|
|
|
40
|
+
const CONTAINER_WIDTH = 300;
|
|
41
|
+
const CONTAINER_HEIGHT = 200;
|
|
42
|
+
const PIECE_SIZE = 44;
|
|
43
|
+
|
|
37
44
|
/**
|
|
38
|
-
* The real
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
45
|
+
* The real translator reaches for an http backend the moment it is asked for a
|
|
46
|
+
* string, which jsdom refuses. The English defaults the canvas ships stand in
|
|
47
|
+
* instead, interpolated the way i18next would, so the assertions below read as
|
|
48
|
+
* the copy a user is actually given.
|
|
42
49
|
*/
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
options?: { defaultValue?: string } & Record<string, unknown>,
|
|
48
|
-
): string =>
|
|
49
|
-
(options?.defaultValue ?? key).replace(
|
|
50
|
+
const translator = (): Translator => ({
|
|
51
|
+
t: (key: string, options?: Record<string, unknown>): string => {
|
|
52
|
+
const template = (options?.defaultValue as string | undefined) ?? key;
|
|
53
|
+
return template.replace(
|
|
50
54
|
/{{(\w+)}}/g,
|
|
51
55
|
(placeholder: string, name: string) =>
|
|
52
56
|
options && name in options ? String(options[name]) : placeholder,
|
|
53
57
|
);
|
|
54
|
-
|
|
58
|
+
},
|
|
59
|
+
isReady: () => true,
|
|
60
|
+
subscribe: () => () => undefined,
|
|
61
|
+
i18n: {} as Translator["i18n"],
|
|
55
62
|
});
|
|
56
63
|
|
|
57
|
-
|
|
58
|
-
const CONTAINER_HEIGHT = 200;
|
|
59
|
-
const PIECE_SIZE = 44;
|
|
60
|
-
|
|
61
|
-
interface CanvasProps {
|
|
62
|
-
originX: number;
|
|
63
|
-
originY: number;
|
|
64
|
-
background: string;
|
|
65
|
-
piece: string;
|
|
66
|
-
pieceSize: number;
|
|
67
|
-
onComplete: Mock<
|
|
68
|
-
(finalX: number, finalY: number, puzzleEvents: PuzzleEvent[]) => void
|
|
69
|
-
>;
|
|
70
|
-
showRetry: boolean;
|
|
71
|
-
submitting: boolean;
|
|
72
|
-
theme: Theme;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
let container: HTMLDivElement;
|
|
76
|
-
let root: Root;
|
|
64
|
+
let canvas: Component<PuzzleCanvasProps> | undefined;
|
|
77
65
|
let onComplete: Mock<
|
|
78
66
|
(finalX: number, finalY: number, puzzleEvents: PuzzleEvent[]) => void
|
|
79
67
|
>;
|
|
80
68
|
|
|
81
|
-
const props = (
|
|
69
|
+
const props = (
|
|
70
|
+
overrides: Partial<PuzzleCanvasProps> = {},
|
|
71
|
+
): PuzzleCanvasProps => ({
|
|
82
72
|
originX: 20,
|
|
83
73
|
originY: 100,
|
|
84
74
|
background: "data:image/webp;base64,UklGRg==",
|
|
85
75
|
piece: "data:image/webp;base64,UklGRg==",
|
|
86
|
-
pieceSize:
|
|
76
|
+
pieceSize: PIECE_SIZE,
|
|
87
77
|
onComplete,
|
|
88
78
|
showRetry: false,
|
|
89
79
|
submitting: false,
|
|
90
80
|
theme: lightTheme,
|
|
81
|
+
translator: translator(),
|
|
91
82
|
...overrides,
|
|
92
83
|
});
|
|
93
84
|
|
|
94
|
-
const render = (canvasProps:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
85
|
+
const render = (canvasProps: PuzzleCanvasProps): void => {
|
|
86
|
+
if (canvas) {
|
|
87
|
+
canvas.update(canvasProps);
|
|
88
|
+
} else {
|
|
89
|
+
canvas = mountPuzzleCanvas(canvasProps);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const destroy = (): void => {
|
|
94
|
+
canvas?.destroy();
|
|
95
|
+
canvas = undefined;
|
|
98
96
|
};
|
|
99
97
|
|
|
100
98
|
/**
|
|
101
|
-
* The canvas
|
|
102
|
-
*
|
|
103
|
-
*
|
|
99
|
+
* The canvas puts itself on the body — it has to escape the query container
|
|
100
|
+
* the widget skeleton wraps it in — so everything that reads the rendered
|
|
101
|
+
* output reads the body.
|
|
104
102
|
*/
|
|
105
103
|
const overlay = (): HTMLElement => document.body;
|
|
106
104
|
|
|
@@ -119,25 +117,19 @@ const piecePosition = (): { x: number; y: number } => ({
|
|
|
119
117
|
});
|
|
120
118
|
|
|
121
119
|
const mouseDown = (clientX: number, clientY: number): void => {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
);
|
|
126
|
-
});
|
|
120
|
+
piece().dispatchEvent(
|
|
121
|
+
new MouseEvent("mousedown", { bubbles: true, clientX, clientY }),
|
|
122
|
+
);
|
|
127
123
|
};
|
|
128
124
|
|
|
129
125
|
const mouseMove = (clientX: number, clientY: number): void => {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
);
|
|
134
|
-
});
|
|
126
|
+
document.dispatchEvent(
|
|
127
|
+
new MouseEvent("mousemove", { bubbles: true, clientX, clientY }),
|
|
128
|
+
);
|
|
135
129
|
};
|
|
136
130
|
|
|
137
131
|
const mouseUp = (): void => {
|
|
138
|
-
|
|
139
|
-
document.dispatchEvent(new MouseEvent("mouseup", { bubbles: true }));
|
|
140
|
-
});
|
|
132
|
+
document.dispatchEvent(new MouseEvent("mouseup", { bubbles: true }));
|
|
141
133
|
};
|
|
142
134
|
|
|
143
135
|
/**
|
|
@@ -154,34 +146,26 @@ const touchEvent = (
|
|
|
154
146
|
};
|
|
155
147
|
|
|
156
148
|
const touchStart = (touches: { clientX: number; clientY: number }[]): void => {
|
|
157
|
-
|
|
158
|
-
piece().dispatchEvent(touchEvent("touchstart", touches));
|
|
159
|
-
});
|
|
149
|
+
piece().dispatchEvent(touchEvent("touchstart", touches));
|
|
160
150
|
};
|
|
161
151
|
|
|
162
152
|
const touchMove = (touches: { clientX: number; clientY: number }[]): void => {
|
|
163
|
-
|
|
164
|
-
document.dispatchEvent(touchEvent("touchmove", touches));
|
|
165
|
-
});
|
|
153
|
+
document.dispatchEvent(touchEvent("touchmove", touches));
|
|
166
154
|
};
|
|
167
155
|
|
|
168
156
|
const touchEnd = (): void => {
|
|
169
|
-
|
|
170
|
-
document.dispatchEvent(touchEvent("touchend", []));
|
|
171
|
-
});
|
|
157
|
+
document.dispatchEvent(touchEvent("touchend", []));
|
|
172
158
|
};
|
|
173
159
|
|
|
174
160
|
const keyDown = (key: string, options: { shiftKey?: boolean } = {}): void => {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
);
|
|
184
|
-
});
|
|
161
|
+
piece().dispatchEvent(
|
|
162
|
+
new KeyboardEvent("keydown", {
|
|
163
|
+
key,
|
|
164
|
+
bubbles: true,
|
|
165
|
+
cancelable: true,
|
|
166
|
+
shiftKey: options.shiftKey ?? false,
|
|
167
|
+
}),
|
|
168
|
+
);
|
|
185
169
|
};
|
|
186
170
|
|
|
187
171
|
/**
|
|
@@ -222,18 +206,11 @@ beforeEach(() => {
|
|
|
222
206
|
vi.fn<
|
|
223
207
|
(finalX: number, finalY: number, puzzleEvents: PuzzleEvent[]) => void
|
|
224
208
|
>();
|
|
225
|
-
|
|
226
|
-
document.body.appendChild(container);
|
|
227
|
-
act(() => {
|
|
228
|
-
root = createRoot(container);
|
|
229
|
-
});
|
|
209
|
+
canvas = undefined;
|
|
230
210
|
});
|
|
231
211
|
|
|
232
212
|
afterEach(() => {
|
|
233
|
-
|
|
234
|
-
root.unmount();
|
|
235
|
-
});
|
|
236
|
-
container.remove();
|
|
213
|
+
destroy();
|
|
237
214
|
vi.useRealTimers();
|
|
238
215
|
vi.unstubAllGlobals();
|
|
239
216
|
vi.restoreAllMocks();
|
|
@@ -291,9 +268,7 @@ describe("what it puts on screen", () => {
|
|
|
291
268
|
test("the shake on a retry stops on its own", () => {
|
|
292
269
|
vi.useFakeTimers();
|
|
293
270
|
render(props({ showRetry: true }));
|
|
294
|
-
|
|
295
|
-
vi.advanceTimersByTime(600);
|
|
296
|
-
});
|
|
271
|
+
vi.advanceTimersByTime(600);
|
|
297
272
|
// Nothing to assert beyond survival: the timer fires into a live
|
|
298
273
|
// component rather than leaking past the shake.
|
|
299
274
|
expect(piecePosition()).toEqual({ x: 20, y: 100 });
|
|
@@ -302,15 +277,8 @@ describe("what it puts on screen", () => {
|
|
|
302
277
|
test("unmounting mid-shake cancels the timer", () => {
|
|
303
278
|
vi.useFakeTimers();
|
|
304
279
|
render(props({ showRetry: true }));
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
});
|
|
308
|
-
act(() => {
|
|
309
|
-
vi.advanceTimersByTime(600);
|
|
310
|
-
});
|
|
311
|
-
act(() => {
|
|
312
|
-
root = createRoot(container);
|
|
313
|
-
});
|
|
280
|
+
destroy();
|
|
281
|
+
vi.advanceTimersByTime(600);
|
|
314
282
|
expect(
|
|
315
283
|
overlay().querySelector('[data-cy="prosopo-puzzle-piece"]'),
|
|
316
284
|
).toBeNull();
|
|
@@ -366,7 +334,7 @@ describe("dragging with a mouse", () => {
|
|
|
366
334
|
const [finalX, finalY, events] = onComplete.mock.calls[0] ?? [];
|
|
367
335
|
expect(finalX).toBe(200);
|
|
368
336
|
expect(finalY).toBe(80);
|
|
369
|
-
expect(events?.map((event) => [event.x, event.y])).toEqual([
|
|
337
|
+
expect(events?.map((event: PuzzleEvent) => [event.x, event.y])).toEqual([
|
|
370
338
|
[100, 95],
|
|
371
339
|
[200, 80],
|
|
372
340
|
]);
|
|
@@ -412,7 +380,9 @@ describe("dragging with a mouse", () => {
|
|
|
412
380
|
mouseMove(200, 80);
|
|
413
381
|
mouseUp();
|
|
414
382
|
const events = onComplete.mock.calls[1]?.[2];
|
|
415
|
-
expect(events?.map((event) => [event.x, event.y])).toEqual([
|
|
383
|
+
expect(events?.map((event: PuzzleEvent) => [event.x, event.y])).toEqual([
|
|
384
|
+
[200, 80],
|
|
385
|
+
]);
|
|
416
386
|
});
|
|
417
387
|
|
|
418
388
|
test("the trail is timestamped in order", () => {
|
|
@@ -586,11 +556,9 @@ describe("driving it from the keyboard", () => {
|
|
|
586
556
|
|
|
587
557
|
test("tab cannot leave the dialog for the page behind it", () => {
|
|
588
558
|
render(props());
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
);
|
|
593
|
-
});
|
|
559
|
+
document.dispatchEvent(
|
|
560
|
+
new KeyboardEvent("keydown", { key: "Tab", bubbles: true }),
|
|
561
|
+
);
|
|
594
562
|
expect(document.activeElement).toBe(piece());
|
|
595
563
|
});
|
|
596
564
|
|
|
@@ -602,15 +570,10 @@ describe("driving it from the keyboard", () => {
|
|
|
602
570
|
render(props());
|
|
603
571
|
expect(document.activeElement).toBe(piece());
|
|
604
572
|
|
|
605
|
-
|
|
606
|
-
root.unmount();
|
|
607
|
-
});
|
|
573
|
+
destroy();
|
|
608
574
|
expect(document.activeElement).toBe(opener);
|
|
609
575
|
|
|
610
576
|
opener.remove();
|
|
611
|
-
act(() => {
|
|
612
|
-
root = createRoot(container);
|
|
613
|
-
});
|
|
614
577
|
});
|
|
615
578
|
});
|
|
616
579
|
|
|
@@ -647,9 +610,7 @@ describe("what it says to a screen reader", () => {
|
|
|
647
610
|
keyDown("ArrowRight");
|
|
648
611
|
keyDown("ArrowRight");
|
|
649
612
|
expect(liveRegion().textContent).toBe("7 percent across, 50 percent down");
|
|
650
|
-
|
|
651
|
-
vi.advanceTimersByTime(500);
|
|
652
|
-
});
|
|
613
|
+
vi.advanceTimersByTime(500);
|
|
653
614
|
expect(liveRegion().textContent).toBe("13 percent across, 50 percent down");
|
|
654
615
|
});
|
|
655
616
|
|
|
@@ -677,14 +638,9 @@ describe("after it goes away", () => {
|
|
|
677
638
|
test("its document listeners go with it", () => {
|
|
678
639
|
render(props());
|
|
679
640
|
mouseDown(20, 100);
|
|
680
|
-
|
|
681
|
-
root.unmount();
|
|
682
|
-
});
|
|
641
|
+
destroy();
|
|
683
642
|
mouseMove(150, 90);
|
|
684
643
|
mouseUp();
|
|
685
644
|
expect(onComplete).not.toHaveBeenCalled();
|
|
686
|
-
act(() => {
|
|
687
|
-
root = createRoot(container);
|
|
688
|
-
});
|
|
689
645
|
});
|
|
690
646
|
});
|
package/tsconfig.cjs.json
CHANGED