src/app/list-card-headings.render.test.tsx
Rama main · réplica de lectura del repositorio original.
import { readFileSync, readdirSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
import { render } from "@testing-library/react";
import { CertificationsPageFrame } from "@/components/certifications/certifications-page-frame";
import { ContactsPageFrame } from "@/components/contacts/contacts-page-frame";
import { CoursesPageFrame } from "@/components/courses/courses-page-frame";
import { DivesPageFrame } from "@/components/dives/dives-page-frame";
import { PeoplePageFrame } from "@/components/people/people-page-frame";
import { SpeciesPageFrame } from "@/components/species/species-page-frame";
import { TripsPageFrame } from "@/components/trips/trips-page-frame";
// A one-list page's card shows no title, but it is still a section of its page, so it
// keeps an `sr-only` `h2`. Nothing on screen would miss that heading if it went - the
// only thing that notices is an empty page's outline, where the empty state's `h3` would
// then sit straight under the page's `h1`. So this asserts the outline of the *empty*
// render rather than the presence of the element: what the heading is for, rather than
// the fact of it.
const FRAMES = {
"dives-page-frame": DivesPageFrame,
"trips-page-frame": TripsPageFrame,
"species-page-frame": SpeciesPageFrame,
"courses-page-frame": CoursesPageFrame,
"certifications-page-frame": CertificationsPageFrame,
"contacts-page-frame": ContactsPageFrame,
"people-page-frame": PeoplePageFrame,
};
// The admin frames are checked in their own directories, since nothing out here may
// import that section (`lib/admin-isolation.test.ts`).
const CHECKED_INSIDE_ADMIN = [
"invite-queue-frame",
"proposal-queue-frame",
"species-catalog-frame",
];
const COMPONENTS = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
"../components",
);
// Derived rather than listed: a one-list frame is one that draws a `CountBadge`, itself
// or through `TableListFrame`, so a new one fails here rather than going unchecked.
// `TableListFrame` is checked through the pages that draw it. The gear cards are not
// frames and hold two lists between them, which is why their titles stay on screen
// while there is anything under them (`app/gear/page.render.test.tsx`).
const countingFrames = () =>
readdirSync(COMPONENTS, { withFileTypes: true })
.filter((entry) => entry.isDirectory() && entry.name !== "ui")
.flatMap((entry) =>
readdirSync(path.join(COMPONENTS, entry.name))
.filter((file) => file.endsWith("-frame.tsx"))
.filter((file) =>
/\b(CountBadge|TableListFrame)\b/.test(
readFileSync(path.join(COMPONENTS, entry.name, file), "utf8"),
),
)
.map((file) => file.replace(/\.tsx$/, "")),
);
describe("a one-list page's card keeps a heading nobody sees", () => {
it("covers every frame that counts a list", () => {
expect([...countingFrames()].sort()).toEqual(
[...Object.keys(FRAMES), ...CHECKED_INSIDE_ADMIN].sort(),
);
});
it.each(Object.entries(FRAMES))("%s", (_name, Frame) => {
const { container } = render(
<Frame isLoading={false} totalCount={0} itemsPerPage={10} />,
);
const headings = [...container.querySelectorAll("h1, h2, h3, h4, h5, h6")];
expect(headings.map((heading) => Number(heading.tagName[1]))).toEqual([
1, 2, 3,
]);
expect(headings[1]).toHaveClass("sr-only");
});
// And that heading is all it keeps. "0 total dives" over "No dives logged
// yet" counts the thing the sentence below has just said there is none of,
// and a search box that would search nothing sits beside it. Each frame's own
// tests cover the list *narrowed* to nothing, which keeps both.
it.each(Object.entries(FRAMES))("%s, and nothing else", (_name, Frame) => {
const { container } = render(
<Frame isLoading={false} totalCount={0} itemsPerPage={10} />,
);
const heading = container.querySelector("h2")!;
expect([...heading.parentElement!.children]).toEqual([heading]);
});
});