Carries the rollback refusal in db and the matching CLI message, together with the auth, captcha, csr, dev-server, dev-toolbar, ui and validation changes that were already in the working tree, since publishing stages from it and those files would otherwise ship without being recorded here. @wrnexus/auth 0.8.28 -> 0.8.29 @wrnexus/captcha 0.8.15 -> 0.8.16 @wrnexus/cli 0.8.64 -> 0.8.65 @wrnexus/csr 0.8.37 -> 0.8.38 @wrnexus/db 0.8.20 -> 0.8.21 @wrnexus/dev-server 0.8.60 -> 0.8.61 @wrnexus/dev-toolbar 0.8.17 -> 0.8.18 @wrnexus/ui 0.8.75 -> 0.8.76 @wrnexus/validation 0.8.12 -> 0.8.13 Also declares Event among the browser globals captcha's client asset already lists, which was the last lint failure, and reformats four files. typecheck, lint, 1998 tests and format:check all pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
81 lines
2.9 KiB
TypeScript
81 lines
2.9 KiB
TypeScript
import { expect, test } from "bun:test";
|
|
import { readFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { parse } from "@wrnexus/syntax";
|
|
|
|
const componentPath = join(import.meta.dir, "../components/Captcha.wrn");
|
|
const clientPath = join(import.meta.dir, "../assets/client/captcha.js");
|
|
|
|
test("Captcha.wrn parses and exposes the full public contract", async () => {
|
|
const source = await readFile(componentPath, "utf8");
|
|
const ast = parse(source);
|
|
expect(ast.kind).toBe("component");
|
|
expect(ast.name).toBe("Captcha");
|
|
for (const prop of [
|
|
"provider",
|
|
"siteKey",
|
|
"type",
|
|
"action",
|
|
"presentation",
|
|
"difficulty",
|
|
"disturbance",
|
|
"imageStyle",
|
|
"allowedStyles",
|
|
"excludedStyles",
|
|
"randomizeStyle",
|
|
"size",
|
|
"color",
|
|
"class",
|
|
"showListen",
|
|
"resetOnError",
|
|
]) {
|
|
expect(source).toContain(`${prop} =`);
|
|
}
|
|
for (const event of [
|
|
"ready",
|
|
"challenge",
|
|
"input",
|
|
"verify",
|
|
"success",
|
|
"failure",
|
|
"expired",
|
|
"refresh",
|
|
"audioStart",
|
|
"audioEnd",
|
|
"error",
|
|
]) {
|
|
expect(source).toContain(`@event ${event} = function`);
|
|
}
|
|
expect(source).toContain("data-captcha-disturbance='{disturbance}'");
|
|
expect(source).toContain("data-captcha-image-style='{imageStyle}'");
|
|
expect(source).toContain("data-captcha-allowed-styles='{allowedStyles}'");
|
|
expect(source).toContain("data-captcha-excluded-styles='{excludedStyles}'");
|
|
expect(source).toContain("data-captcha-randomize-style='{randomizeStyle}'");
|
|
expect(source).toContain("data-captcha-show-listen='{showListen}'");
|
|
expect(source).toContain("data-captcha-reset-on-error='{resetOnError}'");
|
|
expect(source).toContain("data-captcha-not-robot-button");
|
|
expect(source).toContain("data-[captcha-size=compact]");
|
|
expect(source).toContain("data-[captcha-size=compact]:max-w-xs");
|
|
expect(source).toContain("group-data-[captcha-size=compact]/captcha:max-h-24");
|
|
expect(source).toContain("group-data-[captcha-size=compact]/captcha:h-8");
|
|
expect(source).toContain("group-data-[captcha-size=compact]/captcha:hidden");
|
|
expect(source).toContain("[data-captcha-footer]");
|
|
expect(source).toContain("display: contents");
|
|
expect(source).toContain("order: -1");
|
|
expect(source).toContain("data-[captcha-size=big]");
|
|
expect(source).toContain("{...attrs}");
|
|
expect(source).toContain("--wrn-");
|
|
expect(source).not.toMatch(/=\{/);
|
|
expect(source).toContain('data-wrnexus-runtime="captcha"');
|
|
expect(source).not.toContain("<script");
|
|
expect(source).not.toContain("captcha.js");
|
|
expect(source).not.toContain("lifecycle {");
|
|
});
|
|
|
|
test("CAPTCHA response tokens remain visible to enhanced form serializers", async () => {
|
|
const source = await readFile(clientPath, "utf8");
|
|
expect(source).toContain('input.setAttribute("value", state.responseToken)');
|
|
expect(source).toContain('new Event("input", { bubbles: true })');
|
|
expect(source).toContain('new Event("change", { bubbles: true })');
|
|
});
|