@prosopo/procaptcha-pow 2.12.7 → 2.13.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.
Files changed (50) hide show
  1. package/.turbo/turbo-build$colon$cjs.log +5 -5
  2. package/.turbo/turbo-build$colon$tsc.log +19 -19
  3. package/.turbo/turbo-build.log +6 -6
  4. package/CHANGELOG.md +43 -0
  5. package/dist/cjs/components/procaptchaPoW.cjs +22 -0
  6. package/dist/cjs/components/procaptchaWidget.cjs +143 -0
  7. package/dist/cjs/index.cjs +5 -2
  8. package/dist/components/procaptchaPoW.d.ts +7 -0
  9. package/dist/components/procaptchaPoW.d.ts.map +1 -0
  10. package/dist/components/procaptchaPoW.js +21 -0
  11. package/dist/components/procaptchaPoW.js.map +1 -0
  12. package/dist/components/procaptchaWidget.d.ts +7 -0
  13. package/dist/components/procaptchaWidget.d.ts.map +1 -0
  14. package/dist/components/procaptchaWidget.js +143 -0
  15. package/dist/components/procaptchaWidget.js.map +1 -0
  16. package/dist/index.d.ts +2 -2
  17. package/dist/index.js +3 -2
  18. package/dist/tests/domHarness.d.ts +20 -0
  19. package/dist/tests/domHarness.d.ts.map +1 -0
  20. package/dist/tests/domHarness.js +56 -0
  21. package/dist/tests/domHarness.js.map +1 -0
  22. package/dist/tests/procaptchaPoW.unit.test.js +46 -30
  23. package/dist/tests/procaptchaPoW.unit.test.js.map +1 -1
  24. package/dist/tests/procaptchaPow.test-d.js +5 -5
  25. package/dist/tests/procaptchaPow.test-d.js.map +1 -1
  26. package/dist/tests/procaptchaWidget.unit.test.js +79 -137
  27. package/dist/tests/procaptchaWidget.unit.test.js.map +1 -1
  28. package/package.json +9 -13
  29. package/src/components/procaptchaPoW.ts +53 -0
  30. package/src/components/procaptchaWidget.ts +281 -0
  31. package/src/index.ts +2 -2
  32. package/src/tests/domHarness.ts +122 -0
  33. package/src/tests/procaptchaPoW.unit.test.ts +59 -36
  34. package/src/tests/procaptchaPow.test-d.ts +15 -9
  35. package/src/tests/procaptchaWidget.unit.test.ts +105 -171
  36. package/tsconfig.cjs.json +1 -2
  37. package/tsconfig.json +1 -2
  38. package/tsconfig.tsbuildinfo +1 -1
  39. package/dist/cjs/components/ProcaptchaPoW.cjs +0 -7
  40. package/dist/cjs/components/ProcaptchaWidget.cjs +0 -130
  41. package/dist/components/ProcaptchaPoW.d.ts +0 -3
  42. package/dist/components/ProcaptchaPoW.d.ts.map +0 -1
  43. package/dist/components/ProcaptchaPoW.js +0 -7
  44. package/dist/components/ProcaptchaPoW.js.map +0 -1
  45. package/dist/components/ProcaptchaWidget.d.ts +0 -4
  46. package/dist/components/ProcaptchaWidget.d.ts.map +0 -1
  47. package/dist/components/ProcaptchaWidget.js +0 -131
  48. package/dist/components/ProcaptchaWidget.js.map +0 -1
  49. package/src/components/ProcaptchaPoW.tsx +0 -39
  50. package/src/components/ProcaptchaWidget.tsx +0 -231
@@ -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/ProcaptchaWidget.js", async () => {
10
- const { createElement } = await import("react");
11
- return {
12
- default: (props) => {
13
- mocks.received.push(props);
14
- return createElement("div", { "data-testid": "widget" });
15
- },
16
- };
17
- });
18
- let container;
19
- let root;
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
- await act(async () => {
28
- root.render(createElement(entrypoint.ProcaptchaPow, widgetProps));
29
- });
32
+ widget = entrypoint.mountProcaptchaPow(mounted.container, widgetProps);
33
+ await settle();
30
34
  };
31
35
  beforeEach(() => {
32
36
  mocks.received.length = 0;
33
- container = document.createElement("div");
34
- document.body.appendChild(container);
35
- act(() => {
36
- root = createRoot(container);
37
- });
37
+ mocks.destroyed.count = 0;
38
+ widget = undefined;
39
+ mounted = mount();
38
40
  });
