Files
ditto.site/compiler/test/marqueeDiscriminator.test.ts
T
Samraaj BathandClaude Fable 5 d3ec154bae Capture & emission fidelity wave: dotLottie support, scroll-state isolation, authored-geometry trust, honest quality scoring
Capture:
- Preserve real asset extensions (.lottie no longer truncated); extract the
  default animation JSON from dotLottie ZIP containers at materialization
  (minimal built-in ZIP reader, no new deps)
- Reset scroll + settle before every viewport snapshot so scroll-linked
  styles can't bake into captured computed values
- Marquee detection now requires content overflow (>1.35x), sustained
  velocity across separated windows, scroll independence (jiggle test), and
  genuine content repetition — kills scroll-settle-lerp false positives
- Sizing probe trusts authored explicit heights (px/vh/calc) over circular
  parent/child reflow verdicts, fixing dropped 100vh heroes

Normalize/generate:
- Canonicalize identity transforms to "none" per viewport; emit explicit
  transform resets across bands when any band has a real transform
- DittoLottie keeps the captured placeholder until DOMLoaded; failed loads
  no longer blank the container
- mx-auto only for true auto-margin centering; literal per-band margins are
  preserved (fixes full-bleed regressions)
- Spacing-scale snap tightened to 0.25px (3.5px stays p-[3.5px]); emit
  whitespace-nowrap for wrap-vulnerable single-line text leaves
- Grid recipes no longer override captured track counts or column spans;
  responsive re-flow plans apply only when geometry agrees

Quality scoring:
- Rewritten 6-dimension rubric (payload, decomposition, duplication,
  semantics, hygiene, runtime) with catastrophe caps and per-dimension
  reporting; thresholds are documented calibration guides

218 tests pass (74 new), typecheck clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 23:09:16 -07:00

105 lines
4.5 KiB
TypeScript

import { describe, it } from "node:test";
import assert from "node:assert/strict";
import {
medianVelocityPxPerSec,
classifyVelocitySamples,
hasRepeatedChildren,
} from "../src/capture/motion.js";
// These cover the pure discriminators extracted from detectMarquees' in-browser sampling.
// The regression they defend: on a scroll-LINKED-easing page, a static logo row is still
// lerping toward its scroll-target right after scrollIntoView, reading as a constant
// velocity over ONE short window → four phantom marquees at identical -34px/s. The layered
// tests below make that classification fail.
describe("medianVelocityPxPerSec", () => {
it("converts a steady per-120ms delta to px/s and ignores the wrap-reset outlier", () => {
// steady -4px per 120ms ≈ -33px/s; one big +200 wrap jump is an outlier the median drops.
const deltas = [-4, -4, 200, -4, -4];
assert.equal(medianVelocityPxPerSec(deltas, 120), Math.round((-4 / 120) * 1000));
});
it("returns 0 for empty deltas or a non-positive cadence", () => {
assert.equal(medianVelocityPxPerSec([], 120), 0);
assert.equal(medianVelocityPxPerSec([-4, -4], 0), 0);
});
});
describe("classifyVelocitySamples — sustained constant velocity (discriminator 2)", () => {
const MS = 120;
it("PASSES a real marquee: velocity holds across both windows", () => {
const w1 = [-4, -4, -4, -4, -4];
const w2 = [-4, -4, -4, -4, -4];
const r = classifyVelocitySamples(w1, w2, MS);
assert.equal(r.isMarquee, true);
assert.equal(r.pxPerSec, medianVelocityPxPerSec(w1, MS));
});
it("REJECTS a scroll-settle lerp: velocity decays sharply in window 2 (the false positive)", () => {
// window1 still lerping fast toward scroll-target; window2 nearly settled.
const w1 = [-4, -4, -4, -4, -4]; // ≈ -33px/s (the phantom "-34px/s")
const w2 = [-0.4, -0.4, -0.4, -0.4, -0.4]; // decayed to ~-3px/s
assert.equal(classifyVelocitySamples(w1, w2, MS).isMarquee, false);
});
it("REJECTS when window 2 has fully settled (velocity ~0)", () => {
const w1 = [-4, -4, -4, -4, -4];
const w2 = [0, 0, 0, 0, 0];
assert.equal(classifyVelocitySamples(w1, w2, MS).isMarquee, false);
});
it("REJECTS when the direction reverses between windows", () => {
const w1 = [-4, -4, -4, -4, -4];
const w2 = [4, 4, 4, 4, 4];
assert.equal(classifyVelocitySamples(w1, w2, MS).isMarquee, false);
});
it("REJECTS a static row: no motion in either window", () => {
assert.equal(classifyVelocitySamples([0, 0, 0], [0, 0, 0], MS).isMarquee, false);
});
it("PASSES a slightly slower-but-still-moving second window (within tolerance)", () => {
// small natural jitter (10% slower) must not disqualify a genuine ticker.
const w1 = [-4, -4, -4, -4, -4];
const w2 = [-3.6, -3.6, -3.6, -3.6, -3.6];
assert.equal(classifyVelocitySamples(w1, w2, MS).isMarquee, true);
});
});
describe("hasRepeatedChildren — genuine duplication (discriminator 4)", () => {
it("PASSES two consecutive children with an identical outerHTML hash (a cloned copy)", () => {
const hashes = [111, 111, 222]; // first two are a literal duplicate
const widths = [80, 80, 120];
assert.equal(hasRepeatedChildren(hashes, widths), true);
});
it("PASSES a duplicated content block via the repeated width sequence [A B A B]", () => {
// hashes differ (cloned then attribute-tweaked) but geometry repeats: [100,60,100,60].
const hashes = [1, 2, 3, 4];
const widths = [100, 60, 100, 60];
assert.equal(hasRepeatedChildren(hashes, widths), true);
});
it("REJECTS a row of distinct logos: no consecutive repetition (the false-positive shape)", () => {
// four DIFFERENT logos — the exact static-logo-row case that produced phantom marquees.
const hashes = [10, 20, 30, 40];
const widths = [90, 110, 75, 130];
assert.equal(hasRepeatedChildren(hashes, widths), false);
});
it("REJECTS fewer than two children", () => {
assert.equal(hasRepeatedChildren([5], [50]), false);
assert.equal(hasRepeatedChildren([], []), false);
});
it("does NOT treat a run of zero-width nodes as repetition (hash 0 / width 0 guards)", () => {
assert.equal(hasRepeatedChildren([0, 0], [0, 0]), false);
assert.equal(hasRepeatedChildren([1, 2, 3, 4], [0, 0, 0, 0]), false);
});
it("REJECTS an odd-length width sequence that can't split into halves", () => {
const hashes = [1, 2, 3];
const widths = [100, 60, 100];
assert.equal(hasRepeatedChildren(hashes, widths), false);
});
});