feat: coordinate sections signs and native navigation
This commit is contained in:
137
tests/unit/section-controller.test.ts
Normal file
137
tests/unit/section-controller.test.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
activationLineDocumentY,
|
||||
deriveSectionState,
|
||||
isAtDocumentEnd,
|
||||
selectActiveSection,
|
||||
type SectionHeadingGeometry,
|
||||
} from "@/journey/section-controller";
|
||||
|
||||
const headings: readonly SectionHeadingGeometry[] = [
|
||||
{ id: "start", top: 100 },
|
||||
{ id: "profile", top: 350 },
|
||||
{ id: "experience", top: 700 },
|
||||
{ id: "infrastructure", top: 1100 },
|
||||
{ id: "selected-builds", top: 1500 },
|
||||
{ id: "additional-work", top: 1900 },
|
||||
{ id: "toolkit", top: 2300 },
|
||||
{ id: "contact", top: 2700 },
|
||||
];
|
||||
|
||||
describe("active section selection", () => {
|
||||
it("places the activation line at a literal 35 percent of the visual viewport", () => {
|
||||
expect(
|
||||
activationLineDocumentY({
|
||||
scrollY: 200,
|
||||
visualViewportOffsetTop: 25,
|
||||
visualViewportHeight: 1000,
|
||||
}),
|
||||
).toBe(575);
|
||||
});
|
||||
|
||||
it("forces document end within exactly two CSS pixels using current geometry", () => {
|
||||
expect(
|
||||
isAtDocumentEnd({
|
||||
scrollY: 498,
|
||||
scrollHeight: 1000,
|
||||
visualViewportHeight: 500,
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
isAtDocumentEnd({
|
||||
scrollY: 497.999,
|
||||
scrollHeight: 1000,
|
||||
visualViewportHeight: 500,
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(
|
||||
isAtDocumentEnd({
|
||||
scrollY: 498,
|
||||
scrollHeight: 1200,
|
||||
visualViewportHeight: 500,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps the first section before any heading reaches the activation line", () => {
|
||||
expect(
|
||||
selectActiveSection({ headings, activationY: 99, atDocumentEnd: false }),
|
||||
).toBe("start");
|
||||
});
|
||||
|
||||
it("selects the last heading at or above the literal 35 percent activation line", () => {
|
||||
expect(
|
||||
selectActiveSection({
|
||||
headings,
|
||||
activationY: 699.999,
|
||||
atDocumentEnd: false,
|
||||
}),
|
||||
).toBe("profile");
|
||||
expect(
|
||||
selectActiveSection({ headings, activationY: 700, atDocumentEnd: false }),
|
||||
).toBe("experience");
|
||||
});
|
||||
|
||||
it("lets later document order win when heading positions are identical", () => {
|
||||
expect(
|
||||
selectActiveSection({
|
||||
headings: [
|
||||
{ id: "start", top: 0 },
|
||||
{ id: "profile", top: 350 },
|
||||
{ id: "experience", top: 350 },
|
||||
],
|
||||
activationY: 350,
|
||||
atDocumentEnd: false,
|
||||
}),
|
||||
).toBe("experience");
|
||||
});
|
||||
|
||||
it("forces the final canonical section at document end", () => {
|
||||
expect(
|
||||
selectActiveSection({ headings, activationY: 1400, atDocumentEnd: true }),
|
||||
).toBe("contact");
|
||||
});
|
||||
|
||||
it("uses fresh late-growth geometry on every selection", () => {
|
||||
const grown = headings.map((heading) =>
|
||||
heading.id === "experience" ? { ...heading, top: 900 } : heading,
|
||||
);
|
||||
|
||||
expect(
|
||||
selectActiveSection({ headings, activationY: 750, atDocumentEnd: false }),
|
||||
).toBe("experience");
|
||||
expect(
|
||||
selectActiveSection({
|
||||
headings: grown,
|
||||
activationY: 750,
|
||||
atDocumentEnd: false,
|
||||
}),
|
||||
).toBe("profile");
|
||||
});
|
||||
|
||||
it("maps every canonical section to its chapter and arrival state", () => {
|
||||
expect([
|
||||
deriveSectionState("start"),
|
||||
deriveSectionState("profile"),
|
||||
deriveSectionState("experience"),
|
||||
deriveSectionState("infrastructure"),
|
||||
deriveSectionState("selected-builds"),
|
||||
deriveSectionState("additional-work"),
|
||||
deriveSectionState("toolkit"),
|
||||
deriveSectionState("contact"),
|
||||
]).toEqual([
|
||||
{ activeSectionId: "start", chapter: "deep-night", arrived: false },
|
||||
{ activeSectionId: "profile", chapter: "deep-night", arrived: false },
|
||||
{ activeSectionId: "experience", chapter: "systems", arrived: false },
|
||||
{
|
||||
activeSectionId: "infrastructure",
|
||||
chapter: "infrastructure",
|
||||
arrived: false,
|
||||
},
|
||||
{ activeSectionId: "selected-builds", chapter: "builds", arrived: false },
|
||||
{ activeSectionId: "additional-work", chapter: "builds", arrived: false },
|
||||
{ activeSectionId: "toolkit", chapter: "pre-dawn", arrived: false },
|
||||
{ activeSectionId: "contact", chapter: "destination", arrived: true },
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user