import { describe, expect, it } from "vitest"; import { FEED_VIEWS, FEED_VIEW_SPEC } from "../../lib/feedView"; import { GRID_GAP, columnsFor, itemWidthFor } from "./gridColumns"; // The floor of the viewport contract (1024x768) and its ceiling (ultrawide) are both column-count // questions, so they are both testable here without a layout engine. const FLOOR_GRID = 700; // the feed's grid width at a 1024px viewport with the filter rail pinned const ULTRAWIDE_GRID = 3069; // measured: the feed's grid width at a 3440px viewport describe("columnsFor", () => { it("fits as many columns as the width allows when uncapped", () => { expect(columnsFor(260, 3069, null)).toBe(11); // the pre-cap ultrawide measurement expect(columnsFor(260, 700, null)).toBe(2); }); // The GUTTERS have to be in the count, not just in itemWidthFor. Every other assertion in this file // survives replacing `floor((w + GAP) / (minCol + GAP))` with the naive `floor(w / (minCol + GAP))` // — the fixed widths agree, the capped cases are cap-bound, and the two loops compare columnsFor // against itself, so they cancel any shared change. These are the exact-fit // boundaries where the two formulas first disagree: an n-column row needs n*minCol + (n-1)*GAP, so // at exactly that width the naive form loses the last column and wastes a third of the row. it("counts the gutters, not one gap per column", () => { const exactly3 = 3 * 260 + 2 * GRID_GAP; // 828 expect(columnsFor(260, exactly3, null)).toBe(3); expect(columnsFor(260, exactly3 - 1, null)).toBe(2); const exactly2 = 2 * 260 + GRID_GAP; // 536 — the FIRST width where the naive form is wrong expect(columnsFor(260, exactly2, null)).toBe(2); expect(columnsFor(260, exactly2 - 1, null)).toBe(1); }); it("never returns 0 columns, however narrow", () => { expect(columnsFor(260, 10, null)).toBe(1); expect(columnsFor(260, 0, null)).toBe(1); }); it("returns 1 for the single-column layout regardless of width or cap", () => { expect(columnsFor(null, 3069, 8)).toBe(1); }); it("caps the count, and the saved width goes into the items", () => { expect(columnsFor(260, ULTRAWIDE_GRID, 8)).toBe(8); expect(itemWidthFor(ULTRAWIDE_GRID, 11)).toBe(264); // uncapped: pinned near minCol expect(itemWidthFor(ULTRAWIDE_GRID, 8)).toBe(369); // capped: the width lands in the card }); it("does nothing below the width where that many columns already fit", () => { // The cap must be a pure ultrawide affordance: at every width up to the point where 8 columns // fit, capping at 8 has to return exactly what the uncapped grid would have. const eightFitsAt = 8 * (260 + GRID_GAP) - GRID_GAP; for (let w = 320; w < eightFitsAt; w += 17) { expect(columnsFor(260, w, 8)).toBe(columnsFor(260, w, null)); } }); }); describe("FEED_VIEW_SPEC ultrawide policy", () => { it("caps every multi-column view and leaves the single-column ones to their width cap", () => { for (const view of FEED_VIEWS) { const { minCol, maxCols, colMaxWidth } = FEED_VIEW_SPEC[view]; if (minCol == null) { // A single column is capped by reading width, not by a column count. expect(maxCols, `${view} is single-column`).toBeNull(); expect(colMaxWidth, `${view} needs a reading-width cap`).not.toBeNull(); } else { expect(maxCols, `${view} would dilute on an ultrawide`).not.toBeNull(); } } }); it("keeps a capped view recognisably itself — its item stays near its own minCol", () => { // The policy behind each number: on a 3440px ultrawide an item tops out around 1.5x the view's // own minimum. Looser and the view dilutes anyway; tighter and e.g. cardsSmall grows into cards. for (const view of FEED_VIEWS) { const { minCol, maxCols } = FEED_VIEW_SPEC[view]; if (minCol == null || maxCols == null) continue; const w = itemWidthFor(ULTRAWIDE_GRID, columnsFor(minCol, ULTRAWIDE_GRID, maxCols)); expect(w, `${view} item width at 3440`).toBeGreaterThanOrEqual(minCol); expect(w, `${view} item width at 3440`).toBeLessThanOrEqual(Math.round(minCol * 1.6)); } }); it("leaves the 1024px floor untouched — the cap never binds there", () => { for (const view of FEED_VIEWS) { const { minCol, maxCols } = FEED_VIEW_SPEC[view]; if (minCol == null) continue; expect(columnsFor(minCol, FLOOR_GRID, maxCols)).toBe(columnsFor(minCol, FLOOR_GRID, null)); } }); it("never lets the dense card grow as wide as the standard card", () => { const at = (v: "cards" | "cardsSmall") => { const { minCol, maxCols } = FEED_VIEW_SPEC[v]; return itemWidthFor(ULTRAWIDE_GRID, columnsFor(minCol, ULTRAWIDE_GRID, maxCols)); }; expect(at("cardsSmall")).toBeLessThan(at("cards")); }); });