src/components/dives/species-multi-select.browser.test.tsx
Rama main · réplica de lectura del repositorio original.
import { describe, expect, it } from "vitest";
import { render } from "@testing-library/react";
import { page } from "vitest/browser";
import { SpeciesMultiSelect } from "./species-multi-select";
import type { SightingWrite } from "@/lib/api/dives";
import type { SpeciesSummary } from "@/lib/api/species";
// **Load-bearing, and it looks like a stray import** - the one every geometry
// test in this lane carries. Without it the count box and the notes box have no
// height of their own, a bare row collapses to three lines of text, and the level
// test below passes against any markup at all. The third test is the canary.
import "@/app/globals.css";
// Two claims about the picker's rows that jsdom cannot check (DECISIONS.md, "jsdom
// answers no layout question"): **a row whose note fits one line stands as tall
// as a row with no note, and a row carrying a band as tall as one carrying a
// count.** Every input is on every row whatever it holds, and the notes box is one
// line until its note needs more - so typing the first word of a note, or
// choosing a band, does not push the rest of the form down.
const MANTA: SpeciesSummary = {
uuid: "species-manta",
scientific_name: "Mobula birostris",
common_name: "Giant manta ray",
rank: "Species",
};
const CLOWNFISH: SpeciesSummary = {
uuid: "species-clownfish",
scientific_name: "Amphiprion ocellaris",
common_name: "Ocellaris clownfish",
rank: "Species",
};
// A phone, where the boxes compute to 16px text, and Tailwind's `md`, where they
// drop to 14px - the row's height comes out different at each, and it has to be
// level at both.
const NARROW = 393;
const WIDE = 1024;
function rowHeights(value: SightingWrite[]): number[] {
const { container, unmount } = render(
<SpeciesMultiSelect
value={value}
knownSpecies={[MANTA, CLOWNFISH]}
onChange={() => {}}
/>,
);
// Measured before unmounting: a detached element's rect is all zeros.
const heights = [...container.querySelectorAll("li")].map(
(row) => row.getBoundingClientRect().height,
);
unmount();
return heights;
}
function expectLevel(a: number, b: number) {
expect(Math.abs(a - b)).toBeLessThanOrEqual(1);
}
describe("the species picker's rows stay level", () => {
for (const width of [NARROW, WIDE]) {
it(`gives a row with a one-line note the height of a bare row at ${width}px`, async () => {
await page.viewport(width, 800);
const sightings = [
{ species_uuid: MANTA.uuid, count: 3 },
{ species_uuid: CLOWNFISH.uuid },
];
const bare = rowHeights(sightings);
const noted = rowHeights(
sightings.map((sighting) => ({
...sighting,
notes: "Cleaning station",
})),
);
// Compare each row with itself: different species titles can wrap.
noted.forEach((height, index) => expectLevel(height, bare[index]));
});
}
it("grows a row whose note runs past one line", async () => {
// The control for the test above: a notes box of fixed height would pass it
// too, while clipping every longer note to its first line. Narrow, where the
// notes box stacks under the count: side by side, the count's quick choices
// stand taller than a three-line note.
await page.viewport(NARROW, 800);
const [noted, bare] = rowHeights([
{ species_uuid: MANTA.uuid, notes: "Cleaning station\nat 18 m\ntwo" },
{ species_uuid: CLOWNFISH.uuid },
]);
expect(noted).toBeGreaterThan(bare + 20);
});
it("stands a bare row taller than its padding, header and boxes, which only the stylesheet makes true", async () => {
// The stylesheet canary. At `md` the row's `p-4`, its 36px header, the
// labelled count box over its quick choices and the notes box beside them
// come to about 200px; without Tailwind a bare row is a few lines of text and
// bare controls, about 100px, and the level tests measure nothing.
await page.viewport(WIDE, 800);
const [bare] = rowHeights([{ species_uuid: CLOWNFISH.uuid }]);
expect(bare).toBeGreaterThan(120);
});
for (const width of [NARROW, WIDE]) {
it(`gives a row carrying a band the height of a row carrying a count at ${width}px`, async () => {
// The quick choices are on every row whatever it carries, and a count
// marking its band changes the border's style, never its width.
await page.viewport(width, 800);
const sightings = [
{ species_uuid: MANTA.uuid },
{ species_uuid: CLOWNFISH.uuid },
];
const banded = rowHeights(
sightings.map((sighting) => ({ ...sighting, abundance: "abundant" })),
);
const counted = rowHeights(
sightings.map((sighting) => ({ ...sighting, count: 37 })),
);
banded.forEach((height, index) => expectLevel(height, counted[index]));
});
}
});