/** * getVariableUsage (declaration ↔ script-scan cross-reference) and * setPreviewVariables (preview adapter delegation). */ import { describe, it, expect } from "vitest"; import { openComposition } from "./session.js"; import type { PreviewAdapter } from "./adapters/types.js"; const DECLS = JSON.stringify([ { id: "title", type: "string", label: "Title", default: "Hello" }, { id: "accent", type: "color", label: "Accent", default: "#00C3FF" }, { id: "orphan", type: "string", label: "Never read", default: "x" }, ]); function doc(script: string, decls: string | null = DECLS): string { const attr = decls ? ` data-composition-variables='${decls}'` : ""; return `

Hello

`; } describe("getVariableUsage", () => { it("cross-references used, unused, and undeclared ids", async () => { const comp = await openComposition( doc(` const { title, ghost } = __hyperframes.getVariables(); document.querySelector("h1").textContent = title; const vars = __hyperframes.getVariables(); el.style.color = vars.accent; `), ); const usage = comp.getVariableUsage(); expect(usage.usedIds).toEqual(["title", "ghost", "accent"]); expect(usage.unusedDeclarations).toEqual(["orphan"]); expect(usage.undeclaredReads).toEqual(["ghost"]); expect(usage.scanIncomplete).toBe(false); }); it("reports all declarations unused when no script reads variables", async () => { const comp = await openComposition(doc(`gsap.timeline({ paused: true });`)); const usage = comp.getVariableUsage(); expect(usage.usedIds).toEqual([]); expect(usage.unusedDeclarations).toEqual(["title", "accent", "orphan"]); expect(usage.scanIncomplete).toBe(false); }); it("propagates scanIncomplete from opaque access", async () => { const comp = await openComposition( doc(`const vars = getVariables(); const v = vars[pickKey()];`), ); expect(comp.getVariableUsage().scanIncomplete).toBe(true); }); it("counts a variable used only via var(--id) in a `, ), ); const usage = comp.getVariableUsage(); // accent is CSS-consumed, so it must NOT be badged unused. expect(usage.unusedDeclarations).toEqual(["title", "orphan"]); }); it("does not count var(--id) as usage of a prefix-extended custom property", async () => { // `accent` must not be marked used by an unrelated `var(--accent-shadow)`. const comp = await openComposition( doc(`gsap.timeline({ paused: true });`).replace( "", ``, ), ); const usage = comp.getVariableUsage(); expect(usage.unusedDeclarations).toContain("accent"); }); it("counts declarative data-var-src / data-var-text bindings as usage", async () => { const comp = await openComposition(`

t

`); const usage = comp.getVariableUsage(); expect(usage.usedIds).toEqual(expect.arrayContaining(["accent", "title"])); expect(usage.unusedDeclarations).toEqual(["orphan"]); expect(usage.scanIncomplete).toBe(false); }); it("handles compositions with no declarations and no scripts", async () => { const comp = await openComposition( `

x

`, ); expect(comp.getVariableUsage()).toEqual({ usedIds: [], unusedDeclarations: [], undeclaredReads: [], scanIncomplete: false, }); }); }); describe("setPreviewVariables", () => { function makeAdapter(calls: Array | null>): PreviewAdapter { return { elementAtPoint: () => null, applyDraft: () => {}, commitPreview: () => {}, cancelPreview: () => {}, select: () => {}, on: () => () => {}, setPreviewVariables: (values) => calls.push(values), }; } it("delegates to the preview adapter and reports handling", async () => { const calls: Array | null> = []; const comp = await openComposition(doc(""), { preview: makeAdapter(calls) }); expect(comp.setPreviewVariables({ title: "Custom" })).toBe(true); expect(comp.setPreviewVariables(null)).toBe(true); expect(calls).toEqual([{ title: "Custom" }, null]); }); it("returns false without an adapter or without adapter support", async () => { const noAdapter = await openComposition(doc("")); expect(noAdapter.setPreviewVariables({ a: 1 })).toBe(false); const bare = makeAdapter([]); delete bare.setPreviewVariables; const unsupported = await openComposition(doc(""), { preview: bare }); expect(unsupported.setPreviewVariables({ a: 1 })).toBe(false); }); });