mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
feat(studio): route element delete through SDK removeElement (§3.1) (#1465)
Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com>
This commit is contained in:
co-authored by
Miguel Ángel
parent
8585fffc92
commit
bce571c2a1
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { shouldUseSdkCutover, sdkCutoverPersist } from "./sdkCutover";
|
||||
import { shouldUseSdkCutover, sdkCutoverPersist, sdkDeletePersist } from "./sdkCutover";
|
||||
import { openComposition } from "@hyperframes/sdk";
|
||||
import { createMemoryAdapter } from "@hyperframes/sdk/adapters/memory";
|
||||
import type { PatchOperation } from "./sourcePatcher";
|
||||
@@ -298,6 +298,74 @@ describe("sdkCutoverPersist", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("sdkDeletePersist", () => {
|
||||
const makeRef = <T>(val: T): MutableRefObject<T> => ({ current: val });
|
||||
const makeDeps = () => ({
|
||||
editHistory: { recordEdit: vi.fn().mockResolvedValue(undefined) },
|
||||
writeProjectFile: vi.fn().mockResolvedValue(undefined),
|
||||
reloadPreview: vi.fn(),
|
||||
domEditSaveTimestampRef: makeRef(0),
|
||||
});
|
||||
|
||||
const makeSession = (hasEl = true) =>
|
||||
({
|
||||
getElement: vi.fn().mockReturnValue(hasEl ? { id: "hf-abc" } : null),
|
||||
removeElement: vi.fn(),
|
||||
serialize: vi.fn().mockReturnValue("<html>after</html>"),
|
||||
}) as unknown as Parameters<typeof sdkDeletePersist>[3];
|
||||
|
||||
it("returns false when session is null", async () => {
|
||||
expect(await sdkDeletePersist("hf-abc", "before", "/comp.html", null, makeDeps())).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false when element not found in session", async () => {
|
||||
const session = makeSession(false);
|
||||
expect(await sdkDeletePersist("hf-abc", "before", "/comp.html", session, makeDeps())).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it("calls removeElement and writes serialized content", async () => {
|
||||
const deps = makeDeps();
|
||||
const session = makeSession(true);
|
||||
const result = await sdkDeletePersist("hf-abc", "before", "/comp.html", session, deps);
|
||||
expect(result).toBe(true);
|
||||
expect(session!.removeElement).toHaveBeenCalledWith("hf-abc");
|
||||
expect(deps.writeProjectFile).toHaveBeenCalledWith("/comp.html", "<html>after</html>");
|
||||
});
|
||||
|
||||
it("records edit history with before/after diff", async () => {
|
||||
const deps = makeDeps();
|
||||
const session = makeSession(true);
|
||||
await sdkDeletePersist("hf-abc", "before-content", "/comp.html", session, deps);
|
||||
expect(deps.editHistory.recordEdit).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
label: "Delete element",
|
||||
files: { "/comp.html": { before: "before-content", after: "<html>after</html>" } },
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("calls reloadPreview on success", async () => {
|
||||
const deps = makeDeps();
|
||||
const session = makeSession(true);
|
||||
await sdkDeletePersist("hf-abc", "before", "/comp.html", session, deps);
|
||||
expect(deps.reloadPreview).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns false and does not write on removeElement error", async () => {
|
||||
const deps = makeDeps();
|
||||
const session = makeSession(true);
|
||||
(session!.removeElement as ReturnType<typeof vi.fn>).mockImplementation(() => {
|
||||
throw new Error("remove failed");
|
||||
});
|
||||
const result = await sdkDeletePersist("hf-abc", "before", "/comp.html", session, deps);
|
||||
expect(result).toBe(false);
|
||||
expect(deps.writeProjectFile).not.toHaveBeenCalled();
|
||||
expect(deps.reloadPreview).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("sdkCutoverPersist — GSAP script preservation (integration)", () => {
|
||||
const makeRef = <T>(val: T): MutableRefObject<T> => ({ current: val });
|
||||
const makeDeps = () => ({
|
||||
|
||||
@@ -83,6 +83,26 @@ interface CutoverOptions {
|
||||
coalesceKey?: string;
|
||||
}
|
||||
|
||||
// ponytail: internal; export only if a third caller appears
|
||||
async function persistSdkSerialize(
|
||||
sdkSession: Composition,
|
||||
targetPath: string,
|
||||
originalContent: string,
|
||||
deps: CutoverDeps,
|
||||
options?: CutoverOptions,
|
||||
): Promise<void> {
|
||||
const after = sdkSession.serialize();
|
||||
deps.domEditSaveTimestampRef.current = Date.now();
|
||||
await deps.writeProjectFile(targetPath, after);
|
||||
await deps.editHistory.recordEdit({
|
||||
label: options?.label ?? "Edit layer",
|
||||
kind: "manual",
|
||||
...(options?.coalesceKey ? { coalesceKey: options.coalesceKey } : {}),
|
||||
files: { [targetPath]: { before: originalContent, after } },
|
||||
});
|
||||
deps.reloadPreview();
|
||||
}
|
||||
|
||||
export async function sdkCutoverPersist(
|
||||
selection: DomEditSelection,
|
||||
ops: PatchOperation[],
|
||||
@@ -104,16 +124,7 @@ export async function sdkCutoverPersist(
|
||||
sdkSession.dispatch(editOp);
|
||||
}
|
||||
});
|
||||
const after = sdkSession.serialize();
|
||||
deps.domEditSaveTimestampRef.current = Date.now();
|
||||
await deps.writeProjectFile(targetPath, after);
|
||||
await deps.editHistory.recordEdit({
|
||||
label: options?.label ?? "Edit layer",
|
||||
kind: "manual",
|
||||
...(options?.coalesceKey ? { coalesceKey: options.coalesceKey } : {}),
|
||||
files: { [targetPath]: { before: originalContent, after } },
|
||||
});
|
||||
deps.reloadPreview();
|
||||
await persistSdkSerialize(sdkSession, targetPath, originalContent, deps, options);
|
||||
trackStudioEvent("sdk_cutover_success", { hfId, opCount: ops.length });
|
||||
return true;
|
||||
} catch (err) {
|
||||
@@ -124,3 +135,24 @@ export async function sdkCutoverPersist(
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function sdkDeletePersist(
|
||||
hfId: string,
|
||||
originalContent: string,
|
||||
targetPath: string,
|
||||
sdkSession: Composition | null | undefined,
|
||||
deps: CutoverDeps,
|
||||
): Promise<boolean> {
|
||||
if (!sdkSession || !sdkSession.getElement(hfId)) return false;
|
||||
try {
|
||||
sdkSession.removeElement(hfId);
|
||||
await persistSdkSerialize(sdkSession, targetPath, originalContent, deps, {
|
||||
label: "Delete element",
|
||||
});
|
||||
trackStudioEvent("sdk_cutover_success", { hfId, opCount: 1 });
|
||||
return true;
|
||||
} catch (err) {
|
||||
trackStudioEvent("sdk_cutover_fallback", { hfId, error: String(err) });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user