feat(app): wire the voyage together
Controller drives world and curtain; caption with Back to sea; play, previous and next in the timeline bar; keyboard; no-WebGL frieze stage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ac017787b5
commit
d355b3a41f
5 files changed
+185
-18
No files matched your search
+32
-1
@@ -29,11 +29,13 @@ function fakes() {
|
||||
sailTo: (id) => later(`sail:${id}`),
|
||||
setLanding: (id) => later(`land:${id}`),
|
||||
setRibbons: () => {},
|
||||
horizonFromFrieze: (y) => { log.push(`horizon:${y}`); },
|
||||
};
|
||||
const curtain: ControllerCurtain = {
|
||||
drop: (saga) => later(`drop:${saga}`),
|
||||
lift: () => later("lift"),
|
||||
fade: () => later("fade"),
|
||||
groundY: () => 500,
|
||||
};
|
||||
const flush = async () => {
|
||||
for (let i = 0; i < 50; i++) {
|
||||
@@ -57,7 +59,9 @@ test("rapid navigation ends at the last requested song with the curtain lifted",
|
||||
const drops = f.log.filter((l) => l.startsWith("drop:") || l === "fade").length;
|
||||
const lifts = f.log.filter((l) => l === "lift").length;
|
||||
expect(lifts).toBeGreaterThanOrEqual(drops === 0 ? 0 : 1);
|
||||
expect(f.log.at(-1)).toBe("land:arena");
|
||||
// covered → sail → landing → lift: the stage is uncovered last, already inside the arena
|
||||
expect(f.log.lastIndexOf("land:arena")).toBeLessThan(f.log.lastIndexOf("lift"));
|
||||
expect(f.log.at(-1)).toBe("lift");
|
||||
expect(ctl.busy()).toBe(false);
|
||||
});
|
||||
|
||||
@@ -71,6 +75,19 @@ test("without a world the controller still follows the songs (frieze-only browsi
|
||||
expect(f.log).toContain("drop:cyclops");
|
||||
});
|
||||
|
||||
test("the horizon is matched to the frieze's ground line before the curtain lifts", async () => {
|
||||
const f = fakes();
|
||||
const store = createStore(DEFAULT_STATE, { history: false });
|
||||
createController({ store, world: f.world, curtain: f.curtain, reducedMotion: false });
|
||||
await f.flush();
|
||||
store.set({ songId: "just-a-man" }); // same saga: no curtain
|
||||
await f.flush();
|
||||
expect(f.log.filter((l) => l.startsWith("horizon:"))).toHaveLength(0);
|
||||
store.set({ songId: "polyphemus" }); // next saga: curtain
|
||||
await f.flush();
|
||||
expect(f.log.indexOf("horizon:500")).toBeLessThan(f.log.lastIndexOf("lift"));
|
||||
});
|
||||
|
||||
test("reduced motion turns every scene change into a fade", async () => {
|
||||
const f = fakes();
|
||||
const store = createStore(DEFAULT_STATE, { history: false });
|
||||
@@ -80,3 +97,17 @@ test("reduced motion turns every scene change into a fade", async () => {
|
||||
expect(f.log).toContain("fade");
|
||||
expect(f.log.some((l) => l.startsWith("drop:"))).toBe(false);
|
||||
});
|
||||
|
||||
test("Back to sea leaves the landing without changing the song", async () => {
|
||||
const f = fakes();
|
||||
const store = createStore({ ...DEFAULT_STATE, songId: "survive" }, { history: false });
|
||||
const ctl = createController({ store, world: f.world, curtain: f.curtain, reducedMotion: false });
|
||||
await f.flush();
|
||||
expect(ctl.landing()).toBe("cave");
|
||||
const left = ctl.leaveLanding();
|
||||
await f.flush();
|
||||
await left;
|
||||
expect(f.log.at(-1)).toBe("land:null");
|
||||
expect(ctl.landing()).toBeNull();
|
||||
expect(store.get().songId).toBe("survive");
|
||||
});
|
||||
Reference in new issue
Block a user