diff --git a/apps/web/src/app/login/login-form-client.tsx b/apps/web/src/app/login/login-form-client.tsx index 15cd900..41a2efb 100644 --- a/apps/web/src/app/login/login-form-client.tsx +++ b/apps/web/src/app/login/login-form-client.tsx @@ -87,7 +87,7 @@ export function LoginFormClient({ next }: { next: string }) { Contact your administrator to reset it. - + when `showCloseButton` is set. Every dialog + * we have that already provides its own primary action also includes + * a Cancel/dismiss button (either via DialogClose or by closing the + * Dialog state on submit) — and Radix's auto-rendered corner X + * already gives users a third way out. The redundant Close button + * cluttered the footer and shipped to production multiple times + * before this guard existed; this test stops it from regressing. + */ + +const SRC_ROOT = join(__dirname, ".."); + +function listTsxFiles(dir: string): string[] { + const out: string[] = []; + for (const entry of readdirSync(dir)) { + const full = join(dir, entry); + const st = statSync(full); + if (st.isDirectory()) { + out.push(...listTsxFiles(full)); + } else if (entry.endsWith(".tsx")) { + out.push(full); + } + } + return out; +} + +interface Hit { + file: string; + line: number; + excerpt: string; +} + +function findHits(content: string): Array<{ line: number; excerpt: string }> { + const hits: Array<{ line: number; excerpt: string }> = []; + // Match `` so we don't accidentally cross into the + // children. Multi-line opening tags are handled by `[\s\S]`. + const matches = content.matchAll( + //g, + ); + for (const m of matches) { + const idx = m.index ?? 0; + const line = content.slice(0, idx).split("\n").length; + hits.push({ line, excerpt: m[0].slice(0, 120).replace(/\s+/g, " ") }); + } + return hits; +} + +describe("static guard: no ", () => { + // Skip this test file (it intentionally contains the pattern strings) + // and all other .test.tsx files (they're examples, not production UI). + const files = listTsxFiles(SRC_ROOT).filter( + (f) => !/\.test\.tsx?$/.test(f), + ); + + it("scans at least one source file (sanity)", () => { + expect(files.length).toBeGreaterThan(0); + }); + + it("finds no in any production .tsx file", () => { + const allHits: Hit[] = []; + for (const file of files) { + const content = readFileSync(file, "utf8"); + for (const h of findHits(content)) { + allHits.push({ file: relative(SRC_ROOT, file), ...h }); + } + } + if (allHits.length > 0) { + const message = allHits + .map((h) => ` ${h.file}:${h.line} → ${h.excerpt}`) + .join("\n"); + throw new Error( + `Redundant Close button detected — :\n${message}\n` + + `The DialogFooter component injects an extra "Close" button when this prop\n` + + `is set. Every existing caller already has its own Cancel/Close action plus\n` + + `Radix's corner X — a third Close is just visual noise. Remove the prop.`, + ); + } + expect(allHits).toEqual([]); + }); +}); + +describe("findHits parser", () => { + it("matches a single-line ", () => { + expect( + findHits("foo"), + ).toHaveLength(1); + }); + it("matches when other props are present alongside showCloseButton", () => { + expect( + findHits(''), + ).toHaveLength(1); + }); + it("matches across multiple lines", () => { + const src = ``; + expect(findHits(src)).toHaveLength(1); + }); + it("does NOT match a clean ", () => { + expect(findHits("x")).toHaveLength(0); + }); + it("does NOT match a similarly-named prop on an unrelated component", () => { + expect(findHits("")).toHaveLength(0); + }); +});