@prosopo/procaptcha-react 2.9.97 → 2.9.98
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 +7 -7
- package/.turbo/turbo-build$colon$tsc.log +21 -21
- package/.turbo/turbo-build.log +8 -8
- package/CHANGELOG.md +24 -0
- package/dist/cjs/components/CaptchaComponent.cjs +4 -5
- package/dist/cjs/components/CaptchaWidget.cjs +4 -12
- package/dist/cjs/components/ProcaptchaWidget.cjs +8 -6
- package/dist/components/CaptchaComponent.d.ts.map +1 -1
- package/dist/components/CaptchaComponent.js +4 -5
- package/dist/components/CaptchaComponent.js.map +1 -1
- package/dist/components/CaptchaWidget.d.ts.map +1 -1
- package/dist/components/CaptchaWidget.js +4 -12
- package/dist/components/CaptchaWidget.js.map +1 -1
- package/dist/components/ProcaptchaWidget.d.ts.map +1 -1
- package/dist/components/ProcaptchaWidget.js +8 -6
- package/dist/components/ProcaptchaWidget.js.map +1 -1
- package/dist/tests/button.unit.test.d.ts +2 -0
- package/dist/tests/button.unit.test.d.ts.map +1 -0
- package/dist/tests/button.unit.test.js +113 -0
- package/dist/tests/button.unit.test.js.map +1 -0
- package/dist/tests/captchaComponent.unit.test.d.ts +2 -0
- package/dist/tests/captchaComponent.unit.test.d.ts.map +1 -0
- package/dist/tests/captchaComponent.unit.test.js +141 -0
- package/dist/tests/captchaComponent.unit.test.js.map +1 -0
- package/dist/tests/captchaWidget.unit.test.d.ts +2 -0
- package/dist/tests/captchaWidget.unit.test.d.ts.map +1 -0
- package/dist/tests/captchaWidget.unit.test.js +191 -0
- package/dist/tests/captchaWidget.unit.test.js.map +1 -0
- package/dist/tests/collector.unit.test.d.ts +2 -0
- package/dist/tests/collector.unit.test.d.ts.map +1 -0
- package/dist/tests/collector.unit.test.js +109 -0
- package/dist/tests/collector.unit.test.js.map +1 -0
- package/dist/tests/harness.d.ts +12 -0
- package/dist/tests/harness.d.ts.map +1 -0
- package/dist/tests/harness.js +69 -0
- package/dist/tests/harness.js.map +1 -0
- package/dist/tests/modal.unit.test.d.ts +2 -0
- package/dist/tests/modal.unit.test.d.ts.map +1 -0
- package/dist/tests/modal.unit.test.js +72 -0
- package/dist/tests/modal.unit.test.js.map +1 -0
- package/dist/tests/procaptcha.unit.test.d.ts +2 -0
- package/dist/tests/procaptcha.unit.test.d.ts.map +1 -0
- package/dist/tests/procaptcha.unit.test.js +57 -0
- package/dist/tests/procaptcha.unit.test.js.map +1 -0
- package/dist/tests/procaptchaReact.test-d.d.ts +2 -0
- package/dist/tests/procaptchaReact.test-d.d.ts.map +1 -0
- package/dist/tests/procaptchaReact.test-d.js +72 -0
- package/dist/tests/procaptchaReact.test-d.js.map +1 -0
- package/dist/tests/procaptchaWidget.unit.test.d.ts +2 -0
- package/dist/tests/procaptchaWidget.unit.test.d.ts.map +1 -0
- package/dist/tests/procaptchaWidget.unit.test.js +403 -0
- package/dist/tests/procaptchaWidget.unit.test.js.map +1 -0
- package/dist/tests/render.d.ts +23 -0
- package/dist/tests/render.d.ts.map +1 -0
- package/dist/tests/render.js +77 -0
- package/dist/tests/render.js.map +1 -0
- package/dist/tests/setup.d.ts +5 -0
- package/dist/tests/setup.d.ts.map +1 -0
- package/dist/tests/setup.js +3 -0
- package/dist/tests/setup.js.map +1 -0
- package/dist/tests/util.unit.test.d.ts +2 -0
- package/dist/tests/util.unit.test.d.ts.map +1 -0
- package/dist/tests/util.unit.test.js +84 -0
- package/dist/tests/util.unit.test.js.map +1 -0
- package/package.json +8 -4
- package/vite.test.config.ts +28 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { jsx as _jsx } from "@emotion/react/jsx-runtime";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
3
|
+
import Modal from "../components/Modal.js";
|
|
4
|
+
import { mount } from "./render.js";
|
|
5
|
+
let mounted;
|
|
6
|
+
const render = (show, children = _jsx("p", { children: "challenge" })) => {
|
|
7
|
+
mounted.render(_jsx(Modal, { show: show, children: children }));
|
|
8
|
+
};
|
|
9
|
+
const outer = () => {
|
|
10
|
+
const element = document.querySelector(".prosopo-modalOuter");
|
|
11
|
+
if (!element)
|
|
12
|
+
throw new Error("expected the modal to be rendered");
|
|
13
|
+
return element;
|
|
14
|
+
};
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
mounted = mount();
|
|
17
|
+
});
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
mounted.unmount();
|
|
20
|
+
});
|
|
21
|
+
describe("where the modal renders", () => {
|
|
22
|
+
test("attaches itself to the document body, not to the widget", () => {
|
|
23
|
+
render(true);
|
|
24
|
+
expect(outer().parentElement).toBe(document.body);
|
|
25
|
+
expect(mounted.container.querySelector(".prosopo-modalOuter")).toBeNull();
|
|
26
|
+
});
|
|
27
|
+
test("renders its children inside the inner panel", () => {
|
|
28
|
+
render(true);
|
|
29
|
+
const inner = outer().querySelector(".prosopo-modalInner");
|
|
30
|
+
expect(inner?.textContent).toBe("challenge");
|
|
31
|
+
});
|
|
32
|
+
test("renders an empty panel when there is nothing to show", () => {
|
|
33
|
+
render(true, null);
|
|
34
|
+
expect(outer().querySelector(".prosopo-modalInner")?.textContent).toBe("");
|
|
35
|
+
});
|
|
36
|
+
test("is removed from the body when the widget unmounts", () => {
|
|
37
|
+
render(true);
|
|
38
|
+
mounted.unmount();
|
|
39
|
+
expect(document.querySelector(".prosopo-modalOuter")).toBeNull();
|
|
40
|
+
mounted = mount();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
describe("showing and hiding", () => {
|
|
44
|
+
test("a shown modal lays its contents out", () => {
|
|
45
|
+
render(true);
|
|
46
|
+
expect(outer().style.display).toBe("flex");
|
|
47
|
+
});
|
|
48
|
+
test("a hidden modal stays in the DOM but is not displayed", () => {
|
|
49
|
+
render(false);
|
|
50
|
+
expect(outer().style.display).toBe("none");
|
|
51
|
+
expect(outer().textContent).toBe("challenge");
|
|
52
|
+
});
|
|
53
|
+
test("toggling show flips the display without remounting", () => {
|
|
54
|
+
render(false);
|
|
55
|
+
const before = outer();
|
|
56
|
+
render(true);
|
|
57
|
+
expect(outer()).toBe(before);
|
|
58
|
+
expect(outer().style.display).toBe("flex");
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
describe("stacking", () => {
|
|
62
|
+
test("sits above everything a host page is likely to stack", () => {
|
|
63
|
+
render(true);
|
|
64
|
+
expect(outer().style.zIndex).toBe("2147483646");
|
|
65
|
+
});
|
|
66
|
+
test("covers the viewport so the page behind cannot be clicked", () => {
|
|
67
|
+
render(true);
|
|
68
|
+
expect(outer().style.position).toBe("fixed");
|
|
69
|
+
expect(outer().style.minHeight).toBe("100vh");
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
//# sourceMappingURL=modal.unit.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modal.unit.test.js","sourceRoot":"","sources":["../../src/tests/modal.unit.test.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"procaptcha.unit.test.d.ts","sourceRoot":"","sources":["../../src/tests/procaptcha.unit.test.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { jsx as _jsx } from "@emotion/react/jsx-runtime";
|
|
2
|
+
import { act } from "react";
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
4
|
+
import Procaptcha from "../components/Procaptcha.js";
|
|
5
|
+
import { config } from "./harness.js";
|
|
6
|
+
import { mount } from "./render.js";
|
|
7
|
+
const seen = [];
|
|
8
|
+
vi.mock("../components/ProcaptchaWidget.js", () => ({
|
|
9
|
+
default: (props) => {
|
|
10
|
+
seen.push(props);
|
|
11
|
+
return _jsx("div", { "data-cy": "widget-stub", children: "widget" });
|
|
12
|
+
},
|
|
13
|
+
}));
|
|
14
|
+
let mounted;
|
|
15
|
+
const props = (overrides = {}) => ({
|
|
16
|
+
config: config(),
|
|
17
|
+
callbacks: {},
|
|
18
|
+
i18n: undefined,
|
|
19
|
+
...overrides,
|
|
20
|
+
});
|
|
21
|
+
const settle = async () => {
|
|
22
|
+
await act(async () => {
|
|
23
|
+
await Promise.resolve();
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
seen.length = 0;
|
|
28
|
+
mounted = mount();
|
|
29
|
+
});
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
mounted.unmount();
|
|
32
|
+
});
|
|
33
|
+
describe("the lazy boundary", () => {
|
|
34
|
+
test("renders the widget once the chunk arrives", async () => {
|
|
35
|
+
mounted.render(_jsx(Procaptcha, { ...props() }));
|
|
36
|
+
await settle();
|
|
37
|
+
expect(mounted.container.querySelector('[data-cy="widget-stub"]')).not.toBeNull();
|
|
38
|
+
});
|
|
39
|
+
test("hands the widget every prop it was given", async () => {
|
|
40
|
+
const callbacks = { onHuman: vi.fn() };
|
|
41
|
+
const given = props({ callbacks, autoStart: true });
|
|
42
|
+
mounted.render(_jsx(Procaptcha, { ...given }));
|
|
43
|
+
await settle();
|
|
44
|
+
expect(seen[0]).toMatchObject({
|
|
45
|
+
callbacks,
|
|
46
|
+
autoStart: true,
|
|
47
|
+
config: given.config,
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
test("does not add props of its own", async () => {
|
|
51
|
+
const given = props();
|
|
52
|
+
mounted.render(_jsx(Procaptcha, { ...given }));
|
|
53
|
+
await settle();
|
|
54
|
+
expect(Object.keys(seen[0] ?? {}).sort()).toEqual(Object.keys(given).sort());
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
//# sourceMappingURL=procaptcha.unit.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"procaptcha.unit.test.js","sourceRoot":"","sources":["../../src/tests/procaptcha.unit.test.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"procaptchaReact.test-d.d.ts","sourceRoot":"","sources":["../../src/tests/procaptchaReact.test-d.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { assertType, describe, expectTypeOf, test } from "vitest";
|
|
2
|
+
import Procaptcha from "../components/Procaptcha.js";
|
|
3
|
+
import addDataAttr from "../util/index.js";
|
|
4
|
+
describe("the package's public surface", () => {
|
|
5
|
+
test("Procaptcha takes exactly the shared props type", () => {
|
|
6
|
+
expectTypeOf(Procaptcha).parameter(0).toEqualTypeOf();
|
|
7
|
+
});
|
|
8
|
+
test("Procaptcha renders an element rather than a fragment or void", () => {
|
|
9
|
+
expectTypeOf(Procaptcha).returns.toMatchTypeOf();
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
describe("CaptchaWidget's props", () => {
|
|
13
|
+
test("takes a single captcha, not the whole challenge", () => {
|
|
14
|
+
expectTypeOf().toEqualTypeOf();
|
|
15
|
+
});
|
|
16
|
+
test("reports a selection as a hash and a pair of coordinates", () => {
|
|
17
|
+
expectTypeOf().toEqualTypeOf();
|
|
18
|
+
});
|
|
19
|
+
test("accepts only the two themes the palette defines", () => {
|
|
20
|
+
expectTypeOf().toEqualTypeOf();
|
|
21
|
+
});
|
|
22
|
+
test("rejects a theme name the palette does not have", () => {
|
|
23
|
+
assertType("blue");
|
|
24
|
+
});
|
|
25
|
+
test("holds the solution as hash-plus-coordinates triples", () => {
|
|
26
|
+
expectTypeOf().toEqualTypeOf();
|
|
27
|
+
});
|
|
28
|
+
test("cannot be rendered without a solution to display", () => {
|
|
29
|
+
assertType({
|
|
30
|
+
challenge: {},
|
|
31
|
+
solution: [],
|
|
32
|
+
onClick: () => undefined,
|
|
33
|
+
themeColor: "light",
|
|
34
|
+
});
|
|
35
|
+
assertType({
|
|
36
|
+
challenge: {},
|
|
37
|
+
onClick: () => undefined,
|
|
38
|
+
themeColor: "light",
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
describe("CaptchaComponent's props", () => {
|
|
43
|
+
test("takes the round to render as a number", () => {
|
|
44
|
+
expectTypeOf().toEqualTypeOf();
|
|
45
|
+
});
|
|
46
|
+
test("takes one solution list per round", () => {
|
|
47
|
+
expectTypeOf().toEqualTypeOf();
|
|
48
|
+
});
|
|
49
|
+
test("lets a click be reported without coordinates", () => {
|
|
50
|
+
expectTypeOf().toEqualTypeOf();
|
|
51
|
+
});
|
|
52
|
+
test("takes callbacks that report nothing back", () => {
|
|
53
|
+
expectTypeOf().toEqualTypeOf();
|
|
54
|
+
expectTypeOf().toEqualTypeOf();
|
|
55
|
+
expectTypeOf().toEqualTypeOf();
|
|
56
|
+
expectTypeOf().toEqualTypeOf();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
describe("addDataAttr", () => {
|
|
60
|
+
test("returns an object of attributes, not the input", () => {
|
|
61
|
+
expectTypeOf(addDataAttr({ general: { cy: "x" } })).toBeObject();
|
|
62
|
+
});
|
|
63
|
+
test("takes both groups as optional", () => {
|
|
64
|
+
assertType(addDataAttr({}));
|
|
65
|
+
assertType(addDataAttr({ general: { cy: "x" } }));
|
|
66
|
+
assertType(addDataAttr({ dev: { cy: "x" } }));
|
|
67
|
+
});
|
|
68
|
+
test("rejects attribute values that are not strings", () => {
|
|
69
|
+
assertType(addDataAttr({ general: { cy: 1 } }));
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
//# sourceMappingURL=procaptchaReact.test-d.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"procaptchaReact.test-d.js","sourceRoot":"","sources":["../../src/tests/procaptchaReact.test-d.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"procaptchaWidget.unit.test.d.ts","sourceRoot":"","sources":["../../src/tests/procaptchaWidget.unit.test.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import { jsx as _jsx } from "@emotion/react/jsx-runtime";
|
|
2
|
+
import { ModeEnum, } from "@prosopo/types";
|
|
3
|
+
import { act } from "react";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, test, vi, vitest, } from "vitest";
|
|
5
|
+
import ProcaptchaWidget from "../components/ProcaptchaWidget.js";
|
|
6
|
+
import { challengeResponse, config, frictionless } from "./harness.js";
|
|
7
|
+
import { fire, mount } from "./render.js";
|
|
8
|
+
const start = vi.fn(() => Promise.resolve());
|
|
9
|
+
const cancel = vi.fn();
|
|
10
|
+
const submit = vi.fn(() => Promise.resolve());
|
|
11
|
+
const select = vi.fn();
|
|
12
|
+
const nextRound = vi.fn();
|
|
13
|
+
const reload = vi.fn(() => Promise.resolve());
|
|
14
|
+
let update;
|
|
15
|
+
let readHoneypot;
|
|
16
|
+
const managerArgs = [];
|
|
17
|
+
vi.mock("@prosopo/procaptcha", () => ({
|
|
18
|
+
Manager: (...args) => {
|
|
19
|
+
managerArgs.push(args);
|
|
20
|
+
update = args[2];
|
|
21
|
+
readHoneypot = args[5];
|
|
22
|
+
return { start, cancel, submit, select, nextRound, reload };
|
|
23
|
+
},
|
|
24
|
+
}));
|
|
25
|
+
const changeLanguage = vi.fn(() => Promise.resolve());
|
|
26
|
+
const loadI18next = vi.fn(() => Promise.resolve());
|
|
27
|
+
vi.mock("@prosopo/locale", async (importOriginal) => {
|
|
28
|
+
const actual = await importOriginal();
|
|
29
|
+
return {
|
|
30
|
+
...actual,
|
|
31
|
+
useTranslation: () => ({ t: (key) => key, ready: true }),
|
|
32
|
+
loadI18next: (sync, language) => loadI18next(sync, language),
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
let mounted;
|
|
36
|
+
const i18n = (language) => ({ language, changeLanguage });
|
|
37
|
+
const render = (overrides = {}) => {
|
|
38
|
+
mounted.render(_jsx(ProcaptchaWidget, { config: config(), callbacks: {}, i18n: undefined, ...overrides }));
|
|
39
|
+
};
|
|
40
|
+
const openChallenge = () => ({
|
|
41
|
+
challenge: challengeResponse(),
|
|
42
|
+
solutions: [[]],
|
|
43
|
+
});
|
|
44
|
+
const setState = (next) => {
|
|
45
|
+
act(() => {
|
|
46
|
+
update?.(next);
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
const checkbox = () => {
|
|
50
|
+
const element = mounted.container.querySelector('[data-cy="captcha-checkbox"]');
|
|
51
|
+
if (!element)
|
|
52
|
+
throw new Error("expected the checkbox to be rendered");
|
|
53
|
+
return element;
|
|
54
|
+
};
|
|
55
|
+
const modalText = () => document.querySelector(".prosopo-modalOuter")?.textContent ?? "";
|
|
56
|
+
const flush = async () => {
|
|
57
|
+
await act(async () => {
|
|
58
|
+
await Promise.resolve();
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
beforeEach(() => {
|
|
62
|
+
vi.clearAllMocks();
|
|
63
|
+
managerArgs.length = 0;
|
|
64
|
+
update = undefined;
|
|
65
|
+
readHoneypot = undefined;
|
|
66
|
+
start.mockImplementation(() => Promise.resolve());
|
|
67
|
+
mounted = mount();
|
|
68
|
+
});
|
|
69
|
+
afterEach(() => {
|
|
70
|
+
mounted.unmount();
|
|
71
|
+
vi.useRealTimers();
|
|
72
|
+
});
|
|
73
|
+
describe("what the user sees at rest", () => {
|
|
74
|
+
test("shows the checkbox for a visible widget", () => {
|
|
75
|
+
render();
|
|
76
|
+
expect(checkbox()).toBeDefined();
|
|
77
|
+
});
|
|
78
|
+
test("labels the checkbox with the translated prompt", () => {
|
|
79
|
+
render();
|
|
80
|
+
expect(checkbox().getAttribute("aria-label")).toBe("WIDGET.I_AM_HUMAN");
|
|
81
|
+
});
|
|
82
|
+
test("keeps the modal closed until there is something to show", () => {
|
|
83
|
+
render();
|
|
84
|
+
const outer = document.querySelector(".prosopo-modalOuter");
|
|
85
|
+
expect(outer?.style.display).toBe("none");
|
|
86
|
+
});
|
|
87
|
+
test("says so when the modal opens with no challenge", () => {
|
|
88
|
+
render();
|
|
89
|
+
setState({ showModal: true });
|
|
90
|
+
expect(modalText()).toContain("No challenge set.");
|
|
91
|
+
});
|
|
92
|
+
test("shows no honeypot unless the provider asked for one", () => {
|
|
93
|
+
render();
|
|
94
|
+
expect(document.querySelector("input[type='text']")).toBeNull();
|
|
95
|
+
});
|
|
96
|
+
test("warns when configured with a reserved test site key", () => {
|
|
97
|
+
render({ config: config({ account: { address: "0" } }) });
|
|
98
|
+
expect(mounted.container.querySelector('[data-cy="test-mode-banner"]')).toBeDefined();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
describe("invisible mode", () => {
|
|
102
|
+
const invisible = () => ({
|
|
103
|
+
config: config({ mode: ModeEnum.invisible }),
|
|
104
|
+
});
|
|
105
|
+
test("renders no checkbox for the user to click", () => {
|
|
106
|
+
render(invisible());
|
|
107
|
+
expect(mounted.container.querySelector('[data-cy="captcha-checkbox"]')).toBeNull();
|
|
108
|
+
});
|
|
109
|
+
test("renders no widget chrome at all", () => {
|
|
110
|
+
render(invisible());
|
|
111
|
+
expect(mounted.container.querySelector(".image-captcha")).toBeNull();
|
|
112
|
+
});
|
|
113
|
+
test("stays silent rather than announcing a missing challenge", () => {
|
|
114
|
+
render(invisible());
|
|
115
|
+
setState({ showModal: true });
|
|
116
|
+
expect(modalText()).toBe("");
|
|
117
|
+
});
|
|
118
|
+
test("still shows the challenge when one arrives", () => {
|
|
119
|
+
render(invisible());
|
|
120
|
+
setState({ showModal: true, ...openChallenge() });
|
|
121
|
+
expect(modalText()).toContain("WIDGET.SELECT_ALL");
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
describe("the honeypot", () => {
|
|
125
|
+
test("is rendered when the frictionless flow supplies a question", () => {
|
|
126
|
+
render({ frictionlessState: frictionless({ hp: btoa("dit dah") }) });
|
|
127
|
+
expect(document.body.textContent).toContain("dit dah");
|
|
128
|
+
});
|
|
129
|
+
test("gives the manager a way to read whatever a bot typed", () => {
|
|
130
|
+
render({ frictionlessState: frictionless({ hp: btoa("dit dah") }) });
|
|
131
|
+
const input = document.querySelector("input:not([data-cy='captcha-checkbox'])");
|
|
132
|
+
if (!input)
|
|
133
|
+
throw new Error("expected the honeypot input");
|
|
134
|
+
input.value = "answered";
|
|
135
|
+
expect(readHoneypot?.()).toBe("answered");
|
|
136
|
+
});
|
|
137
|
+
test("reads as undefined while the honeypot is untouched", () => {
|
|
138
|
+
render({ frictionlessState: frictionless({ hp: btoa("dit dah") }) });
|
|
139
|
+
expect(readHoneypot?.()).toBeUndefined();
|
|
140
|
+
});
|
|
141
|
+
test("reads as undefined when there is no honeypot at all", () => {
|
|
142
|
+
render();
|
|
143
|
+
expect(readHoneypot?.()).toBeUndefined();
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
describe("clicking the checkbox", () => {
|
|
147
|
+
test("starts the challenge where the user clicked", () => {
|
|
148
|
+
render();
|
|
149
|
+
fire(checkbox(), "click", { clientX: 11, clientY: 22 });
|
|
150
|
+
expect(start).toHaveBeenCalledWith(11, 22);
|
|
151
|
+
});
|
|
152
|
+
test("ignores a synthetic click", () => {
|
|
153
|
+
render();
|
|
154
|
+
fire(checkbox(), "click", { trusted: false });
|
|
155
|
+
expect(start).not.toHaveBeenCalled();
|
|
156
|
+
});
|
|
157
|
+
test("only starts once while a start is still in flight", () => {
|
|
158
|
+
let finish;
|
|
159
|
+
start.mockImplementation(() => new Promise((resolve) => {
|
|
160
|
+
finish = resolve;
|
|
161
|
+
}));
|
|
162
|
+
render();
|
|
163
|
+
fire(checkbox(), "click", { clientX: 1, clientY: 1 });
|
|
164
|
+
expect(mounted.container.querySelector('[aria-label="Loading spinner"]')).not.toBeNull();
|
|
165
|
+
expect(start).toHaveBeenCalledTimes(1);
|
|
166
|
+
finish?.();
|
|
167
|
+
});
|
|
168
|
+
test("shows the checkbox again once the start resolves", async () => {
|
|
169
|
+
render();
|
|
170
|
+
fire(checkbox(), "click");
|
|
171
|
+
await flush();
|
|
172
|
+
expect(checkbox()).toBeDefined();
|
|
173
|
+
});
|
|
174
|
+
test("shows the checkbox again when the start fails", async () => {
|
|
175
|
+
start.mockImplementation(() => Promise.reject(new Error("network down")));
|
|
176
|
+
render();
|
|
177
|
+
fire(checkbox(), "click");
|
|
178
|
+
await flush();
|
|
179
|
+
expect(mounted.container.querySelector('[aria-label="Loading spinner"]')).toBeNull();
|
|
180
|
+
});
|
|
181
|
+
test("reflects a verified user as a ticked box", () => {
|
|
182
|
+
render();
|
|
183
|
+
setState({ isHuman: true });
|
|
184
|
+
expect(checkbox().checked).toBe(true);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
describe("the challenge", () => {
|
|
188
|
+
const withChallenge = () => {
|
|
189
|
+
render();
|
|
190
|
+
setState({ showModal: true, ...openChallenge() });
|
|
191
|
+
};
|
|
192
|
+
test("wires the grid to the manager's select", () => {
|
|
193
|
+
withChallenge();
|
|
194
|
+
const image = document.querySelector(".prosopo-modalOuter img");
|
|
195
|
+
if (!image)
|
|
196
|
+
throw new Error("expected a captcha image");
|
|
197
|
+
fire(image, "click", { clientX: 3, clientY: 4 });
|
|
198
|
+
expect(select).toHaveBeenCalledWith("hash-1", 3, 4);
|
|
199
|
+
});
|
|
200
|
+
test("wires cancel to the manager", () => {
|
|
201
|
+
withChallenge();
|
|
202
|
+
const cancelButton = document.querySelector('.prosopo-modalOuter button[aria-label="WIDGET.CANCEL"]');
|
|
203
|
+
if (!cancelButton)
|
|
204
|
+
throw new Error("expected a cancel button");
|
|
205
|
+
fire(cancelButton, "click");
|
|
206
|
+
expect(cancel).toHaveBeenCalledTimes(1);
|
|
207
|
+
});
|
|
208
|
+
test("wires submit to the manager on the last round", () => {
|
|
209
|
+
withChallenge();
|
|
210
|
+
const submitButton = document.querySelector('.prosopo-modalOuter button[aria-label="WIDGET.SUBMIT"]');
|
|
211
|
+
if (!submitButton)
|
|
212
|
+
throw new Error("expected a submit button");
|
|
213
|
+
fire(submitButton, "click");
|
|
214
|
+
expect(submit).toHaveBeenCalledTimes(1);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
describe("recovering from an error", () => {
|
|
218
|
+
test("stops the spinner so the user can try again", () => {
|
|
219
|
+
render();
|
|
220
|
+
fire(checkbox(), "click");
|
|
221
|
+
setState({ error: { message: "boom", key: "CAPTCHA.UNKNOWN" } });
|
|
222
|
+
expect(mounted.container.querySelector('[aria-label="Loading spinner"]')).toBeNull();
|
|
223
|
+
});
|
|
224
|
+
test("shows the error text against the checkbox", () => {
|
|
225
|
+
render();
|
|
226
|
+
setState({ error: { message: "boom", key: "CAPTCHA.UNKNOWN" } });
|
|
227
|
+
expect(mounted.container.textContent).toContain("boom");
|
|
228
|
+
});
|
|
229
|
+
test("asks the host to re-mint an invalidated session", () => {
|
|
230
|
+
const onSessionInvalidated = vi.fn();
|
|
231
|
+
render({ onSessionInvalidated });
|
|
232
|
+
fire(checkbox(), "click", { clientX: 7, clientY: 8 });
|
|
233
|
+
setState({
|
|
234
|
+
error: { message: "gone", key: "CAPTCHA.NO_SESSION_FOUND" },
|
|
235
|
+
});
|
|
236
|
+
expect(onSessionInvalidated).toHaveBeenCalledWith(7, 8);
|
|
237
|
+
});
|
|
238
|
+
test("asks only once, however often the error re-renders", () => {
|
|
239
|
+
const onSessionInvalidated = vi.fn();
|
|
240
|
+
render({ onSessionInvalidated });
|
|
241
|
+
setState({ error: { message: "gone", key: "CAPTCHA.NO_SESSION_FOUND" } });
|
|
242
|
+
setState({
|
|
243
|
+
error: { message: "gone again", key: "CAPTCHA.NO_SESSION_FOUND" },
|
|
244
|
+
});
|
|
245
|
+
expect(onSessionInvalidated).toHaveBeenCalledTimes(1);
|
|
246
|
+
});
|
|
247
|
+
test("reports no coordinates when the user never clicked", () => {
|
|
248
|
+
const onSessionInvalidated = vi.fn();
|
|
249
|
+
render({ onSessionInvalidated });
|
|
250
|
+
setState({ error: { message: "gone", key: "CAPTCHA.NO_SESSION_FOUND" } });
|
|
251
|
+
expect(onSessionInvalidated).toHaveBeenCalledWith(undefined, undefined);
|
|
252
|
+
});
|
|
253
|
+
test("falls back to restarting the frictionless flow", () => {
|
|
254
|
+
vi.useFakeTimers();
|
|
255
|
+
const restart = vi.fn();
|
|
256
|
+
render({ frictionlessState: frictionless({ restart }) });
|
|
257
|
+
setState({ error: { message: "gone", key: "CAPTCHA.NO_SESSION_FOUND" } });
|
|
258
|
+
expect(restart).not.toHaveBeenCalled();
|
|
259
|
+
act(() => {
|
|
260
|
+
vi.advanceTimersByTime(100);
|
|
261
|
+
});
|
|
262
|
+
expect(restart).toHaveBeenCalledTimes(1);
|
|
263
|
+
});
|
|
264
|
+
test("leaves an ordinary error to the host, restarting nothing", () => {
|
|
265
|
+
vi.useFakeTimers();
|
|
266
|
+
const restart = vi.fn();
|
|
267
|
+
render({ frictionlessState: frictionless({ restart }) });
|
|
268
|
+
setState({ error: { message: "boom", key: "CAPTCHA.UNKNOWN" } });
|
|
269
|
+
act(() => {
|
|
270
|
+
vi.advanceTimersByTime(1000);
|
|
271
|
+
});
|
|
272
|
+
expect(restart).not.toHaveBeenCalled();
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
describe("starting without a click", () => {
|
|
276
|
+
test("does nothing on mount by default", () => {
|
|
277
|
+
render();
|
|
278
|
+
expect(start).not.toHaveBeenCalled();
|
|
279
|
+
});
|
|
280
|
+
test("starts on mount when the host asked it to", () => {
|
|
281
|
+
render({ autoStart: true });
|
|
282
|
+
expect(start).toHaveBeenCalledWith(0, 0);
|
|
283
|
+
});
|
|
284
|
+
test("resumes at the coordinates the user already clicked", () => {
|
|
285
|
+
render({ autoStart: true, startCoords: { x: 5, y: 6 } });
|
|
286
|
+
expect(start).toHaveBeenCalledWith(5, 6);
|
|
287
|
+
});
|
|
288
|
+
test("remembers those coordinates for a session re-mint", () => {
|
|
289
|
+
const onSessionInvalidated = vi.fn();
|
|
290
|
+
render({
|
|
291
|
+
autoStart: true,
|
|
292
|
+
startCoords: { x: 5, y: 6 },
|
|
293
|
+
onSessionInvalidated,
|
|
294
|
+
});
|
|
295
|
+
setState({ error: { message: "gone", key: "CAPTCHA.NO_SESSION_FOUND" } });
|
|
296
|
+
expect(onSessionInvalidated).toHaveBeenCalledWith(5, 6);
|
|
297
|
+
});
|
|
298
|
+
test("clears the spinner when an auto start fails", async () => {
|
|
299
|
+
start.mockImplementation(() => Promise.reject(new Error("network down")));
|
|
300
|
+
render({ autoStart: true });
|
|
301
|
+
await flush();
|
|
302
|
+
expect(mounted.container.querySelector('[aria-label="Loading spinner"]')).toBeNull();
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
describe("the execute event", () => {
|
|
306
|
+
const execute = () => {
|
|
307
|
+
act(() => {
|
|
308
|
+
document.dispatchEvent(new Event("procaptcha:execute"));
|
|
309
|
+
});
|
|
310
|
+
};
|
|
311
|
+
test("opens the modal", () => {
|
|
312
|
+
render();
|
|
313
|
+
execute();
|
|
314
|
+
expect(document.querySelector(".prosopo-modalOuter")?.style.display).toBe("flex");
|
|
315
|
+
});
|
|
316
|
+
test("fetches a challenge when there is none", () => {
|
|
317
|
+
render();
|
|
318
|
+
execute();
|
|
319
|
+
expect(start).toHaveBeenCalledTimes(1);
|
|
320
|
+
});
|
|
321
|
+
test("does not re-fetch when a challenge is already open", () => {
|
|
322
|
+
render();
|
|
323
|
+
setState(openChallenge());
|
|
324
|
+
execute();
|
|
325
|
+
expect(start).not.toHaveBeenCalled();
|
|
326
|
+
});
|
|
327
|
+
test("survives a manager that throws on start", () => {
|
|
328
|
+
start.mockImplementation(() => {
|
|
329
|
+
throw new Error("no provider");
|
|
330
|
+
});
|
|
331
|
+
const consoleError = vi
|
|
332
|
+
.spyOn(console, "error")
|
|
333
|
+
.mockImplementation(() => undefined);
|
|
334
|
+
render();
|
|
335
|
+
expect(() => execute()).not.toThrow();
|
|
336
|
+
expect(consoleError).toHaveBeenCalled();
|
|
337
|
+
consoleError.mockRestore();
|
|
338
|
+
});
|
|
339
|
+
test("stops listening once the widget is gone", () => {
|
|
340
|
+
render();
|
|
341
|
+
mounted.unmount();
|
|
342
|
+
document.dispatchEvent(new Event("procaptcha:execute"));
|
|
343
|
+
expect(start).not.toHaveBeenCalled();
|
|
344
|
+
mounted = mount();
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
describe("language", () => {
|
|
348
|
+
test("leaves i18n alone when the site pins no language", () => {
|
|
349
|
+
render();
|
|
350
|
+
expect(loadI18next).not.toHaveBeenCalled();
|
|
351
|
+
expect(changeLanguage).not.toHaveBeenCalled();
|
|
352
|
+
});
|
|
353
|
+
test("switches a supplied i18n instance to the configured language", () => {
|
|
354
|
+
render({ config: config({ language: "fr" }), i18n: i18n("en") });
|
|
355
|
+
expect(changeLanguage).toHaveBeenCalledWith("fr");
|
|
356
|
+
});
|
|
357
|
+
test("leaves a matching i18n instance alone", () => {
|
|
358
|
+
render({ config: config({ language: "fr" }), i18n: i18n("fr") });
|
|
359
|
+
expect(changeLanguage).not.toHaveBeenCalled();
|
|
360
|
+
});
|
|
361
|
+
test("boots i18n itself for a direct React consumer", () => {
|
|
362
|
+
render({ config: config({ language: "de" }) });
|
|
363
|
+
expect(loadI18next).toHaveBeenCalledWith(false, "de");
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
describe("theming", () => {
|
|
367
|
+
test("passes the manager the parsed config, not the raw props", () => {
|
|
368
|
+
render({ config: config({ theme: "dark" }) });
|
|
369
|
+
expect(managerArgs[0]?.[0].theme).toBe("dark");
|
|
370
|
+
});
|
|
371
|
+
test("defaults an unthemed widget to the light palette", () => {
|
|
372
|
+
render();
|
|
373
|
+
setState({ showModal: true, ...openChallenge() });
|
|
374
|
+
const panel = document.querySelector(".prosopo-modalInner > div");
|
|
375
|
+
expect(panel?.style.backgroundColor).toBe("rgb(255, 255, 255)");
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
describe("the manager itself", () => {
|
|
379
|
+
test("is built once, so the click coordinates survive a re-render", () => {
|
|
380
|
+
render();
|
|
381
|
+
const first = managerArgs.length;
|
|
382
|
+
setState({ isHuman: true });
|
|
383
|
+
expect(vitest.isMockFunction(start)).toBe(true);
|
|
384
|
+
expect(managerArgs.length).toBeGreaterThanOrEqual(first);
|
|
385
|
+
fire(checkbox(), "click", { clientX: 9, clientY: 9 });
|
|
386
|
+
expect(start).toHaveBeenCalledWith(9, 9);
|
|
387
|
+
});
|
|
388
|
+
test("is given the frictionless state so it can reuse the session", () => {
|
|
389
|
+
const frictionlessState = frictionless();
|
|
390
|
+
render({ frictionlessState });
|
|
391
|
+
expect(managerArgs[0]?.[4]).toBe(frictionlessState);
|
|
392
|
+
});
|
|
393
|
+
test("is given the host's callbacks", () => {
|
|
394
|
+
const callbacks = { onHuman: vi.fn() };
|
|
395
|
+
render({ callbacks });
|
|
396
|
+
expect(managerArgs[0]?.[3]).toBe(callbacks);
|
|
397
|
+
});
|
|
398
|
+
test("is given an empty callback set when the host passes none", () => {
|
|
399
|
+
render({ callbacks: undefined });
|
|
400
|
+
expect(managerArgs[0]?.[3]).toEqual({});
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
//# sourceMappingURL=procaptchaWidget.unit.test.js.map
|