mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-05 10:14:30 +00:00
The keyframe cache writes three key variants per element: the source-prefixed key (sourceFile#id), the index.html fallback (index.html#id), and the bare element id (id). The clear paths only dropped the prefixed variants, leaving the bare entry behind. PropertyPanel reads the bare key and gives it precedence over live data (cacheEntry?.keyframes ?? gsapKeyframes), so after an element's keyframes are removed the inspector kept rendering the deleted keyframes. Consumers that fall back to the bare id (timeline diamonds, preview overlay) saw the same stale entry. Add clearKeyframeCacheForElement and clearKeyframeCacheForFile and route the three clear sites through them so the bare key is dropped alongside the prefixed ones. Each delete is guarded by has to avoid reallocating the cache map for an absent key. Co-authored-by: Carlos Alcaraz <193642530+calcarazgre646@users.noreply.github.com>
122 lines
3.8 KiB
TypeScript
122 lines
3.8 KiB
TypeScript
import { describe, it, expect, beforeEach } from "vitest";
|
|
import type { GsapAnimation } from "@hyperframes/core/gsap-parser";
|
|
import { usePlayerStore, type KeyframeCacheEntry } from "../player/store/playerStore";
|
|
import {
|
|
clearKeyframeCacheForElement,
|
|
clearKeyframeCacheForFile,
|
|
updateKeyframeCacheFromParsed,
|
|
} from "./gsapKeyframeCacheHelpers";
|
|
|
|
const entry = (): KeyframeCacheEntry => ({
|
|
format: "percentage",
|
|
keyframes: [{ percentage: 0, properties: { x: 0 } }],
|
|
});
|
|
|
|
const seed = (key: string) => usePlayerStore.getState().setKeyframeCache(key, entry());
|
|
const cache = () => usePlayerStore.getState().keyframeCache;
|
|
|
|
const animWithKeyframes = (id: string): GsapAnimation => ({
|
|
id,
|
|
targetSelector: `#${id}`,
|
|
method: "to",
|
|
position: 0,
|
|
properties: {},
|
|
duration: 1,
|
|
resolvedStart: 0,
|
|
propertyGroup: "position",
|
|
keyframes: { format: "percentage", keyframes: [{ percentage: 50, properties: { x: 100 } }] },
|
|
});
|
|
|
|
beforeEach(() => {
|
|
usePlayerStore.setState({ keyframeCache: new Map(), elements: [] });
|
|
});
|
|
|
|
describe("clearKeyframeCacheForElement", () => {
|
|
it("drops the prefixed, index.html fallback, and bare key for a non-index source", () => {
|
|
seed("comp.html#box");
|
|
seed("index.html#box");
|
|
seed("box");
|
|
|
|
clearKeyframeCacheForElement("comp.html", "box");
|
|
|
|
expect(cache().has("comp.html#box")).toBe(false);
|
|
expect(cache().has("index.html#box")).toBe(false);
|
|
// The bare key is what PropertyPanel's keyframe nav reads (element.id), so
|
|
// it must be cleared too, not just the prefixed variants.
|
|
expect(cache().has("box")).toBe(false);
|
|
});
|
|
|
|
it("drops the prefixed and bare key for an index.html source", () => {
|
|
seed("index.html#hero");
|
|
seed("hero");
|
|
|
|
clearKeyframeCacheForElement("index.html", "hero");
|
|
|
|
expect(cache().has("index.html#hero")).toBe(false);
|
|
expect(cache().has("hero")).toBe(false);
|
|
});
|
|
|
|
it("leaves other elements' keys untouched", () => {
|
|
seed("index.html#box");
|
|
seed("box");
|
|
seed("index.html#other");
|
|
seed("other");
|
|
|
|
clearKeyframeCacheForElement("index.html", "box");
|
|
|
|
expect(cache().has("index.html#other")).toBe(true);
|
|
expect(cache().has("other")).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("clearKeyframeCacheForFile", () => {
|
|
it("clears the prefixed, fallback, and bare keys for every element of the file", () => {
|
|
seed("comp.html#a");
|
|
seed("index.html#a");
|
|
seed("a");
|
|
seed("comp.html#b");
|
|
seed("b");
|
|
|
|
clearKeyframeCacheForFile("comp.html");
|
|
|
|
for (const key of ["comp.html#a", "index.html#a", "a", "comp.html#b", "b"]) {
|
|
expect(cache().has(key)).toBe(false);
|
|
}
|
|
});
|
|
|
|
it("leaves entries that belong to a different source file", () => {
|
|
seed("comp.html#a");
|
|
seed("a");
|
|
seed("other.html#z");
|
|
seed("z");
|
|
|
|
clearKeyframeCacheForFile("comp.html");
|
|
|
|
expect(cache().has("other.html#z")).toBe(true);
|
|
expect(cache().has("z")).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("updateKeyframeCacheFromParsed", () => {
|
|
it("clears the bare key when the selected element no longer has keyframes", () => {
|
|
// Element previously had keyframes, so a bare entry exists (writes set both).
|
|
seed("index.html#box");
|
|
seed("box");
|
|
|
|
// A mutation leaves #box without any keyframes in the parsed animations.
|
|
updateKeyframeCacheFromParsed([], "index.html", "box", {});
|
|
|
|
expect(cache().has("index.html#box")).toBe(false);
|
|
// Without the bare-key clear this assertion fails: the stale entry survives
|
|
// and PropertyPanel keeps rendering the removed keyframes.
|
|
expect(cache().has("box")).toBe(false);
|
|
});
|
|
|
|
it("still writes the bare key for elements that have keyframes", () => {
|
|
updateKeyframeCacheFromParsed([animWithKeyframes("hero")], "index.html", "hero", {});
|
|
|
|
expect(cache().has("index.html#hero")).toBe(true);
|
|
expect(cache().has("hero")).toBe(true);
|
|
});
|
|
});
|