Skip to content

Commit 19f46cd

Browse files
authored
test(studio): add T5a pathOffset+boxSize build-patches characterization (heygen-com#1257)
## Summary T5 (part 1 of 3) — characterization test suite for `manualEditsDomPatches.ts`. The source module exports 8 functions (4 build/clear pairs) that write and restore draft-marker attributes and inline styles onto iframe elements before source-patch operations. It had zero tests. This PR covers the **pathOffset** and **boxSize** pairs with 4 patterns each: - **populated** — fully-configured element produces exact expected `PatchOperation[]` in declaration order - **empty** — bare element yields only the mandatory marker attribute op - **clear restores originals** — `buildClear*` reads `STUDIO_ORIGINAL_*` attrs and produces correct restore values - **build/clear symmetry** — every `{type, property}` key that `build*` can emit is also addressed by `buildClear*`; an orphan here means a property stranded in committed source HTML Uses `@vitest-environment happy-dom` matching the Studio package convention. Element setup via `document.createElement` + `style.setProperty` / `setAttribute`. ## Stack - **heygen-com#1257** (this PR) — pathOffset + boxSize pairs - **heygen-com#1258** — rotation + motion pairs - **heygen-com#1259** — review-fix gaps (ordered clear assertion, coercion paths, edge cases)
1 parent fc01717 commit 19f46cd

1 file changed

Lines changed: 212 additions & 0 deletions

File tree

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
// @vitest-environment happy-dom
2+
3+
import { describe, it, expect } from "vitest";
4+
import type { PatchOperation } from "../../utils/sourcePatcher";
5+
import {
6+
STUDIO_OFFSET_X_PROP,
7+
STUDIO_OFFSET_Y_PROP,
8+
STUDIO_WIDTH_PROP,
9+
STUDIO_HEIGHT_PROP,
10+
STUDIO_PATH_OFFSET_ATTR,
11+
STUDIO_BOX_SIZE_ATTR,
12+
STUDIO_ORIGINAL_TRANSLATE_ATTR,
13+
STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR,
14+
STUDIO_ORIGINAL_WIDTH_ATTR,
15+
STUDIO_ORIGINAL_HEIGHT_ATTR,
16+
STUDIO_ORIGINAL_MIN_WIDTH_ATTR,
17+
STUDIO_ORIGINAL_MIN_HEIGHT_ATTR,
18+
STUDIO_ORIGINAL_MAX_WIDTH_ATTR,
19+
STUDIO_ORIGINAL_MAX_HEIGHT_ATTR,
20+
STUDIO_ORIGINAL_FLEX_BASIS_ATTR,
21+
STUDIO_ORIGINAL_FLEX_GROW_ATTR,
22+
STUDIO_ORIGINAL_FLEX_SHRINK_ATTR,
23+
STUDIO_ORIGINAL_BOX_SIZING_ATTR,
24+
STUDIO_ORIGINAL_SCALE_ATTR,
25+
STUDIO_ORIGINAL_TRANSFORM_ORIGIN_ATTR,
26+
STUDIO_ORIGINAL_DISPLAY_ATTR,
27+
STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR,
28+
} from "./manualEditsTypes";
29+
import {
30+
buildPathOffsetPatches,
31+
buildClearPathOffsetPatches,
32+
buildBoxSizePatches,
33+
buildClearBoxSizePatches,
34+
} from "./manualEditsDomPatches";
35+
36+
/* ── helpers ── */
37+
38+
function div(): HTMLElement {
39+
return document.createElement("div");
40+
}
41+
42+
function opKey(op: PatchOperation): string {
43+
return `${op.type}:${op.property}`;
44+
}
45+
46+
function assertClearCoversKeys(buildOps: PatchOperation[], clearOps: PatchOperation[]): void {
47+
const clearKeys = new Set(clearOps.map(opKey));
48+
for (const op of buildOps) {
49+
expect(clearKeys.has(opKey(op)), `clear missing key "${opKey(op)}"`).toBe(true);
50+
}
51+
}
52+
53+
/* ── Path offset ─────────────────────────────────────────────────────────── */
54+
55+
describe("buildPathOffsetPatches / buildClearPathOffsetPatches", () => {
56+
function populatedPathEl(): HTMLElement {
57+
const e = div();
58+
e.style.setProperty(STUDIO_OFFSET_X_PROP, "10px");
59+
e.style.setProperty(STUDIO_OFFSET_Y_PROP, "20px");
60+
e.style.setProperty("translate", "10px 20px");
61+
e.setAttribute(STUDIO_ORIGINAL_TRANSLATE_ATTR, "5px 10px");
62+
e.setAttribute(STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR, "3px");
63+
e.style.setProperty("display", "flex");
64+
e.setAttribute(STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, "block");
65+
return e;
66+
}
67+
68+
it("populated: captures offset styles, attrs, display, and transform-display marker in declaration order", () => {
69+
const ops = buildPathOffsetPatches(populatedPathEl());
70+
expect(ops).toEqual([
71+
{ type: "inline-style", property: STUDIO_OFFSET_X_PROP, value: "10px" },
72+
{ type: "inline-style", property: STUDIO_OFFSET_Y_PROP, value: "20px" },
73+
{ type: "inline-style", property: "translate", value: "10px 20px" },
74+
{ type: "attribute", property: STUDIO_PATH_OFFSET_ATTR, value: "true" },
75+
{ type: "attribute", property: STUDIO_ORIGINAL_TRANSLATE_ATTR, value: "5px 10px" },
76+
{ type: "attribute", property: STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR, value: "3px" },
77+
{ type: "inline-style", property: "display", value: "flex" },
78+
{ type: "attribute", property: STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, value: "block" },
79+
]);
80+
});
81+
82+
it("empty: bare element yields only the path-offset marker", () => {
83+
expect(buildPathOffsetPatches(div())).toEqual([
84+
{ type: "attribute", property: STUDIO_PATH_OFFSET_ATTR, value: "true" },
85+
]);
86+
});
87+
88+
it("clear: restores translate from STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR and display from STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR", () => {
89+
const e = div();
90+
e.setAttribute(STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR, "5px");
91+
e.setAttribute(STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, "grid");
92+
const ops = buildClearPathOffsetPatches(e);
93+
expect(ops).toEqual([
94+
{ type: "inline-style", property: STUDIO_OFFSET_X_PROP, value: null },
95+
{ type: "inline-style", property: STUDIO_OFFSET_Y_PROP, value: null },
96+
{ type: "inline-style", property: "translate", value: "5px" },
97+
{ type: "attribute", property: STUDIO_PATH_OFFSET_ATTR, value: null },
98+
{ type: "attribute", property: STUDIO_ORIGINAL_TRANSLATE_ATTR, value: null },
99+
{ type: "attribute", property: STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR, value: null },
100+
{ type: "inline-style", property: "display", value: "grid" },
101+
{ type: "attribute", property: STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, value: null },
102+
]);
103+
});
104+
105+
it("build/clear symmetry: clear addresses every {type,property} key that build emits", () => {
106+
const e = populatedPathEl();
107+
assertClearCoversKeys(buildPathOffsetPatches(e), buildClearPathOffsetPatches(e));
108+
});
109+
});
110+
111+
/* ── Box size ────────────────────────────────────────────────────────────── */
112+
113+
describe("buildBoxSizePatches / buildClearBoxSizePatches", () => {
114+
function populatedBoxEl(): HTMLElement {
115+
const e = div();
116+
e.style.setProperty(STUDIO_WIDTH_PROP, "300px");
117+
e.style.setProperty(STUDIO_HEIGHT_PROP, "200px");
118+
e.style.setProperty("width", "300px");
119+
e.style.setProperty("height", "200px");
120+
e.style.setProperty("min-width", "100px");
121+
e.style.setProperty("min-height", "50px");
122+
e.style.setProperty("max-width", "500px");
123+
e.style.setProperty("max-height", "400px");
124+
e.style.setProperty("flex-basis", "auto");
125+
e.style.setProperty("flex-grow", "1");
126+
e.style.setProperty("flex-shrink", "0");
127+
e.style.setProperty("box-sizing", "border-box");
128+
e.style.setProperty("scale", "1.5");
129+
e.style.setProperty("transform-origin", "center");
130+
e.style.setProperty("display", "block");
131+
e.setAttribute(STUDIO_ORIGINAL_WIDTH_ATTR, "250px");
132+
e.setAttribute(STUDIO_ORIGINAL_HEIGHT_ATTR, "150px");
133+
e.setAttribute(STUDIO_ORIGINAL_MIN_WIDTH_ATTR, "0px");
134+
e.setAttribute(STUDIO_ORIGINAL_MIN_HEIGHT_ATTR, "0px");
135+
e.setAttribute(STUDIO_ORIGINAL_MAX_WIDTH_ATTR, "none");
136+
e.setAttribute(STUDIO_ORIGINAL_MAX_HEIGHT_ATTR, "none");
137+
e.setAttribute(STUDIO_ORIGINAL_FLEX_BASIS_ATTR, "0px");
138+
e.setAttribute(STUDIO_ORIGINAL_FLEX_GROW_ATTR, "0");
139+
e.setAttribute(STUDIO_ORIGINAL_FLEX_SHRINK_ATTR, "1");
140+
e.setAttribute(STUDIO_ORIGINAL_BOX_SIZING_ATTR, "content-box");
141+
e.setAttribute(STUDIO_ORIGINAL_SCALE_ATTR, "1");
142+
e.setAttribute(STUDIO_ORIGINAL_TRANSFORM_ORIGIN_ATTR, "50% 50%");
143+
e.setAttribute(STUDIO_ORIGINAL_DISPLAY_ATTR, "flex");
144+
e.setAttribute(STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, "");
145+
return e;
146+
}
147+
148+
it("populated: captures studio-width/height, all BOX_SIZE_STYLE_PROPS, marker, and all orig attrs", () => {
149+
const ops = buildBoxSizePatches(populatedBoxEl());
150+
expect(ops).toEqual([
151+
{ type: "inline-style", property: STUDIO_WIDTH_PROP, value: "300px" },
152+
{ type: "inline-style", property: STUDIO_HEIGHT_PROP, value: "200px" },
153+
{ type: "inline-style", property: "width", value: "300px" },
154+
{ type: "inline-style", property: "height", value: "200px" },
155+
{ type: "inline-style", property: "min-width", value: "100px" },
156+
{ type: "inline-style", property: "min-height", value: "50px" },
157+
{ type: "inline-style", property: "max-width", value: "500px" },
158+
{ type: "inline-style", property: "max-height", value: "400px" },
159+
{ type: "inline-style", property: "flex-basis", value: "auto" },
160+
{ type: "inline-style", property: "flex-grow", value: "1" },
161+
{ type: "inline-style", property: "flex-shrink", value: "0" },
162+
{ type: "inline-style", property: "box-sizing", value: "border-box" },
163+
{ type: "inline-style", property: "scale", value: "1.5" },
164+
{ type: "inline-style", property: "transform-origin", value: "center" },
165+
{ type: "inline-style", property: "display", value: "block" },
166+
{ type: "attribute", property: STUDIO_BOX_SIZE_ATTR, value: "true" },
167+
{ type: "attribute", property: STUDIO_ORIGINAL_WIDTH_ATTR, value: "250px" },
168+
{ type: "attribute", property: STUDIO_ORIGINAL_HEIGHT_ATTR, value: "150px" },
169+
{ type: "attribute", property: STUDIO_ORIGINAL_MIN_WIDTH_ATTR, value: "0px" },
170+
{ type: "attribute", property: STUDIO_ORIGINAL_MIN_HEIGHT_ATTR, value: "0px" },
171+
{ type: "attribute", property: STUDIO_ORIGINAL_MAX_WIDTH_ATTR, value: "none" },
172+
{ type: "attribute", property: STUDIO_ORIGINAL_MAX_HEIGHT_ATTR, value: "none" },
173+
{ type: "attribute", property: STUDIO_ORIGINAL_FLEX_BASIS_ATTR, value: "0px" },
174+
{ type: "attribute", property: STUDIO_ORIGINAL_FLEX_GROW_ATTR, value: "0" },
175+
{ type: "attribute", property: STUDIO_ORIGINAL_FLEX_SHRINK_ATTR, value: "1" },
176+
{ type: "attribute", property: STUDIO_ORIGINAL_BOX_SIZING_ATTR, value: "content-box" },
177+
{ type: "attribute", property: STUDIO_ORIGINAL_SCALE_ATTR, value: "1" },
178+
{ type: "attribute", property: STUDIO_ORIGINAL_TRANSFORM_ORIGIN_ATTR, value: "50% 50%" },
179+
{ type: "attribute", property: STUDIO_ORIGINAL_DISPLAY_ATTR, value: "flex" },
180+
{ type: "attribute", property: STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, value: "" },
181+
]);
182+
});
183+
184+
it("empty: bare element yields only the box-size marker", () => {
185+
expect(buildBoxSizePatches(div())).toEqual([
186+
{ type: "attribute", property: STUDIO_BOX_SIZE_ATTR, value: "true" },
187+
]);
188+
});
189+
190+
it("clear: restores width and height from orig attrs, nulls all orig attrs", () => {
191+
const e = div();
192+
e.setAttribute(STUDIO_ORIGINAL_WIDTH_ATTR, "200px");
193+
e.setAttribute(STUDIO_ORIGINAL_HEIGHT_ATTR, "100px");
194+
const ops = buildClearBoxSizePatches(e);
195+
expect(ops).toEqual(
196+
expect.arrayContaining([
197+
{ type: "inline-style", property: STUDIO_WIDTH_PROP, value: null },
198+
{ type: "inline-style", property: STUDIO_HEIGHT_PROP, value: null },
199+
{ type: "attribute", property: STUDIO_BOX_SIZE_ATTR, value: null },
200+
{ type: "inline-style", property: "width", value: "200px" },
201+
{ type: "attribute", property: STUDIO_ORIGINAL_WIDTH_ATTR, value: null },
202+
{ type: "inline-style", property: "height", value: "100px" },
203+
{ type: "attribute", property: STUDIO_ORIGINAL_HEIGHT_ATTR, value: null },
204+
]),
205+
);
206+
});
207+
208+
it("build/clear symmetry: clear addresses every {type,property} key that build emits", () => {
209+
const e = populatedBoxEl();
210+
assertClearCoversKeys(buildBoxSizePatches(e), buildClearBoxSizePatches(e));
211+
});
212+
});

0 commit comments

Comments
 (0)