Files
siftlode/frontend/src/components/ui/ItemCount.dom.test.tsx
T
peter c4298b0a73 docs(comments): no WebSocket and no CI in the present tense
The chat cut removed the only WebSocket and CI went before it, but comments still leaned on both:
`?account=` was justified by the socket, job polling by an in-process WS registry, the e2e flows as
"proxy-safe (none use the WebSocket)", and pyrightconfig named a CI container the engine contract
test never checked. Each now says what holds today (the wallet gate, no push channel, the two
contract test halves, the image build's `npm ci`, the gate).
2026-10-05 00:50:36 +02:00

87 lines
3.9 KiB
TypeScript

// @vitest-environment jsdom
//
// Contract of the fixed-width count pill. Uses the REAL i18n instance (not a key-passthrough) so the
// plural selection (itemCount_one/_other) and the "1M+" overflow interpolation are actually exercised —
// those are the two branches worth guarding. English is forced so the singular/plural strings are stable.
import { describe, it, expect, beforeAll, afterEach } from "vitest";
import { render, screen, cleanup } from "@testing-library/react";
import "../../i18n"; // side-effect: initialise the shared i18next instance
import i18n from "i18next";
import ItemCount from "./ItemCount";
import { formatNumber } from "../../lib/format";
beforeAll(async () => {
await i18n.changeLanguage("en");
});
afterEach(async () => {
cleanup();
await i18n.changeLanguage("en"); // a case that switches language must not leak it to the next
});
const text = () => screen.getByTestId("item-count").textContent;
describe("ItemCount", () => {
it("uses the singular string at 1 and the plural above it", () => {
render(<ItemCount count={1} />);
expect(text()).toBe("1 item");
});
it("pluralises and thousands-separates a normal count", () => {
render(<ItemCount count={192045} />);
// Separators come from the shared formatNumber — compare against it, not a fixed en-US string,
// so the test asserts ItemCount's formatting path rather than the runner's locale.
expect(text()).toBe(`${formatNumber(192045, "en")} items`);
});
// The pill used `count.toLocaleString()`, which groups in the BROWSER's locale: a Hungarian UI on
// an English browser showed "192,045" instead of "192 045". The title carries the pure number (no
// plural string), and both languages are asserted — comparing against the runtime's own default
// would pass under the old bug on a machine whose locale happens to match the UI language.
it("groups in the UI language, not the browser's", async () => {
const title = () => screen.getByTestId("item-count").getAttribute("title");
render(<ItemCount count={192045} />);
const asEnglish = title();
cleanup();
await i18n.changeLanguage("hu");
render(<ItemCount count={192045} />);
expect(asEnglish).toBe(new Intl.NumberFormat("en").format(192045));
expect(title()).toBe(new Intl.NumberFormat("hu").format(192045));
expect(title()).not.toBe(asEnglish);
});
it("collapses a count of a million or more to 1M+, with the exact figure in the tooltip", () => {
render(<ItemCount count={1_000_000} />);
expect(text()).toBe("1M+ items"); // the "1M+" collapse is locale-independent
expect(screen.getByTestId("item-count").getAttribute("title")).toBe(
formatNumber(1_000_000, "en"),
);
});
it("shows the icon-only fixed box (empty label) while the count is undefined", () => {
render(<ItemCount />);
expect(text()).toBe(""); // the Layers icon carries no text
expect(screen.getByTestId("item-count").getAttribute("title")).toBeNull();
});
it("dims the previous filter's count and marks it busy while the new one loads", () => {
const { rerender } = render(<ItemCount count={5} />);
expect(screen.getByTestId("item-count").getAttribute("aria-busy")).toBeNull();
rerender(<ItemCount count={5} stale />);
expect(screen.getByTestId("item-count").getAttribute("aria-busy")).toBe("true");
expect(screen.getByText("5 items").className).toContain("opacity-60");
});
// The dim and aria-busy were once the only stale cue, and a screen reader voices neither on
// plain text; a failed count looked like a first load.
it("tells a screen reader a stale count is updating", () => {
render(<ItemCount count={5} stale />);
expect(text()).toBe("5 itemsupdating");
});
it("says a count that failed, rather than looking like a first load", () => {
render(<ItemCount failed />);
expect(text()).toBe("—Couldn't count the items");
expect(screen.getByTestId("item-count").getAttribute("title")).toBe("Couldn't count the items");
});
});