Files
doormile_react/src/components/strategy/theme.test.ts
2026-06-03 19:19:56 +05:30

93 lines
3.1 KiB
TypeScript

import { STAGES, N, D_SPACING, X_SWAY, districtPosition, samplePath, cameraFor } from "./theme";
describe("strategy/theme — stage data", () => {
it("exposes five stages and a matching N", () => {
expect(STAGES).toHaveLength(5);
expect(N).toBe(5);
});
it("has unique, well-formed stage descriptors", () => {
const keys = STAGES.map((s) => s.key);
expect(new Set(keys).size).toBe(keys.length);
for (const s of STAGES) {
expect(s.n).toMatch(/^\d{2}$/);
expect(s.theme).toMatch(/^#[0-9A-Fa-f]{6}$/);
expect(s.kicker.length).toBeGreaterThan(0);
}
});
});
describe("strategy/theme — districtPosition()", () => {
it("sits on the floor (y=0), sways x by parity, steps z by -D_SPACING", () => {
expect(districtPosition(0)).toEqual([-X_SWAY, 0, -0]);
expect(districtPosition(1)).toEqual([X_SWAY, 0, -D_SPACING]);
expect(districtPosition(2)).toEqual([-X_SWAY, 0, -2 * D_SPACING]);
expect(districtPosition(3)).toEqual([X_SWAY, 0, -3 * D_SPACING]);
});
it("places even districts left and odd districts right", () => {
for (let i = 0; i < N; i++) {
const [x] = districtPosition(i);
if (i % 2 === 0) expect(x).toBeLessThan(0);
else expect(x).toBeGreaterThan(0);
}
});
it("marches monotonically into -Z", () => {
for (let i = 1; i < N; i++) {
expect(districtPosition(i)[2]).toBeLessThan(districtPosition(i - 1)[2]);
}
});
});
describe("strategy/theme — samplePath()", () => {
it("equals districtPosition() at integer indices", () => {
for (let i = 0; i < N; i++) {
const sampled = samplePath(i);
const exact = districtPosition(i);
sampled.forEach((v, k) => expect(v).toBeCloseTo(exact[k], 10));
}
});
it("linearly interpolates between adjacent districts", () => {
const [x, y, z] = samplePath(0.5);
expect(x).toBeCloseTo(0, 10); // midpoint of -X_SWAY and +X_SWAY
expect(y).toBeCloseTo(0, 10);
expect(z).toBeCloseTo(-D_SPACING / 2, 10);
});
it("clamps indices below 0 to the first district", () => {
expect(samplePath(-3)).toEqual(districtPosition(0));
});
it("clamps indices above N-1 to the last district", () => {
expect(samplePath(99)).toEqual(districtPosition(N - 1));
});
it("never produces NaN across a fine sweep", () => {
for (let p = 0; p <= 1.0001; p += 0.05) {
const idx = p * (N - 1);
for (const v of samplePath(idx)) expect(Number.isNaN(v)).toBe(false);
}
});
});
describe("strategy/theme — cameraFor()", () => {
it("keeps the camera behind (+Z) and above (+Y) the active district", () => {
for (let i = 0; i < N; i++) {
const { pos, look } = cameraFor(i);
const [, , dz] = districtPosition(i);
expect(pos[2]).toBeGreaterThan(dz); // camera is back from the district
expect(pos[1]).toBeGreaterThan(look[1]); // camera looks down
expect(look[2]).toBeLessThan(pos[2]); // look target leads forward
}
});
it("never produces NaN across a fine sweep", () => {
for (let p = 0; p <= 1.0001; p += 0.05) {
const { pos, look } = cameraFor(p * (N - 1));
for (const v of [...pos, ...look]) expect(Number.isNaN(v)).toBe(false);
}
});
});