mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 06:30:03 +00:00
feat(studio): timeline leaf helpers — audio inspector, zoom math, UI prefs (#2192)
What: extends three leaf modules to their final NLE-stack form, tests in the same change: timelineInspector (isAudioTimelineElement, resolveBeatSourceTrack), timelineZoom (zoom/pps math incl. computePinnedZoomPercent), and studioUiPreferences (persisted editor prefs). Why: leaf dependencies of the NLE timeline stack; landing them first keeps the later glue PRs to wiring. How: additive from the consumer side — every export main already uses is unchanged (typecheck against main's consumers passes untouched); every new export is exercised by a test in this PR. Test plan: bunx vitest run on the three test files; tsc --noEmit in packages/studio; fallow audit --base origin/main clean.
This commit is contained in:
@@ -1,12 +1,15 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
clampTimelineZoomPercent,
|
||||
computePinnedZoomPercent,
|
||||
getNextTimelineZoomPercent,
|
||||
getPinchTimelineZoomPercent,
|
||||
getTimelinePixelsPerSecond,
|
||||
getTimelineZoomPercent,
|
||||
MAX_TIMELINE_ZOOM_PERCENT,
|
||||
MIN_TIMELINE_ZOOM_PERCENT,
|
||||
timelineZoomPercentToSlider,
|
||||
timelineSliderToZoomPercent,
|
||||
} from "./timelineZoom";
|
||||
|
||||
describe("clampTimelineZoomPercent", () => {
|
||||
@@ -81,3 +84,67 @@ describe("getPinchTimelineZoomPercent", () => {
|
||||
expect(getPinchTimelineZoomPercent(-10000, "manual", 100)).toBe(MAX_TIMELINE_ZOOM_PERCENT);
|
||||
});
|
||||
});
|
||||
|
||||
describe("timelineZoomPercentToSlider", () => {
|
||||
it("maps min zoom to slider position 0", () => {
|
||||
expect(timelineZoomPercentToSlider(MIN_TIMELINE_ZOOM_PERCENT)).toBeCloseTo(0, 5);
|
||||
});
|
||||
|
||||
it("maps max zoom to slider position 100", () => {
|
||||
expect(timelineZoomPercentToSlider(MAX_TIMELINE_ZOOM_PERCENT)).toBeCloseTo(100, 5);
|
||||
});
|
||||
|
||||
it("maps 100% to the log midpoint between 10 and 2000", () => {
|
||||
const expected = ((Math.log(100) - Math.log(10)) / (Math.log(2000) - Math.log(10))) * 100;
|
||||
expect(timelineZoomPercentToSlider(100)).toBeCloseTo(expected, 3);
|
||||
});
|
||||
});
|
||||
|
||||
describe("timelineSliderToZoomPercent", () => {
|
||||
it("maps slider 0 to min zoom", () => {
|
||||
expect(timelineSliderToZoomPercent(0)).toBe(MIN_TIMELINE_ZOOM_PERCENT);
|
||||
});
|
||||
|
||||
it("maps slider 100 to max zoom", () => {
|
||||
expect(timelineSliderToZoomPercent(100)).toBe(MAX_TIMELINE_ZOOM_PERCENT);
|
||||
});
|
||||
});
|
||||
|
||||
describe("computePinnedZoomPercent", () => {
|
||||
it("returns 100 when current pps equals the fit pps (a no-op pin at the current fit)", () => {
|
||||
expect(computePinnedZoomPercent(42, 42)).toBe(100);
|
||||
});
|
||||
|
||||
it("reproduces the current pps: percent × fitPps / 100 === currentPps", () => {
|
||||
const fitPps = 20;
|
||||
const currentPps = 50; // user zoomed in 2.5×
|
||||
const percent = computePinnedZoomPercent(currentPps, fitPps);
|
||||
expect(percent).toBe(250);
|
||||
// Round-trips through getTimelinePixelsPerSecond back to the on-screen pps.
|
||||
expect(getTimelinePixelsPerSecond(fitPps, "manual", percent)).toBeCloseTo(currentPps, 5);
|
||||
});
|
||||
|
||||
it("clamps a pin that would exceed the manual-zoom bounds", () => {
|
||||
// currentPps 10000 / fitPps 1 = 1_000_000% → clamped to MAX.
|
||||
expect(computePinnedZoomPercent(10000, 1)).toBe(MAX_TIMELINE_ZOOM_PERCENT);
|
||||
// Tiny ratio → clamped up to MIN.
|
||||
expect(computePinnedZoomPercent(0.001, 1000)).toBe(MIN_TIMELINE_ZOOM_PERCENT);
|
||||
});
|
||||
|
||||
it("falls back to 100 for unusable inputs (a safe no-op pin)", () => {
|
||||
expect(computePinnedZoomPercent(Number.NaN, 20)).toBe(100);
|
||||
expect(computePinnedZoomPercent(50, 0)).toBe(100);
|
||||
expect(computePinnedZoomPercent(-5, 20)).toBe(100);
|
||||
expect(computePinnedZoomPercent(50, Number.POSITIVE_INFINITY)).toBe(100);
|
||||
});
|
||||
});
|
||||
|
||||
describe("timelineZoomPercentToSlider / timelineSliderToZoomPercent round-trip", () => {
|
||||
for (const percent of [10, 100, 500, 2000]) {
|
||||
it(`round-trips ${percent}% within ±1%`, () => {
|
||||
const slider = timelineZoomPercentToSlider(percent);
|
||||
const back = timelineSliderToZoomPercent(slider);
|
||||
expect(Math.abs(back - percent) / percent).toBeLessThan(0.01);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user