Public: anyone can read this; the crew can change it.
Errors on the site pages are §5's field error, not .error-note #5092
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/5071-errors-on-the-site-pages-are-5-s-field-e → main 4 commits fork
2 files
changed +52
−1
src/web/pages/site.ts +1 −1
| @@ -359,7 +359,7 @@ function authForm( | ||
| 359 | 359 | A passkey signs you in with a fingerprint, face or device PIN; there |
| 360 | 360 | is no password to remember. |
| 361 | 361 | </p> |
| 362 | ${passkeyNote("passkey-signup-note", errors.password)} | |
| 362 | ${passkeyNote("passkey-signup-note")} | |
| 363 | 363 | <noscript><p class="muted small">Signing up needs JavaScript for the passkey.</p></noscript>`, |
| 364 | 364 | )} |
| 365 | 365 | ${when( |
test/site-field-errors.test.ts added +51
| @@ -0,0 +1,51 @@ | ||
| 1 | /** | |
| 2 | * The site pages' errors are §5's field error, not `.error-note` (#5071): the | |
| 3 | * passkey-only sign-up's refusal sits in a `.field` as `.error`, and the notes | |
| 4 | * the passkey script fills name `error` as the class a failure takes. | |
| 5 | */ | |
| 6 | import { afterAll, beforeAll, expect, test } from "bun:test"; | |
| 7 | import { mkdtemp, rm } from "node:fs/promises"; | |
| 8 | import { tmpdir } from "node:os"; | |
| 9 | import { join } from "node:path"; | |
| 10 | import { createApp, type App } from "../src/app.ts"; | |
| 11 | import { serve, type Servers } from "../src/http/server.ts"; | |
| 12 | ||
| 13 | let app: App; | |
| 14 | let servers: Servers; | |
| 15 | let dir: string; | |
| 16 | let base: string; | |
| 17 | ||
| 18 | beforeAll(async () => { | |
| 19 | dir = await mkdtemp(join(tmpdir(), "forge-site-errors-")); | |
| 20 | app = createApp({ dataDir: dir, port: 0, passkeyOnlySignup: true }); | |
| 21 | servers = await serve(app); | |
| 22 | base = servers.url; | |
| 23 | }); | |
| 24 | ||
| 25 | afterAll(async () => { | |
| 26 | servers.stop(); | |
| 27 | app.close(); | |
| 28 | await rm(dir, { recursive: true, force: true }); | |
| 29 | }); | |
| 30 | ||
| 31 | test("the sign-in note's failure class is the field error", async () => { | |
| 32 | const page = await (await fetch(`${base}/signin`)).text(); | |
| 33 | expect(page).not.toContain("error-note"); | |
| 34 | expect(page).toMatch(/<div class="field"><span [^>]*id="passkey-signin-note" data-fail="error"/); | |
| 35 | }); | |
| 36 | ||
| 37 | test("the sign-up note waits in a field; a refused password form shows its error at the form's head", async () => { | |
| 38 | const page = await (await fetch(`${base}/signup`)).text(); | |
| 39 | expect(page).not.toContain("error-note"); | |
| 40 | expect(page).toMatch(/<div class="field"><span [^>]*id="passkey-signup-note" data-fail="error" role="status">/); | |
| 41 | ||
| 42 | const res = await fetch(`${base}/signup`, { | |
| 43 | method: "POST", | |
| 44 | headers: { "content-type": "application/x-www-form-urlencoded", origin: base }, | |
| 45 | body: new URLSearchParams({ username: "pw", email: "", password: "a-long-passphrase" }), | |
| 46 | }); | |
| 47 | const refused = await res.text(); | |
| 48 | expect(res.status).toBe(422); | |
| 49 | expect(refused).not.toContain("error-note"); | |
| 50 | expect(refused).toMatch(/<p class="error mb" role="alert">New accounts sign up with a passkey/); | |
| 51 | }); |
new above
offline