@prosopo/procaptcha-pow 2.12.7 → 2.13.1
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 -5
- package/.turbo/turbo-build$colon$tsc.log +20 -20
- package/.turbo/turbo-build.log +6 -6
- package/CHANGELOG.md +62 -0
- package/dist/cjs/components/procaptchaPoW.cjs +22 -0
- package/dist/cjs/components/procaptchaWidget.cjs +143 -0
- package/dist/cjs/index.cjs +5 -2
- package/dist/components/procaptchaPoW.d.ts +7 -0
- package/dist/components/procaptchaPoW.d.ts.map +1 -0
- package/dist/components/procaptchaPoW.js +21 -0
- package/dist/components/procaptchaPoW.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 +143 -0
- package/dist/components/procaptchaWidget.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -2
- 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/procaptchaPoW.unit.test.js +46 -30
- package/dist/tests/procaptchaPoW.unit.test.js.map +1 -1
- package/dist/tests/procaptchaPow.test-d.js +5 -5
- package/dist/tests/procaptchaPow.test-d.js.map +1 -1
- package/dist/tests/procaptchaWidget.unit.test.js +79 -137
- package/dist/tests/procaptchaWidget.unit.test.js.map +1 -1
- package/package.json +11 -15
- package/src/components/procaptchaPoW.ts +53 -0
- package/src/components/procaptchaWidget.ts +281 -0
- package/src/index.ts +2 -2
- package/src/tests/domHarness.ts +122 -0
- package/src/tests/procaptchaPoW.unit.test.ts +59 -36
- package/src/tests/procaptchaPow.test-d.ts +15 -9
- package/src/tests/procaptchaWidget.unit.test.ts +105 -171
- package/tsconfig.cjs.json +1 -2
- package/tsconfig.json +1 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/components/ProcaptchaPoW.cjs +0 -7
- package/dist/cjs/components/ProcaptchaWidget.cjs +0 -130
- package/dist/components/ProcaptchaPoW.d.ts +0 -3
- package/dist/components/ProcaptchaPoW.d.ts.map +0 -1
- package/dist/components/ProcaptchaPoW.js +0 -7
- package/dist/components/ProcaptchaPoW.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 -131
- package/dist/components/ProcaptchaWidget.js.map +0 -1
- package/src/components/ProcaptchaPoW.tsx +0 -39
- package/src/components/ProcaptchaWidget.tsx +0 -231
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export const mount = () => {
|
|
2
|
+
const container = document.createElement("div");
|
|
3
|
+
document.body.appendChild(container);
|
|
4
|
+
return {
|
|
5
|
+
container,
|
|
6
|
+
unmount: () => {
|
|
7
|
+
container.remove();
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
const setTrusted = (event, trusted) => {
|
|
12
|
+
for (const symbol of Object.getOwnPropertySymbols(event)) {
|
|
13
|
+
const impl = Reflect.get(event, symbol);
|
|
14
|
+
if (impl && typeof impl === "object" && "isTrusted" in impl) {
|
|
15
|
+
Object.defineProperty(impl, "isTrusted", {
|
|
16
|
+
configurable: true,
|
|
17
|
+
get: () => trusted,
|
|
18
|
+
set: () => undefined,
|
|
19
|
+
});
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
throw new Error("could not reach the jsdom event implementation");
|
|
24
|
+
};
|
|
25
|
+
const build = (type, options) => {
|
|
26
|
+
if (undefined !== options.key) {
|
|
27
|
+
return new KeyboardEvent(type, {
|
|
28
|
+
bubbles: true,
|
|
29
|
+
cancelable: true,
|
|
30
|
+
key: options.key,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return new MouseEvent(type, {
|
|
34
|
+
bubbles: true,
|
|
35
|
+
cancelable: true,
|
|
36
|
+
clientX: options.clientX ?? 0,
|
|
37
|
+
clientY: options.clientY ?? 0,
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
export const fire = (element, type, options = {}) => {
|
|
41
|
+
fireAndReturn(element, type, options);
|
|
42
|
+
};
|
|
43
|
+
export const fireAndReturn = (element, type, options = {}) => {
|
|
44
|
+
const event = build(type, options);
|
|
45
|
+
setTrusted(event, options.trusted ?? true);
|
|
46
|
+
if (options.touches) {
|
|
47
|
+
Object.defineProperty(event, "touches", { value: options.touches });
|
|
48
|
+
}
|
|
49
|
+
element.dispatchEvent(event);
|
|
50
|
+
return event;
|
|
51
|
+
};
|
|
52
|
+
export const settle = async () => {
|
|
53
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
54
|
+
await Promise.resolve();
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=domHarness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domHarness.js","sourceRoot":"","sources":["../../src/tests/domHarness.ts"],"names":[],"mappings":"AAyBA,MAAM,CAAC,MAAM,KAAK,GAAG,GAAY,EAAE;IAClC,MAAM,SAAS,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IAChD,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;IACrC,OAAO;QACN,SAAS;QACT,OAAO,EAAE,GAAG,EAAE;YACb,SAAS,CAAC,MAAM,EAAE,CAAC;QACpB,CAAC;KACD,CAAC;AACH,CAAC,CAAC;AAgBF,MAAM,UAAU,GAAG,CAAC,KAAY,EAAE,OAAgB,EAAQ,EAAE;IAC3D,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,qBAAqB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1D,MAAM,IAAI,GAAY,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACjD,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,WAAW,IAAI,IAAI,EAAE,CAAC;YAI7D,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,WAAW,EAAE;gBACxC,YAAY,EAAE,IAAI;gBAClB,GAAG,EAAE,GAAG,EAAE,CAAC,OAAO;gBAClB,GAAG,EAAE,GAAG,EAAE,CAAC,SAAS;aACpB,CAAC,CAAC;YACH,OAAO;QACR,CAAC;IACF,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;AACnE,CAAC,CAAC;AAEF,MAAM,KAAK,GAAG,CAAC,IAAY,EAAE,OAAoB,EAAS,EAAE;IAC3D,IAAI,SAAS,KAAK,OAAO,CAAC,GAAG,EAAE,CAAC;QAC/B,OAAO,IAAI,aAAa,CAAC,IAAI,EAAE;YAC9B,OAAO,EAAE,IAAI;YACb,UAAU,EAAE,IAAI;YAChB,GAAG,EAAE,OAAO,CAAC,GAAG;SAChB,CAAC,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,UAAU,CAAC,IAAI,EAAE;QAC3B,OAAO,EAAE,IAAI;QACb,UAAU,EAAE,IAAI;QAChB,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,CAAC;QAC7B,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,CAAC;KAC7B,CAAC,CAAC;AACJ,CAAC,CAAC;AAMF,MAAM,CAAC,MAAM,IAAI,GAAG,CACnB,OAAgB,EAChB,IAAY,EACZ,UAAuB,EAAE,EAClB,EAAE;IACT,aAAa,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;AACvC,CAAC,CAAC;AAIF,MAAM,CAAC,MAAM,aAAa,GAAG,CAC5B,OAAgB,EAChB,IAAY,EACZ,UAAuB,EAAE,EACjB,EAAE;IACV,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACnC,UAAU,CAAC,KAAK,EAAE,OAAO,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC;IAC3C,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACrB,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IACrE,CAAC;IACD,OAAO,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IAC7B,OAAO,KAAK,CAAC;AACd,CAAC,CAAC;AAMF,MAAM,CAAC,MAAM,MAAM,GAAG,KAAK,IAAmB,EAAE;IAG/C,MAAM,IAAI,OAAO,CAAO,CAAC,OAAmB,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;IACzE,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;AACzB,CAAC,CAAC"}
|
|
@@ -1,22 +1,27 @@
|
|
|
1
|
-
import { act, createElement } from "react";
|
|
2
|
-
import { createRoot } from "react-dom/client";
|
|
3
1
|
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
4
2
|
import * as entrypoint from "../index.js";
|
|
3
|
+
import { mount, settle } from "./domHarness.js";
|
|
5
4
|
import { config } from "./managerHarness.js";
|
|
6
5
|
const mocks = vi.hoisted(() => ({
|
|
7
6
|
received: [],
|
|
7
|
+
destroyed: { count: 0 },
|
|
8
8
|
}));
|
|
9
|
-
vi.mock("../components/
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
9
|
+
vi.mock("../components/procaptchaWidget.js", () => ({
|
|
10
|
+
mountProcaptchaPowWidget: (container, props) => {
|
|
11
|
+
mocks.received.push(props);
|
|
12
|
+
const element = document.createElement("div");
|
|
13
|
+
element.setAttribute("data-testid", "widget");
|
|
14
|
+
container.appendChild(element);
|
|
15
|
+
return {
|
|
16
|
+
destroy: () => {
|
|
17
|
+
element.remove();
|
|
18
|
+
mocks.destroyed.count += 1;
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
}));
|
|
23
|
+
let mounted;
|
|
24
|
+
let widget;
|
|
20
25
|
const props = (overrides = {}) => ({
|
|
21
26
|
config: config(),
|
|
22
27
|
callbacks: {},
|
|
@@ -24,28 +29,24 @@ const props = (overrides = {}) => ({
|
|
|
24
29
|
...overrides,
|
|
25
30
|
});
|
|
26
31
|
const render = async (widgetProps) => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
});
|
|
32
|
+
widget = entrypoint.mountProcaptchaPow(mounted.container, widgetProps);
|
|
33
|
+
await settle();
|
|
30
34
|
};
|
|
31
35
|
beforeEach(() => {
|
|
32
36
|
mocks.received.length = 0;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
root = createRoot(container);
|
|
37
|
-
});
|
|
37
|
+
mocks.destroyed.count = 0;
|
|
38
|
+
widget = undefined;
|
|
39
|
+
mounted = mount();
|
|
38
40
|
});
|
|
39
41
|
afterEach(() => {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
container.remove();
|
|
42
|
+
widget?.destroy();
|
|
43
|
+
widget = undefined;
|
|
44
|
+
mounted.unmount();
|
|
44
45
|
});
|
|
45
|
-
describe("
|
|
46
|
+
describe("mountProcaptchaPow", () => {
|
|
46
47
|
test("renders the widget once it has loaded", async () => {
|
|
47
48
|
await render(props());
|
|
48
|
-
expect(container.querySelector('[data-testid="widget"]')).not.toBeNull();
|
|
49
|
+
expect(mounted.container.querySelector('[data-testid="widget"]')).not.toBeNull();
|
|
49
50
|
});
|
|
50
51
|
test("passes on the props the widget needs but never names", async () => {
|
|
51
52
|
const onEscalate = vi.fn();
|
|
@@ -69,11 +70,26 @@ describe("ProcaptchaPow", () => {
|
|
|
69
70
|
expect(mocks.received[0]?.callbacks).toBe(callbacks);
|
|
70
71
|
expect(mocks.received[0]?.config).toEqual(config());
|
|
71
72
|
});
|
|
73
|
+
test("tears the loaded widget down", async () => {
|
|
74
|
+
await render(props());
|
|
75
|
+
widget?.destroy();
|
|
76
|
+
widget = undefined;
|
|
77
|
+
expect(mocks.destroyed.count).toBe(1);
|
|
78
|
+
expect(mounted.container.querySelector('[data-testid="widget"]')).toBeNull();
|
|
79
|
+
});
|
|
80
|
+
test("cancels a mount that is still loading", async () => {
|
|
81
|
+
widget = entrypoint.mountProcaptchaPow(mounted.container, props());
|
|
82
|
+
widget.destroy();
|
|
83
|
+
widget = undefined;
|
|
84
|
+
await settle();
|
|
85
|
+
expect(mocks.received).toHaveLength(0);
|
|
86
|
+
});
|
|
72
87
|
});
|
|
73
88
|
describe("the package entrypoint", () => {
|
|
74
|
-
test("exports the
|
|
75
|
-
expect(typeof entrypoint.
|
|
76
|
-
expect(
|
|
89
|
+
test("exports the mount functions a consumer needs", () => {
|
|
90
|
+
expect(typeof entrypoint.mountProcaptchaPow).toBe("function");
|
|
91
|
+
expect(typeof entrypoint.loadProcaptchaPow).toBe("function");
|
|
92
|
+
expect(typeof entrypoint.mountProcaptchaPowWidget).toBe("function");
|
|
77
93
|
});
|
|
78
94
|
});
|
|
79
95
|
//# sourceMappingURL=procaptchaPoW.unit.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"procaptchaPoW.unit.test.js","sourceRoot":"","sources":["../../src/tests/procaptchaPoW.unit.test.ts"],"names":[],"mappings":"AAgBA,OAAO,
|
|
1
|
+
{"version":3,"file":"procaptchaPoW.unit.test.js","sourceRoot":"","sources":["../../src/tests/procaptchaPoW.unit.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { assertType, describe, expectTypeOf, test } from "vitest";
|
|
2
|
-
import {
|
|
2
|
+
import { mountProcaptchaPow } from "../index.js";
|
|
3
3
|
import { Manager } from "../services/Manager.js";
|
|
4
4
|
import { config, frictionless, state } from "./managerHarness.js";
|
|
5
5
|
const i18n = () => undefined;
|
|
6
6
|
describe("the package entrypoint's types", () => {
|
|
7
|
-
test("
|
|
8
|
-
expectTypeOf(
|
|
9
|
-
expectTypeOf(
|
|
7
|
+
test("mountProcaptchaPow takes a host element and the shared widget props", () => {
|
|
8
|
+
expectTypeOf(mountProcaptchaPow).parameters.toEqualTypeOf();
|
|
9
|
+
expectTypeOf(mountProcaptchaPow).returns.toExtend();
|
|
10
10
|
});
|
|
11
|
-
test("the
|
|
11
|
+
test("the entrypoint exposes the lazy wrapper and the widget itself", () => {
|
|
12
12
|
expectTypeOf().toEqualTypeOf();
|
|
13
13
|
});
|
|
14
14
|
test("config, callbacks and i18n are all required", () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"procaptchaPow.test-d.js","sourceRoot":"","sources":["../../src/tests/procaptchaPow.test-d.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"procaptchaPow.test-d.js","sourceRoot":"","sources":["../../src/tests/procaptchaPow.test-d.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import { ModeEnum, } from "@prosopo/types";
|
|
2
|
-
import { act, createElement } from "react";
|
|
3
|
-
import { createRoot } from "react-dom/client";
|
|
4
2
|
import { afterEach, beforeEach, describe, expect, test, vi, } from "vitest";
|
|
5
|
-
import
|
|
3
|
+
import { mountProcaptchaPowWidget, } from "../components/procaptchaWidget.js";
|
|
4
|
+
import { fire, mount, settle } from "./domHarness.js";
|
|
6
5
|
import { config, frictionless } from "./managerHarness.js";
|
|
7
6
|
const mocks = vi.hoisted(() => {
|
|
8
7
|
const start = vi.fn();
|
|
9
8
|
const resetState = vi.fn();
|
|
10
9
|
const constructions = [];
|
|
11
10
|
const loadI18next = vi.fn();
|
|
12
|
-
const checkboxProps = { current: undefined };
|
|
13
|
-
const honeypotQuestions = [];
|
|
14
11
|
const translationsReady = { current: true };
|
|
15
12
|
return {
|
|
16
13
|
translationsReady,
|
|
@@ -18,8 +15,6 @@ const mocks = vi.hoisted(() => {
|
|
|
18
15
|
resetState,
|
|
19
16
|
constructions,
|
|
20
17
|
loadI18next,
|
|
21
|
-
checkboxProps,
|
|
22
|
-
honeypotQuestions,
|
|
23
18
|
};
|
|
24
19
|
});
|
|
25
20
|
vi.mock("../services/Manager.js", () => ({
|
|
@@ -28,39 +23,21 @@ vi.mock("../services/Manager.js", () => ({
|
|
|
28
23
|
return { start: mocks.start, resetState: mocks.resetState };
|
|
29
24
|
},
|
|
30
25
|
}));
|
|
31
|
-
vi.mock("@prosopo/procaptcha-common", async (importOriginal) => {
|
|
32
|
-
const actual = await importOriginal();
|
|
33
|
-
const { createElement, forwardRef } = await import("react");
|
|
34
|
-
const Checkbox = (props) => {
|
|
35
|
-
mocks.checkboxProps.current = props;
|
|
36
|
-
return createElement("input", {
|
|
37
|
-
type: "checkbox",
|
|
38
|
-
readOnly: true,
|
|
39
|
-
checked: props.checked,
|
|
40
|
-
"aria-label": props.labelText,
|
|
41
|
-
"data-error": props.error,
|
|
42
|
-
"data-loading": String(props.loading),
|
|
43
|
-
});
|
|
44
|
-
};
|
|
45
|
-
const Honeypot = forwardRef(({ encodedQuestion }, ref) => {
|
|
46
|
-
mocks.honeypotQuestions.push(encodedQuestion);
|
|
47
|
-
return createElement("input", { type: "text", ref, name: "honeypot" });
|
|
48
|
-
});
|
|
49
|
-
return { ...actual, Checkbox, Honeypot };
|
|
50
|
-
});
|
|
51
26
|
vi.mock("@prosopo/locale", async (importOriginal) => {
|
|
52
27
|
const actual = await importOriginal();
|
|
53
28
|
return {
|
|
54
29
|
...actual,
|
|
55
30
|
loadI18next: mocks.loadI18next,
|
|
56
|
-
|
|
31
|
+
createTranslator: () => ({
|
|
57
32
|
t: (key) => key,
|
|
58
|
-
|
|
33
|
+
isReady: () => mocks.translationsReady.current,
|
|
34
|
+
subscribe: () => () => undefined,
|
|
35
|
+
i18n: undefined,
|
|
59
36
|
}),
|
|
60
37
|
};
|
|
61
38
|
});
|
|
62
|
-
let
|
|
63
|
-
let
|
|
39
|
+
let mounted;
|
|
40
|
+
let widget;
|
|
64
41
|
const i18nStub = (language, changeLanguage) => ({ language, changeLanguage });
|
|
65
42
|
const props = (overrides = {}) => ({
|
|
66
43
|
config: config(),
|
|
@@ -69,53 +46,42 @@ const props = (overrides = {}) => ({
|
|
|
69
46
|
...overrides,
|
|
70
47
|
});
|
|
71
48
|
const render = (widgetProps) => {
|
|
72
|
-
|
|
73
|
-
root.render(createElement(Procaptcha, widgetProps));
|
|
74
|
-
});
|
|
49
|
+
widget = mountProcaptchaPowWidget(mounted.container, widgetProps);
|
|
75
50
|
};
|
|
76
51
|
beforeEach(() => {
|
|
77
52
|
vi.clearAllMocks();
|
|
78
53
|
mocks.constructions.length = 0;
|
|
79
|
-
mocks.honeypotQuestions.length = 0;
|
|
80
54
|
mocks.translationsReady.current = true;
|
|
81
|
-
mocks.checkboxProps.current = undefined;
|
|
82
55
|
mocks.start.mockResolvedValue(undefined);
|
|
83
56
|
mocks.loadI18next.mockResolvedValue(undefined);
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
act(() => {
|
|
87
|
-
root = createRoot(container);
|
|
88
|
-
});
|
|
57
|
+
widget = undefined;
|
|
58
|
+
mounted = mount();
|
|
89
59
|
});
|
|
90
60
|
afterEach(() => {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
container.remove();
|
|
61
|
+
widget?.destroy();
|
|
62
|
+
widget = undefined;
|
|
63
|
+
mounted.unmount();
|
|
95
64
|
vi.restoreAllMocks();
|
|
96
65
|
});
|
|
97
66
|
const checkbox = () => {
|
|
98
|
-
const element = container.querySelector('input[type="checkbox"]');
|
|
67
|
+
const element = mounted.container.querySelector('input[type="checkbox"]');
|
|
99
68
|
if (!element)
|
|
100
69
|
throw new Error("expected a checkbox to be rendered");
|
|
101
70
|
return element;
|
|
102
71
|
};
|
|
103
72
|
const honeypotInput = () => {
|
|
104
|
-
const element =
|
|
73
|
+
const element = document.querySelector('input[name="email_confirm"]');
|
|
105
74
|
if (!element)
|
|
106
75
|
throw new Error("expected a honeypot to be rendered");
|
|
107
76
|
return element;
|
|
108
77
|
};
|
|
78
|
+
const spinner = () => mounted.container.querySelector('[role="status"]');
|
|
109
79
|
const click = (options = {}) => {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
};
|
|
116
|
-
act(() => {
|
|
117
|
-
void mocks.checkboxProps.current?.onChange({ nativeEvent });
|
|
118
|
-
});
|
|
80
|
+
fire(checkbox(), "click", options);
|
|
81
|
+
};
|
|
82
|
+
const setState = async (next) => {
|
|
83
|
+
mocks.constructions[0]?.updateState(next);
|
|
84
|
+
await settle();
|
|
119
85
|
};
|
|
120
86
|
describe("what the widget renders", () => {
|
|
121
87
|
test("a visible widget shows a checkbox", () => {
|
|
@@ -124,26 +90,26 @@ describe("what the widget renders", () => {
|
|
|
124
90
|
});
|
|
125
91
|
test("an invisible widget shows no checkbox at all", () => {
|
|
126
92
|
render(props({ config: config({ mode: ModeEnum.invisible }) }));
|
|
127
|
-
expect(container.querySelector('[
|
|
93
|
+
expect(mounted.container.querySelector('input[type="checkbox"]')).toBeNull();
|
|
128
94
|
});
|
|
129
95
|
test("a session with a honeypot question renders the honeypot", () => {
|
|
130
|
-
render(props({ frictionlessState: frictionless({ hp: "question" }) }));
|
|
96
|
+
render(props({ frictionlessState: frictionless({ hp: btoa("question") }) }));
|
|
131
97
|
expect(honeypotInput()).toBeDefined();
|
|
132
|
-
expect(
|
|
98
|
+
expect(document.body.textContent).toContain("question");
|
|
133
99
|
});
|
|
134
100
|
test("a session without one renders no bait at all", () => {
|
|
135
101
|
render(props({ frictionlessState: frictionless() }));
|
|
136
|
-
expect(
|
|
102
|
+
expect(document.querySelector('input[name="email_confirm"]')).toBeNull();
|
|
137
103
|
});
|
|
138
104
|
test("an invisible widget still renders the honeypot, as bait", () => {
|
|
139
105
|
render(props({
|
|
140
106
|
config: config({ mode: ModeEnum.invisible }),
|
|
141
|
-
frictionlessState: frictionless({ hp: "question" }),
|
|
107
|
+
frictionlessState: frictionless({ hp: btoa("question") }),
|
|
142
108
|
}));
|
|
143
109
|
expect(honeypotInput()).toBeDefined();
|
|
144
110
|
});
|
|
145
111
|
test("the manager can read the honeypot input the widget rendered", () => {
|
|
146
|
-
render(props({ frictionlessState: frictionless({ hp: "question" }) }));
|
|
112
|
+
render(props({ frictionlessState: frictionless({ hp: btoa("question") }) }));
|
|
147
113
|
honeypotInput().value = "bot@example.com";
|
|
148
114
|
expect(mocks.constructions[0]?.getHoneypotValue?.()).toBe("bot@example.com");
|
|
149
115
|
});
|
|
@@ -153,12 +119,12 @@ describe("what the widget renders", () => {
|
|
|
153
119
|
});
|
|
154
120
|
test("the checkbox is labelled once the translations are ready", () => {
|
|
155
121
|
render(props());
|
|
156
|
-
expect(
|
|
122
|
+
expect(checkbox().getAttribute("aria-label")).toBe("WIDGET.I_AM_HUMAN");
|
|
157
123
|
});
|
|
158
124
|
test("the checkbox is left unlabelled until the translations load", () => {
|
|
159
125
|
mocks.translationsReady.current = false;
|
|
160
126
|
render(props());
|
|
161
|
-
expect(
|
|
127
|
+
expect(checkbox().getAttribute("aria-label")).toBe("");
|
|
162
128
|
});
|
|
163
129
|
test("a page that registered no callbacks still renders", () => {
|
|
164
130
|
render(props({
|
|
@@ -166,23 +132,27 @@ describe("what the widget renders", () => {
|
|
|
166
132
|
}));
|
|
167
133
|
expect(checkbox()).toBeDefined();
|
|
168
134
|
});
|
|
169
|
-
test("the checkbox shows the current error", () => {
|
|
135
|
+
test("the checkbox shows the current error", async () => {
|
|
170
136
|
render(props());
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
error: { message: "no session", key: "API.UNKNOWN_ERROR" },
|
|
174
|
-
});
|
|
137
|
+
await setState({
|
|
138
|
+
error: { message: "no session", key: "API.UNKNOWN_ERROR" },
|
|
175
139
|
});
|
|
176
|
-
expect(
|
|
140
|
+
expect(mounted.container.textContent).toContain("no session");
|
|
177
141
|
});
|
|
178
142
|
test("an unfilled honeypot reads as nothing, not as an empty answer", () => {
|
|
179
|
-
render(props({ frictionlessState: frictionless({ hp: "question" }) }));
|
|
143
|
+
render(props({ frictionlessState: frictionless({ hp: btoa("question") }) }));
|
|
180
144
|
expect(mocks.constructions[0]?.getHoneypotValue?.()).toBeUndefined();
|
|
181
145
|
});
|
|
182
146
|
test("with no honeypot rendered the reader still answers safely", () => {
|
|
183
147
|
render(props());
|
|
184
148
|
expect(mocks.constructions[0]?.getHoneypotValue?.()).toBeUndefined();
|
|
185
149
|
});
|
|
150
|
+
test("the honeypot leaves the page with the widget", () => {
|
|
151
|
+
render(props({ frictionlessState: frictionless({ hp: btoa("question") }) }));
|
|
152
|
+
widget?.destroy();
|
|
153
|
+
widget = undefined;
|
|
154
|
+
expect(document.querySelector('input[name="email_confirm"]')).toBeNull();
|
|
155
|
+
});
|
|
186
156
|
});
|
|
187
157
|
describe("starting a solve", () => {
|
|
188
158
|
test("a click starts the manager with the coordinates of the click", () => {
|
|
@@ -200,18 +170,14 @@ describe("starting a solve", () => {
|
|
|
200
170
|
click({ touches: [], clientX: 3, clientY: 4 });
|
|
201
171
|
expect(mocks.start).toHaveBeenCalledWith(3, 4);
|
|
202
172
|
});
|
|
203
|
-
test("
|
|
173
|
+
test("a synthetic click never reaches the manager", () => {
|
|
204
174
|
render(props());
|
|
205
175
|
click({ trusted: false, clientX: 12, clientY: 34 });
|
|
206
|
-
expect(mocks.start).
|
|
176
|
+
expect(mocks.start).not.toHaveBeenCalled();
|
|
207
177
|
});
|
|
208
|
-
test("
|
|
178
|
+
test("a keyboard activation is treated as the origin", () => {
|
|
209
179
|
render(props());
|
|
210
|
-
|
|
211
|
-
void mocks.checkboxProps.current?.onChange({
|
|
212
|
-
nativeEvent: { isTrusted: true },
|
|
213
|
-
});
|
|
214
|
-
});
|
|
180
|
+
fire(checkbox(), "keydown", { key: "Enter" });
|
|
215
181
|
expect(mocks.start).toHaveBeenCalledWith(0, 0);
|
|
216
182
|
});
|
|
217
183
|
test("the checkbox shows a spinner for as long as the solve runs", async () => {
|
|
@@ -221,29 +187,26 @@ describe("starting a solve", () => {
|
|
|
221
187
|
}));
|
|
222
188
|
render(props());
|
|
223
189
|
click();
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
expect(
|
|
190
|
+
await settle();
|
|
191
|
+
expect(spinner()).not.toBeNull();
|
|
192
|
+
release();
|
|
193
|
+
await settle();
|
|
194
|
+
expect(spinner()).toBeNull();
|
|
229
195
|
});
|
|
230
196
|
test("a solve that rejects still clears the spinner", async () => {
|
|
231
197
|
vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
232
198
|
mocks.start.mockRejectedValue(new Error("provider down"));
|
|
233
199
|
render(props({ autoStart: true }));
|
|
234
|
-
await
|
|
235
|
-
expect(
|
|
200
|
+
await settle();
|
|
201
|
+
expect(spinner()).toBeNull();
|
|
236
202
|
});
|
|
237
203
|
test("a click whose solve rejects clears the spinner too", async () => {
|
|
238
204
|
vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
239
205
|
mocks.start.mockRejectedValue(new Error("provider down"));
|
|
240
206
|
render(props());
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
});
|
|
245
|
-
});
|
|
246
|
-
expect(mocks.checkboxProps.current?.loading).toBe(false);
|
|
207
|
+
click({ clientX: 1, clientY: 2 });
|
|
208
|
+
await settle();
|
|
209
|
+
expect(spinner()).toBeNull();
|
|
247
210
|
});
|
|
248
211
|
test("a second click while the first solve is running is ignored", async () => {
|
|
249
212
|
let release = () => undefined;
|
|
@@ -254,9 +217,8 @@ describe("starting a solve", () => {
|
|
|
254
217
|
click();
|
|
255
218
|
click();
|
|
256
219
|
expect(mocks.start).toHaveBeenCalledTimes(1);
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
});
|
|
220
|
+
release();
|
|
221
|
+
await settle();
|
|
260
222
|
});
|
|
261
223
|
test("an autoStart widget solves without waiting for a click", () => {
|
|
262
224
|
render(props({ autoStart: true }));
|
|
@@ -273,9 +235,7 @@ describe("starting a solve", () => {
|
|
|
273
235
|
});
|
|
274
236
|
describe("the invisible-mode execute event", () => {
|
|
275
237
|
const execute = () => {
|
|
276
|
-
|
|
277
|
-
document.dispatchEvent(new Event("procaptcha:execute"));
|
|
278
|
-
});
|
|
238
|
+
document.dispatchEvent(new Event("procaptcha:execute"));
|
|
279
239
|
};
|
|
280
240
|
test("starts a solve", () => {
|
|
281
241
|
render(props({ config: config({ mode: ModeEnum.invisible }) }));
|
|
@@ -288,10 +248,8 @@ describe("the invisible-mode execute event", () => {
|
|
|
288
248
|
expect(mocks.start).not.toHaveBeenCalled();
|
|
289
249
|
});
|
|
290
250
|
const executeOn = async (target) => {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
294
|
-
});
|
|
251
|
+
target.dispatchEvent(new Event("procaptcha:execute"));
|
|
252
|
+
await settle();
|
|
295
253
|
};
|
|
296
254
|
test("a targeted execute on the container runs a visible widget", async () => {
|
|
297
255
|
const target = document.createElement("div");
|
|
@@ -310,10 +268,8 @@ describe("the invisible-mode execute event", () => {
|
|
|
310
268
|
});
|
|
311
269
|
test("stops being listened for once the widget is gone", () => {
|
|
312
270
|
render(props({ config: config({ mode: ModeEnum.invisible }) }));
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
root = createRoot(container);
|
|
316
|
-
});
|
|
271
|
+
widget?.destroy();
|
|
272
|
+
widget = undefined;
|
|
317
273
|
execute();
|
|
318
274
|
expect(mocks.start).not.toHaveBeenCalled();
|
|
319
275
|
});
|
|
@@ -332,71 +288,57 @@ describe("the invisible-mode execute event", () => {
|
|
|
332
288
|
mocks.start.mockRejectedValue(new Error("no provider"));
|
|
333
289
|
render(props({ config: config({ mode: ModeEnum.invisible }) }));
|
|
334
290
|
execute();
|
|
335
|
-
await
|
|
291
|
+
await settle();
|
|
336
292
|
expect(reported).toHaveBeenCalled();
|
|
337
293
|
});
|
|
338
294
|
});
|
|
339
295
|
describe("an invalidated session", () => {
|
|
340
|
-
const fail = (key) => {
|
|
341
|
-
|
|
342
|
-
mocks.constructions[0]?.updateState({
|
|
343
|
-
error: { message: "boom", key },
|
|
344
|
-
});
|
|
345
|
-
});
|
|
296
|
+
const fail = async (key) => {
|
|
297
|
+
await setState({ error: { message: "boom", key } });
|
|
346
298
|
};
|
|
347
|
-
test("is handed to the recovery-aware parent, with the original click", () => {
|
|
299
|
+
test("is handed to the recovery-aware parent, with the original click", async () => {
|
|
348
300
|
const onSessionInvalidated = vi.fn();
|
|
349
301
|
render(props({ onSessionInvalidated }));
|
|
350
302
|
click({ clientX: 9, clientY: 8 });
|
|
351
|
-
fail("CAPTCHA.NO_SESSION_FOUND");
|
|
303
|
+
await fail("CAPTCHA.NO_SESSION_FOUND");
|
|
352
304
|
expect(onSessionInvalidated).toHaveBeenCalledWith(9, 8);
|
|
353
305
|
});
|
|
354
|
-
test("is escalated once only, so a failing retry cannot loop", () => {
|
|
306
|
+
test("is escalated once only, so a failing retry cannot loop", async () => {
|
|
355
307
|
const onSessionInvalidated = vi.fn();
|
|
356
308
|
const restart = vi.fn();
|
|
357
|
-
vi.useFakeTimers();
|
|
358
309
|
render(props({
|
|
359
310
|
onSessionInvalidated,
|
|
360
311
|
frictionlessState: frictionless({ restart }),
|
|
361
312
|
}));
|
|
362
|
-
fail("CAPTCHA.NO_SESSION_FOUND");
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
});
|
|
366
|
-
fail("CAPTCHA.NO_SESSION_FOUND");
|
|
313
|
+
await fail("CAPTCHA.NO_SESSION_FOUND");
|
|
314
|
+
await setState({ error: undefined });
|
|
315
|
+
await fail("CAPTCHA.NO_SESSION_FOUND");
|
|
367
316
|
expect(onSessionInvalidated).toHaveBeenCalledTimes(1);
|
|
368
|
-
|
|
369
|
-
vi.advanceTimersByTime(100);
|
|
370
|
-
});
|
|
317
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
371
318
|
expect(restart).toHaveBeenCalledTimes(1);
|
|
372
|
-
vi.useRealTimers();
|
|
373
319
|
});
|
|
374
|
-
test("without a recovery-aware parent the frictionless session restarts", () => {
|
|
320
|
+
test("without a recovery-aware parent the frictionless session restarts", async () => {
|
|
375
321
|
const restart = vi.fn();
|
|
376
|
-
vi.useFakeTimers();
|
|
377
322
|
render(props({ frictionlessState: frictionless({ restart }) }));
|
|
378
|
-
fail("CAPTCHA.NO_SESSION_FOUND");
|
|
323
|
+
await fail("CAPTCHA.NO_SESSION_FOUND");
|
|
379
324
|
expect(restart).not.toHaveBeenCalled();
|
|
380
|
-
|
|
381
|
-
vi.advanceTimersByTime(100);
|
|
382
|
-
});
|
|
325
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
383
326
|
expect(restart).toHaveBeenCalledTimes(1);
|
|
384
|
-
vi.useRealTimers();
|
|
385
327
|
});
|
|
386
|
-
test("any other error is left for the widget to display", () => {
|
|
328
|
+
test("any other error is left for the widget to display", async () => {
|
|
387
329
|
const onSessionInvalidated = vi.fn();
|
|
388
330
|
const restart = vi.fn();
|
|
389
331
|
render(props({
|
|
390
332
|
onSessionInvalidated,
|
|
391
333
|
frictionlessState: frictionless({ restart }),
|
|
392
334
|
}));
|
|
393
|
-
fail("API.UNKNOWN_ERROR");
|
|
335
|
+
await fail("API.UNKNOWN_ERROR");
|
|
394
336
|
expect(onSessionInvalidated).not.toHaveBeenCalled();
|
|
395
337
|
expect(restart).not.toHaveBeenCalled();
|
|
396
338
|
});
|
|
397
|
-
test("an error with nowhere to escalate to is simply shown", () => {
|
|
339
|
+
test("an error with nowhere to escalate to is simply shown", async () => {
|
|
398
340
|
render(props());
|
|
399
|
-
expect(
|
|
341
|
+
await expect(fail("CAPTCHA.NO_SESSION_FOUND")).resolves.toBeUndefined();
|
|
400
342
|
});
|
|
401
343
|
});
|
|
402
344
|
describe("language", () => {
|