fix(sdk): preserve unquoted escapes in inline styles (#5246)

This commit is contained in:
Abdullah M. Nazif
2026-10-08 21:30:16 +00:00
committed by GitHub
parent 1449d0d2a1
commit 2df897aa43
3 changed files with 74 additions and 4 deletions
+4 -4
View File
@@ -236,11 +236,11 @@ interface StyleDeclarationScan {
}
function advanceStyleDeclarationScan(scan: StyleDeclarationScan, ch: string, next: string): void {
if (ch === "\\" && next) {
scan.skip = true;
return;
}
if (scan.quote) {
if (ch === "\\" && next) {
scan.skip = true;
return;
}
if (ch === scan.quote) scan.quote = null;
return;
}
+46
View File
@@ -914,6 +914,52 @@ describe("setElementStyles key normalization", () => {
});
});
describe("setElementStyles unquoted CSS escapes", () => {
it.each([
[
"semicolon",
String.raw`--label: alpha\;beta; width: 120px; color: red`,
String.raw`alpha\;beta`,
],
[
"opening parenthesis",
String.raw`--label: alpha\(beta; width: 120px; color: red`,
String.raw`alpha\(beta`,
],
[
"double quote",
String.raw`--label: alpha\"beta; width: 120px; color: red`,
String.raw`alpha\"beta`,
],
[
"single quote",
String.raw`--label: alpha\'beta; width: 120px; color: red`,
String.raw`alpha\'beta`,
],
])("preserves an escaped %s and the following declarations", (_name, style, label) => {
const parsed = parseMutable('<div data-hf-id="hf-x" data-hf-root></div>');
const el = parsed.document.querySelector('[data-hf-id="hf-x"]');
if (!el) throw new Error("missing fixture element");
el.setAttribute("style", style);
expect(getElementStyles(el)).toMatchObject({ "--label": label, width: "120px", color: "red" });
setElementStyles(el, { color: "blue" });
expect(getElementStyles(el)).toMatchObject({ "--label": label, width: "120px", color: "blue" });
});
it("treats the semicolon after an escaped backslash as a declaration boundary", () => {
const parsed = parseMutable('<div data-hf-id="hf-x" data-hf-root></div>');
const el = parsed.document.querySelector('[data-hf-id="hf-x"]');
if (!el) throw new Error("missing fixture element");
el.setAttribute("style", String.raw`--label: alpha\\; width: 120px; color: red`);
setElementStyles(el, { color: "blue" });
expect(getElementStyles(el)).toMatchObject({
"--label": String.raw`alpha\\`,
width: "120px",
color: "blue",
});
});
});
// ─── setVariableValue ─────────────────────────────────────────────────────────
describe("setVariableValue", () => {
@@ -185,3 +185,27 @@ describe("serialize() render-faithfulness (WS-F)", () => {
expect(comp2.getElement("hf-body")?.text).toContain("Round-tripped body");
});
});
describe("serialize() preserves unquoted CSS escapes", () => {
it.each([
String.raw`alpha\;beta`,
String.raw`alpha\(beta`,
String.raw`alpha\"beta`,
String.raw`alpha\'beta`,
])("keeps %s through edit, undo, redo, and saved overrides", async (label) => {
const style = `--label: ${label}; width: 120px; color: red`.replace(/"/g, "&quot;");
const source = `<div data-hf-id="hf-box" data-hf-root style="${style}"></div>`;
const comp = await openComposition(source);
const expected = { "--label": label, width: "120px", color: "blue" };
comp.setStyle("hf-box", { color: "blue" });
expect(comp.getElement("hf-box")?.inlineStyles).toMatchObject(expected);
comp.undo();
expect(comp.getElement("hf-box")?.inlineStyles).toMatchObject({ ...expected, color: "red" });
comp.redo();
expect(comp.getElement("hf-box")?.inlineStyles).toMatchObject(expected);
const reopened = await openComposition(comp.serialize());
expect(reopened.getElement("hf-box")?.inlineStyles).toMatchObject(expected);
const overrides = await openComposition(source, { overrides: comp.getOverrides() });
expect(overrides.getElement("hf-box")?.inlineStyles).toMatchObject(expected);
});
});