@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.
Files changed (50) hide show
  1. package/.turbo/turbo-build$colon$cjs.log +5 -5
  2. package/.turbo/turbo-build$colon$tsc.log +20 -20
  3. package/.turbo/turbo-build.log +6 -6
  4. package/CHANGELOG.md +62 -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 +11 -15
  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
@@ -14,33 +14,42 @@
14
14
 
15
15
  import type { Ti18n } from "@prosopo/locale";
16
16
  import type { ProcaptchaProps } from "@prosopo/types";
17
- import { type ReactElement, act, createElement } from "react";
18
- import { type Root, createRoot } from "react-dom/client";
19
17
  import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
18
+ import type { ProcaptchaPowHandle } from "../components/procaptchaWidget.js";
20
19
  import * as entrypoint from "../index.js";
20
+ import { type Mounted, mount, settle } from "./domHarness.js";
21
21
  import { config } from "./managerHarness.js";
22
22
 
23
23
  /**
24
- * The outer component exists only to lazy-load the widget and hand it the
24
+ * The outer entry point exists only to lazy-load the widget and hand it the
25
25
  * props it was given — which it once did by naming them one at a time, quietly
26
26
  * dropping `onEscalate` and `autoStart`.
27
27
  */
28
28
  const mocks = vi.hoisted(() => ({
29
29
  received: [] as ProcaptchaProps[],
30
+ destroyed: { count: 0 },
30
31
  }));
31
32
 
32
- vi.mock("../components/ProcaptchaWidget.js", async () => {
33
- const { createElement } = await import("react");
34
- return {
35
- default: (props: ProcaptchaProps) => {
36
- mocks.received.push(props);
37
- return createElement("div", { "data-testid": "widget" });
38
- },
39
- };
40
- });
33
+ vi.mock("../components/procaptchaWidget.js", () => ({
34
+ mountProcaptchaPowWidget: (
35
+ container: HTMLElement,
36
+ props: ProcaptchaProps,
37
+ ): ProcaptchaPowHandle => {
38
+ mocks.received.push(props);
39
+ const element = document.createElement("div");
40
+ element.setAttribute("data-testid", "widget");
41
+ container.appendChild(element);
42
+ return {
43
+ destroy: () => {
44
+ element.remove();
45
+ mocks.destroyed.count += 1;
46
+ },
47
+ };
48
+ },
49
+ }));
41
50
 
42
- let container: HTMLDivElement;
43
- let root: Root;
51
+ let mounted: Mounted;
52
+ let widget: ProcaptchaPowHandle | undefined;
44
53
 