39
41
  afterEach(() => {
40
- act(() => {
41
- root.unmount();
42
- });
43
- container.remove();
42
+ widget?.destroy();
43
+ widget = undefined;
44
+ mounted.unmount();
44
45
  });
45
- describe("ProcaptchaPow", () => {
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 widget a consumer mounts, and nothing else", () => {
75
- expect(typeof entrypoint.ProcaptchaPow).toBe("function");
76
- expect(Object.keys(entrypoint)).toEqual(["ProcaptchaPow"]);
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":"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"}
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 { ProcaptchaPow } from "../index.js";
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("ProcaptchaPow takes the shared widget props and renders an element", () => {
8
- expectTypeOf(ProcaptchaPow).parameters.toEqualTypeOf();
9
- expectTypeOf(ProcaptchaPow).returns.toExtend();
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 inner widget's default export is not re-exported", () => {
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":"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"}
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 Procaptcha from "../components/ProcaptchaWidget.js";
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
- useTranslation: () => ({
31
+ createTranslator: () => ({
57
32
  t: (key) => key,
58
- ready: mocks.translationsReady.current,
33
+ isReady: () => mocks.translationsReady.current,
34
+ subscribe: () => () => undefined,
35
+ i18n: undefined,
59
36
  }),
60
37
  };
61
38
  });
62
- let container;
63
- let root;
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
- act(() => {
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
- container = document.createElement("div");
85
- document.body.appendChild(container);
86
- act(() => {
87
- root = createRoot(container);
88
- });
57
+ widget = undefined;
58
+ mounted = mount();
89
59
  });
90
60
  afterEach(() => {
91
- act(() => {
92
- root.unmount();
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 = container.querySelector('input[name="honeypot"]');
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
- const nativeEvent = {
111
- isTrusted: options.trusted ?? true,
112
- clientX: options.clientX ?? 0,
113
- clientY: options.clientY ?? 0,
114
- ...(options.touches ? { touches: options.touches } : {}),
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('[aria-label="human checkbox"]')).toBeNull();
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(mocks.honeypotQuestions.at(-1)).toBe("question");
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(container.querySelector('input[name="honeypot"]')).toBeNull();
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(mocks.checkboxProps.current?.labelText).toBe("WIDGET.I_AM_HUMAN");
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(mocks.checkboxProps.current?.labelText).toBe("");
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
- act(() => {
172
- mocks.constructions[0]?.updateState({
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(mocks.checkboxProps.current?.error).toBe("no session");
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("an untrusted click solves, but its coordinates are discarded", () => {
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).toHaveBeenCalledWith(0, 0);
176
+ expect(mocks.start).not.toHaveBeenCalled();
207
177
  });
208
- test("an event that reports no position at all is treated as the origin", () => {
178
+ test("a keyboard activation is treated as the origin", () => {
209
179
  render(props());
210
- act(() => {
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
- expect(mocks.checkboxProps.current?.loading).toBe(true);
225
- await act(async () => {
226
- release();
227
- });
228
- expect(mocks.checkboxProps.current?.loading).toBe(false);
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 act(async () => undefined);
235
- expect(mocks.checkboxProps.current?.loading).toBe(false);
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
- await act(async () => {
242
- await mocks.checkboxProps.current?.onChange({
243
- nativeEvent: { isTrusted: true, clientX: 1, clientY: 2 },
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
- await act(async () => {
258
- release();
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
- act(() => {
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
- await act(async () => {
292
- target.dispatchEvent(new Event("procaptcha:execute"));
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
- act(() => {
314
- root.unmount();
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 act(async () => undefined);
291
+ await settle();
336
292
  expect(reported).toHaveBeenCalled();
337
293
  });
338
294
  });
339
295
  describe("an invalidated session", () => {
340
- const fail = (key) => {
341
- act(() => {
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
- act(() => {
364
- mocks.constructions[0]?.updateState({ error: undefined });
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
- act(() => {
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
- act(() => {
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(() => fail("CAPTCHA.NO_SESSION_FOUND")).not.toThrow();
341
+ await expect(fail("CAPTCHA.NO_SESSION_FOUND")).resolves.toBeUndefined();
400
342
  });
401
343
  });
402
344
  describe("language", () => {