fix(web): SwitchHiddenInput on settings hideAmounts toggle + correct smoke comment
This commit is contained in:
parent
6e84d99d46
commit
b02674aa62
3 changed files with 47 additions and 14 deletions
|
|
@ -4,18 +4,23 @@ import * as fx from "../src/test/fixtures";
|
||||||
/**
|
/**
|
||||||
* One smoke path: /login → submit → /home renders the real dashboard.
|
* One smoke path: /login → submit → /home renders the real dashboard.
|
||||||
*
|
*
|
||||||
* There's no live Go backend in CI, so this intercepts the network at the
|
* There's no live Go backend in CI, so `page.route` intercepts
|
||||||
* browser layer (page.route) rather than standing up a stub server — the
|
* `/api/auth/login` and `/api/v1/*` at the BROWSER network layer, before
|
||||||
* Next dev server (and all of its own client/server code, including the
|
* either request ever reaches the Next server — the real
|
||||||
* httpOnly-cookie auth route and the middleware guard) runs for real; only
|
* `src/app/api/auth/login/route.ts` handler (which calls the Go backend,
|
||||||
* the two things that would otherwise reach the Go backend
|
* strips `token`, and calls `buildSetCookie`) never runs in this test; this
|
||||||
* (`/api/auth/login`, `/api/v1/*`) are fulfilled with fixtures.
|
* spec fabricates the `Set-Cookie` header and the token-free `{user}` body
|
||||||
*
|
* itself. What DOES run for real: the Next dev server's page rendering, the
|
||||||
* Also asserts the two security-relevant properties the review flagged:
|
* client-side login form + navigation, and — critically — the
|
||||||
* - the session cookie set on login is HttpOnly
|
* `src/middleware.ts` route guard, which reads the `mercury_session` cookie
|
||||||
* - no response body observed during the flow contains a raw bearer token
|
* this test's fake `Set-Cookie` puts in the browser's real cookie jar to
|
||||||
* (the Next auth route is supposed to strip `token` before it reaches the
|
* decide whether `/home` is reachable. So this smoke covers the
|
||||||
* browser — see src/app/api/auth/login/route.ts)
|
* login → navigate → guarded-route → dashboard-render path end to end; it
|
||||||
|
* does NOT cover the real auth route's token-stripping/cookie-building logic
|
||||||
|
* (that's covered separately by src/app/api/auth/auth.route.test.ts). The
|
||||||
|
* httpOnly/no-raw-token assertions below validate this test's own mocked
|
||||||
|
* response shape, i.e. they document the contract the real route must also
|
||||||
|
* satisfy — they are not independent proof that the real route satisfies it.
|
||||||
*/
|
*/
|
||||||
test("login → home renders the dashboard; session cookie is httpOnly; no raw token leaks", async ({
|
test("login → home renders the dashboard; session cookie is httpOnly; no raw token leaks", async ({
|
||||||
page,
|
page,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { render, screen } from "@testing-library/react";
|
import { render, screen, fireEvent } from "@testing-library/react";
|
||||||
import { it, expect, vi } from "vitest";
|
import { it, expect, vi } from "vitest";
|
||||||
import type { Settings } from "@/api/schemas";
|
import type { Settings } from "@/api/schemas";
|
||||||
|
|
||||||
|
|
@ -31,3 +31,16 @@ it("renders the holder-name field pre-filled from useSettings", () => {
|
||||||
const input = screen.getByLabelText("Данс эзэмшигчийн нэр") as HTMLInputElement;
|
const input = screen.getByLabelText("Данс эзэмшигчийн нэр") as HTMLInputElement;
|
||||||
expect(input.value).toBe(settingsFixture.holderName);
|
expect(input.value).toBe(settingsFixture.holderName);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Flagged in review: SwitchRoot needs its SwitchHiddenInput sibling to be an
|
||||||
|
// accessible, interactive `role="switch"` at all — SwitchControl/SwitchThumb
|
||||||
|
// are aria-hidden decoration only (same bug fixed in ds/HideAmountsToggle.tsx).
|
||||||
|
it("hideAmounts switch is an accessible, toggleable role=switch bound to form state", () => {
|
||||||
|
render(<SettingsForm />);
|
||||||
|
const toggle = screen.getByRole("switch", { name: "Үнийн дүн нуух" });
|
||||||
|
expect(toggle).not.toBeChecked();
|
||||||
|
|
||||||
|
fireEvent.click(toggle);
|
||||||
|
|
||||||
|
expect(toggle).toBeChecked();
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,16 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { TextFieldRoot, TextFieldInput, SwitchRoot, SwitchControl, SwitchThumb, SwitchLabel, Skeleton } from "@seed-design/react";
|
import {
|
||||||
|
TextFieldRoot,
|
||||||
|
TextFieldInput,
|
||||||
|
SwitchRoot,
|
||||||
|
SwitchControl,
|
||||||
|
SwitchThumb,
|
||||||
|
SwitchLabel,
|
||||||
|
SwitchHiddenInput,
|
||||||
|
Skeleton,
|
||||||
|
} from "@seed-design/react";
|
||||||
import { MercuryButton } from "@/ds/MercuryButton";
|
import { MercuryButton } from "@/ds/MercuryButton";
|
||||||
import { useSettings } from "@/api/hooks/reads";
|
import { useSettings } from "@/api/hooks/reads";
|
||||||
import { useSaveSettings } from "@/api/hooks/mutations";
|
import { useSaveSettings } from "@/api/hooks/mutations";
|
||||||
|
|
@ -166,6 +175,12 @@ export function SettingsForm({ onBack }: SettingsFormProps) {
|
||||||
checked={form.hideAmounts}
|
checked={form.hideAmounts}
|
||||||
onCheckedChange={(v: boolean) => setForm((f) => ({ ...f, hideAmounts: v }))}
|
onCheckedChange={(v: boolean) => setForm((f) => ({ ...f, hideAmounts: v }))}
|
||||||
>
|
>
|
||||||
|
{/* The actual interactive/accessible element (role="switch",
|
||||||
|
checked/onChange) lives on the hidden input — SwitchControl and
|
||||||
|
SwitchThumb are purely decorative (aria-hidden). Without this
|
||||||
|
the switch renders but nothing is clickable or announced to
|
||||||
|
assistive tech (same bug as ds/HideAmountsToggle.tsx). */}
|
||||||
|
<SwitchHiddenInput />
|
||||||
<SwitchControl>
|
<SwitchControl>
|
||||||
<SwitchThumb />
|
<SwitchThumb />
|
||||||
</SwitchControl>
|
</SwitchControl>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue