@prosopo/procaptcha-common 2.9.19 → 2.10.17

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.
Files changed (107) hide show
  1. package/.turbo/turbo-build$colon$cjs.log +22 -15
  2. package/.turbo/turbo-build$colon$tsc.log +52 -0
  3. package/.turbo/turbo-build.log +26 -16
  4. package/CHANGELOG.md +392 -0
  5. package/dist/callbacks/defaultCallbacks.d.ts +4 -0
  6. package/dist/callbacks/defaultCallbacks.d.ts.map +1 -0
  7. package/dist/callbacks/defaultCallbacks.js.map +1 -0
  8. package/dist/callbacks/defaultEvents.d.ts +14 -0
  9. package/dist/callbacks/defaultEvents.d.ts.map +1 -0
  10. package/dist/callbacks/defaultEvents.js.map +1 -0
  11. package/dist/cjs/elements/form.cjs +8 -2
  12. package/dist/cjs/elements/window.cjs +7 -0
  13. package/dist/cjs/index.cjs +5 -0
  14. package/dist/cjs/reactComponents/Checkbox.cjs +11 -7
  15. package/dist/cjs/reactComponents/Honeypot.cjs +67 -0
  16. package/dist/cjs/reactComponents/TestModeBanner.cjs +47 -0
  17. package/dist/elements/form.d.ts +5 -0
  18. package/dist/elements/form.d.ts.map +1 -0
  19. package/dist/elements/form.js +8 -2
  20. package/dist/elements/form.js.map +1 -0
  21. package/dist/elements/window.d.ts +3 -0
  22. package/dist/elements/window.d.ts.map +1 -0
  23. package/dist/elements/window.js +8 -1
  24. package/dist/elements/window.js.map +1 -0
  25. package/dist/extensionLoader.d.ts +2 -0
  26. package/dist/extensionLoader.d.ts.map +1 -0
  27. package/dist/extensionLoader.js.map +1 -0
  28. package/dist/index.d.ts +11 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +6 -1
  31. package/dist/index.js.map +1 -0
  32. package/dist/providers.d.ts +4 -0
  33. package/dist/providers.d.ts.map +1 -0
  34. package/dist/providers.js.map +1 -0
  35. package/dist/reactComponents/Checkbox.d.ts +13 -0
  36. package/dist/reactComponents/Checkbox.d.ts.map +1 -0
  37. package/dist/reactComponents/Checkbox.js +11 -7
  38. package/dist/reactComponents/Checkbox.js.map +1 -0
  39. package/dist/reactComponents/Honeypot.d.ts +6 -0
  40. package/dist/reactComponents/Honeypot.d.ts.map +1 -0
  41. package/dist/reactComponents/Honeypot.js +67 -0
  42. package/dist/reactComponents/Honeypot.js.map +1 -0
  43. package/dist/reactComponents/Reload.d.ts +8 -0
  44. package/dist/reactComponents/Reload.d.ts.map +1 -0
  45. package/dist/reactComponents/Reload.js.map +1 -0
  46. package/dist/reactComponents/TestModeBanner.d.ts +7 -0
  47. package/dist/reactComponents/TestModeBanner.d.ts.map +1 -0
  48. package/dist/reactComponents/TestModeBanner.js +47 -0
  49. package/dist/reactComponents/TestModeBanner.js.map +1 -0
  50. package/dist/state/builder.d.ts +9 -0
  51. package/dist/state/builder.d.ts.map +1 -0
  52. package/dist/state/builder.js.map +1 -0
  53. package/dist/tests/defaultCallbacks.test.d.ts +2 -0
  54. package/dist/tests/defaultCallbacks.test.d.ts.map +1 -0
  55. package/dist/tests/defaultCallbacks.test.js +219 -0
  56. package/dist/tests/defaultCallbacks.test.js.map +1 -0
  57. package/dist/tests/defaultEvents.test.d.ts +2 -0
  58. package/dist/tests/defaultEvents.test.d.ts.map +1 -0
  59. package/dist/tests/defaultEvents.test.js +54 -0
  60. package/dist/tests/defaultEvents.test.js.map +1 -0
  61. package/dist/tests/extensionLoader.test.d.ts +2 -0
  62. package/dist/tests/extensionLoader.test.d.ts.map +1 -0
  63. package/dist/tests/extensionLoader.test.js +21 -0
  64. package/dist/tests/extensionLoader.test.js.map +1 -0
  65. package/dist/tests/form.test.d.ts +2 -0
  66. package/dist/tests/form.test.d.ts.map +1 -0
  67. package/dist/tests/form.test.js +98 -0
  68. package/dist/tests/form.test.js.map +1 -0
  69. package/dist/tests/providers.test.d.ts +2 -0
  70. package/dist/tests/providers.test.d.ts.map +1 -0
  71. package/dist/tests/providers.test.js +111 -0
  72. package/dist/tests/providers.test.js.map +1 -0
  73. package/dist/tests/state-builder.test.d.ts +2 -0
  74. package/dist/tests/state-builder.test.d.ts.map +1 -0
  75. package/dist/tests/state-builder.test.js +193 -0
  76. package/dist/tests/state-builder.test.js.map +1 -0
  77. package/dist/tests/window.test.d.ts +2 -0
  78. package/dist/tests/window.test.d.ts.map +1 -0
  79. package/dist/tests/window.test.js +80 -0
  80. package/dist/tests/window.test.js.map +1 -0
  81. package/package.json +13 -9
  82. package/src/callbacks/defaultCallbacks.ts +197 -0
  83. package/src/callbacks/defaultEvents.ts +21 -0
  84. package/src/elements/form.ts +41 -0
  85. package/src/elements/window.ts +39 -0
  86. package/src/extensionLoader.ts +17 -0
  87. package/src/index.ts +24 -0
  88. package/src/providers.ts +49 -0
  89. package/src/reactComponents/Checkbox.tsx +203 -0
  90. package/src/reactComponents/Honeypot.tsx +133 -0
  91. package/src/reactComponents/Reload.tsx +99 -0
  92. package/src/reactComponents/TestModeBanner.tsx +75 -0
  93. package/src/state/builder.ts +137 -0
  94. package/src/tests/defaultCallbacks.test.ts +372 -0
  95. package/src/tests/defaultEvents.test.ts +80 -0
  96. package/src/tests/extensionLoader.test.ts +41 -0
  97. package/src/tests/form.test.ts +154 -0
  98. package/src/tests/providers.test.ts +175 -0
  99. package/src/tests/state-builder.test.ts +264 -0
  100. package/src/tests/window.test.ts +137 -0
  101. package/tsconfig.cjs.json +32 -0
  102. package/tsconfig.json +33 -0
  103. package/tsconfig.tsbuildinfo +1 -0
  104. package/tsconfig.types.json +9 -0
  105. package/vite.cjs.config.ts +1 -1
  106. package/vite.esm.config.ts +1 -1
  107. package/vite.test.config.ts +1 -1
