import { describe, expect, it } from "vitest"; // Every `useMutation` NAMES what failed (E11 S2b, owner decision: the toast's title says what, the // line under it why). The app-wide reporter (lib/queryClient) reports a mutation that names nothing // too — so nothing is silent any more — but under the generic "Couldn't complete that", which is the // very toast that says nothing about what it is about when it lands a few seconds after the click. // // A mutation passes when it carries one of: // - `meta: { failTitle }` the reporter's title for it, // - `meta: { errorShownInline }` the page itself renders the failure, // - an `onError` which reports (or rolls back — the reporter then speaks for it, // under the title from `meta` if there is one). // // The assertion pins the SET of unnamed mutations, never a count, and checks the glob matched at // all: a guard whose glob stops matching is green over nothing (E10 review round 4, round 7). const NAMES = /\bfailTitle\b|\berrorShownInline\b|\bonError\b/; /** The source of each `useMutation(...)` call, found by paren balance with comments stripped — * a comment with a stray "(" would otherwise run one block into the next one's `failTitle`. */ export function mutationCalls(source: string): string[] { const code = source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:"'`])\/\/.*$/gm, "$1"); const calls: string[] = []; let from = 0; for (;;) { const at = code.indexOf("useMutation(", from); if (at < 0) return calls; let depth = 0; let i = at + "useMutation".length; for (; i < code.length; i++) { if (code[i] === "(") depth++; else if (code[i] === ")" && --depth === 0) break; } calls.push(code.slice(at, i + 1)); from = i + 1; } } function unnamed(): { hits: string[]; mutations: number } { const files = import.meta.glob("../**/*.{ts,tsx}", { query: "?raw", import: "default", eager: true, }); const hits: string[] = []; let mutations = 0; for (const [rel, source] of Object.entries(files)) { const path = rel.startsWith("./") ? `lib/${rel.slice(2)}` : rel.replace(/^\.\.\//, ""); if (/\.test\.tsx?$|\.d\.ts$/.test(path)) continue; mutationCalls(String(source)).forEach((call, n) => { mutations++; if (!NAMES.test(call)) hits.push(`${path}#${n + 1}`); }); } return { hits: hits.sort(), mutations }; } describe("the failed-write contract", () => { it("has no mutation that leaves its failure unnamed", () => { const { hits, mutations } = unnamed(); expect(mutations).toBeGreaterThan(50); expect(hits).toEqual([]); }); it("reads a mutation call to its end, whatever its body holds", () => { const src = ` const a = useMutation({ mutationFn: () => api.x(fn(1), (2)), // a stray "(" in a comment must not swallow the next call }); const b = useMutation({ mutationFn: api.y, meta: { failTitle: "k" } }); `; const calls = mutationCalls(src); expect(calls).toHaveLength(2); expect(NAMES.test(calls[0] ?? "")).toBe(false); expect(NAMES.test(calls[1] ?? "")).toBe(true); }); });