fix(web): SwitchHiddenInput on settings hideAmounts toggle + correct smoke comment

This commit is contained in:
Munkherdene 2026-08-22 21:16:45 +08:00
parent 6e84d99d46
commit b02674aa62
3 changed files with 47 additions and 14 deletions

View file

@ -4,18 +4,23 @@ import * as fx from "../src/test/fixtures";
/**
* 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
* browser layer (page.route) rather than standing up a stub server the
* Next dev server (and all of its own client/server code, including the
* httpOnly-cookie auth route and the middleware guard) runs for real; only
* the two things that would otherwise reach the Go backend
* (`/api/auth/login`, `/api/v1/*`) are fulfilled with fixtures.
*
* Also asserts the two security-relevant properties the review flagged:
* - the session cookie set on login is HttpOnly
* - no response body observed during the flow contains a raw bearer token
* (the Next auth route is supposed to strip `token` before it reaches the
* browser see src/app/api/auth/login/route.ts)
* There's no live Go backend in CI, so `page.route` intercepts
* `/api/auth/login` and `/api/v1/*` at the BROWSER network layer, before
* either request ever reaches the Next server the real
* `src/app/api/auth/login/route.ts` handler (which calls the Go backend,
* strips `token`, and calls `buildSetCookie`) never runs in this test; this
* 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
* client-side login form + navigation, and critically the
* `src/middleware.ts` route guard, which reads the `mercury_session` cookie
* this test's fake `Set-Cookie` puts in the browser's real cookie jar to
* decide whether `/home` is reachable. So this smoke covers the
* 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 ({
page,

View file

@ -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 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;
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();
});

View file

@ -1,7 +1,16 @@
"use client";
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 { useSettings } from "@/api/hooks/reads";
import { useSaveSettings } from "@/api/hooks/mutations";
@ -166,6 +175,12 @@ export function SettingsForm({ onBack }: SettingsFormProps) {
checked={form.hideAmounts}
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>
<SwitchThumb />
</SwitchControl>