Files
hyperframes/packages/cli/src/commands/snapshot.test.ts
T
Miguel Ángel a908af11a8 feat(cli): keyframes command (surface GSAP/CSS/Anime keyframes + 3D onion-skin --shot) (#1603)
Renames the motion-surfacing tool from `hyperframes keyframes` to `hyperframes motion`,
renames the implementation from keyframes*.ts to motion*.ts (keeping the keyframe data
model name where still accurate), and renames the shipped skill from
hyperframes-keyframes to hyperframes-motion. Expands the skill from a command
reference into a full motion-design workflow: reading motion, 3D angle verification,
layered GSAP motion, one-shot reference reproduction, diagnostic checks, and
eval-derived craft guidance.
2026-07-01 19:51:55 -07:00

62 lines
2.3 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { computeSnapshotTimes, tailFrameTime } from "./snapshot.js";
describe("tailFrameTime", () => {
it("backs off ~3% of duration so the final frame isn't the blank exact-end", () => {
// Verified on the V4 3D artifact: t=8.0 of an 8s clip rendered blank white,
// t=7.76 rendered the final hero. 8 - 8*0.03 = 7.76.
expect(tailFrameTime(8)).toBeCloseTo(7.76, 5);
});
it("uses a 50ms floor for short clips", () => {
expect(tailFrameTime(1)).toBeCloseTo(0.95, 5); // 1 - 0.05 (floor beats 3%)
});
it("never goes negative", () => {
expect(tailFrameTime(0)).toBe(0);
});
});
describe("computeSnapshotTimes (FINDING [7]: tail is always captured)", () => {
it("default frames: last point is the readable tail, never exact duration", () => {
const { times, appendedTail } = computeSnapshotTimes(8, { frames: 5 });
expect(times).toHaveLength(5);
expect(times[0]).toBe(0);
expect(times[times.length - 1]).toBeCloseTo(7.76, 5);
expect(times[times.length - 1]).toBeLessThan(8); // not the blank exact-end
expect(appendedTail).toBe(false);
});
it("single frame samples the midpoint", () => {
expect(computeSnapshotTimes(8, { frames: 1 }).times).toEqual([4]);
});
it("explicit --at: keeps the user's times AND appends an end-of-timeline frame", () => {
const { times, appendedTail } = computeSnapshotTimes(8, { frames: 5, at: [1, 2, 3] });
expect(times.slice(0, 3)).toEqual([1, 2, 3]);
expect(times[times.length - 1]).toBeCloseTo(7.76, 5);
expect(appendedTail).toBe(true);
});
it("explicit --at: does not double-add when the user already sampled the tail", () => {
const { times, appendedTail } = computeSnapshotTimes(8, { frames: 5, at: [1, 7.76] });
expect(times).toEqual([1, 7.76]);
expect(appendedTail).toBe(false);
});
it("explicit --at: a sample at exact duration counts as the tail (no append)", () => {
const { appendedTail } = computeSnapshotTimes(8, { frames: 5, at: [1, 8] });
expect(appendedTail).toBe(false);
});
it("respects includeEnd:false opt-out for --at", () => {
const { times, appendedTail } = computeSnapshotTimes(8, {
frames: 5,
at: [1, 2],
includeEnd: false,
});
expect(times).toEqual([1, 2]);
expect(appendedTail).toBe(false);
});
});