import { describe, expect, it } from "vitest"; import { nextSign } from "@/journey/sign-controller"; import type { SignSnapshot } from "@/journey/types"; const passiveSample = ( distanceVh: number, overrides: Partial[1]> = {}, ): Parameters[1] => ({ source: "passive-scroll", deltaY: 10, viewportHeight: 100, crossedBoundaries: 0, candidateId: "profile", distanceVh, motionAllowed: true, ...overrides, }); describe("forward-only sign latching", () => { it("latches a first eligible candidate at the exact approach boundary", () => { expect(nextSign(null, passiveSample(1.2))).toEqual({ targetId: "profile", phase: "approach", progress: 0, }); }); it("maps the literal approach and pass ranges independently", () => { expect(nextSign(null, passiveSample(0.875))).toEqual({ targetId: "profile", phase: "approach", progress: expect.closeTo(0.5, 14), }); expect(nextSign(null, passiveSample(-0.05))).toEqual({ targetId: "profile", phase: "pass", progress: 0.5, }); }); it("uses exact stabilized, pass, and completion boundaries", () => { const latched: SignSnapshot = { targetId: "profile", phase: "approach", progress: 0, }; expect(nextSign(latched, passiveSample(0.55))).toEqual({ targetId: "profile", phase: "stabilized", progress: 1, }); expect(nextSign(latched, passiveSample(0.15))).toEqual({ targetId: "profile", phase: "pass", progress: 0, }); expect(nextSign(latched, passiveSample(-0.25))).toBeNull(); }); it("retains the one latched target while the semantic section advances", () => { const latched: SignSnapshot = { targetId: "profile", phase: "stabilized", progress: 1, }; expect( nextSign(latched, passiveSample(0.1, { candidateId: "experience" })), ).toEqual({ targetId: "profile", phase: "pass", progress: expect.closeTo(0.125, 14), }); }); it.each([ ["reverse input", { deltaY: -2 }], ["zero-direction input", { deltaY: 0 }], ["explicit navigation", { source: "explicit-navigation" as const }], ["history restoration", { source: "history-restoration" as const }], ["initial positioning", { source: "initial" as const }], ["more than one activation boundary", { crossedBoundaries: 2 }], ["a jump over 0.75 viewport heights", { deltaY: 76 }], ["disabled motion", { motionAllowed: false }], ])("clears or suppresses signs for %s", (_label, overrides) => { const latched: SignSnapshot = { targetId: "profile", phase: "approach", progress: 0.5, }; expect(nextSign(latched, passiveSample(0.5, overrides))).toBeNull(); }); it("does not latch before visibility or after a jump directly past completion", () => { expect(nextSign(null, passiveSample(1.20001))).toBeNull(); expect(nextSign(null, passiveSample(-0.25))).toBeNull(); expect(nextSign(null, passiveSample(-1))).toBeNull(); }); });