@prosopo/procaptcha-wrapper 2.7.14 → 2.7.16
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 +2 -5
- package/.turbo/turbo-build$colon$tsc.log +9 -12
- package/.turbo/turbo-build.log +3 -9
- package/CHANGELOG.md +27 -0
- package/LICENSE +201 -0
- package/package.json +59 -59
- package/src/index.ts +0 -37
- package/src/render/renderFunction.ts +0 -91
- package/src/render/renderer.ts +0 -65
- package/src/tests/index.unit.test.ts +0 -47
- package/src/tests/renderFunction.unit.test.ts +0 -275
- package/src/tests/renderer.test-d.ts +0 -186
- package/src/tests/renderer.unit.test.ts +0 -331
- package/tsconfig.cjs.json +0 -19
- package/tsconfig.json +0 -25
- package/tsconfig.tsbuildinfo +0 -1
- package/tsconfig.types.json +0 -9
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
// Copyright 2021-2026 Prosopo (UK) Ltd.
|
|
2
|
-
//
|
|
3
|
-
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
// you may not use this file except in compliance with the License.
|
|
5
|
-
// You may obtain a copy of the License at
|
|
6
|
-
//
|
|
7
|
-
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
//
|
|
9
|
-
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
// See the License for the specific language governing permissions and
|
|
13
|
-
// limitations under the License.
|
|
14
|
-
|
|
15
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
16
|
-
import { renderProcaptcha } from "../index.js";
|
|
17
|
-
|
|
18
|
-
afterEach(() => {
|
|
19
|
-
document.head.innerHTML = "";
|
|
20
|
-
document.body.innerHTML = "";
|
|
21
|
-
window.procaptcha = undefined;
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
describe("renderProcaptcha", () => {
|
|
25
|
-
it("is exported as a renderer function", () => {
|
|
26
|
-
expect(typeof renderProcaptcha).toBe("function");
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it("does not touch the DOM until it is called", () => {
|
|
30
|
-
// The renderer is built at import time, but the script must only be
|
|
31
|
-
// fetched on first render — importing the package must cost nothing.
|
|
32
|
-
expect(document.head.querySelectorAll("script")).toHaveLength(0);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it("injects the build-time script url on first render", async () => {
|
|
36
|
-
const rendering = renderProcaptcha(document.createElement("div"), {
|
|
37
|
-
siteKey: "site-key",
|
|
38
|
-
});
|
|
39
|
-
await Promise.resolve();
|
|
40
|
-
|
|
41
|
-
const script = document.head.querySelector("script");
|
|
42
|
-
expect(script).not.toBeNull();
|
|
43
|
-
|
|
44
|
-
script?.dispatchEvent(new Event("error"));
|
|
45
|
-
await expect(rendering).rejects.toThrow("Failed to load script");
|
|
46
|
-
});
|
|
47
|
-
});
|
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
// Copyright 2021-2026 Prosopo (UK) Ltd.
|
|
2
|
-
//
|
|
3
|
-
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
// you may not use this file except in compliance with the License.
|
|
5
|
-
// You may obtain a copy of the License at
|
|
6
|
-
//
|
|
7
|
-
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
//
|
|
9
|
-
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
// See the License for the specific language governing permissions and
|
|
13
|
-
// limitations under the License.
|
|
14
|
-
|
|
15
|
-
import type { ProcaptchaRenderOptions } from "@prosopo/types";
|
|
16
|
-
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
17
|
-
import {
|
|
18
|
-
type RendererFunction,
|
|
19
|
-
loadRenderFunction,
|
|
20
|
-
loadScript,
|
|
21
|
-
} from "../render/renderFunction.js";
|
|
22
|
-
|
|
23
|
-
const SCRIPT_URL = "https://example.test/procaptcha.bundle.js";
|
|
24
|
-
const SCRIPT_ID = "procaptcha-script";
|
|
25
|
-
|
|
26
|
-
const noopRender: RendererFunction = async (): Promise<void> => {};
|
|
27
|
-
|
|
28
|
-
/** The most recently appended script tag, i.e. the one a load is waiting on. */
|
|
29
|
-
const lastScript = (): HTMLScriptElement => {
|
|
30
|
-
const scripts = document.head.querySelectorAll("script");
|
|
31
|
-
const script = scripts[scripts.length - 1];
|
|
32
|
-
if (undefined === script) {
|
|
33
|
-
throw new Error("no script tag was appended");
|
|
34
|
-
}
|
|
35
|
-
return script;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* jsdom never fetches, so nothing ever fires load/error by itself. Wait for the
|
|
40
|
-
* tag to appear, then drive the outcome by hand.
|
|
41
|
-
*/
|
|
42
|
-
const settleScript = async (outcome: "load" | "error"): Promise<void> => {
|
|
43
|
-
await Promise.resolve();
|
|
44
|
-
lastScript().dispatchEvent(new Event(outcome));
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
afterEach(() => {
|
|
48
|
-
document.head.innerHTML = "";
|
|
49
|
-
document.body.innerHTML = "";
|
|
50
|
-
window.procaptcha = undefined;
|
|
51
|
-
vi.restoreAllMocks();
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
describe("loadScript", () => {
|
|
55
|
-
it("appends a script tag carrying the requested src", async () => {
|
|
56
|
-
const loading = loadScript(SCRIPT_URL);
|
|
57
|
-
await settleScript("load");
|
|
58
|
-
await loading;
|
|
59
|
-
|
|
60
|
-
expect(lastScript().src).toBe(SCRIPT_URL);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("applies the supplied attributes to the tag", async () => {
|
|
64
|
-
const loading = loadScript(SCRIPT_URL, {
|
|
65
|
-
id: SCRIPT_ID,
|
|
66
|
-
type: "module",
|
|
67
|
-
async: true,
|
|
68
|
-
defer: true,
|
|
69
|
-
});
|
|
70
|
-
await settleScript("load");
|
|
71
|
-
await loading;
|
|
72
|
-
|
|
73
|
-
const script = lastScript();
|
|
74
|
-
expect(script.id).toBe(SCRIPT_ID);
|
|
75
|
-
expect(script.type).toBe("module");
|
|
76
|
-
expect(script.async).toBe(true);
|
|
77
|
-
expect(script.defer).toBe(true);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("works with no attributes at all", async () => {
|
|
81
|
-
const loading = loadScript(SCRIPT_URL);
|
|
82
|
-
await settleScript("load");
|
|
83
|
-
|
|
84
|
-
await expect(loading).resolves.toBeUndefined();
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it("lets an attribute override the src", async () => {
|
|
88
|
-
const loading = loadScript(SCRIPT_URL, {
|
|
89
|
-
src: "https://example.test/other.js",
|
|
90
|
-
});
|
|
91
|
-
await settleScript("load");
|
|
92
|
-
await loading;
|
|
93
|
-
|
|
94
|
-
expect(lastScript().src).toBe("https://example.test/other.js");
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it("appends the tag to document.head, not the body", async () => {
|
|
98
|
-
const loading = loadScript(SCRIPT_URL);
|
|
99
|
-
await settleScript("load");
|
|
100
|
-
await loading;
|
|
101
|
-
|
|
102
|
-
expect(document.head.querySelectorAll("script")).toHaveLength(1);
|
|
103
|
-
expect(document.body.querySelectorAll("script")).toHaveLength(0);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it("rejects with an Error, not the raw DOM Event", async () => {
|
|
107
|
-
const loading = loadScript(SCRIPT_URL);
|
|
108
|
-
await settleScript("error");
|
|
109
|
-
|
|
110
|
-
await expect(loading).rejects.toBeInstanceOf(Error);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it("names the failing url in the rejection message", async () => {
|
|
114
|
-
const loading = loadScript(SCRIPT_URL);
|
|
115
|
-
await settleScript("error");
|
|
116
|
-
|
|
117
|
-
await expect(loading).rejects.toThrow(
|
|
118
|
-
`Failed to load script: ${SCRIPT_URL}`,
|
|
119
|
-
);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it("removes the failed tag so a retry leaves no duplicate id behind", async () => {
|
|
123
|
-
const first = loadScript(SCRIPT_URL, { id: SCRIPT_ID });
|
|
124
|
-
await settleScript("error");
|
|
125
|
-
await expect(first).rejects.toThrow();
|
|
126
|
-
|
|
127
|
-
expect(document.head.querySelectorAll("script")).toHaveLength(0);
|
|
128
|
-
|
|
129
|
-
const second = loadScript(SCRIPT_URL, { id: SCRIPT_ID });
|
|
130
|
-
await settleScript("load");
|
|
131
|
-
await second;
|
|
132
|
-
|
|
133
|
-
expect(document.querySelectorAll(`#${SCRIPT_ID}`)).toHaveLength(1);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it("stays pending until the script settles", async () => {
|
|
137
|
-
let settled = false;
|
|
138
|
-
const loading = loadScript(SCRIPT_URL).then(() => {
|
|
139
|
-
settled = true;
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
await Promise.resolve();
|
|
143
|
-
expect(settled).toBe(false);
|
|
144
|
-
|
|
145
|
-
await settleScript("load");
|
|
146
|
-
await loading;
|
|
147
|
-
expect(settled).toBe(true);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it("accepts an empty url without throwing synchronously", async () => {
|
|
151
|
-
const loading = loadScript("");
|
|
152
|
-
await settleScript("load");
|
|
153
|
-
|
|
154
|
-
await expect(loading).resolves.toBeUndefined();
|
|
155
|
-
});
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
describe("loadRenderFunction", () => {
|
|
159
|
-
it("returns the render function the script installed on window", async () => {
|
|
160
|
-
const loading = loadRenderFunction(SCRIPT_URL, SCRIPT_ID);
|
|
161
|
-
await Promise.resolve();
|
|
162
|
-
window.procaptcha = { render: noopRender };
|
|
163
|
-
lastScript().dispatchEvent(new Event("load"));
|
|
164
|
-
|
|
165
|
-
await expect(loading).resolves.toBe(noopRender);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it("requests the script as a deferred async module with the given id", async () => {
|
|
169
|
-
const loading = loadRenderFunction(SCRIPT_URL, SCRIPT_ID);
|
|
170
|
-
await Promise.resolve();
|
|
171
|
-
const script = lastScript();
|
|
172
|
-
|
|
173
|
-
expect(script.src).toBe(SCRIPT_URL);
|
|
174
|
-
expect(script.id).toBe(SCRIPT_ID);
|
|
175
|
-
expect(script.type).toBe("module");
|
|
176
|
-
expect(script.async).toBe(true);
|
|
177
|
-
expect(script.defer).toBe(true);
|
|
178
|
-
|
|
179
|
-
window.procaptcha = { render: noopRender };
|
|
180
|
-
script.dispatchEvent(new Event("load"));
|
|
181
|
-
await loading;
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
it("removes the tag when the script loads but is the wrong script", async () => {
|
|
185
|
-
// Same reasoning as a failed load: a retry must not find a stale tag
|
|
186
|
-
// sitting on the id it is about to reuse.
|
|
187
|
-
const loading = loadRenderFunction(SCRIPT_URL, SCRIPT_ID);
|
|
188
|
-
await settleScript("load");
|
|
189
|
-
await expect(loading).rejects.toThrow();
|
|
190
|
-
|
|
191
|
-
expect(document.getElementById(SCRIPT_ID)).toBeNull();
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it("leaves a retry with exactly one tag after a wrong-script load", async () => {
|
|
195
|
-
const first = loadRenderFunction(SCRIPT_URL, SCRIPT_ID);
|
|
196
|
-
await settleScript("load");
|
|
197
|
-
await expect(first).rejects.toThrow();
|
|
198
|
-
|
|
199
|
-
const second = loadRenderFunction(SCRIPT_URL, SCRIPT_ID);
|
|
200
|
-
await Promise.resolve();
|
|
201
|
-
window.procaptcha = { render: noopRender };
|
|
202
|
-
lastScript().dispatchEvent(new Event("load"));
|
|
203
|
-
|
|
204
|
-
await expect(second).resolves.toBe(noopRender);
|
|
205
|
-
expect(document.querySelectorAll(`#${SCRIPT_ID}`)).toHaveLength(1);
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
it("keeps the tag when the script loads successfully", async () => {
|
|
209
|
-
const loading = loadRenderFunction(SCRIPT_URL, SCRIPT_ID);
|
|
210
|
-
await Promise.resolve();
|
|
211
|
-
window.procaptcha = { render: noopRender };
|
|
212
|
-
lastScript().dispatchEvent(new Event("load"));
|
|
213
|
-
await loading;
|
|
214
|
-
|
|
215
|
-
expect(document.getElementById(SCRIPT_ID)).not.toBeNull();
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
it("throws when the script loads but installs no procaptcha global", async () => {
|
|
219
|
-
const loading = loadRenderFunction(SCRIPT_URL, SCRIPT_ID);
|
|
220
|
-
await settleScript("load");
|
|
221
|
-
|
|
222
|
-
await expect(loading).rejects.toThrow(
|
|
223
|
-
"Render script does not contain the render function",
|
|
224
|
-
);
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
it("throws when the global exists but carries no render function", async () => {
|
|
228
|
-
const loading = loadRenderFunction(SCRIPT_URL, SCRIPT_ID);
|
|
229
|
-
await Promise.resolve();
|
|
230
|
-
// A partially-initialised global is indistinguishable from a stale one.
|
|
231
|
-
window.procaptcha = { render: undefined } as unknown as {
|
|
232
|
-
render: RendererFunction;
|
|
233
|
-
};
|
|
234
|
-
lastScript().dispatchEvent(new Event("load"));
|
|
235
|
-
|
|
236
|
-
await expect(loading).rejects.toThrow(
|
|
237
|
-
"Render script does not contain the render function",
|
|
238
|
-
);
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
it("propagates the load failure rather than reporting a missing function", async () => {
|
|
242
|
-
const loading = loadRenderFunction(SCRIPT_URL, SCRIPT_ID);
|
|
243
|
-
await settleScript("error");
|
|
244
|
-
|
|
245
|
-
await expect(loading).rejects.toThrow(
|
|
246
|
-
`Failed to load script: ${SCRIPT_URL}`,
|
|
247
|
-
);
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
it("does not check the global until the script has loaded", async () => {
|
|
251
|
-
const loading = loadRenderFunction(SCRIPT_URL, SCRIPT_ID);
|
|
252
|
-
await Promise.resolve();
|
|
253
|
-
// Set late — after injection but before load — to prove the check is
|
|
254
|
-
// deferred until the script has actually run.
|
|
255
|
-
window.procaptcha = { render: noopRender };
|
|
256
|
-
lastScript().dispatchEvent(new Event("load"));
|
|
257
|
-
|
|
258
|
-
await expect(loading).resolves.toBe(noopRender);
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
it("returns a function that forwards its arguments to the loaded render", async () => {
|
|
262
|
-
const render = vi.fn<RendererFunction>(async (): Promise<void> => {});
|
|
263
|
-
const loading = loadRenderFunction(SCRIPT_URL, SCRIPT_ID);
|
|
264
|
-
await Promise.resolve();
|
|
265
|
-
window.procaptcha = { render };
|
|
266
|
-
lastScript().dispatchEvent(new Event("load"));
|
|
267
|
-
|
|
268
|
-
const loaded = await loading;
|
|
269
|
-
const element = document.createElement("div");
|
|
270
|
-
const options: ProcaptchaRenderOptions = { siteKey: "site-key" };
|
|
271
|
-
await loaded(element, options);
|
|
272
|
-
|
|
273
|
-
expect(render).toHaveBeenCalledWith(element, options);
|
|
274
|
-
});
|
|
275
|
-
});
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
// Copyright 2021-2026 Prosopo (UK) Ltd.
|
|
2
|
-
//
|
|
3
|
-
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
// you may not use this file except in compliance with the License.
|
|
5
|
-
// You may obtain a copy of the License at
|
|
6
|
-
//
|
|
7
|
-
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
//
|
|
9
|
-
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
// See the License for the specific language governing permissions and
|
|
13
|
-
// limitations under the License.
|
|
14
|
-
|
|
15
|
-
import type { Languages } from "@prosopo/locale";
|
|
16
|
-
import type { CaptchaType, ProcaptchaRenderOptions } from "@prosopo/types";
|
|
17
|
-
import { assertType, describe, expectTypeOf, it } from "vitest";
|
|
18
|
-
import type {
|
|
19
|
-
ProcaptchaRenderOptions as ExportedRenderOptions,
|
|
20
|
-
ProcaptchaLanguages,
|
|
21
|
-
ProcaptchaType,
|
|
22
|
-
} from "../index.js";
|
|
23
|
-
import { renderProcaptcha } from "../index.js";
|
|
24
|
-
import {
|
|
25
|
-
type RendererFunction,
|
|
26
|
-
loadRenderFunction,
|
|
27
|
-
loadScript,
|
|
28
|
-
} from "../render/renderFunction.js";
|
|
29
|
-
import {
|
|
30
|
-
type LoadRenderFunction,
|
|
31
|
-
type RendererSettings,
|
|
32
|
-
createRenderer,
|
|
33
|
-
} from "../render/renderer.js";
|
|
34
|
-
|
|
35
|
-
// This package is published, so these types are its contract with consumers.
|
|
36
|
-
// Runtime tests cannot catch a signature that silently widens or drifts.
|
|
37
|
-
|
|
38
|
-
describe("RendererFunction", () => {
|
|
39
|
-
it("takes an element and options, and resolves to void", () => {
|
|
40
|
-
expectTypeOf<RendererFunction>().parameters.toEqualTypeOf<
|
|
41
|
-
[HTMLElement, ProcaptchaRenderOptions]
|
|
42
|
-
>();
|
|
43
|
-
expectTypeOf<RendererFunction>().returns.toEqualTypeOf<Promise<void>>();
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it("does not accept a bare Element for the mount point", () => {
|
|
47
|
-
// Narrower than Element on purpose: the render script sets style and
|
|
48
|
-
// dataset properties that only exist on HTMLElement.
|
|
49
|
-
expectTypeOf<Element>().not.toMatchTypeOf<
|
|
50
|
-
Parameters<RendererFunction>[0]
|
|
51
|
-
>();
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("requires a siteKey in the options", () => {
|
|
55
|
-
assertType<Parameters<RendererFunction>[1]>({ siteKey: "site-key" });
|
|
56
|
-
// @ts-expect-error siteKey is mandatory
|
|
57
|
-
assertType<Parameters<RendererFunction>[1]>({});
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it("rejects an unknown option key", () => {
|
|
61
|
-
assertType<Parameters<RendererFunction>[1]>({
|
|
62
|
-
siteKey: "site-key",
|
|
63
|
-
// @ts-expect-error not part of the render options
|
|
64
|
-
notAnOption: true,
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
describe("renderProcaptcha", () => {
|
|
70
|
-
it("is exposed as a RendererFunction", () => {
|
|
71
|
-
expectTypeOf(renderProcaptcha).toEqualTypeOf<RendererFunction>();
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
describe("re-exported types", () => {
|
|
76
|
-
it("aliases the shared types rather than redeclaring them", () => {
|
|
77
|
-
// These are the names consumers import; they must stay tied to the
|
|
78
|
-
// shared definitions so a change upstream is not silently absorbed.
|
|
79
|
-
expectTypeOf<ProcaptchaType>().toEqualTypeOf<CaptchaType>();
|
|
80
|
-
expectTypeOf<ExportedRenderOptions>().toEqualTypeOf<ProcaptchaRenderOptions>();
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it("carries the placement and bind options through to consumers", () => {
|
|
84
|
-
assertType<ExportedRenderOptions>({ siteKey: "k", placement: "float" });
|
|
85
|
-
assertType<ExportedRenderOptions>({ siteKey: "k", placement: "popup" });
|
|
86
|
-
assertType<ExportedRenderOptions>({ siteKey: "k", bind: "#submit" });
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it("rejects a placement outside the union", () => {
|
|
90
|
-
// @ts-expect-error inline is not a supported placement
|
|
91
|
-
assertType<ExportedRenderOptions>({ siteKey: "k", placement: "inline" });
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("exports the language union as a usable type", () => {
|
|
95
|
-
// `Languages` is a const object with no type meaning, so re-exporting the
|
|
96
|
-
// name as a type produced a binding consumers could not use at all.
|
|
97
|
-
expectTypeOf<ProcaptchaLanguages>().toEqualTypeOf<
|
|
98
|
-
(typeof Languages)[keyof typeof Languages]
|
|
99
|
-
>();
|
|
100
|
-
assertType<ProcaptchaLanguages>("en");
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it("keeps the language union aligned with the render options", () => {
|
|
104
|
-
expectTypeOf<ProcaptchaLanguages>().toEqualTypeOf<
|
|
105
|
-
NonNullable<ProcaptchaRenderOptions["language"]>
|
|
106
|
-
>();
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it("rejects a language outside the union", () => {
|
|
110
|
-
// @ts-expect-error not a supported language
|
|
111
|
-
assertType<ProcaptchaLanguages>("not-a-language");
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
describe("createRenderer", () => {
|
|
116
|
-
it("returns a RendererFunction", () => {
|
|
117
|
-
expectTypeOf(createRenderer).returns.toEqualTypeOf<RendererFunction>();
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it("takes settings and an optional loader override", () => {
|
|
121
|
-
expectTypeOf(createRenderer).parameters.toEqualTypeOf<
|
|
122
|
-
[RendererSettings, (LoadRenderFunction | undefined)?]
|
|
123
|
-
>();
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it("accepts a call with settings alone", () => {
|
|
127
|
-
expectTypeOf(createRenderer).toBeCallableWith({
|
|
128
|
-
scriptUrl: "url",
|
|
129
|
-
scriptId: "id",
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it("requires both settings fields", () => {
|
|
134
|
-
// @ts-expect-error scriptId is mandatory
|
|
135
|
-
assertType<RendererSettings>({ scriptUrl: "url" });
|
|
136
|
-
// @ts-expect-error scriptUrl is mandatory
|
|
137
|
-
assertType<RendererSettings>({ scriptId: "id" });
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it("rejects a loader that resolves to the wrong shape", () => {
|
|
141
|
-
// @ts-expect-error the loader must resolve to a RendererFunction
|
|
142
|
-
createRenderer({ scriptUrl: "url", scriptId: "id" }, async () => "nope");
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
describe("LoadRenderFunction", () => {
|
|
147
|
-
it("takes two strings and resolves to a RendererFunction", () => {
|
|
148
|
-
expectTypeOf<LoadRenderFunction>().parameters.toEqualTypeOf<
|
|
149
|
-
[string, string]
|
|
150
|
-
>();
|
|
151
|
-
expectTypeOf<LoadRenderFunction>().returns.toEqualTypeOf<
|
|
152
|
-
Promise<RendererFunction>
|
|
153
|
-
>();
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it("is satisfied by the real loader", () => {
|
|
157
|
-
// The default argument of createRenderer depends on this holding.
|
|
158
|
-
expectTypeOf(loadRenderFunction).toMatchTypeOf<LoadRenderFunction>();
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
describe("loadScript", () => {
|
|
163
|
-
it("resolves to void rather than the tag it created", () => {
|
|
164
|
-
expectTypeOf(loadScript).returns.toEqualTypeOf<Promise<void>>();
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it("takes an optional partial set of script attributes", () => {
|
|
168
|
-
expectTypeOf(loadScript).toBeCallableWith("url");
|
|
169
|
-
expectTypeOf(loadScript).toBeCallableWith("url", { id: "id" });
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
it("rejects an attribute that is not on a script element", () => {
|
|
173
|
-
// @ts-expect-error href belongs to link elements, not script
|
|
174
|
-
loadScript("url", { href: "url" });
|
|
175
|
-
});
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
describe("window.procaptcha", () => {
|
|
179
|
-
it("is declared as possibly absent", () => {
|
|
180
|
-
// The global only exists once the render script has run, so consumers
|
|
181
|
-
// must be forced to narrow before reaching for render.
|
|
182
|
-
expectTypeOf<typeof window.procaptcha>().toEqualTypeOf<
|
|
183
|
-
{ render: RendererFunction } | undefined
|
|
184
|
-
>();
|
|
185
|
-
});
|
|
186
|
-
});
|