Files
hyperframes/packages/core/src/figma/bindings.test.ts
T
Vance IngallsandClaude Fable 5 fb13797d2f feat(core,cli): figma REST client, asset import command, binding index (M0+M1) (#1870)
M0: renderNode/imageFills/variables/styles/nodeTree/fileVersion over
api.figma.com with injectable fetch and typed capability errors
(NO_TOKEN/BAD_TOKEN/REQUIRES_ENTERPRISE/RATE_LIMITED/RENDER_FAILED/
NODE_NOT_FOUND/HTTP_ERROR) per design spec 4.4.

M1: svg sanitizer (scripts/foreignObject/handlers/external hrefs) +
hyperframes figma asset: render -> sanitize -> freeze under .media/ ->
manifest provenance -> snippet. Idempotent on
fileKey:nodeId:format:scale:version; re-imports when the version moves.

Plus the 7.1 binding index store (.media/figma-bindings.jsonl): exact-ID
lookup incl. alias chains, per-project library-file answers, shared
jsonl reader with the asset manifest.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 18:11:33 -07:00

83 lines
2.9 KiB
TypeScript

// @vitest-environment node
import { describe, expect, it, afterEach, beforeEach } from "vitest";
import { appendFileSync, mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import {
appendBinding,
upsertBindings,
findBindingByFigmaId,
readBindings,
readLibraryMap,
recordLibraryFile,
type FigmaBindingRecord,
} from "./bindings";
let dir = "";
beforeEach(() => {
dir = mkdtempSync(join(tmpdir(), "hf-bindings-"));
});
afterEach(() => rmSync(dir, { recursive: true, force: true }));
const REC: FigmaBindingRecord = {
kind: "binding",
figmaId: "VariableID:1:23",
key: "abc123",
sourceFileKey: "FILE",
compositionVariableId: "figma:Blue/500",
version: "7",
};
describe("bindings index", () => {
it("round-trips binding records through .media/figma-bindings.jsonl", () => {
expect(readBindings(dir)).toEqual([]);
appendBinding(dir, REC);
appendBinding(dir, {
...REC,
figmaId: "VariableID:1:24",
compositionVariableId: "figma:Red/500",
});
const all = readBindings(dir);
expect(all).toHaveLength(2);
expect(all[0]?.compositionVariableId).toBe("figma:Blue/500");
});
it("findBindingByFigmaId matches exact ids only — never values or names", () => {
appendBinding(dir, REC);
expect(findBindingByFigmaId(dir, "VariableID:1:23")?.key).toBe("abc123");
expect(findBindingByFigmaId(dir, "VariableID:1:99")).toBeNull();
expect(findBindingByFigmaId(dir, "Blue/500")).toBeNull();
});
it("matches alias-chain members too (semantic id bound, primitive in chain)", () => {
appendBinding(dir, { ...REC, aliasChain: ["VariableID:9:1", "VariableID:1:23"] });
expect(findBindingByFigmaId(dir, "VariableID:9:1")?.compositionVariableId).toBe(
"figma:Blue/500",
);
});
it("persists answered library-file mappings (asked once per project)", () => {
expect(readLibraryMap(dir)).toEqual({});
recordLibraryFile(dir, "libkey-1", "LIBFILE");
expect(readLibraryMap(dir)).toEqual({ "libkey-1": "LIBFILE" });
});
it("skips malformed lines instead of crashing", () => {
appendBinding(dir, REC);
appendFileSync(join(dir, ".media", "figma-bindings.jsonl"), "not json\n");
expect(readBindings(dir)).toHaveLength(1);
});
it("upsert replaces stale rows for re-imported figmaIds, keeps others + library rows", () => {
appendBinding(dir, REC);
appendBinding(dir, { ...REC, figmaId: "VariableID:2:2", compositionVariableId: "figma:Red" });
recordLibraryFile(dir, "libkey-2", "LIB2");
upsertBindings(dir, [{ ...REC, compositionVariableId: "figma:Blue/500-v2", version: "9" }]);
const all = readBindings(dir);
expect(all).toHaveLength(2);
expect(findBindingByFigmaId(dir, REC.figmaId)?.compositionVariableId).toBe("figma:Blue/500-v2");
expect(findBindingByFigmaId(dir, "VariableID:2:2")?.compositionVariableId).toBe("figma:Red");
expect(readLibraryMap(dir)["libkey-2"]).toBe("LIB2");
});
});