|
| 1 | +// @vitest-environment jsdom |
| 2 | +/** |
| 3 | + * TimeRail.test.tsx — 共享时间轴尺的**行为级契约**(批 6 T25 Step 1 · R5.5 #5 的承载面)。 |
| 4 | + * |
| 5 | + * @ai-context 本件覆盖「尺」这一半:刻度的**序列**(逐序相等,不是「约等于」)· 刻度文案 |
| 6 | + * = `fmtMs` 的输出 · 轨道宽度 = 总长的像素换算(换总长就换宽度)。播放头、`<audio>` 属性契约 |
| 7 | + * 与降级提示行是 T25 的第二步(同一个文件里随后追加,见 `task-25-report.md` 的 V1–V7)。 |
| 8 | + * @ai-context 仪器边界:jsdom **不排版** ⇒ 只判结构事实(`data-tick-ms` 序列、行内宽度、 |
| 9 | + * DOM 归属),**不判**「刻度看起来在哪」。 |
| 10 | + * 副作用:只挂 React 树;不写盘、不发请求、不碰媒体。 |
| 11 | + */ |
| 12 | +import { cleanup, render } from "@testing-library/react"; |
| 13 | +import { afterEach, describe, expect, it } from "vitest"; |
| 14 | +import TimeRail, { pxOf, ticksOf } from "./TimeRail"; |
| 15 | + |
| 16 | +const RAIL = '[data-testid="session-timerail"]'; |
| 17 | +const VIEWPORT = '[data-testid="session-timerail-viewport"]'; |
| 18 | + |
| 19 | +function mount(totalMs: number) { |
| 20 | + const view = render(<TimeRail totalMs={totalMs} />); |
| 21 | + const ticks = [...view.container.querySelectorAll("[data-tick-ms]")] as HTMLElement[]; |
| 22 | + return { ...view, ticks }; |
| 23 | +} |
| 24 | +afterEach(cleanup); |
| 25 | + |
| 26 | +describe("尺的刻度:序列与文案都逐字(自己实现一个格式化会当场露馅)", () => { |
| 27 | + it("130000ms ⇒ 刻度逐序 [0,60000,120000]、文案逐序 [00:00,01:00,02:00];单测 `ticksOf`", () => { |
| 28 | + const { ticks } = mount(130_000); |
| 29 | + expect(ticks.map((el) => Number(el.dataset.tickMs)), "刻度序列漂了(或漏了末格前的一格)").toEqual([0, 60_000, 120_000]); |
| 30 | + expect(ticks.map((el) => el.textContent), "刻度文案不是 `fmtMs` 的输出").toEqual(["00:00", "01:00", "02:00"]); |
| 31 | + expect(ticksOf(130_000)).toEqual([0, 60_000, 120_000]); |
| 32 | + expect(ticksOf(0)).toEqual([0]); |
| 33 | + // 反例守卫:过一小时的会话出 h:mm:ss(`Math.floor(ms/1000)+"s"` 给不出它) |
| 34 | + cleanup(); |
| 35 | + const hour = mount(3_725_000); |
| 36 | + expect(hour.ticks.at(-1)?.textContent).toBe("1:02:00"); |
| 37 | + }); |
| 38 | +}); |
| 39 | + |
| 40 | +describe("轨的宽度 = 总长的像素换算(不是常量、也不是视口宽)", () => { |
| 41 | + it("120000ms ⇒ 120px;换 30000ms ⇒ 30px(常数实现当场红),且刻度都在轨子树里", () => { |
| 42 | + const { container } = mount(120_000); |
| 43 | + const track = container.querySelector(".ed-timerail__track"); |
| 44 | + if (track === null) throw new Error("没有渲染出轨道"); |
| 45 | + expect((track as HTMLElement).style.width).toBe("120px"); |
| 46 | + expect(track.querySelectorAll("[data-tick-ms]").length, "刻度不在轨道子树里(尺就没画出来)").toBe(3); |
| 47 | + expect(container.querySelector(RAIL)?.contains(container.querySelector(VIEWPORT))).toBe(true); |
| 48 | + expect([0, 52_500, 90_000, 120_000].map(pxOf)).toEqual([0, 52.5, 90, 120]); |
| 49 | + |
| 50 | + cleanup(); |
| 51 | + const short = mount(30_000); |
| 52 | + expect((short.container.querySelector(".ed-timerail__track") as HTMLElement).style.width, "轨宽是常量/写死的").toBe("30px"); |
| 53 | + expect(short.ticks.map((el) => el.textContent)).toEqual(["00:00"]); |
| 54 | + }); |
| 55 | +}); |
0 commit comments