Files
ditto.site/compiler/test/screens.test.ts
T
Samraaj BathandClaude Fable 5 9aed2540aa Clone-fidelity fix waves 1-3 (+ wave 4 in flight): capture settling, media, iframes
Fixes from the cropin.com / ooni.com audit reviews:
- walker: preserve whitespace-only text in inline elements ("ofthe" bug);
  capture ::placeholder styles for inputs
- css: emit visibility (hidden-at-rest wordmark artifact); exempt list
  markers from inherited elision (lost bullets); ::placeholder rules
- seo: sanitize og:type to Next's enum (render crash -> dev badge leak);
  generated next.config disables devIndicators
- capture/stabilize: promote lazy-loader data attrs before snapshots
  (collapsed sections, viewport-inconsistent captures); settle autoplay
  carousels to home slide before every snapshot; force-reveal hidden
  videos for poster shots + log failures
- generate: ship downloaded video files as local <video src>; keep
  picture>source through IR prune with srcset rewrite (mobile-crop-on-
  desktop heroes); pin Tailwind named screens to computeBands boundaries
- capture/graft: capture cross-origin iframe subtrees (Klaviyo signup
  forms) with element-screenshot fallback; asset download retry +
  visual-assets-missing reporting

Checkpoint commit: wave 4 (reveal settling, 206 range-response fix,
hidden-geometry banding, bare-zero utility gating) is mid-implementation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 17:45:42 -07:00

65 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { computeBands } from "../src/generate/css.js";
import { bandScreens, prefixFor, tailwindGlobalsCss } from "../src/generate/tailwind.js";
/** Interval covered by a ditto.css band media query (integer px, inclusive). */
function bandInterval(media: string | null): [number, number] {
if (!media) return [0, Infinity];
const min = /min-width:\s*(\d+)px/.exec(media);
const max = /max-width:\s*(\d+)px/.exec(media);
return [min ? +min[1]! : 0, max ? +max[1]! : Infinity];
}
/** Integer interval covered by a Tailwind variant prefix under the given screens.
* v4 semantics for named AND arbitrary variants: `X:` = width >= X; `max-X:` = width < X. */
function prefixInterval(prefix: string, screens: Map<string, number>): [number, number] {
let lo = 0, hi = Infinity;
for (const m of prefix.matchAll(/(max-)?(?:(sm|md|lg|xl|2xl)|(?:min-)?\[(\d+)px\]):/g)) {
const px = m[3] ? +m[3] : screens.get(m[2]!);
assert.ok(px !== undefined, `screen defined for ${m[0]}`);
if (m[1]) hi = Math.min(hi, px! - 1);
else lo = Math.max(lo, px!);
}
return [lo, hi];
}
function assertAgreement(viewports: number[], canonical: number): void {
const screens = new Map(bandScreens(viewports, canonical));
for (const b of computeBands(viewports, canonical)) {
if (!b.media) continue;
assert.deepEqual(
prefixInterval(prefixFor(b.media), screens),
bandInterval(b.media),
`band vp${b.vp} (${b.media}) matches its Tailwind prefix`,
);
}
}
describe("tailwind screens agree with computeBands boundaries", () => {
it("standard 375/768/1280/1920 ladder: md/lg/2xl pinned to the band midpoint boundaries", () => {
const screens = new Map(bandScreens([375, 768, 1280, 1920], 1280));
// Midpoints 571/1024/1600 → bands ≤571, 5721024, base, ≥1601.
assert.deepEqual([...screens], [["md", 572], ["lg", 1025], ["2xl", 1601]]);
// NOT Tailwind's stock 768/1024/1536 — stock 2xl (1536) would flip utility-classed
// nodes to the 1920 layout in 15361600 while ditto.css still holds the 1280 layout.
assert.notEqual(screens.get("2xl"), 1536);
assertAgreement([375, 768, 1280, 1920], 1280);
});
it("odd viewport set: bands fall back to exact arbitrary prefixes (no named screens needed)", () => {
assert.deepEqual(bandScreens([320, 900, 1440], 900), []);
assertAgreement([320, 900, 1440], 900);
});
it("emits the derived screens into the generated @theme", () => {
const css = tailwindGlobalsCss({
reset: "", fontCss: "", tokensCss: "", htmlBg: "#fff", bodyFont: "sans-serif",
clip: "", colorTokens: [], viewports: [375, 768, 1280, 1920], canonical: 1280,
});
assert.ok(css.includes("--breakpoint-md: 572px;"));
assert.ok(css.includes("--breakpoint-lg: 1025px;"));
assert.ok(css.includes("--breakpoint-2xl: 1601px;"));
});
});