@prosopo/client-bundle-example 2.10.24 → 2.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build$colon$cjs.log +5 -4
- package/.turbo/turbo-build$colon$tsc.log +11 -11
- package/.turbo/turbo-build.log +6 -5
- package/CHANGELOG.md +21 -0
- package/dist/cjs/index.cjs +13 -41
- package/dist/cjs/plugins/pages.cjs +113 -0
- package/dist/index.js +2 -42
- package/dist/plugins/pages.js +103 -0
- package/package.json +5 -5
- package/src/frictionless-explicit-web3.html +10 -23
- package/src/frictionless-explicit.html +10 -22
- package/src/frictionless-implicit.html +60 -86
- package/src/frictionless-manual-start.html +13 -33
- package/src/image-explicit-web3.html +8 -24
- package/src/image-explicit.html +8 -23
- package/src/image-implicit.html +192 -0
- package/src/index.html +11 -26
- package/src/index.ts +1 -62
- package/src/invisible-frictionless-explicit.html +8 -17
- package/src/invisible-frictionless-implicit.html +8 -25
- package/src/invisible-image-explicit.html +8 -23
- package/src/invisible-image-implicit.html +8 -24
- package/src/invisible-pow-explicit.html +8 -23
- package/src/invisible-pow-implicit.html +9 -21
- package/src/invisible-puzzle-explicit.html +8 -23
- package/src/invisible-puzzle-implicit.html +9 -21
- package/src/plugins/code-snippet-injector.ts +179 -0
- package/src/plugins/form-filler-injector.ts +36 -121
- package/src/plugins/layout-injector.ts +256 -0
- package/src/plugins/logo.ts +15 -0
- package/src/plugins/pages.ts +219 -0
- package/src/plugins/placement-injector.ts +25 -64
- package/src/plugins/slots.ts +32 -0
- package/src/plugins/status-log-injector.ts +35 -114
- package/src/pow-explicit-web3.html +8 -24
- package/src/pow-explicit.html +8 -23
- package/src/pow-implicit-sessionid.html +10 -24
- package/src/pow-implicit.html +9 -24
- package/src/puzzle-bind-explicit.html +9 -24
- package/src/puzzle-explicit.html +8 -23
- package/src/puzzle-implicit.html +9 -24
- package/src/styles/demo.css +803 -0
- package/src/tests/plugins.unit.test.ts +326 -118
- package/tsconfig.tsbuildinfo +1 -1
- package/vite.config.ts +5 -5
- package/src/plugins/explanation-injector.ts +0 -294
- package/src/plugins/navigation-injector.ts +0 -606
- package/src/styles/captcha.css +0 -78
- package/src/styles/field.css +0 -12
- package/src/tests/floatLabel.unit.test.ts +0 -195
- package/src/tests/floatLabelReady.unit.test.ts +0 -36
|
@@ -17,11 +17,25 @@
|
|
|
17
17
|
// what they do with malformed or already-transformed input: they must return
|
|
18
18
|
// the html untouched rather than producing a broken page.
|
|
19
19
|
|
|
20
|
+
import { CaptchaType } from "@prosopo/types";
|
|
20
21
|
import type { IndexHtmlTransformContext, Plugin } from "vite";
|
|
21
|
-
import {
|
|
22
|
-
import
|
|
22
|
+
import { describe, expect, it } from "vitest";
|
|
23
|
+
import codeSnippetInjector, {
|
|
24
|
+
snippetFor,
|
|
25
|
+
} from "../plugins/code-snippet-injector.js";
|
|
23
26
|
import formFillerInjector from "../plugins/form-filler-injector.js";
|
|
24
|
-
import
|
|
27
|
+
import layoutInjector, { DEMO_HOSTNAME } from "../plugins/layout-injector.js";
|
|
28
|
+
import {
|
|
29
|
+
type DemoRendering,
|
|
30
|
+
demoPages,
|
|
31
|
+
findPageByPath,
|
|
32
|
+
pagePathFromFilename,
|
|
33
|
+
relativePrefix,
|
|
34
|
+
renderingsFor,
|
|
35
|
+
resolvePage,
|
|
36
|
+
} from "../plugins/pages.js";
|
|
37
|
+
import placementInjector from "../plugins/placement-injector.js";
|
|
38
|
+
import { fillSlot, slotMarkup } from "../plugins/slots.js";
|
|
25
39
|
import statusLogInjector from "../plugins/status-log-injector.js";
|
|
26
40
|
|
|
27
41
|
type Handler = (html: string, ctx: IndexHtmlTransformContext) => string;
|
|
@@ -47,65 +61,302 @@ const ctx = (filename: string): IndexHtmlTransformContext =>
|
|
|
47
61
|
const page = (body = "<p>hello</p>"): string =>
|
|
48
62
|
`<html><head><title>demo</title></head><body>${body}</body></html>`;
|
|
49
63
|
|
|
50
|
-
|
|
51
|
-
const handler = getHandler(formFillerInjector());
|
|
64
|
+
const noBody = "<div>no body here</div>";
|
|
52
65
|
|
|
53
|
-
|
|
54
|
-
const transform = formFillerInjector().transformIndexHtml;
|
|
66
|
+
const cwd = process.cwd();
|
|
55
67
|
|
|
56
|
-
|
|
68
|
+
describe("pages", () => {
|
|
69
|
+
it("has one page per setup and one setup per page", () => {
|
|
70
|
+
const setups = demoPages.map(
|
|
71
|
+
({ captchaType, mode, rendering }) =>
|
|
72
|
+
`${captchaType}/${mode}/${rendering}`,
|
|
73
|
+
);
|
|
74
|
+
const paths = demoPages.map(({ path }) => path);
|
|
75
|
+
|
|
76
|
+
expect(new Set(setups).size).toBe(demoPages.length);
|
|
77
|
+
expect(new Set(paths).size).toBe(demoPages.length);
|
|
57
78
|
});
|
|
58
79
|
|
|
59
|
-
it("
|
|
60
|
-
|
|
80
|
+
it("makes standard implicit frictionless the index page", () => {
|
|
81
|
+
expect(findPageByPath("index.html")).toMatchObject({
|
|
82
|
+
captchaType: CaptchaType.frictionless,
|
|
83
|
+
mode: "standard",
|
|
84
|
+
rendering: "implicit",
|
|
85
|
+
});
|
|
86
|
+
});
|
|
61
87
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
88
|
+
it("falls back to implicit rendering when a setup has no page", () => {
|
|
89
|
+
const resolved = resolvePage({
|
|
90
|
+
captchaType: CaptchaType.frictionless,
|
|
91
|
+
mode: "invisible",
|
|
92
|
+
rendering: "manual",
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
expect(resolved.path).toBe("invisible-frictionless-implicit.html");
|
|
65
96
|
});
|
|
66
97
|
|
|
67
|
-
it("
|
|
68
|
-
const
|
|
98
|
+
it("only offers renderings that have a page", () => {
|
|
99
|
+
const values = (captchaType: CaptchaType.pow | CaptchaType.puzzle) =>
|
|
100
|
+
renderingsFor(captchaType, "standard").map(
|
|
101
|
+
({ value }): DemoRendering => value,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
expect(values(CaptchaType.pow)).toEqual(["implicit", "explicit"]);
|
|
105
|
+
expect(values(CaptchaType.puzzle)).toEqual([
|
|
106
|
+
"implicit",
|
|
107
|
+
"explicit",
|
|
108
|
+
"bound",
|
|
109
|
+
]);
|
|
110
|
+
});
|
|
69
111
|
|
|
70
|
-
|
|
112
|
+
it("reads the page path from a src, dist or absolute filename", () => {
|
|
113
|
+
expect(pagePathFromFilename("src/index.html")).toBe("index.html");
|
|
114
|
+
expect(pagePathFromFilename(`${cwd}/dist/pow-explicit.html`)).toBe(
|
|
115
|
+
"pow-explicit.html",
|
|
116
|
+
);
|
|
117
|
+
expect(pagePathFromFilename(`${cwd}/src/nested/deep.html`)).toBe(
|
|
118
|
+
"nested/deep.html",
|
|
119
|
+
);
|
|
120
|
+
expect(pagePathFromFilename(undefined)).toBe("");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("climbs out of nested pages", () => {
|
|
124
|
+
expect(relativePrefix("index.html")).toBe("");
|
|
125
|
+
expect(relativePrefix("nested/deep.html")).toBe("../");
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe("slots", () => {
|
|
130
|
+
it("returns undefined when the slot is not on the page", () => {
|
|
131
|
+
expect(fillSlot(page(), "events", "x")).toBeUndefined();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("inserts content literally, even when it looks like a replace pattern", () => {
|
|
135
|
+
const html = fillSlot(page(slotMarkup("events")), "events", "$&");
|
|
136
|
+
|
|
137
|
+
expect(html).toBe(page("$&"));
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe("layoutInjector", () => {
|
|
142
|
+
const handler = getHandler(layoutInjector());
|
|
143
|
+
|
|
144
|
+
it("is registered as a pre transform", () => {
|
|
145
|
+
expect(layoutInjector().transformIndexHtml).toMatchObject({
|
|
146
|
+
order: "pre",
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("wraps the page in the header, sidebar and panel with a slot for each plugin", () => {
|
|
151
|
+
const html = handler(page(), ctx(`${cwd}/src/pow-explicit.html`));
|
|
152
|
+
|
|
153
|
+
expect(html).toContain('<body class="demo">');
|
|
154
|
+
expect(html).toContain('class="demo-header"');
|
|
155
|
+
expect(html).toContain('href="styles/demo.css"');
|
|
156
|
+
for (const slot of ["placement", "toolbar", "events", "code"] as const) {
|
|
157
|
+
expect(html).toContain(slotMarkup(slot));
|
|
158
|
+
}
|
|
159
|
+
expect(html.indexOf("<p>hello</p>")).toBeLessThan(
|
|
160
|
+
html.indexOf('class="demo-panel"'),
|
|
161
|
+
);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("loads Plausible only when served from the public demo host", () => {
|
|
165
|
+
const html = handler(page(), ctx(`${cwd}/src/pow-explicit.html`));
|
|
166
|
+
const inlineScript = html.match(
|
|
167
|
+
/<script>\s*\(function \(\) \{([\s\S]*?)\}\)\(\);\s*<\/script>/,
|
|
168
|
+
)?.[1];
|
|
169
|
+
expect(inlineScript).toBeDefined();
|
|
170
|
+
|
|
171
|
+
type FakeElement = Record<string, unknown>;
|
|
172
|
+
const loadedFrom = (hostname: string): FakeElement[] => {
|
|
173
|
+
const appended: FakeElement[] = [];
|
|
174
|
+
const fakeDocument = {
|
|
175
|
+
createElement: (): FakeElement => {
|
|
176
|
+
const element: FakeElement = {};
|
|
177
|
+
element.setAttribute = (name: string, value: string): void => {
|
|
178
|
+
element[name] = value;
|
|
179
|
+
};
|
|
180
|
+
return element;
|
|
181
|
+
},
|
|
182
|
+
head: {
|
|
183
|
+
appendChild: (element: FakeElement): void => {
|
|
184
|
+
appended.push(element);
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
new Function("location", "document", inlineScript ?? "")(
|
|
189
|
+
{ hostname },
|
|
190
|
+
fakeDocument,
|
|
191
|
+
);
|
|
192
|
+
return appended;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
expect(loadedFrom("localhost")).toHaveLength(0);
|
|
196
|
+
expect(loadedFrom(`staging.${DEMO_HOSTNAME}`)).toHaveLength(0);
|
|
197
|
+
const [tracker] = loadedFrom(DEMO_HOSTNAME);
|
|
198
|
+
expect(tracker).toMatchObject({
|
|
199
|
+
defer: true,
|
|
200
|
+
"data-domain": DEMO_HOSTNAME,
|
|
201
|
+
src: "https://prosopo.io/js/script.kairee5buy1chae8eit0so8ahphae9Oo.js",
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("marks the current type, mode and rendering", () => {
|
|
206
|
+
const html = handler(page(), ctx(`${cwd}/src/pow-explicit.html`));
|
|
207
|
+
|
|
208
|
+
expect(html.match(/aria-current="page"/g)).toHaveLength(3);
|
|
209
|
+
expect(html).toContain(
|
|
210
|
+
'<a class="demo-choice" data-demo-nav href="pow-explicit.html" aria-current="page">',
|
|
211
|
+
);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("folds rendering away unless the page uses a non-default rendering", () => {
|
|
215
|
+
const implicit = handler(page(), ctx(`${cwd}/src/pow-implicit.html`));
|
|
216
|
+
const explicit = handler(page(), ctx(`${cwd}/src/pow-explicit.html`));
|
|
217
|
+
|
|
218
|
+
expect(implicit).toContain('<details class="demo-advanced">');
|
|
219
|
+
expect(explicit).toContain('<details class="demo-advanced" open>');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("keeps the mode and rendering when switching captcha type", () => {
|
|
223
|
+
const html = handler(
|
|
224
|
+
page(),
|
|
225
|
+
ctx(`${cwd}/src/invisible-image-explicit.html`),
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
expect(html).toContain('href="invisible-pow-explicit.html"');
|
|
229
|
+
expect(html).toContain('href="invisible-puzzle-explicit.html"');
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("marks nothing current and shows no code for a page outside the registry", () => {
|
|
233
|
+
const html = handler(page(), ctx(`${cwd}/src/pow-implicit-sessionid.html`));
|
|
234
|
+
|
|
235
|
+
expect(html).not.toContain('aria-current="page"');
|
|
236
|
+
expect(html).not.toContain(slotMarkup("code"));
|
|
237
|
+
expect(html).toContain('href="index.html"');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("resolves links relative to a nested page", () => {
|
|
241
|
+
const html = handler(page(), ctx(`${cwd}/src/nested/deep.html`));
|
|
242
|
+
|
|
243
|
+
expect(html).toContain('href="../index.html"');
|
|
244
|
+
expect(html).toContain('href="../styles/demo.css"');
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("tolerates a context with no filename", () => {
|
|
248
|
+
const html = handler(page(), {} as IndexHtmlTransformContext);
|
|
249
|
+
|
|
250
|
+
expect(html).toContain('class="demo-header"');
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("returns the html unchanged when there is no body tag", () => {
|
|
254
|
+
expect(handler(noBody, ctx("src/index.html"))).toBe(noBody);
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
describe("codeSnippetInjector", () => {
|
|
259
|
+
const handler = getHandler(codeSnippetInjector());
|
|
260
|
+
|
|
261
|
+
it("fills the code slot with the escaped snippet for the page", () => {
|
|
262
|
+
const html = handler(
|
|
263
|
+
page(slotMarkup("code")),
|
|
264
|
+
ctx(`${cwd}/src/frictionless-explicit.html`),
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
expect(html).not.toContain(slotMarkup("code"));
|
|
268
|
+
expect(html).toContain("data-demo-copy");
|
|
269
|
+
expect(html).toContain("<div id="procaptcha-container">");
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("falls back to the end of the body when there is no slot", () => {
|
|
273
|
+
const html = handler(page(), ctx(`${cwd}/src/index.html`));
|
|
274
|
+
|
|
275
|
+
expect(html.indexOf("data-demo-copy")).toBeLessThan(
|
|
71
276
|
html.indexOf("</body>"),
|
|
72
277
|
);
|
|
73
278
|
});
|
|
74
279
|
|
|
75
|
-
it("
|
|
76
|
-
const
|
|
280
|
+
it("gives every demo page a snippet that uses a placeholder site key", () => {
|
|
281
|
+
for (const demoPage of demoPages) {
|
|
282
|
+
expect(snippetFor(demoPage)).toContain("YOUR_SITE_KEY");
|
|
283
|
+
}
|
|
284
|
+
});
|
|
77
285
|
|
|
78
|
-
|
|
286
|
+
it("matches the snippet to the rendering and mode", () => {
|
|
287
|
+
const snippet = (path: string): string => {
|
|
288
|
+
const demoPage = findPageByPath(path);
|
|
289
|
+
if (!demoPage) {
|
|
290
|
+
throw new Error(`${path} is not a demo page`);
|
|
291
|
+
}
|
|
292
|
+
return snippetFor(demoPage);
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
expect(snippet("pow-implicit.html")).toContain('class="procaptcha"');
|
|
296
|
+
expect(snippet("frictionless-manual-start.html")).toContain(
|
|
297
|
+
'data-start-mode="manual"',
|
|
298
|
+
);
|
|
299
|
+
expect(snippet("puzzle-bind-explicit.html")).toContain("bind:");
|
|
300
|
+
expect(snippet("invisible-image-implicit.html")).toContain(
|
|
301
|
+
'data-size="invisible"',
|
|
302
|
+
);
|
|
303
|
+
expect(snippet("invisible-image-explicit.html")).toContain("execute()");
|
|
79
304
|
});
|
|
80
305
|
|
|
81
|
-
it("
|
|
82
|
-
const
|
|
306
|
+
it("returns the html unchanged for a page outside the registry", () => {
|
|
307
|
+
const html = page();
|
|
83
308
|
|
|
84
|
-
expect(handler(
|
|
309
|
+
expect(handler(html, ctx(`${cwd}/src/some-other-page.html`))).toBe(html);
|
|
310
|
+
expect(handler(html, {} as IndexHtmlTransformContext)).toBe(html);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it("is idempotent", () => {
|
|
314
|
+
const once = handler(page(), ctx(`${cwd}/src/index.html`));
|
|
315
|
+
|
|
316
|
+
expect(handler(once, ctx(`${cwd}/src/index.html`))).toBe(once);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it("leaves html without a body untouched", () => {
|
|
320
|
+
expect(handler(noBody, ctx(`${cwd}/src/index.html`))).toBe(noBody);
|
|
85
321
|
});
|
|
86
322
|
});
|
|
87
323
|
|
|
88
324
|
describe("statusLogInjector", () => {
|
|
89
325
|
const handler = getHandler(statusLogInjector());
|
|
90
326
|
|
|
91
|
-
it("injects the status log container
|
|
327
|
+
it("injects the status log container and js", () => {
|
|
92
328
|
const html = handler(page(), ctx("src/index.html"));
|
|
93
329
|
|
|
94
330
|
expect(html).toContain('id="captcha-status"');
|
|
95
331
|
expect(html).toContain("updateCaptchaStatus");
|
|
96
332
|
});
|
|
97
333
|
|
|
98
|
-
it("
|
|
334
|
+
it("fills the events slot when the layout provides one", () => {
|
|
99
335
|
const html = handler(
|
|
100
|
-
page(
|
|
336
|
+
page(`<form></form>${slotMarkup("events")}`),
|
|
101
337
|
ctx("src/index.html"),
|
|
102
338
|
);
|
|
103
339
|
|
|
340
|
+
expect(html).not.toContain(slotMarkup("events"));
|
|
104
341
|
expect(html.indexOf('id="captcha-status"')).toBeGreaterThan(
|
|
105
342
|
html.indexOf("</form>"),
|
|
106
343
|
);
|
|
107
344
|
});
|
|
108
345
|
|
|
346
|
+
it("places the status log directly after a form when there is no slot", () => {
|
|
347
|
+
const html = handler(
|
|
348
|
+
page("<form><input name='a'/></form><p>after</p>"),
|
|
349
|
+
ctx("src/index.html"),
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
expect(html.indexOf('id="captcha-status"')).toBeGreaterThan(
|
|
353
|
+
html.indexOf("</form>"),
|
|
354
|
+
);
|
|
355
|
+
expect(html.indexOf('id="captcha-status"')).toBeLessThan(
|
|
356
|
+
html.indexOf("<p>after</p>"),
|
|
357
|
+
);
|
|
358
|
+
});
|
|
359
|
+
|
|
109
360
|
it("falls back to the end of the body when there is no form", () => {
|
|
110
361
|
const html = handler(page(), ctx("src/index.html"));
|
|
111
362
|
|
|
@@ -121,136 +372,93 @@ describe("statusLogInjector", () => {
|
|
|
121
372
|
});
|
|
122
373
|
|
|
123
374
|
it("leaves html without a body untouched", () => {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
expect(handler(fragment, ctx("src/index.html"))).toBe(fragment);
|
|
375
|
+
expect(handler(noBody, ctx("src/index.html"))).toBe(noBody);
|
|
127
376
|
});
|
|
128
377
|
});
|
|
129
378
|
|
|
130
|
-
describe("
|
|
131
|
-
const handler = getHandler(
|
|
132
|
-
|
|
133
|
-
const cases: Array<{ file: string; heading: string }> = [
|
|
134
|
-
{ file: "src/frictionless-explicit.html", heading: "Frictionless CAPTCHA" },
|
|
135
|
-
{ file: "src/image-explicit.html", heading: "Image CAPTCHA" },
|
|
136
|
-
{ file: "src/pow-explicit.html", heading: "Proof of Work" },
|
|
137
|
-
{ file: "src/puzzle-explicit.html", heading: "Puzzle CAPTCHA" },
|
|
138
|
-
];
|
|
139
|
-
|
|
140
|
-
for (const { file, heading } of cases) {
|
|
141
|
-
it(`generates an explanation for ${file}`, () => {
|
|
142
|
-
const html = handler(page(), ctx(file));
|
|
143
|
-
|
|
144
|
-
expect(html).toContain('<div class="explanation">');
|
|
145
|
-
expect(html).toContain(heading);
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
it("labels an invisible page as invisible", () => {
|
|
150
|
-
const html = handler(page(), ctx("src/invisible-image-explicit.html"));
|
|
379
|
+
describe("placementInjector", () => {
|
|
380
|
+
const handler = getHandler(placementInjector());
|
|
151
381
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
it("describes implicit rendering for an implicit page", () => {
|
|
156
|
-
const html = handler(page(), ctx("src/image-implicit.html"));
|
|
382
|
+
it("fills the placement slot with both options and the script", () => {
|
|
383
|
+
const html = handler(page(slotMarkup("placement")), ctx("src/index.html"));
|
|
157
384
|
|
|
158
|
-
expect(html).toContain("
|
|
385
|
+
expect(html).not.toContain(slotMarkup("placement"));
|
|
386
|
+
expect(html).toContain('data-placement-option="popup"');
|
|
387
|
+
expect(html).toContain('data-placement-option="float"');
|
|
388
|
+
expect(html).toContain("data-placement");
|
|
159
389
|
});
|
|
160
390
|
|
|
161
|
-
it("
|
|
162
|
-
const html = page();
|
|
391
|
+
it("uses links rather than buttons for the options", () => {
|
|
392
|
+
const html = handler(page(slotMarkup("placement")), ctx("src/index.html"));
|
|
163
393
|
|
|
164
|
-
expect(
|
|
394
|
+
expect(html).not.toMatch(/<button[^>]*data-placement-option/);
|
|
165
395
|
});
|
|
166
396
|
|
|
167
|
-
it("
|
|
168
|
-
const html =
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
397
|
+
it("goes above the status log when there is no slot", () => {
|
|
398
|
+
const html = handler(
|
|
399
|
+
page('<div id="captcha-status" class="captcha-status"></div>'),
|
|
400
|
+
ctx("src/index.html"),
|
|
401
|
+
);
|
|
172
402
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
'<div class="explanation"><div>stale copy</div></div>',
|
|
403
|
+
expect(html.indexOf('id="placement-switcher"')).toBeLessThan(
|
|
404
|
+
html.indexOf('id="captcha-status"'),
|
|
176
405
|
);
|
|
406
|
+
});
|
|
177
407
|
|
|
178
|
-
|
|
408
|
+
it("is idempotent", () => {
|
|
409
|
+
const once = handler(page(), ctx("src/index.html"));
|
|
179
410
|
|
|
180
|
-
expect(html).
|
|
181
|
-
expect(html.match(/<div class="explanation">/g)).toHaveLength(1);
|
|
411
|
+
expect(handler(once, ctx("src/index.html"))).toBe(once);
|
|
182
412
|
});
|
|
183
413
|
|
|
184
414
|
it("leaves html without a body untouched", () => {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
expect(handler(fragment, ctx("src/image-explicit.html"))).toBe(fragment);
|
|
415
|
+
expect(handler(noBody, ctx("src/index.html"))).toBe(noBody);
|
|
188
416
|
});
|
|
189
417
|
});
|
|
190
418
|
|
|
191
|
-
describe("
|
|
192
|
-
const handler = getHandler(
|
|
193
|
-
|
|
194
|
-
beforeEach(() => {
|
|
195
|
-
// the plugin logs the file it is processing on every transform
|
|
196
|
-
vi.spyOn(console, "log").mockImplementation(() => undefined);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
afterEach(() => {
|
|
200
|
-
vi.restoreAllMocks();
|
|
201
|
-
});
|
|
419
|
+
describe("formFillerInjector", () => {
|
|
420
|
+
const handler = getHandler(formFillerInjector());
|
|
202
421
|
|
|
203
|
-
it("is registered as a
|
|
204
|
-
expect(
|
|
205
|
-
order: "
|
|
422
|
+
it("is registered as a post transform", () => {
|
|
423
|
+
expect(formFillerInjector().transformIndexHtml).toMatchObject({
|
|
424
|
+
order: "post",
|
|
206
425
|
});
|
|
207
426
|
});
|
|
208
427
|
|
|
209
|
-
it("injects the
|
|
428
|
+
it("injects the button and its script", () => {
|
|
210
429
|
const html = handler(page(), ctx("src/index.html"));
|
|
211
430
|
|
|
212
|
-
expect(html).toContain('id="
|
|
213
|
-
expect(html).toContain("
|
|
214
|
-
expect(html).toContain("Invisible Captchas");
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
it("marks the current page as active", () => {
|
|
218
|
-
const cwd = process.cwd();
|
|
219
|
-
const html = handler(page(), ctx(`${cwd}/src/image-explicit.html`));
|
|
220
|
-
|
|
221
|
-
expect(html).toContain('<a href="image-explicit.html" class="active">');
|
|
431
|
+
expect(html).toContain('id="form-filler-button"');
|
|
432
|
+
expect(html).toContain("fillFormWithDefaults");
|
|
222
433
|
});
|
|
223
434
|
|
|
224
|
-
it("
|
|
225
|
-
const
|
|
226
|
-
|
|
435
|
+
it("puts the button in the toolbar slot when the layout provides one", () => {
|
|
436
|
+
const html = handler(
|
|
437
|
+
page(`<div class="demo-toolbar">${slotMarkup("toolbar")}</div>`),
|
|
438
|
+
ctx("src/index.html"),
|
|
439
|
+
);
|
|
227
440
|
|
|
228
|
-
expect(html).not.toContain(
|
|
441
|
+
expect(html).not.toContain(slotMarkup("toolbar"));
|
|
442
|
+
expect(html).toMatch(
|
|
443
|
+
/<div class="demo-toolbar">\s*<button id="form-filler-button"/,
|
|
444
|
+
);
|
|
229
445
|
});
|
|
230
446
|
|
|
231
|
-
it("
|
|
232
|
-
const
|
|
233
|
-
const html = handler(page(), ctx(`${cwd}/src/nested/deep.html`));
|
|
447
|
+
it("keeps the injected markup inside the body", () => {
|
|
448
|
+
const html = handler(page(), ctx("src/index.html"));
|
|
234
449
|
|
|
235
|
-
expect(html
|
|
450
|
+
expect(html.indexOf('id="form-filler-button"')).toBeLessThan(
|
|
451
|
+
html.indexOf("</body>"),
|
|
452
|
+
);
|
|
236
453
|
});
|
|
237
454
|
|
|
238
|
-
it("
|
|
239
|
-
|
|
240
|
-
const html = handler(page(), ctx(`${cwd}/dist/pow-explicit.html`));
|
|
241
|
-
|
|
242
|
-
expect(html).toContain('<a href="pow-explicit.html" class="active">');
|
|
455
|
+
it("leaves html without a body untouched", () => {
|
|
456
|
+
expect(handler(noBody, ctx("src/index.html"))).toBe(noBody);
|
|
243
457
|
});
|
|
244
458
|
|
|
245
|
-
it("
|
|
246
|
-
const fragment = "<div>
|
|
459
|
+
it("leaves html with an opening but no closing body untouched", () => {
|
|
460
|
+
const fragment = "<html><body><div>truncated</div>";
|
|
247
461
|
|
|
248
462
|
expect(handler(fragment, ctx("src/index.html"))).toBe(fragment);
|
|
249
463
|
});
|
|
250
|
-
|
|
251
|
-
it("tolerates a context with no filename", () => {
|
|
252
|
-
const html = handler(page(), {} as IndexHtmlTransformContext);
|
|
253
|
-
|
|
254
|
-
expect(html).toContain('id="nav-topbar"');
|
|
255
|
-
});
|
|
256
464
|
});
|