@@ -0,0 +1,137 @@
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
+ import type {
15
+ Account,
16
+ CaptchaResponseBody,
17
+ ProcaptchaApiInterface,
18
+ ProcaptchaState,
19
+ ProcaptchaStateUpdateFn,
20
+ TCaptchaSubmitResult,
21
+ } from "@prosopo/types";
22
+
23
+ type useRefType = <T>(defaultValue: T) => { current: T };
24
+ type useStateType = <T>(defaultValue: T) => [T, (value: T) => void];
25
+
26
+ export const buildUpdateState =
27
+ (state: ProcaptchaState, onStateUpdate: ProcaptchaStateUpdateFn) =>
28
+ (nextState: Partial<ProcaptchaState>) => {
29
+ // mutate the current state. Note that this is in order of properties in the nextState object.
30
+ // e.g. given {b: 2, c: 3, a: 1}, b will be set, then c, then a. This is because JS stores fields in insertion order by default, unless you override it with a class or such by changing the key enumeration order.
31
+ Object.assign(state, nextState);
32
+ // then call the update function for the frontend to do the same
33
+ onStateUpdate(nextState);
34
+ };
35
+
36
+ /**
37
+ * Wrap a ref to be the same format as useState.
38
+ * @param useRef the useRef function from react
39
+ * @param defaultValue the default value if the state is not already initialised
40
+ * @returns a ref in the same format as a state, e.g. [value, setValue]
41
+ */
42
+ const useRefAsState = <T>(
43
+ useRef: useRefType,
44
+ defaultValue: T,
45
+ ): [T, (value: T) => void] => {
46
+ const ref = useRef<T>(defaultValue);
47
+ const setter = (value: T) => {
48
+ ref.current = value;
49
+ };
50
+ const value: T = ref.current;
51
+ return [value, setter];
52
+ };
53
+
54
+ export const useProcaptcha = (
55
+ useState: useStateType,
56
+ useRef: useRefType,
57
+ ): [ProcaptchaState, ProcaptchaStateUpdateFn] => {
58
+ const [isHuman, setIsHuman] = useState(false);
59
+ const [index, setIndex] = useState(0);
60
+ const [solutions, setSolutions] = useState(
61
+ [] as [string, number, number][][],
62
+ );
63
+ const [captchaApi, setCaptchaApi] = useRefAsState<
64
+ ProcaptchaApiInterface | undefined
65
+ >(useRef, undefined);
66
+ const [showModal, setShowModal] = useState(false);
67
+ const [challenge, setChallenge] = useState<CaptchaResponseBody | undefined>(
68
+ undefined,
69
+ );
70
+ const [loading, setLoading] = useState(false);
71
+ const [account, setAccount] = useState<Account | undefined>(undefined);
72
+ const [dappAccount, setDappAccount] = useState<string | undefined>(undefined);
73
+ const [submission, setSubmission] = useRefAsState<
74
+ TCaptchaSubmitResult | undefined
75
+ >(useRef, undefined);
76
+ const [timeout, setTimeout] = useRefAsState<NodeJS.Timeout | undefined>(
77
+ useRef,
78
+ undefined,
79
+ );
80
+ const [successfullChallengeTimeout, setSuccessfullChallengeTimeout] =
81
+ useRefAsState<NodeJS.Timeout | undefined>(useRef, undefined);
82
+ const [sendData, setSendData] = useState(false);
83
+ const [attemptCount, setAttemptCount] = useState(0);
84
+ const [error, setError] = useState<
85
+ { message: string; key: string } | undefined
86
+ >(undefined);
87
+ const [sessionId, setSessionId] = useState<string | undefined>(undefined);
88
+ return [
89
+ // the state
90
+ {
91
+ isHuman,
92
+ index,
93
+ solutions,
94
+ captchaApi,
95
+ showModal,
96
+ challenge,
97
+ loading,
98
+ account,
99
+ dappAccount,
100
+ submission,
101
+ timeout,
102
+ successfullChallengeTimeout,
103
+ sendData,
104
+ attemptCount,
105
+ error,
106
+ sessionId,
107
+ },
108
+ // and method to update the state
109
+ (nextState: Partial<ProcaptchaState>) => {
110
+ if (nextState.account !== undefined) setAccount(nextState.account);
111
+ if (nextState.isHuman !== undefined) setIsHuman(nextState.isHuman);
112
+ if (nextState.index !== undefined) setIndex(nextState.index);
113
+ // force a copy of the array to ensure a re-render
114
+ // nutshell: react doesn't look inside an array for changes, hence changes to the array need to result in a fresh array
115
+ if (nextState.solutions !== undefined)
116
+ setSolutions(nextState.solutions.slice());
117
+ if (nextState.captchaApi !== undefined)
118
+ setCaptchaApi(nextState.captchaApi);
119
+ if (nextState.showModal !== undefined) setShowModal(nextState.showModal);
120
+ if (nextState.challenge !== undefined) setChallenge(nextState.challenge);
121
+ if (nextState.loading !== undefined) setLoading(nextState.loading);
122
+ if (nextState.showModal !== undefined) setShowModal(nextState.showModal);
123
+ if (nextState.dappAccount !== undefined)
124
+ setDappAccount(nextState.dappAccount);
125
+ if (nextState.submission !== undefined)
126
+ setSubmission(nextState.submission);
127
+ if (nextState.timeout !== undefined) setTimeout(nextState.timeout);
128
+ if (nextState.successfullChallengeTimeout !== undefined)
129
+ setSuccessfullChallengeTimeout(nextState.timeout);
130
+ if (nextState.sendData !== undefined) setSendData(nextState.sendData);
131
+ if (nextState.attemptCount !== undefined)
132
+ setAttemptCount(nextState.attemptCount);
133
+ if (nextState.error !== undefined) setError(nextState.error);
134
+ if (nextState.sessionId !== undefined) setSessionId(nextState.sessionId);
135
+ },
136
+ ];
137
+ };
@@ -0,0 +1,372 @@
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 { ApiParams } from "@prosopo/types";
16
+ import type { ProcaptchaRenderOptions, ProcaptchaToken } from "@prosopo/types";
17
+ import { beforeEach, describe, expect, it, vi } from "vitest";
18
+ import {
19
+ getDefaultCallbacks,
20
+ setUserCallbacks,
21
+ } from "../callbacks/defaultCallbacks.js";
22
+
23
+ describe("callbacks/defaultCallbacks", () => {
24
+ beforeEach(() => {
25
+ document.body.innerHTML = "";
26
+ vi.clearAllMocks();
27
+ });
28
+
29
+ describe("getDefaultCallbacks", () => {
30
+ it("should return default callbacks object", () => {
31
+ const callbacks = getDefaultCallbacks();
32
+
33
+ expect(callbacks).toHaveProperty("onHuman");
34
+ expect(callbacks).toHaveProperty("onChallengeExpired");
35
+ expect(callbacks).toHaveProperty("onExtensionNotFound");
36
+ expect(callbacks).toHaveProperty("onExpired");
37
+ expect(callbacks).toHaveProperty("onError");
38
+ expect(callbacks).toHaveProperty("onClose");
39
+ expect(callbacks).toHaveProperty("onOpen");
40
+ expect(callbacks).toHaveProperty("onFailed");
41
+ expect(callbacks).toHaveProperty("onReset");
42
+ expect(callbacks).toHaveProperty("onReload");
43
+
44
+ expect(typeof callbacks.onHuman).toBe("function");
45
+ expect(typeof callbacks.onChallengeExpired).toBe("function");
46
+ expect(typeof callbacks.onError).toBe("function");
47
+ });
48
+
49
+ it("onHuman should add hidden input to form with token", () => {
50
+ const form = document.createElement("form");
51
+ const widget = document.createElement("div");
52
+ form.appendChild(widget);
53
+ document.body.appendChild(form);
54
+
55
+ const callbacks = getDefaultCallbacks(widget);
56
+ const token = "test-token-123" as ProcaptchaToken;
57
+
58
+ callbacks.onHuman(token);
59
+
60
+ const inputs = form.querySelectorAll(
61
+ `input[name="${ApiParams.procaptchaResponse}"]`,
62
+ );
63
+ expect(inputs.length).toBe(1);
64
+ expect((inputs[0] as HTMLInputElement).value).toBe(token);
65
+ expect((inputs[0] as HTMLInputElement).type).toBe("hidden");
66
+ });
67
+
68
+ it("onHuman should handle missing parent form gracefully", () => {
69
+ const widget = document.createElement("div");
70
+ document.body.appendChild(widget);
71
+
72
+ const consoleErrorSpy = vi
73
+ .spyOn(console, "error")
74
+ .mockImplementation(() => {});
75
+
76
+ const callbacks = getDefaultCallbacks(widget);
77
+ const token = "test-token-123" as ProcaptchaToken;
78
+
79
+ callbacks.onHuman(token);
80
+
81
+ expect(consoleErrorSpy).toHaveBeenCalledWith(
82
+ "Parent form not found for the element:",
83
+ widget,
84
+ );
85
+
86
+ consoleErrorSpy.mockRestore();
87
+ });
88
+
89
+ it("onChallengeExpired should remove procaptcha response", () => {
90
+ const input = document.createElement("input");
91
+ input.name = ApiParams.procaptchaResponse;
92
+ document.body.appendChild(input);
93
+
94
+ const callbacks = getDefaultCallbacks();
95
+ callbacks.onChallengeExpired();
96
+
97
+ expect(
98
+ document.getElementsByName(ApiParams.procaptchaResponse).length,
99
+ ).toBe(0);
100
+ });
101
+
102
+ it("onExpired should remove procaptcha response", () => {
103
+ const input = document.createElement("input");
104
+ input.name = ApiParams.procaptchaResponse;
105
+ document.body.appendChild(input);
106
+
107
+ const callbacks = getDefaultCallbacks();
108
+ callbacks.onExpired();
109
+
110
+ expect(
111
+ document.getElementsByName(ApiParams.procaptchaResponse).length,
112
+ ).toBe(0);
113
+ });
114
+
115
+ it("onError should remove procaptcha response and log error", () => {
116
+ const input = document.createElement("input");
117
+ input.name = ApiParams.procaptchaResponse;
118
+ document.body.appendChild(input);
119
+
120
+ const consoleErrorSpy = vi
121
+ .spyOn(console, "error")
122
+ .mockImplementation(() => {});
123
+ const callbacks = getDefaultCallbacks();
124
+ const error = new Error("Test error");
125
+
126
+ callbacks.onError(error);
127
+
128
+ expect(
129
+ document.getElementsByName(ApiParams.procaptchaResponse).length,
130
+ ).toBe(0);
131
+ expect(consoleErrorSpy).toHaveBeenCalledWith(error);
132
+
133
+ consoleErrorSpy.mockRestore();
134
+ });
135
+
136
+ it("onFailed should show alert", () => {
137
+ const alertSpy = vi.spyOn(window, "alert").mockImplementation(() => {});
138
+ const callbacks = getDefaultCallbacks();
139
+
140
+ callbacks.onFailed();
141
+
142
+ expect(alertSpy).toHaveBeenCalledWith(
143
+ "Captcha challenge failed. Please try again",
144
+ );
145
+
146
+ alertSpy.mockRestore();
147
+ });
148
+
149
+ it("onReset should remove procaptcha response", () => {
150
+ const input = document.createElement("input");
151
+ input.name = ApiParams.procaptchaResponse;
152
+ document.body.appendChild(input);
153
+
154
+ const callbacks = getDefaultCallbacks();
155
+ callbacks.onReset();
156
+
157
+ expect(
158
+ document.getElementsByName(ApiParams.procaptchaResponse).length,
159
+ ).toBe(0);
160
+ });
161
+ });
162
+
163
+ describe("setUserCallbacks", () => {
164
+ it("should wrap user callback for onHuman", () => {
165
+ const form = document.createElement("form");
166
+ const widget = document.createElement("div");
167
+ form.appendChild(widget);
168
+ document.body.appendChild(form);
169
+
170
+ const userCallback = vi.fn();
171
+ const renderOptions: Partial<ProcaptchaRenderOptions> = {
172
+ callback: userCallback,
173
+ };
174
+
175
+ const callbacks = getDefaultCallbacks(widget);
176
+ setUserCallbacks(
177
+ renderOptions as ProcaptchaRenderOptions,
178
+ callbacks,
179
+ widget,
180
+ );
181
+
182
+ const token = "test-token" as ProcaptchaToken;
183
+ callbacks.onHuman(token);
184
+
185
+ expect(userCallback).toHaveBeenCalledWith(token);
186
+ // Also verify that default behavior (adding to form) still happens
187
+ const inputs = form.querySelectorAll(
188
+ `input[name="${ApiParams.procaptchaResponse}"]`,
189
+ );
190
+ expect(inputs.length).toBeGreaterThan(0);
191
+ });
192
+
193
+ it("should use data attribute callback over renderOptions", () => {
194
+ const form = document.createElement("form");
195
+ const widget = document.createElement("div");
196
+ form.appendChild(widget);
197
+ document.body.appendChild(form);
198
+
199
+ const dataCallback = vi.fn();
200
+ const renderCallback = vi.fn();
201
+
202
+ // biome-ignore lint/suspicious/noExplicitAny: Test setup
203
+ (window as any).dataCallbackFn = dataCallback;
204
+ widget.setAttribute("data-callback", "dataCallbackFn");
205
+
206
+ const renderOptions: Partial<ProcaptchaRenderOptions> = {
207
+ callback: renderCallback,
208
+ };
209
+
210
+ const callbacks = getDefaultCallbacks(widget);
211
+ setUserCallbacks(
212
+ renderOptions as ProcaptchaRenderOptions,
213
+ callbacks,
214
+ widget,
215
+ );
216
+
217
+ const token = "test-token" as ProcaptchaToken;
218
+ callbacks.onHuman(token);
219
+
220
+ expect(dataCallback).toHaveBeenCalledWith(token);
221
+ expect(renderCallback).not.toHaveBeenCalled();
222
+
223
+ // biome-ignore lint/suspicious/noExplicitAny: Test cleanup
224
+ (window as any).dataCallbackFn = undefined;
225
+ });
226
+
227
+ it("should handle string callback name in renderOptions", () => {
228
+ const form = document.createElement("form");
229
+ const widget = document.createElement("div");
230
+ form.appendChild(widget);
231
+ document.body.appendChild(form);
232
+
233
+ const namedCallback = vi.fn();
234
+ // biome-ignore lint/suspicious/noExplicitAny: Test setup
235
+ (window as any).myCallback = namedCallback;
236
+
237
+ const renderOptions: Partial<ProcaptchaRenderOptions> = {
238
+ // biome-ignore lint/suspicious/noExplicitAny: Test string callback
239
+ callback: "myCallback" as any,
240
+ };
241
+
242
+ const callbacks = getDefaultCallbacks(widget);
243
+ setUserCallbacks(
244
+ renderOptions as ProcaptchaRenderOptions,
245
+ callbacks,
246
+ widget,
247
+ );
248
+
249
+ const token = "test-token" as ProcaptchaToken;
250
+ callbacks.onHuman(token);
251
+
252
+ expect(namedCallback).toHaveBeenCalledWith(token);
253
+
254
+ // biome-ignore lint/suspicious/noExplicitAny: Test cleanup
255
+ (window as any).myCallback = undefined;
256
+ });
257
+
258
+ it("should set expired callback from renderOptions", () => {
259
+ const widget = document.createElement("div");
260
+ const expiredCallback = vi.fn();
261
+
262
+ const renderOptions: Partial<ProcaptchaRenderOptions> = {
263
+ "expired-callback": expiredCallback,
264
+ };
265
+
266
+ const callbacks = getDefaultCallbacks(widget);
267
+ setUserCallbacks(
268
+ renderOptions as ProcaptchaRenderOptions,
269
+ callbacks,
270
+ widget,
271
+ );
272
+
273
+ callbacks.onExpired();
274
+
275
+ expect(expiredCallback).toHaveBeenCalled();
276
+ });
277
+
278
+ it("should set error callback from renderOptions", () => {
279
+ const widget = document.createElement("div");
280
+ const errorCallback = vi.fn();
281
+
282
+ const renderOptions: Partial<ProcaptchaRenderOptions> = {
283
+ "error-callback": errorCallback,
284
+ };
285
+
286
+ const callbacks = getDefaultCallbacks(widget);
287
+ setUserCallbacks(
288
+ renderOptions as ProcaptchaRenderOptions,
289
+ callbacks,
290
+ widget,
291
+ );
292
+
293
+ const error = new Error("Test error");
294
+ callbacks.onError(error);
295
+
296
+ expect(errorCallback).toHaveBeenCalledWith(error);
297
+ });
298
+
299
+ it("should set failed callback from renderOptions", () => {
300
+ const widget = document.createElement("div");
301
+ const failedCallback = vi.fn();
302
+
303
+ const renderOptions: Partial<ProcaptchaRenderOptions> = {
304
+ "failed-callback": failedCallback,
305
+ };
306
+
307
+ const callbacks = getDefaultCallbacks(widget);
308
+ setUserCallbacks(
309
+ renderOptions as ProcaptchaRenderOptions,
310
+ callbacks,
311
+ widget,
312
+ );
313
+
314
+ callbacks.onFailed();
315
+
316
+ expect(failedCallback).toHaveBeenCalled();
317
+ });
318
+
319
+ it("should set reset callback from renderOptions", () => {
320
+ const widget = document.createElement("div");
321
+ const resetCallback = vi.fn();
322
+
323
+ const renderOptions: Partial<ProcaptchaRenderOptions> = {
324
+ "reset-callback": resetCallback,
325
+ };
326
+
327
+ const callbacks = getDefaultCallbacks(widget);
328
+ setUserCallbacks(
329
+ renderOptions as ProcaptchaRenderOptions,
330
+ callbacks,
331
+ widget,
332
+ );
333
+
334
+ callbacks.onReset();
335
+
336
+ expect(resetCallback).toHaveBeenCalled();
337
+ });
338
+
339
+ it("should handle undefined renderOptions gracefully", () => {
340
+ const widget = document.createElement("div");
341
+ const callbacks = getDefaultCallbacks(widget);
342
+
343
+ expect(() =>
344
+ setUserCallbacks(undefined, callbacks, widget),
345
+ ).not.toThrow();
346
+ });
347
+
348
+ it("should set open and close callbacks", () => {
349
+ const widget = document.createElement("div");
350
+ const openCallback = vi.fn();
351
+ const closeCallback = vi.fn();
352
+
353
+ const renderOptions: Partial<ProcaptchaRenderOptions> = {
354
+ "open-callback": openCallback,
355
+ "close-callback": closeCallback,
356
+ };
357
+
358
+ const callbacks = getDefaultCallbacks(widget);
359
+ setUserCallbacks(
360
+ renderOptions as ProcaptchaRenderOptions,
361
+ callbacks,
362
+ widget,
363
+ );
364
+
365
+ callbacks.onOpen();
366
+ callbacks.onClose();
367
+
368
+ expect(openCallback).toHaveBeenCalled();
369
+ expect(closeCallback).toHaveBeenCalled();
370
+ });
371
+ });
372
+ });
@@ -0,0 +1,80 @@
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 { describe, expect, it, vi } from "vitest";
16
+ import { getDefaultEvents } from "../callbacks/defaultEvents.js";
17
+
18
+ describe("callbacks/defaultEvents", () => {
19
+ describe("getDefaultEvents", () => {
20
+ it("should merge default callbacks with provided callbacks", () => {
21
+ const customCallback = vi.fn();
22
+ const events = getDefaultEvents({
23
+ onHuman: customCallback,
24
+ });
25
+
26
+ expect(events.onHuman).toBe(customCallback);
27
+ expect(typeof events.onError).toBe("function");
28
+ expect(typeof events.onExpired).toBe("function");
29
+ });
30
+
31
+ it("should return all default callbacks when no custom callbacks provided", () => {
32
+ const events = getDefaultEvents({});
33
+
34
+ expect(typeof events.onHuman).toBe("function");
35
+ expect(typeof events.onChallengeExpired).toBe("function");
36
+ expect(typeof events.onExtensionNotFound).toBe("function");
37
+ expect(typeof events.onExpired).toBe("function");
38
+ expect(typeof events.onError).toBe("function");
39
+ expect(typeof events.onClose).toBe("function");
40
+ expect(typeof events.onOpen).toBe("function");
41
+ expect(typeof events.onFailed).toBe("function");
42
+ expect(typeof events.onReset).toBe("function");
43
+ expect(typeof events.onReload).toBe("function");
44
+ });
45
+
46
+ it("should allow overriding multiple callbacks", () => {
47
+ const customOnError = vi.fn();
48
+ const customOnExpired = vi.fn();
49
+ const customOnClose = vi.fn();
50
+
51
+ const events = getDefaultEvents({
52
+ onError: customOnError,
53
+ onExpired: customOnExpired,
54
+ onClose: customOnClose,
55
+ });
56
+
57
+ expect(events.onError).toBe(customOnError);
58
+ expect(events.onExpired).toBe(customOnExpired);
59
+ expect(events.onClose).toBe(customOnClose);
60
+ expect(typeof events.onHuman).toBe("function");
61
+ expect(typeof events.onFailed).toBe("function");
62
+ });
63
+
64
+ it("should preserve default behavior for non-overridden callbacks", () => {
65
+ const customOnHuman = vi.fn();
66
+
67
+ const events = getDefaultEvents({
68
+ onHuman: customOnHuman,
69
+ });
70
+
71
+ // Test that custom callback is used
72
+ expect(events.onHuman).toBe(customOnHuman);
73
+
74
+ // Test that other callbacks still exist and are functions
75
+ expect(typeof events.onError).toBe("function");
76
+ expect(typeof events.onExpired).toBe("function");
77
+ expect(typeof events.onChallengeExpired).toBe("function");
78
+ });
79
+ });
80
+ });
@@ -0,0 +1,41 @@
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 { describe, expect, it, vi } from "vitest";
16
+ import { ExtensionLoader } from "../extensionLoader.js";
17
+
18
+ // Mock the imports
19
+ vi.mock("@prosopo/account/extension/ExtensionWeb2", () => ({
20
+ default: { name: "ExtensionWeb2" },
21
+ }));
22
+
23
+ vi.mock("@prosopo/account/extension/ExtensionWeb3", () => ({
24
+ default: { name: "ExtensionWeb3" },
25
+ }));
26
+
27
+ describe("extensionLoader", () => {
28
+ describe("ExtensionLoader", () => {
29
+ it("should load ExtensionWeb2 when web2 is true", async () => {
30
+ const result = await ExtensionLoader(true);
31
+
32
+ expect(result).toEqual({ name: "ExtensionWeb2" });
33
+ });
34
+
35
+ it("should load ExtensionWeb3 when web2 is false", async () => {
36
+ const result = await ExtensionLoader(false);
37
+
38
+ expect(result).toEqual({ name: "ExtensionWeb3" });
39
+ });
40
+ });
41
+ });