45
54
  const props = (overrides: Partial<ProcaptchaProps> = {}): ProcaptchaProps => ({
46
55
  config: config(),
@@ -50,33 +59,29 @@ const props = (overrides: Partial<ProcaptchaProps> = {}): ProcaptchaProps => ({
50
59
  });
51
60
 
52
61
  const render = async (widgetProps: ProcaptchaProps): Promise<void> => {
53
- await act(async () => {
54
- root.render(
55
- createElement(entrypoint.ProcaptchaPow, widgetProps) as ReactElement,
56
- );
57
- });
62
+ widget = entrypoint.mountProcaptchaPow(mounted.container, widgetProps);
63
+ await settle();
58
64
  };
59
65
 
60
66
  beforeEach(() => {
61
67
  mocks.received.length = 0;
62
- container = document.createElement("div");
63
- document.body.appendChild(container);
64
- act(() => {
65
- root = createRoot(container);
66
- });
68
+ mocks.destroyed.count = 0;
69
+ widget = undefined;
70
+ mounted = mount();
67
71
  });
68
72
 
69
73
  afterEach(() => {
70
- act(() => {
71
- root.unmount();
72
- });
73
- container.remove();
74
+ widget?.destroy();
75
+ widget = undefined;
76
+ mounted.unmount();
74
77
  });
75
78
 
76
- describe("ProcaptchaPow", () => {
79
+ describe("mountProcaptchaPow", () => {
77
80
  test("renders the widget once it has loaded", async () => {
78
81
  await render(props());
79
- expect(container.querySelector('[data-testid="widget"]')).not.toBeNull();
82
+ expect(
83
+ mounted.container.querySelector('[data-testid="widget"]'),
84
+ ).not.toBeNull();
80
85
  });
81
86
 
82
87
  test("passes on the props the widget needs but never names", async () => {
@@ -107,14 +112,32 @@ describe("ProcaptchaPow", () => {
107
112
  expect(mocks.received[0]?.callbacks).toBe(callbacks);
108
113
  expect(mocks.received[0]?.config).toEqual(config());
109
114
  });
115
+
116
+ test("tears the loaded widget down", async () => {
117
+ await render(props());
118
+ widget?.destroy();
119
+ widget = undefined;
120
+ expect(mocks.destroyed.count).toBe(1);
121
+ expect(
122
+ mounted.container.querySelector('[data-testid="widget"]'),
123
+ ).toBeNull();
124
+ });
125
+
126
+ test("cancels a mount that is still loading", async () => {
127
+ // Otherwise a widget torn down mid-load reappears as an orphan that
128
+ // nothing holds a handle to.
129
+ widget = entrypoint.mountProcaptchaPow(mounted.container, props());
130
+ widget.destroy();
131
+ widget = undefined;
132
+ await settle();
133
+ expect(mocks.received).toHaveLength(0);
134
+ });
110
135
  });
111
136
 
112
137
  describe("the package entrypoint", () => {
113
- test("exports the widget a consumer mounts, and nothing else", () => {
114
- // The inner ProcaptchaWidget is a default export, which `export *` does
115
- // not re-export: consumers get the lazy-loading wrapper only, which is
116
- // the one that works outside a bundler that can code-split.
117
- expect(typeof entrypoint.ProcaptchaPow).toBe("function");
118
- expect(Object.keys(entrypoint)).toEqual(["ProcaptchaPow"]);
138
+ test("exports the mount functions a consumer needs", () => {
139
+ expect(typeof entrypoint.mountProcaptchaPow).toBe("function");
140
+ expect(typeof entrypoint.loadProcaptchaPow).toBe("function");
141
+ expect(typeof entrypoint.mountProcaptchaPowWidget).toBe("function");
119
142
  });
120
143
  });
@@ -22,10 +22,9 @@ import type {
22
22
  ProcaptchaState,
23
23
  ProcaptchaStateUpdateFn,
24
24
  } from "@prosopo/types";
25
- import type { ReactElement } from "react";
26
25
  import { assertType, describe, expectTypeOf, test } from "vitest";
27
26
  import type * as entrypoint from "../index.js";
28
- import { ProcaptchaPow } from "../index.js";
27
+ import { mountProcaptchaPow } from "../index.js";
29
28
  import { Manager } from "../services/Manager.js";
30
29
  import { config, frictionless, state } from "./managerHarness.js";
31
30
 
@@ -33,15 +32,22 @@ import { config, frictionless, state } from "./managerHarness.js";
33
32
  const i18n = (): Ti18n => undefined as unknown as Ti18n;
34
33
 
35
34
  describe("the package entrypoint's types", () => {
36
- test("ProcaptchaPow takes the shared widget props and renders an element", () => {
37
- expectTypeOf(ProcaptchaPow).parameters.toEqualTypeOf<[ProcaptchaProps]>();
38
- expectTypeOf(ProcaptchaPow).returns.toExtend<ReactElement>();
35
+ test("mountProcaptchaPow takes a host element and the shared widget props", () => {
36
+ expectTypeOf(mountProcaptchaPow).parameters.toEqualTypeOf<
37
+ [HTMLElement, ProcaptchaProps]
38
+ >();
39
+ expectTypeOf(mountProcaptchaPow).returns.toExtend<{
40
+ destroy: () => void;
41
+ }>();
39
42
  });
40
43
 
41
- test("the inner widget's default export is not re-exported", () => {
42
- // `export *` skips default exports, so consumers can only reach the lazy
43
- // wrapper — the one that works without a code-splitting bundler.
44
- expectTypeOf<keyof typeof entrypoint>().toEqualTypeOf<"ProcaptchaPow">();
44
+ test("the entrypoint exposes the lazy wrapper and the widget itself", () => {
45
+ // The lazy wrapper is what works without a code-splitting bundler; the
46
+ // direct mount is what ProcaptchaFrictionless imports once it has already
47
+ // paid for the dynamic import of this package.
48
+ expectTypeOf<keyof typeof entrypoint>().toEqualTypeOf<
49
+ "mountProcaptchaPow" | "loadProcaptchaPow" | "mountProcaptchaPowWidget"
50
+ >();
45
51
  });
46
52
 
47
53
  test("config, callbacks and i18n are all required", () => {