import { describe, expect, it } from "vitest"; import { openComposition } from "@hyperframes/sdk"; import { patchElementInHtml } from "../../../core/src/studio-api/helpers/sourceMutation.js"; import type { PatchOperation } from "./sourcePatcher"; import { patchOpsToSdkEditOps } from "./sdkOpMapping"; const shell = (body: string) => ` ${body}`; async function applySdkDomCutover(source: string, ops: PatchOperation[]): Promise { const session = await openComposition(source, { history: false }); session.batch(() => { for (const op of patchOpsToSdkEditOps("hf-target", ops)) { session.dispatch(op); } }); return session.serialize(); } const cases: Array<{ name: string; source: string; ops: PatchOperation[] }> = [ { name: "coalesces multi-property inline style including transform and custom props", source: shell( '
OldChild
', ), ops: [ { type: "inline-style", property: "transform", value: "translateX(10px)" }, { type: "inline-style", property: "transform-origin", value: "50% 50%" }, { type: "inline-style", property: "--x", value: "12px" }, ], }, { name: "removes one inline style from a multi-property declaration", source: shell( '
Old
', ), ops: [{ type: "inline-style", property: "opacity", value: null }], }, { name: "preserves semicolon-bearing CSS values when updating another style", source: shell( '
Old
', ), ops: [{ type: "inline-style", property: "color", value: "blue" }], }, { name: "sets direct text content", source: shell('
Old
'), ops: [{ type: "text-content", property: "text", value: "New" }], }, { name: "sets text on the single child target used by the legacy path", source: shell(''), ops: [{ type: "text-content", property: "text", value: "New" }], }, { name: "sets text on the single child target while preserving parent text", source: shell('
Lead Old
'), ops: [{ type: "text-content", property: "text", value: "New" }], }, { name: "sets data attributes through attribute ops", source: shell('
Old
'), ops: [{ type: "attribute", property: "mode", value: "hero" }], }, { name: "removes data attributes through attribute ops", source: shell('
Old
'), ops: [{ type: "attribute", property: "mode", value: null }], }, { name: "sets allowed html attributes", source: shell('Old'), ops: [{ type: "html-attribute", property: "aria-label", value: "Primary link" }], }, { name: "sets shorthand over existing longhands (inset over top/right/bottom/left)", source: shell( '
Old
', ), ops: [{ type: "inline-style", property: "inset", value: "0" }], }, { name: "sets longhand over existing shorthand (top over inset)", source: shell('
Old
'), ops: [{ type: "inline-style", property: "top", value: "10px" }], }, { name: "mixed-type batch: inline-style + text-content in one op list", source: shell('
Old
'), ops: [ { type: "inline-style", property: "color", value: "blue" }, { type: "text-content", property: "text", value: "New" }, ], }, { name: "mixed-type batch: inline-style + attribute in one op list", source: shell('
Old
'), ops: [ { type: "inline-style", property: "opacity", value: "1" }, { type: "attribute", property: "mode", value: "hero" }, ], }, ]; describe("SDK cutover DOM serialization parity", () => { it.each(cases)("$name", async ({ source, ops }) => { const legacy = patchElementInHtml(source, { hfId: "hf-target" }, ops); expect(legacy.matched).toBe(true); await expect(applySdkDomCutover(source, ops)).resolves.toBe(legacy.html); }); });