import { easeToward, localAtProgress, locateInReel, REEL_HOLD, REEL_LAST_TAIL, reelHolds, reelStarts, videoProgress, } from "./showcase-math"; describe("locateInReel", () => { const weights = [1, 2, 1]; it("maps overall progress to a project and a position inside it", () => { expect(locateInReel(weights, 0)).toEqual({ index: 0, local: 0 }); expect(locateInReel(weights, 0.125)).toEqual({ index: 0, local: 0.5 }); const middle = locateInReel(weights, 0.5); expect(middle.index).toBe(1); expect(middle.local).toBeCloseTo(0.5); expect(locateInReel(weights, 1)).toEqual({ index: 2, local: 1 }); }); it("clamps progress outside the pinned range", () => { expect(locateInReel(weights, -3)).toEqual({ index: 0, local: 0 }); expect(locateInReel(weights, 7)).toEqual({ index: 2, local: 1 }); }); it("copes with an empty reel", () => { expect(locateInReel([], 0.4)).toEqual({ index: 0, local: 0 }); }); it("gives each project the share of the scroll that its weight asks for", () => { expect(reelStarts(weights)).toEqual([0, 0.25, 0.75]); }); }); describe("videoProgress", () => { it("holds still at the start and at the end of each project's stretch", () => { expect(videoProgress(0)).toBe(0); expect(videoProgress(0.1)).toBe(0); expect(videoProgress(0.5)).toBeCloseTo(0.5); expect(videoProgress(0.9)).toBe(1); expect(videoProgress(1)).toBe(1); }); }); describe("videoProgress with a shorter pause at the end", () => { it("lets the video run until the tail starts", () => { expect(videoProgress(0.9, 0.1, 0.03)).toBeCloseTo((0.9 - 0.1) / 0.87, 9); expect(videoProgress(0.97, 0.1, 0.03)).toBe(1); expect(videoProgress(1, 0.1, 0.03)).toBe(1); expect(videoProgress(0.1, 0.1, 0.03)).toBe(0); }); it("localAtProgress is its inverse inside the moving part", () => { for (const progress of [0, 0.25, 0.5, 1]) { const local = localAtProgress(progress, 0.1, 0.03); expect(videoProgress(local, 0.1, 0.03)).toBeCloseTo(progress, 9); } expect(localAtProgress(0)).toBe(REEL_HOLD); expect(localAtProgress(1)).toBeCloseTo(1 - REEL_HOLD, 9); }); }); describe("reelHolds", () => { it("keeps the full pause everywhere but at the end of the last project, which has nothing to wait for", () => { expect(reelHolds(0, 4)).toEqual({ head: REEL_HOLD, tail: REEL_HOLD }); expect(reelHolds(2, 4)).toEqual({ head: REEL_HOLD, tail: REEL_HOLD }); expect(reelHolds(3, 4)).toEqual({ head: REEL_HOLD, tail: REEL_LAST_TAIL }); expect(REEL_LAST_TAIL).toBeLessThan(REEL_HOLD); expect(reelHolds(0, 1).tail).toBe(REEL_LAST_TAIL); }); }); describe("easeToward", () => { it("moves toward the target and never overshoots", () => { const first = easeToward(0, 1, 1 / 60); expect(first).toBeGreaterThan(0); expect(first).toBeLessThan(1); expect(easeToward(0.5, 0.5, 1)).toBe(0.5); expect(easeToward(0, 1, 10)).toBeCloseTo(1, 5); }); it("does not depend on the frame rate", () => { let sixty = 0; for (let i = 0; i < 60; i += 1) sixty = easeToward(sixty, 1, 1 / 60); let thirty = 0; for (let i = 0; i < 30; i += 1) thirty = easeToward(thirty, 1, 1 / 30); expect(sixty).toBeCloseTo(thirty, 6); }); it("stands still when no time has passed", () => { expect(easeToward(0.2, 1, 0)).toBe(0.2); }); });