diff --git a/packages/backend/src/altNodes/jsonNodeConversion.test.ts b/packages/backend/src/altNodes/jsonNodeConversion.test.ts new file mode 100644 index 00000000..1b8b489f --- /dev/null +++ b/packages/backend/src/altNodes/jsonNodeConversion.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from "vitest"; +import { hugSizingIsMeaningless } from "./jsonNodeConversion"; + +describe("hugSizingIsMeaningless", () => { + it("is false for a TEXT node with no children — it hugs its own characters", () => { + expect(hugSizingIsMeaningless({ type: "TEXT" })).toBe(false); + expect(hugSizingIsMeaningless({ type: "TEXT", children: [] })).toBe(false); + }); + + it("is true for an empty non-text frame — nothing to hug around", () => { + expect(hugSizingIsMeaningless({ type: "FRAME" })).toBe(true); + expect(hugSizingIsMeaningless({ type: "FRAME", children: [] })).toBe(true); + }); + + it("is false for a non-text frame with children — hugs its child content", () => { + expect( + hugSizingIsMeaningless({ type: "FRAME", children: [{}] }) + ).toBe(false); + }); +}); diff --git a/packages/backend/src/altNodes/jsonNodeConversion.ts b/packages/backend/src/altNodes/jsonNodeConversion.ts index d9b23f32..c96bdd6d 100644 --- a/packages/backend/src/altNodes/jsonNodeConversion.ts +++ b/packages/backend/src/altNodes/jsonNodeConversion.ts @@ -255,6 +255,24 @@ function adjustChildrenOrder(node: any) { node.children = [...absoluteChildren, ...fixedChildren]; } +/** + * True when a node's HUG sizing has nothing to hug — an empty frame, where + * HUG-with-no-content is meaningless and falling back to FIXED is correct. + * + * A TEXT node never has a `children` array (its content is `characters`, a + * string, not child elements), so a plain "does it have children" check + * reads every TEXT node as having nothing to hug and forces it to FIXED — + * wrong, since text hugs its own characters. TEXT is therefore never + * "meaningless" here regardless of `children`. + */ +export function hugSizingIsMeaningless(node: { + type: string; + children?: unknown[]; +}): boolean { + if (node.type === "TEXT") return false; + return !Array.isArray(node.children) || node.children.length === 0; +} + /** * Recursively process both JSON node and Figma node to update with data not available in JSON * This now includes the functionality from convertNodeToAltNode @@ -537,17 +555,16 @@ const processNodePair = async ( jsonNode.counterAxisAlignItems = "MIN"; } - // If layout sizing is HUG but there are no children, set it to FIXED - const hasChildren = - "children" in jsonNode && - jsonNode.children && - Array.isArray(jsonNode.children) && - jsonNode.children.length > 0; - - if (jsonNode.layoutSizingHorizontal === "HUG" && !hasChildren) { + if ( + jsonNode.layoutSizingHorizontal === "HUG" && + hugSizingIsMeaningless(jsonNode) + ) { jsonNode.layoutSizingHorizontal = "FIXED"; } - if (jsonNode.layoutSizingVertical === "HUG" && !hasChildren) { + if ( + jsonNode.layoutSizingVertical === "HUG" && + hugSizingIsMeaningless(jsonNode) + ) { jsonNode.layoutSizingVertical = "FIXED"; }