mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-05 10:14:30 +00:00
* feat(core): add figma motion easing mapping * feat(core): translate figma motion doc to gsap timeline spec * feat(core): emit paused GSAP timeline script from figma motion spec * fix(core): restore type exports dropped from figma barrel in Task 8 * feat(skills): add /figma import skill + catalog wiring Add the agent-facing /figma skill (asset + Figma Motion import via the Figma MCP connector, built on @hyperframes/core/figma) and wire it into the skill catalog across CLAUDE.md, README.md, docs/guides/skills.mdx, and the hyperframes router's capability map. Bumps the skill count from 19 to 20 in CLAUDE.md and README.md. * fix(core): use replaceAll for figma node-id dash-to-colon conversion * style: format skills catalog tables oxfmt-align the README and router SKILL.md tables after the /figma + /hyperframes-keyframes merge left uneven column padding. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(cli): add missing cache fields to telemetry test fixture ExtractionPhaseBreakdown gained cachePublishFailures/cacheGcEvictions/ cacheGcBytesFreed/cacheAgedPartialsCleared; the studioRenderTelemetry test fixture was never updated, breaking Typecheck on main and every PR based on it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
49 lines
1.7 KiB
TypeScript
49 lines
1.7 KiB
TypeScript
// @vitest-environment node
|
|
import { describe, expect, it } from "vitest";
|
|
import { emitTimelineScript } from "./emitTimelineScript";
|
|
import { motionToGsap } from "./motionToGsap";
|
|
import type { MotionDoc } from "./types";
|
|
|
|
const doc: MotionDoc = {
|
|
selector: "#hero-headline",
|
|
tracks: [
|
|
{
|
|
property: "opacity",
|
|
values: [0, 1, 0],
|
|
times: [0, 0.5, 1],
|
|
ease: ["linear", [0.539, 0, 0.312, 0.995]],
|
|
duration: 2,
|
|
repeat: Infinity,
|
|
},
|
|
],
|
|
};
|
|
|
|
describe("emitTimelineScript", () => {
|
|
const script = emitTimelineScript(motionToGsap(doc));
|
|
|
|
it("creates a paused timeline and never emits repeat:-1", () => {
|
|
expect(script).toContain("gsap.timeline({ paused: true })");
|
|
expect(script).not.toContain("repeat: -1");
|
|
});
|
|
it("registers under a string-literal __timelines key", () => {
|
|
expect(script).toContain('window.__timelines["figma-hero-headline"] = tl;');
|
|
});
|
|
it("uses string-literal selectors and sets the initial value", () => {
|
|
expect(script).toContain('tl.set("#hero-headline", { opacity: 0 }, 0);');
|
|
expect(script).toContain('tl.to("#hero-headline", { keyframes: [');
|
|
});
|
|
it("registers a CustomEase for the bezier segment", () => {
|
|
expect(script).toContain('CustomEase.create("hfCe0", "M0,0 C0.539,0 0.312,0.995 1,1");');
|
|
});
|
|
});
|
|
|
|
describe("emitTimelineScript runtime guard", () => {
|
|
it("wraps the script in an IIFE that warns when gsap/CustomEase are missing", () => {
|
|
const script = emitTimelineScript(motionToGsap(doc));
|
|
expect(script).toContain('typeof gsap === "undefined"');
|
|
expect(script).toContain("console.warn");
|
|
expect(script.startsWith("(function () {")).toBe(true);
|
|
expect(script.endsWith("})();")).toBe(true);
|
|
});
|
|
});
|