@prosopo/procaptcha-puzzle 2.14.6 → 2.15.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 +5 -8
- package/.turbo/turbo-build$colon$tsc.log +19 -22
- package/.turbo/turbo-build.log +6 -12
- package/CHANGELOG.md +68 -0
- package/LICENSE +201 -0
- package/dist/cjs/components/procaptchaWidget.cjs +20 -5
- package/dist/cjs/components/puzzleCanvas.cjs +25 -4
- package/dist/cjs/services/Manager.cjs +18 -2
- package/dist/components/procaptchaWidget.d.ts.map +1 -1
- package/dist/components/procaptchaWidget.js +21 -6
- package/dist/components/procaptchaWidget.js.map +1 -1
- package/dist/components/puzzleCanvas.d.ts +2 -1
- package/dist/components/puzzleCanvas.d.ts.map +1 -1
- package/dist/components/puzzleCanvas.js +26 -5
- package/dist/components/puzzleCanvas.js.map +1 -1
- package/dist/services/Manager.d.ts +1 -0
- package/dist/services/Manager.d.ts.map +1 -1
- package/dist/services/Manager.js +18 -2
- package/dist/services/Manager.js.map +1 -1
- package/dist/tests/invisibleCanvasVisibility.unit.test.js +3 -1
- package/dist/tests/invisibleCanvasVisibility.unit.test.js.map +1 -1
- package/dist/tests/manager.unit.test.js +16 -0
- package/dist/tests/manager.unit.test.js.map +1 -1
- package/dist/tests/procaptchaPuzzle.test-d.js +1 -1
- package/dist/tests/procaptchaPuzzle.test-d.js.map +1 -1
- package/dist/tests/procaptchaWidget.unit.test.js +73 -0
- package/dist/tests/procaptchaWidget.unit.test.js.map +1 -1
- package/dist/tests/puzzleCanvas.unit.test.js +33 -0
- package/dist/tests/puzzleCanvas.unit.test.js.map +1 -1
- package/package.json +72 -71
- package/src/components/procaptchaPuzzle.ts +0 -54
- package/src/components/procaptchaWidget.ts +0 -422
- package/src/components/puzzleCanvas.ts +0 -658
- package/src/index.ts +0 -16
- package/src/services/Manager.ts +0 -508
- package/src/tests/domHarness.ts +0 -122
- package/src/tests/invisibleCanvasVisibility.unit.test.ts +0 -179
- package/src/tests/manager.unit.test.ts +0 -822
- package/src/tests/managerHarness.ts +0 -169
- package/src/tests/procaptchaPuzzle.test-d.ts +0 -212
- package/src/tests/procaptchaPuzzle.unit.test.ts +0 -138
- package/src/tests/procaptchaWidget.unit.test.ts +0 -765
- package/src/tests/puzzleCanvas.unit.test.ts +0 -662
- package/tsconfig.cjs.json +0 -43
- package/tsconfig.json +0 -44
- package/tsconfig.tsbuildinfo +0 -1
- package/tsconfig.types.json +0 -9
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
// Copyright 2021-2026 Prosopo (UK) Ltd.
|
|
2
|
-
//
|
|
3
|
-
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
// you may not use this file except in compliance with the License.
|
|
5
|
-
// You may obtain a copy of the License at
|
|
6
|
-
//
|
|
7
|
-
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
//
|
|
9
|
-
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
// See the License for the specific language governing permissions and
|
|
13
|
-
// limitations under the License.
|
|
14
|
-
|
|
15
|
-
import type {
|
|
16
|
-
Account,
|
|
17
|
-
FrictionlessState,
|
|
18
|
-
GetPuzzleCaptchaResponse,
|
|
19
|
-
MouseMovementPoint,
|
|
20
|
-
ProcaptchaCallbacks,
|
|
21
|
-
ProcaptchaClientConfigInput,
|
|
22
|
-
ProcaptchaState,
|
|
23
|
-
PuzzleCaptchaSolutionResponse,
|
|
24
|
-
PuzzleEvent,
|
|
25
|
-
RandomProvider,
|
|
26
|
-
} from "@prosopo/types";
|
|
27
|
-
import { vi } from "vitest";
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Shared fixtures for the puzzle suites. Kept out of the test files so the mock
|
|
31
|
-
* factories, which vitest hoists above every import, can build their canned
|
|
32
|
-
* responses from the same shapes the assertions use.
|
|
33
|
-
*/
|
|
34
|
-
|
|
35
|
-
export const PROVIDER_URL = "https://provider.one";
|
|
36
|
-
export const OTHER_PROVIDER_URL = "https://provider.two";
|
|
37
|
-
export const USER_ADDRESS = "user-address";
|
|
38
|
-
export const SITE_KEY = "site-key";
|
|
39
|
-
|
|
40
|
-
export const config = (
|
|
41
|
-
overrides: Partial<ProcaptchaClientConfigInput> = {},
|
|
42
|
-
): ProcaptchaClientConfigInput => ({
|
|
43
|
-
account: { address: SITE_KEY },
|
|
44
|
-
defaultEnvironment: "production",
|
|
45
|
-
...overrides,
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
export const state = (
|
|
49
|
-
overrides: Partial<ProcaptchaState> = {},
|
|
50
|
-
): ProcaptchaState => ({
|
|
51
|
-
isHuman: false,
|
|
52
|
-
index: 0,
|
|
53
|
-
solutions: [],
|
|
54
|
-
captchaApi: undefined,
|
|
55
|
-
challenge: undefined,
|
|
56
|
-
showModal: false,
|
|
57
|
-
loading: false,
|
|
58
|
-
account: undefined,
|
|
59
|
-
dappAccount: undefined,
|
|
60
|
-
submission: undefined,
|
|
61
|
-
timeout: undefined,
|
|
62
|
-
successfullChallengeTimeout: undefined,
|
|
63
|
-
sendData: false,
|
|
64
|
-
attemptCount: 0,
|
|
65
|
-
error: undefined,
|
|
66
|
-
sessionId: undefined,
|
|
67
|
-
...overrides,
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
export type SignRaw = NonNullable<
|
|
71
|
-
NonNullable<Account["extension"]>["signer"]["signRaw"]
|
|
72
|
-
>;
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* An account carrying just enough of an injected extension to sign: the rest of
|
|
76
|
-
* the interface is never touched by the manager, but the type demands it.
|
|
77
|
-
*/
|
|
78
|
-
export const account = (signRaw?: SignRaw): Account => ({
|
|
79
|
-
account: { address: USER_ADDRESS },
|
|
80
|
-
extension: {
|
|
81
|
-
name: "test-extension",
|
|
82
|
-
version: "0.0.0",
|
|
83
|
-
accounts: {
|
|
84
|
-
get: async () => [{ address: USER_ADDRESS }],
|
|
85
|
-
subscribe: () => () => undefined,
|
|
86
|
-
},
|
|
87
|
-
signer: signRaw ? { signRaw } : {},
|
|
88
|
-
},
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
export const accountWithoutExtension = (): Account => ({
|
|
92
|
-
account: { address: USER_ADDRESS },
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
export const randomProvider = (url: string = PROVIDER_URL): RandomProvider => ({
|
|
96
|
-
providerAccount: "provider-account",
|
|
97
|
-
provider: { url },
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
export const challengeResponse = (
|
|
101
|
-
overrides: Partial<GetPuzzleCaptchaResponse> = {},
|
|
102
|
-
): GetPuzzleCaptchaResponse => ({
|
|
103
|
-
challenge: "0x1___0xdeadbeef___1700000000000",
|
|
104
|
-
background: "data:image/webp;base64,UklGRg==",
|
|
105
|
-
piece: "data:image/webp;base64,UklGRg==",
|
|
106
|
-
pieceSize: 44,
|
|
107
|
-
originX: 20,
|
|
108
|
-
originY: 100,
|
|
109
|
-
timestamp: "1700000000000",
|
|
110
|
-
signature: { provider: { challenge: "0xprovider-challenge" } },
|
|
111
|
-
status: "ok",
|
|
112
|
-
...overrides,
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
export const solutionResponse = (
|
|
116
|
-
overrides: Partial<PuzzleCaptchaSolutionResponse> = {},
|
|
117
|
-
): PuzzleCaptchaSolutionResponse => ({
|
|
118
|
-
verified: true,
|
|
119
|
-
status: "ok",
|
|
120
|
-
...overrides,
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
export const puzzleEvents = (): PuzzleEvent[] => [
|
|
124
|
-
{ x: 20, y: 100, t: 0 },
|
|
125
|
-
{ x: 120, y: 90, t: 120 },
|
|
126
|
-
{ x: 200, y: 80, t: 260 },
|
|
127
|
-
];
|
|
128
|
-
|
|
129
|
-
export const callbacks = (
|
|
130
|
-
overrides: Partial<ProcaptchaCallbacks> = {},
|
|
131
|
-
): ProcaptchaCallbacks => ({ ...overrides });
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* The signer every fixture account uses, so a test can assert on what the
|
|
135
|
-
* manager asked the extension to sign without rebuilding the frictionless
|
|
136
|
-
* state it was handed.
|
|
137
|
-
*/
|
|
138
|
-
export const signRawMock = vi.fn<SignRaw>();
|
|
139
|
-
|
|
140
|
-
export const frictionless = (
|
|
141
|
-
overrides: Partial<FrictionlessState> = {},
|
|
142
|
-
): FrictionlessState => ({
|
|
143
|
-
provider: randomProvider(),
|
|
144
|
-
userAccount: account(signRawMock),
|
|
145
|
-
restart: () => undefined,
|
|
146
|
-
...overrides,
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* A behaviour collector holding a fixed set of points. The manager only ever
|
|
151
|
-
* calls `getData`, but the type demands the whole lifecycle, so the rest are
|
|
152
|
-
* stubs rather than omissions.
|
|
153
|
-
*/
|
|
154
|
-
export const collector = (
|
|
155
|
-
points: MouseMovementPoint[],
|
|
156
|
-
): NonNullable<FrictionlessState["behaviorCollector1"]> => ({
|
|
157
|
-
start: () => undefined,
|
|
158
|
-
stop: () => undefined,
|
|
159
|
-
getData: () => points,
|
|
160
|
-
clear: () => undefined,
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* jsdom's setTimeout hands back a plain number while the shared state types the
|
|
165
|
-
* handle as Node's Timeout, so tests that seed a pending timer have to bridge
|
|
166
|
-
* the two. The value the manager passes to clearTimeout is the number itself.
|
|
167
|
-
*/
|
|
168
|
-
export const timerHandle = (id: number): ReturnType<typeof setTimeout> =>
|
|
169
|
-
id as unknown as ReturnType<typeof setTimeout>;
|
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
// Copyright 2021-2026 Prosopo (UK) Ltd.
|
|
2
|
-
//
|
|
3
|
-
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
// you may not use this file except in compliance with the License.
|
|
5
|
-
// You may obtain a copy of the License at
|
|
6
|
-
//
|
|
7
|
-
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
//
|
|
9
|
-
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
// See the License for the specific language governing permissions and
|
|
13
|
-
// limitations under the License.
|
|
14
|
-
|
|
15
|
-
import type { Ti18n, Translator } from "@prosopo/locale";
|
|
16
|
-
import type {
|
|
17
|
-
FrictionlessState,
|
|
18
|
-
GetPuzzleCaptchaResponse,
|
|
19
|
-
ProcaptchaCallbacks,
|
|
20
|
-
ProcaptchaClientConfigInput,
|
|
21
|
-
ProcaptchaProps,
|
|
22
|
-
ProcaptchaState,
|
|
23
|
-
ProcaptchaStateUpdateFn,
|
|
24
|
-
PuzzleEvent,
|
|
25
|
-
} from "@prosopo/types";
|
|
26
|
-
import { lightTheme } from "@prosopo/widget-skeleton";
|
|
27
|
-
import { assertType, describe, expectTypeOf, test } from "vitest";
|
|
28
|
-
import {
|
|
29
|
-
type PuzzleCanvasProps,
|
|
30
|
-
mountPuzzleCanvas,
|
|
31
|
-
} from "../components/puzzleCanvas.js";
|
|
32
|
-
import type * as entrypoint from "../index.js";
|
|
33
|
-
import { mountProcaptchaPuzzle } from "../index.js";
|
|
34
|
-
import { Manager } from "../services/Manager.js";
|
|
35
|
-
import {
|
|
36
|
-
challengeResponse,
|
|
37
|
-
config,
|
|
38
|
-
frictionless,
|
|
39
|
-
puzzleEvents,
|
|
40
|
-
state,
|
|
41
|
-
} from "./managerHarness.js";
|
|
42
|
-
|
|
43
|
-
/** The widget only ever reads `language`/`changeLanguage` off this. */
|
|
44
|
-
const i18n = (): Ti18n => undefined as unknown as Ti18n;
|
|
45
|
-
|
|
46
|
-
describe("the package entrypoint's types", () => {
|
|
47
|
-
test("mountProcaptchaPuzzle takes a host element and the shared widget props", () => {
|
|
48
|
-
expectTypeOf(mountProcaptchaPuzzle).parameters.toEqualTypeOf<
|
|
49
|
-
[HTMLElement, ProcaptchaProps]
|
|
50
|
-
>();
|
|
51
|
-
expectTypeOf(mountProcaptchaPuzzle).returns.toExtend<{
|
|
52
|
-
destroy: () => void;
|
|
53
|
-
}>();
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test("the entrypoint exposes the lazy wrapper and the widget itself", () => {
|
|
57
|
-
// The lazy wrapper is what works without a code-splitting bundler; the
|
|
58
|
-
// direct mount is what ProcaptchaFrictionless imports once it has already
|
|
59
|
-
// paid for the dynamic import of this package.
|
|
60
|
-
expectTypeOf<keyof typeof entrypoint>().toEqualTypeOf<
|
|
61
|
-
| "mountProcaptchaPuzzle"
|
|
62
|
-
| "loadProcaptchaPuzzle"
|
|
63
|
-
| "mountProcaptchaPuzzleWidget"
|
|
64
|
-
| "mountPuzzleCanvas"
|
|
65
|
-
>();
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("config, callbacks and i18n are all required", () => {
|
|
69
|
-
// @ts-expect-error - a widget with no config has no provider to talk to.
|
|
70
|
-
assertType<ProcaptchaProps>({ callbacks: {}, i18n: i18n() });
|
|
71
|
-
// @ts-expect-error - callbacks decide what a solve reports back.
|
|
72
|
-
assertType<ProcaptchaProps>({ config: config(), i18n: i18n() });
|
|
73
|
-
assertType<ProcaptchaProps>({
|
|
74
|
-
config: config(),
|
|
75
|
-
callbacks: {},
|
|
76
|
-
i18n: i18n(),
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
describe("Manager's types", () => {
|
|
82
|
-
const updateState: ProcaptchaStateUpdateFn = () => undefined;
|
|
83
|
-
const callbacks: ProcaptchaCallbacks = {};
|
|
84
|
-
|
|
85
|
-
test("only the first four arguments are required", () => {
|
|
86
|
-
assertType<ReturnType<typeof Manager>>(
|
|
87
|
-
Manager(config(), state(), updateState, callbacks),
|
|
88
|
-
);
|
|
89
|
-
// @ts-expect-error - callbacks decide what a solve reports back.
|
|
90
|
-
Manager(config(), state(), updateState);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test("the optional arguments keep their positions", () => {
|
|
94
|
-
expectTypeOf(Manager)
|
|
95
|
-
.parameter(4)
|
|
96
|
-
.toEqualTypeOf<FrictionlessState | undefined>();
|
|
97
|
-
expectTypeOf(Manager)
|
|
98
|
-
.parameter(5)
|
|
99
|
-
.toEqualTypeOf<(() => string | undefined) | undefined>();
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
test("it exposes exactly start, submitSolution and resetState", () => {
|
|
103
|
-
expectTypeOf<keyof ReturnType<typeof Manager>>().toEqualTypeOf<
|
|
104
|
-
"start" | "submitSolution" | "resetState"
|
|
105
|
-
>();
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test("start hands back the challenge the canvas needs to draw", () => {
|
|
109
|
-
// Unlike the POW manager, which reports only through state, the puzzle
|
|
110
|
-
// manager returns the challenge — the widget cannot render a board
|
|
111
|
-
// without the origin/target coordinates.
|
|
112
|
-
expectTypeOf<ReturnType<typeof Manager>["start"]>().toEqualTypeOf<
|
|
113
|
-
(x?: number, y?: number) => Promise<GetPuzzleCaptchaResponse | undefined>
|
|
114
|
-
>();
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
test("submitSolution takes the drop point and the full event trail", () => {
|
|
118
|
-
expectTypeOf<
|
|
119
|
-
Parameters<ReturnType<typeof Manager>["submitSolution"]>
|
|
120
|
-
>().toEqualTypeOf<
|
|
121
|
-
[finalX: number, finalY: number, events: PuzzleEvent[]]
|
|
122
|
-
>();
|
|
123
|
-
expectTypeOf<
|
|
124
|
-
ReturnType<ReturnType<typeof Manager>["submitSolution"]>
|
|
125
|
-
>().toEqualTypeOf<Promise<boolean>>();
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
test("resetState takes the frictionless restart callback and nothing else", () => {
|
|
129
|
-
expectTypeOf<
|
|
130
|
-
Parameters<ReturnType<typeof Manager>["resetState"]>
|
|
131
|
-
>().toEqualTypeOf<[frictionlessRestart?: (() => void) | undefined]>();
|
|
132
|
-
expectTypeOf<
|
|
133
|
-
ReturnType<ReturnType<typeof Manager>["resetState"]>
|
|
134
|
-
>().toEqualTypeOf<void>();
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test("coordinates come off DOM events, so they are numbers", () => {
|
|
138
|
-
const manager: ReturnType<typeof Manager> = Manager(
|
|
139
|
-
config(),
|
|
140
|
-
state(),
|
|
141
|
-
updateState,
|
|
142
|
-
callbacks,
|
|
143
|
-
frictionless(),
|
|
144
|
-
);
|
|
145
|
-
// @ts-expect-error - never strings, whatever the DOM stringifies to.
|
|
146
|
-
manager.start("1", "2");
|
|
147
|
-
// @ts-expect-error - the trail is required; an empty drag still sends [].
|
|
148
|
-
manager.submitSolution(1, 2);
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
describe("PuzzleCanvas' types", () => {
|
|
153
|
-
const translator = (): Translator => undefined as unknown as Translator;
|
|
154
|
-
const onComplete = (
|
|
155
|
-
_finalX: number,
|
|
156
|
-
_finalY: number,
|
|
157
|
-
_events: PuzzleEvent[],
|
|
158
|
-
): void => undefined;
|
|
159
|
-
|
|
160
|
-
test("every prop is required, since none has a sensible default", () => {
|
|
161
|
-
// @ts-expect-error - a board with no imagery cannot be rendered.
|
|
162
|
-
mountPuzzleCanvas({ originX: 0, originY: 0 });
|
|
163
|
-
// @ts-expect-error - `submitting` gates the drag; omitting it unlocks it.
|
|
164
|
-
mountPuzzleCanvas({
|
|
165
|
-
originX: 0,
|
|
166
|
-
originY: 0,
|
|
167
|
-
background: "data:image/webp;base64,UklGRg==",
|
|
168
|
-
piece: "data:image/webp;base64,UklGRg==",
|
|
169
|
-
pieceSize: 44,
|
|
170
|
-
onComplete,
|
|
171
|
-
showRetry: false,
|
|
172
|
-
theme: lightTheme,
|
|
173
|
-
translator: translator(),
|
|
174
|
-
});
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
test("the full prop set mounts a component that can be updated and torn down", () => {
|
|
178
|
-
expectTypeOf(
|
|
179
|
-
mountPuzzleCanvas({
|
|
180
|
-
originX: 0,
|
|
181
|
-
originY: 0,
|
|
182
|
-
background: "data:image/webp;base64,UklGRg==",
|
|
183
|
-
piece: "data:image/webp;base64,UklGRg==",
|
|
184
|
-
pieceSize: 44,
|
|
185
|
-
onComplete,
|
|
186
|
-
showRetry: false,
|
|
187
|
-
submitting: false,
|
|
188
|
-
theme: lightTheme,
|
|
189
|
-
translator: translator(),
|
|
190
|
-
}),
|
|
191
|
-
).toExtend<{
|
|
192
|
-
update: (props: PuzzleCanvasProps) => void;
|
|
193
|
-
destroy: () => void;
|
|
194
|
-
}>();
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
test("the drop is reported synchronously, not as a promise", () => {
|
|
198
|
-
// The widget's own handler is async, but the canvas must not await it:
|
|
199
|
-
// a returned promise here would be dropped on the floor.
|
|
200
|
-
expectTypeOf(onComplete).returns.toEqualTypeOf<void>();
|
|
201
|
-
});
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
describe("the harness fixtures match the shared types", () => {
|
|
205
|
-
test("they build the real shapes, not lookalikes", () => {
|
|
206
|
-
expectTypeOf(config()).toEqualTypeOf<ProcaptchaClientConfigInput>();
|
|
207
|
-
expectTypeOf(state()).toEqualTypeOf<ProcaptchaState>();
|
|
208
|
-
expectTypeOf(frictionless()).toEqualTypeOf<FrictionlessState>();
|
|
209
|
-
expectTypeOf(challengeResponse()).toEqualTypeOf<GetPuzzleCaptchaResponse>();
|
|
210
|
-
expectTypeOf(puzzleEvents()).toEqualTypeOf<PuzzleEvent[]>();
|
|
211
|
-
});
|
|
212
|
-
});
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
// Copyright 2021-2026 Prosopo (UK) Ltd.
|
|
2
|
-
//
|
|
3
|
-
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
// you may not use this file except in compliance with the License.
|
|
5
|
-
// You may obtain a copy of the License at
|
|
6
|
-
//
|
|
7
|
-
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
//
|
|
9
|
-
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
// See the License for the specific language governing permissions and
|
|
13
|
-
// limitations under the License.
|
|
14
|
-
|
|
15
|
-
import type { Ti18n } from "@prosopo/locale";
|
|
16
|
-
import type { ProcaptchaProps } from "@prosopo/types";
|
|
17
|
-
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
18
|
-
import type { ProcaptchaPuzzleHandle } from "../components/procaptchaWidget.js";
|
|
19
|
-
import * as entrypoint from "../index.js";
|
|
20
|
-
import { type Mounted, mount, settle } from "./domHarness.js";
|
|
21
|
-
import { config } from "./managerHarness.js";
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* The outer entry point exists only to lazy-load the widget and hand it the
|
|
25
|
-
* props it was given, so what is worth pinning down is that nothing is dropped
|
|
26
|
-
* on the way through — a wrapper that names props one at a time silently loses
|
|
27
|
-
* the ones added later.
|
|
28
|
-
*/
|
|
29
|
-
const mocks = vi.hoisted(() => ({
|
|
30
|
-
received: [] as ProcaptchaProps[],
|
|
31
|
-
destroyed: { count: 0 },
|
|
32
|
-
}));
|
|
33
|
-
|
|
34
|
-
vi.mock("../components/procaptchaWidget.js", () => ({
|
|
35
|
-
mountProcaptchaPuzzleWidget: (
|
|
36
|
-
container: HTMLElement,
|
|
37
|
-
props: ProcaptchaProps,
|
|
38
|
-
): ProcaptchaPuzzleHandle => {
|
|
39
|
-
mocks.received.push(props);
|
|
40
|
-
const element = document.createElement("div");
|
|
41
|
-
element.setAttribute("data-testid", "widget");
|
|
42
|
-
container.appendChild(element);
|
|
43
|
-
return {
|
|
44
|
-
destroy: () => {
|
|
45
|
-
element.remove();
|
|
46
|
-
mocks.destroyed.count += 1;
|
|
47
|
-
},
|
|
48
|
-
};
|
|
49
|
-
},
|
|
50
|
-
}));
|
|
51
|
-
|
|
52
|
-
let mounted: Mounted;
|
|
53
|
-
let widget: ProcaptchaPuzzleHandle | undefined;
|
|
54
|
-
|
|
55
|
-
const props = (overrides: Partial<ProcaptchaProps> = {}): ProcaptchaProps => ({
|
|
56
|
-
config: config(),
|
|
57
|
-
callbacks: {},
|
|
58
|
-
i18n: undefined as unknown as Ti18n,
|
|
59
|
-
...overrides,
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
const render = async (widgetProps: ProcaptchaProps): Promise<void> => {
|
|
63
|
-
widget = entrypoint.mountProcaptchaPuzzle(mounted.container, widgetProps);
|
|
64
|
-
await settle();
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
beforeEach(() => {
|
|
68
|
-
mocks.received.length = 0;
|
|
69
|
-
mocks.destroyed.count = 0;
|
|
70
|
-
widget = undefined;
|
|
71
|
-
mounted = mount();
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
afterEach(() => {
|
|
75
|
-
widget?.destroy();
|
|
76
|
-
widget = undefined;
|
|
77
|
-
mounted.unmount();
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
describe("mountProcaptchaPuzzle", () => {
|
|
81
|
-
test("renders the widget once it has loaded", async () => {
|
|
82
|
-
await render(props());
|
|
83
|
-
expect(
|
|
84
|
-
mounted.container.querySelector('[data-testid="widget"]'),
|
|
85
|
-
).not.toBeNull();
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test("passes on the props the widget needs but never names", async () => {
|
|
89
|
-
const onSessionInvalidated = vi.fn<(x?: number, y?: number) => void>();
|
|
90
|
-
await render(
|
|
91
|
-
props({
|
|
92
|
-
autoStart: true,
|
|
93
|
-
startCoords: { x: 1, y: 2 },
|
|
94
|
-
onSessionInvalidated,
|
|
95
|
-
}),
|
|
96
|
-
);
|
|
97
|
-
expect(mocks.received[0]).toMatchObject({
|
|
98
|
-
autoStart: true,
|
|
99
|
-
startCoords: { x: 1, y: 2 },
|
|
100
|
-
onSessionInvalidated,
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
test("passes on the props it does name, too", async () => {
|
|
105
|
-
const callbacks = { onHuman: vi.fn<(token: string) => void>() };
|
|
106
|
-
await render(props({ callbacks }));
|
|
107
|
-
expect(mocks.received[0]?.callbacks).toBe(callbacks);
|
|
108
|
-
expect(mocks.received[0]?.config).toEqual(config());
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test("tears the loaded widget down", async () => {
|
|
112
|
-
await render(props());
|
|
113
|
-
widget?.destroy();
|
|
114
|
-
widget = undefined;
|
|
115
|
-
expect(mocks.destroyed.count).toBe(1);
|
|
116
|
-
expect(
|
|
117
|
-
mounted.container.querySelector('[data-testid="widget"]'),
|
|
118
|
-
).toBeNull();
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
test("cancels a mount that is still loading", async () => {
|
|
122
|
-
// Otherwise a widget torn down mid-load reappears as an orphan that
|
|
123
|
-
// nothing holds a handle to.
|
|
124
|
-
widget = entrypoint.mountProcaptchaPuzzle(mounted.container, props());
|
|
125
|
-
widget.destroy();
|
|
126
|
-
widget = undefined;
|
|
127
|
-
await settle();
|
|
128
|
-
expect(mocks.received).toHaveLength(0);
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
describe("the package entrypoint", () => {
|
|
133
|
-
test("exports the mount functions a consumer needs", () => {
|
|
134
|
-
expect(typeof entrypoint.mountProcaptchaPuzzle).toBe("function");
|
|
135
|
-
expect(typeof entrypoint.loadProcaptchaPuzzle).toBe("function");
|
|
136
|
-
expect(typeof entrypoint.mountProcaptchaPuzzleWidget).toBe("function");
|
|
137
|
-
});
|
|
138
|
-
});
|