mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
* test(dashboard): dedicated regression guard for #6815 density guarantee Coverage for the #6815 multi-column density guarantee was only ever asserted incidentally, by two other guards (#7072, #3520) that pinned the literal sm:grid-cols-2 token. That coupling evaporated the coverage when PR #7027 migrated the component to a container-driven auto-fit template and the literal token was removed from both files. Adds a dedicated guard that simulates, from the shipped className, how many columns the per-group card grid renders at a wide container width -- supporting both the breakpoint-ladder and auto-fit mechanisms this component has shipped with -- and asserts >1 column, without asserting any specific Tailwind token. * chore(changelog): fragment for #7291 density guard
This commit is contained in:
committed by
GitHub
parent
865cfa0e87
commit
7f9dfd85f2
@@ -0,0 +1 @@
|
||||
- **test(dashboard):** restore dedicated regression coverage for #6815's `QuotaCardGrid` multi-column density guarantee, decoupled from the specific Tailwind token so it survives the #7027 auto-fit migration ([#7291](https://github.com/diegosouzapw/OmniRoute/pull/7291))
|
||||
190
tests/unit/quota-card-grid-density-6815.test.ts
Normal file
190
tests/unit/quota-card-grid-density-6815.test.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
// #6815 — Provider Quota page horizontal density.
|
||||
//
|
||||
// #6815 changed QuotaCardGrid.tsx's per-group card grid from a
|
||||
// single-column-only layout (`flex flex-col`) to one that packs multiple
|
||||
// QuotaCards side by side on wide screens, instead of stacking every card
|
||||
// vertically no matter how much horizontal space is available.
|
||||
//
|
||||
// That guarantee was only ever asserted *incidentally*, by two other guards
|
||||
// that pinned the literal Tailwind token the #6815 implementation happened
|
||||
// to use at the time (`sm:grid-cols-2` in
|
||||
// tests/unit/quota-card-grid-mobile-7072.test.ts and
|
||||
// tests/unit/quota-card-grid-horizontal-layout.test.ts). When PR #7027
|
||||
// migrated the component from a fixed breakpoint ladder
|
||||
// (`grid-cols-1 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4`) to a
|
||||
// container-driven auto-fit template
|
||||
// (`grid-cols-[repeat(auto-fit,minmax(min(100%,280px),1fr))]`), that literal
|
||||
// token disappeared from the source and both guards were edited to stop
|
||||
// asserting it — silently deleting the only coverage #6815 had.
|
||||
//
|
||||
// This guard re-establishes dedicated coverage for the #6815 density
|
||||
// guarantee itself, decoupled from *how* the component achieves it. Instead
|
||||
// of matching a specific class-name token, it simulates, from the shipped
|
||||
// className(s), how many columns the per-group card grid would actually
|
||||
// render at a wide container width — supporting both mechanisms seen in this
|
||||
// component's history (a Tailwind breakpoint ladder, and a CSS auto-fit
|
||||
// `minmax()` template) — and asserts that count is >1. Reverting to a single
|
||||
// unconditional column (`grid-cols-1` with no responsive/auto-fit variants,
|
||||
// or dropping the grid entirely for `flex flex-col`) must fail this guard,
|
||||
// regardless of which mechanism produced the regression.
|
||||
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import ts from "typescript";
|
||||
|
||||
const COMPONENT_PATH = path.resolve(
|
||||
import.meta.dirname,
|
||||
"../../src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaCardGrid.tsx"
|
||||
);
|
||||
|
||||
/**
|
||||
* Extract the string literal passed to `className={...}` (or `className="..."`)
|
||||
* for every JSX `<div>` opening element in the component's source, in source
|
||||
* order, via the TypeScript compiler API (not a hand-rolled regex — tracks
|
||||
* the real AST so it can't be fooled by comments/whitespace).
|
||||
*/
|
||||
function extractDivClassNames(sourcePath: string): string[] {
|
||||
const sourceText = fs.readFileSync(sourcePath, "utf8");
|
||||
const sourceFile = ts.createSourceFile(
|
||||
sourcePath,
|
||||
sourceText,
|
||||
ts.ScriptTarget.Latest,
|
||||
true,
|
||||
ts.ScriptKind.TSX
|
||||
);
|
||||
const classNames: string[] = [];
|
||||
|
||||
function visit(node: ts.Node) {
|
||||
if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
|
||||
const tagName = node.tagName.getText(sourceFile);
|
||||
if (tagName === "div") {
|
||||
for (const attr of node.attributes.properties) {
|
||||
if (ts.isJsxAttribute(attr) && attr.name.getText(sourceFile) === "className") {
|
||||
const init = attr.initializer;
|
||||
if (init && ts.isStringLiteral(init)) {
|
||||
classNames.push(init.text);
|
||||
} else if (
|
||||
init &&
|
||||
ts.isJsxExpression(init) &&
|
||||
init.expression &&
|
||||
ts.isStringLiteral(init.expression)
|
||||
) {
|
||||
classNames.push(init.expression.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
}
|
||||
visit(sourceFile);
|
||||
return classNames;
|
||||
}
|
||||
|
||||
// Tailwind's default min-width breakpoints (px). Unprefixed utilities apply
|
||||
// at every width (breakpoint 0) and later/larger breakpoints win the cascade
|
||||
// once their min-width is met, mirroring Tailwind's mobile-first source order.
|
||||
const TAILWIND_BREAKPOINTS: Record<string, number> = {
|
||||
sm: 640,
|
||||
md: 768,
|
||||
lg: 1024,
|
||||
xl: 1280,
|
||||
"2xl": 1536,
|
||||
};
|
||||
|
||||
type ColumnRule =
|
||||
| { breakpoint: number; kind: "fixed"; columns: number }
|
||||
| { breakpoint: number; kind: "autofit"; trackPx: number };
|
||||
|
||||
/**
|
||||
* Parse every `grid-cols-*` utility (optionally breakpoint-prefixed) found in
|
||||
* a className string into a column rule, supporting both mechanisms this
|
||||
* component has shipped with:
|
||||
* - a fixed count, e.g. `grid-cols-2`, `md:grid-cols-3`
|
||||
* - a CSS auto-fit template, e.g.
|
||||
* `grid-cols-[repeat(auto-fit,minmax(min(100%,280px),1fr))]`, from which
|
||||
* the minimum track width (in px) is extracted.
|
||||
*/
|
||||
function parseColumnRules(className: string): ColumnRule[] {
|
||||
const rules: ColumnRule[] = [];
|
||||
for (const token of className.split(/\s+/).filter(Boolean)) {
|
||||
const prefixMatch = token.match(/^(?:([a-zA-Z0-9]+):)?grid-cols-(.+)$/);
|
||||
if (!prefixMatch) continue;
|
||||
const [, prefix, rest] = prefixMatch;
|
||||
const breakpoint = prefix ? (TAILWIND_BREAKPOINTS[prefix] ?? 0) : 0;
|
||||
|
||||
if (/^\d+$/.test(rest)) {
|
||||
rules.push({ breakpoint, kind: "fixed", columns: parseInt(rest, 10) });
|
||||
continue;
|
||||
}
|
||||
|
||||
const autoFitMatch = rest.match(/^\[repeat\(auto-fit,\s*minmax\((.+),\s*1fr\)\)\]$/);
|
||||
if (autoFitMatch) {
|
||||
const trackPxMatches = [...autoFitMatch[1].matchAll(/(\d+)px/g)];
|
||||
if (trackPxMatches.length > 0) {
|
||||
const trackPx = parseInt(trackPxMatches[trackPxMatches.length - 1][1], 10);
|
||||
rules.push({ breakpoint, kind: "autofit", trackPx });
|
||||
}
|
||||
}
|
||||
}
|
||||
return rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a className string, estimate how many columns the grid renders at a
|
||||
* given container/viewport width, by picking the widest matching breakpoint
|
||||
* rule (Tailwind cascade) and resolving fixed vs. auto-fit tracks. Returns 1
|
||||
* (single column) when no `grid-cols-*` rule is present at all — e.g. a
|
||||
* `flex flex-col` layout.
|
||||
*/
|
||||
function estimateColumnsAtWidth(className: string, widthPx: number): number {
|
||||
const rules = parseColumnRules(className).filter((r) => r.breakpoint <= widthPx);
|
||||
if (rules.length === 0) return 1;
|
||||
const active = rules.reduce((best, r) => (r.breakpoint >= best.breakpoint ? r : best));
|
||||
if (active.kind === "fixed") return active.columns;
|
||||
return Math.max(1, Math.floor(widthPx / active.trackPx));
|
||||
}
|
||||
|
||||
// --- Self-test of the estimator against known-good and known-bad shapes ---
|
||||
// (independent of the real component, so the simulation logic itself is
|
||||
// pinned before it's trusted to judge the shipped source below).
|
||||
|
||||
test("#6815 density estimator — breakpoint ladder resolves to multiple columns on a wide viewport", () => {
|
||||
const className = "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4 gap-3";
|
||||
assert.equal(estimateColumnsAtWidth(className, 1200), 3);
|
||||
assert.equal(estimateColumnsAtWidth(className, 375), 1);
|
||||
});
|
||||
|
||||
test("#6815 density estimator — auto-fit template resolves to multiple columns on a wide container", () => {
|
||||
const className = "grid grid-cols-[repeat(auto-fit,minmax(min(100%,280px),1fr))] gap-3";
|
||||
assert.ok(estimateColumnsAtWidth(className, 1200) >= 2);
|
||||
assert.equal(estimateColumnsAtWidth(className, 200), 1);
|
||||
});
|
||||
|
||||
test("#6815 density estimator — single unconditional column stays at 1 column regardless of width", () => {
|
||||
assert.equal(estimateColumnsAtWidth("grid grid-cols-1 gap-3", 1920), 1);
|
||||
});
|
||||
|
||||
test("#6815 density estimator — flex column stack (no grid-cols) resolves to 1 column", () => {
|
||||
assert.equal(estimateColumnsAtWidth("flex flex-col gap-3", 1920), 1);
|
||||
});
|
||||
|
||||
// --- The actual regression guard, reading the shipped component source ---
|
||||
|
||||
test("QuotaCardGrid (#6815) — per-group card grid renders multiple columns on a wide container", () => {
|
||||
const classNames = extractDivClassNames(COMPONENT_PATH);
|
||||
const cardGridClassName = classNames.find((c) => /\bgrid\b/.test(c) && /grid-cols-/.test(c));
|
||||
assert.ok(
|
||||
cardGridClassName,
|
||||
"expected to find a grid-based per-group card grid className (not a single-column flex stack)"
|
||||
);
|
||||
|
||||
const columnsOnWideContainer = estimateColumnsAtWidth(cardGridClassName!, 1200);
|
||||
assert.ok(
|
||||
columnsOnWideContainer > 1,
|
||||
`expected the per-group card grid to render more than 1 column at 1200px, got ${columnsOnWideContainer} ` +
|
||||
`from className="${cardGridClassName}" — this is the #6815 density regression`
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user