feat(audio-player): end-time label toggles total / book-left / chapter-left (plan §10 web)

Clicking the end time cycles the three display modes, persisted per
device in localStorage (audio_time_display). Pure helpers (mode list,
normalize, cycle, endTimeLabel with 0-clamp and no-chapters fallback
to book remaining) + vitest; aria-label announces the current mode and
the next. Remaining values track the scrub cursor mid-drag. 140 unit
tests; 15 Playwright checks on KJV + Gardens.
This commit is contained in:
John O'Keefe committed 2026-10-22 13:11:16 -04:00
1 parent 96fd2aad42
commit a9430f3c06
4 files changed
+121 -4

No files matched your search

+48 -2
View File
@@ -2,11 +2,15 @@ import { describe, expect, it } from "vitest";
import {
SPEEDS,
normalizeSpeed,
stepSpeed,
SKIP_SECONDS,
TIME_DISPLAY_MODES,
cycleSkip,
cycleTimeDisplay,
endTimeLabel,
normalizeAutoplay,
normalizeSpeed,
normalizeTimeDisplay,
stepSpeed,
} from "./audio";
describe("SPEEDS", () => {
@@ -93,3 +97,45 @@ describe("normalizeAutoplay", () => {
expect(normalizeAutoplay("2")).toBe(false);
});
});
describe("time display mode (§10 end-label toggle)", () => {
it("cycles total → book remaining → chapter remaining → total", () => {
expect(TIME_DISPLAY_MODES).toEqual(["total", "book-left", "chapter-left"]);
expect(cycleTimeDisplay("total")).toBe("book-left");
expect(cycleTimeDisplay("book-left")).toBe("chapter-left");
expect(cycleTimeDisplay("chapter-left")).toBe("total");
});
it("normalizes persisted values, defaulting to total", () => {
expect(normalizeTimeDisplay("total")).toBe("total");
expect(normalizeTimeDisplay("book-left")).toBe("book-left");
expect(normalizeTimeDisplay("chapter-left")).toBe("chapter-left");
expect(normalizeTimeDisplay(null)).toBe("total");
expect(normalizeTimeDisplay("")).toBe("total");
expect(normalizeTimeDisplay("remaining")).toBe("total");
});
it("renders total as the book duration", () => {
// 93807s = 26:03:27 (Gardens-shaped)
expect(endTimeLabel("total", 0, 93_807, null)).toBe("26:03:27");
});
it("renders book remaining, clamped at zero", () => {
expect(endTimeLabel("book-left", 93_807 - 3_662, 93_807, null)).toBe("−1:01:02");
expect(endTimeLabel("book-left", 93_807, 93_807, null)).toBe("−0:00");
expect(endTimeLabel("book-left", 99_999, 93_807, null)).toBe("−0:00");
});
it("renders chapter remaining with minutes:seconds formatting", () => {
// 842s left in the chapter → −14:02
expect(endTimeLabel("chapter-left", 5_000, 93_807, 5_842)).toBe("−14:02");
});
it("falls back to book remaining when the book has no chapters", () => {
expect(endTimeLabel("chapter-left", 93_807 - 3_662, 93_807, null)).toBe("−1:01:02");
});
it("treats a chapter end in the past as zero remaining", () => {
expect(endTimeLabel("chapter-left", 6_000, 93_807, 5_842)).toBe("−0:00");
});
});