import { test, expect, describe, beforeEach } from "@jest/globals"; import { nanoid } from "nanoid"; import { type NestingRule, createRegularStyleSheet, } from "@webstudio-is/css-engine"; import { initialBreakpoints, type WebstudioFragment, type Instance, } from "@webstudio-is/sdk"; import { $, renderJsx } from "@webstudio-is/sdk/testing"; import * as defaultMetas from "@webstudio-is/sdk-components-react-remix/metas"; import { __testing__ } from "./plugin-webflow"; import { $breakpoints, $project, $registeredComponentMetas, $styleSources, $styles, } from "../../nano-states"; import invariant from "tiny-invariant"; import { WfData } from "./schema"; const { toWebstudioFragment } = __testing__; const equalFragment = (fragment: WebstudioFragment, jsx: JSX.Element) => { const fragmentInstances = new Map(); fragment.instances.forEach((instance: Instance) => { const newId = expect.any(String) as unknown as string; fragmentInstances.set(newId, { ...instance, id: newId, children: instance.children.map((child) => { if (child.type === "text") { return child; } return { type: "id", value: expect.any(String) as unknown as string }; }), }); }); const expected = renderJsx(jsx); const expectedInstances = new Map(); for (const instance of expected.instances.values()) { expectedInstances.set(instance.id, instance); } const fragmentProps = new Map(); for (const prop of fragment.props) { prop.id = expect.any(String) as unknown as string; prop.instanceId = expect.any(String) as unknown as string; fragmentProps.set(prop.id, prop); } const expectedProps = new Map(); for (const prop of expected.props.values()) { prop.id = expect.any(String) as unknown as string; prop.instanceId = expect.any(String) as unknown as string; expectedProps.set(prop.id, prop); } //console.dir(fragmentInstances, { depth: null }); //console.dir(expectedInstances, { depth: null }); //console.dir(expectedProps, { depth: null }); expect(fragmentInstances).toEqual(expectedInstances); expect(fragmentProps).toEqual(expectedProps); }; const toCss = (fragment: WebstudioFragment) => { const sheet = createRegularStyleSheet(); for (const breakpoint of fragment.breakpoints) { sheet.addMediaRule(breakpoint.id, breakpoint); } const ruleByStyleSourceId = new Map(); for (const styleDecl of fragment.styleSources) { const name = styleDecl.type === "token" ? styleDecl.name : "Local"; const rule = sheet.addNestingRule(name); ruleByStyleSourceId.set(styleDecl.id, rule); } for (const styleDecl of fragment.styles) { const rule = ruleByStyleSourceId.get(styleDecl.styleSourceId); rule?.setDeclaration({ breakpoint: styleDecl.breakpointId, selector: styleDecl.state ?? "", property: styleDecl.property, value: styleDecl.value, }); } return sheet.cssText; }; beforeEach(() => { const defaultMetasMap = new Map(Object.entries(defaultMetas)); $registeredComponentMetas.set(defaultMetasMap); $project.set({ id: "test", createdAt: "", domain: "", title: "", userId: "", isDeleted: false, previewImageAsset: null, marketplaceApprovalStatus: "PENDING", }); $breakpoints.set( new Map( initialBreakpoints.map((breakpoint) => { const id = nanoid(); return [id, { ...breakpoint, id }]; }) ) ); }); test("Heading", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "97d91be2-3bba-d340-0f13-a84e975b7497", type: "Heading", tag: "h1", children: ["97d91be2-3bba-d340-0f13-a84e975b7498"], classes: [], }, { _id: "97d91be2-3bba-d340-0f13-a84e975b7498", v: "Turtle in the sea", text: true, }, ], styles: [], assets: [], }, }); equalFragment(fragment, <$.Heading tag="h1">Turtle in the sea); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { h1 { margin-top: 20px; margin-right: 0; margin-bottom: 10px; margin-left: 0; font-size: 38px; font-weight: bold; line-height: 44px } }" `); }); test("Link Block, Button, Text Link", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "97539676-c2ca-2e8f-55f3-6c4a3104a5c0", type: "Link", tag: "a", classes: [], children: [], data: { link: { url: "https://webstudio.is", target: "_blank", }, }, }, ], styles: [], assets: [], }, }); equalFragment( fragment, <$.Link href="https://webstudio.is" target="_blank" /> ); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { a { background-color: rgba(0, 0, 0, 0) } a:active { outline: 0 none currentcolor } a:hover { outline: 0 none currentcolor } }" `); }); test("List and ListItem", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "7e11a800-c8e2-9b14-37cf-09a9e94754ad", type: "List", tag: "ul", classes: [], children: [ "7e11a800-c8e2-9b14-37cf-09a9e94754ae", "7e11a800-c8e2-9b14-37cf-09a9e94754af", "7e11a800-c8e2-9b14-37cf-09a9e94754b0", ], }, { _id: "7e11a800-c8e2-9b14-37cf-09a9e94754ae", type: "ListItem", tag: "li", classes: [], children: [], }, { _id: "7e11a800-c8e2-9b14-37cf-09a9e94754af", type: "ListItem", tag: "li", classes: [], children: [], }, { _id: "7e11a800-c8e2-9b14-37cf-09a9e94754b0", type: "ListItem", tag: "li", classes: [], children: [], }, ], styles: [], assets: [], }, }); equalFragment( fragment, <$.List> <$.ListItem /> <$.ListItem /> <$.ListItem /> ); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { ul { margin-top: 0; margin-bottom: 10px; padding-left: 40px } }" `); }); test("Paragraph", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "dfab64ae-6624-b6db-a909-b85588aa3f8d", type: "Paragraph", tag: "p", classes: [], children: ["dfab64ae-6624-b6db-a909-b85588aa3f8e"], }, { _id: "dfab64ae-6624-b6db-a909-b85588aa3f8e", text: true, v: "Text in a paragraph", }, ], styles: [], assets: [], }, }); equalFragment(fragment, <$.Paragraph>Text in a paragraph); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { p { margin-top: 0; margin-bottom: 10px } }" `); }); test("Text", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "adea2109-96eb-63e0-c27f-632a7f40bce8", type: "Block", tag: "div", classes: [], children: ["adea2109-96eb-63e0-c27f-632a7f40bce9"], data: { text: true, }, }, { _id: "adea2109-96eb-63e0-c27f-632a7f40bce9", text: true, v: "This is some text inside of a div block.", }, ], styles: [], assets: [], }, }); equalFragment( fragment, <$.Text>This is some text inside of a div block. ); expect(toCss(fragment)).toMatchInlineSnapshot(`""`); }); test("Blockquote", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "25ffefdf-c015-5edd-7673-933b41a25328", type: "Blockquote", tag: "blockquote", classes: [], children: ["25ffefdf-c015-5edd-7673-933b41a25329"], }, { _id: "25ffefdf-c015-5edd-7673-933b41a25329", text: true, v: "Block Quote", }, ], styles: [], assets: [], }, }); equalFragment(fragment, <$.Blockquote>Block Quote); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { blockquote { margin-top: 0; margin-right: 0; margin-bottom: 10px; margin-left: 0; padding-top: 10px; padding-right: 20px; padding-bottom: 10px; padding-left: 20px; font-size: 18px; line-height: 22px; border-left: 5px solid rgba(226, 226, 226, 1) } }" `); }); test("Strong", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "25ffefdf-c015-5edd-7673-933b41a25328", type: "Strong", tag: "strong", classes: [], children: ["25ffefdf-c015-5edd-7673-933b41a25329"], }, { _id: "25ffefdf-c015-5edd-7673-933b41a25329", text: true, v: "Bold Text", }, ], styles: [], assets: [], }, }); equalFragment(fragment, <$.Bold>Bold Text); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { strong { font-weight: bold } }" `); }); test("Emphasized", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "25ffefdf-c015-5edd-7673-933b41a25328", type: "Emphasized", tag: "em", classes: [], children: ["25ffefdf-c015-5edd-7673-933b41a25329"], }, { _id: "25ffefdf-c015-5edd-7673-933b41a25329", text: true, v: "Emphasis", }, ], styles: [], assets: [], }, }); equalFragment(fragment, <$.Italic>Emphasis); expect(toCss(fragment)).toMatchInlineSnapshot(`""`); }); test("Superscript", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "25ffefdf-c015-5edd-7673-933b41a25328", type: "Superscript", tag: "sup", classes: [], children: ["25ffefdf-c015-5edd-7673-933b41a25329"], }, { _id: "25ffefdf-c015-5edd-7673-933b41a25329", text: true, v: "Superscript", }, ], styles: [], assets: [], }, }); equalFragment(fragment, <$.Superscript>Superscript); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { sup { vertical-align: baseline; font-size: 75%; line-height: 0; position: relative; top: -0.5em } }" `); }); test("Subscript", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "25ffefdf-c015-5edd-7673-933b41a25328", type: "Subscript", tag: "sub", classes: [], children: ["25ffefdf-c015-5edd-7673-933b41a25329"], }, { _id: "25ffefdf-c015-5edd-7673-933b41a25329", text: true, v: "Subscript", }, ], styles: [], assets: [], }, }); equalFragment(fragment, <$.Subscript>Subscript); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { sub { vertical-align: baseline; font-size: 75%; line-height: 0; position: relative; bottom: -0.25em } }" `); }); test("Section", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "25ffefdf-c015-5edd-7673-933b41a25328", type: "Section", tag: "section", classes: [], children: ["25ffefdf-c015-5edd-7673-933b41a25329"], }, ], styles: [], assets: [], }, }); equalFragment(fragment, <$.Box tag="section" />); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { section { display: block } }" `); }); test("Figure", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "7c6bc1fd-128d-514b-167b-605a910e435c", type: "Block", tag: "figure", classes: [], children: [], }, ], styles: [], assets: [], }, }); equalFragment(fragment, <$.Box tag="figure" />); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { figure { display: block; margin-top: 0; margin-right: 0; margin-bottom: 10px; margin-left: 0 } }" `); }); test("BlockContainer", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "25ffefdf-c015-5edd-7673-933b41a25328", type: "BlockContainer", tag: "div", classes: [], children: [], }, ], styles: [], assets: [], }, }); equalFragment(fragment, <$.Box />); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { w-layout-blockcontainer { max-width: 940px; margin-left: auto; margin-right: auto; display: block } w-container { max-width: 940px; margin-left: auto; margin-right: auto } w-container:after { content: " "; grid-row-start: 1; grid-column-start: 1; grid-row-end: 2; grid-column-end: 2; display: table; clear: both } w-container:before { content: " "; grid-row-start: 1; grid-column-start: 1; grid-row-end: 2; grid-column-end: 2; display: table } } @media all and (max-width: 991px) { w-layout-blockcontainer { max-width: 728px } w-container { max-width: 728px } } @media all and (max-width: 767px) { w-layout-blockcontainer { max-width: none } } @media all and (max-width: 479px) { w-container { max-width: none } }" `); }); test("Block", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "25ffefdf-c015-5edd-7673-933b41a25328", type: "Block", tag: "div", classes: [], children: [], }, ], styles: [], assets: [], }, }); equalFragment(fragment, <$.Box />); expect(toCss(fragment)).toMatchInlineSnapshot(`""`); }); test("V Flex", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "25ffefdf-c015-5edd-7673-933b41a25328", type: "VFlex", tag: "div", classes: [], children: [], }, ], styles: [], assets: [], }, }); equalFragment(fragment, <$.Box />); expect(toCss(fragment)).toMatchInlineSnapshot(`""`); }); test("H Flex", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "25ffefdf-c015-5edd-7673-933b41a25328", type: "HFlex", tag: "div", classes: [], children: [], }, ], styles: [], assets: [], }, }); equalFragment(fragment, <$.Box />); expect(toCss(fragment)).toMatchInlineSnapshot(`""`); }); test("QuickStack with instance styles", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "aeb3895f-67c9-b5a0-69b4-7960b893ec04", type: "Layout", tag: "div", classes: [], children: [ "ca264069-ff87-c2e5-7dc0-a4c0ec0e9146", "ec30cdfd-1a60-eeb0-1bfa-1df6f1956ed5", ], data: { style: { base: { main: { noPseudo: { gridTemplateColumns: "1fr 1fr", gridTemplateRows: "auto", order: 1, }, }, }, }, }, }, { _id: "ca264069-ff87-c2e5-7dc0-a4c0ec0e9146", type: "Cell", tag: "div", classes: [], children: [], }, { _id: "ec30cdfd-1a60-eeb0-1bfa-1df6f1956ed5", type: "Cell", tag: "div", classes: [], children: [], }, ], styles: [], assets: [], }, }); equalFragment( fragment, <$.Box> <$.Box /> <$.Box /> ); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { w-layout-layout { row-gap: 20px; column-gap: 20px; grid-auto-columns: 1fr; justify-content: center; padding: 20px } wf-layout-layout { display: grid } Local { grid-template-columns: 1fr 1fr; grid-template-rows: auto; order: 1 } w-layout-cell { flex-direction: column; justify-content: flex-start; align-items: flex-start; display: flex } }" `); }); test("Grid", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "25ffefdf-c015-5edd-7673-933b41a25328", type: "Grid", tag: "div", classes: [], children: [], }, ], styles: [], assets: [], }, }); equalFragment(fragment, <$.Box />); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { w-layout-grid { row-gap: 16px; column-gap: 16px; grid-template-rows: auto auto; grid-template-columns: 1fr 1fr; grid-auto-columns: 1fr; display: grid } }" `); }); test("Columns", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "08fb88d6-f6ec-5169-f4d4-8dac98df2b58", type: "Row", tag: "div", classes: [], children: [ "08fb88d6-f6ec-5169-f4d4-8dac98df2b59", "08fb88d6-f6ec-5169-f4d4-8dac98df2b5a", ], }, { _id: "08fb88d6-f6ec-5169-f4d4-8dac98df2b59", type: "Column", tag: "div", classes: [], children: [], }, { _id: "08fb88d6-f6ec-5169-f4d4-8dac98df2b5a", type: "Column", tag: "div", classes: [], children: [], }, ], styles: [], assets: [], }, }); equalFragment( fragment, <$.Box> <$.Box /> <$.Box /> ); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { w-row:after { content: " "; grid-row-start: 1; grid-column-start: 1; grid-row-end: 2; grid-column-end: 2; display: table; clear: both } w-row:before { content: " "; grid-row-start: 1; grid-column-start: 1; grid-row-end: 2; grid-column-end: 2; display: table } w-col { float: left; width: 100%; min-height: 1px; padding-left: 10px; padding-right: 10px; position: relative } } @media all and (max-width: 767px) { w-row { margin-left: 0; margin-right: 0 } w-col { width: 100%; left: auto; right: auto } } @media all and (max-width: 479px) { w-col { width: 100% } }" `); }); test("Image", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "3c0b6a7a-830f-4b4a-48c5-4215f9c9389a", type: "Image", tag: "img", classes: [], children: [], data: { attr: { src: "https://test.com/image.jpg", loading: "eager", width: "200", height: "auto", alt: "Test", }, }, }, ], styles: [], assets: [], }, }); equalFragment( fragment, <$.Image alt="Test" loading="eager" width="200" src="https://test.com/image.jpg" /> ); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { img { vertical-align: middle; max-width: 100%; display: inline-block; border: 0 none currentcolor } }" `); }); test("HtmlEmbed", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "88131b38-7a58-8085-38d2-dc51c5ce887e", type: "HtmlEmbed", tag: "div", classes: [], children: [], v: "some html", }, ], styles: [], assets: [], }, }); equalFragment(fragment, <$.HtmlEmbed code="some html" clientOnly={true} />); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { w-embed { display: block } w-embed:after { content: " "; grid-row-start: 1; grid-column-start: 1; grid-row-end: 2; grid-column-end: 2; display: table; clear: both } w-embed:before { content: " "; grid-row-start: 1; grid-column-start: 1; grid-row-end: 2; grid-column-end: 2; display: table } }" `); }); test("CodeBlock", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "f06b6679-6414-3592-a6e3-b59196420d7f", type: "CodeBlock", tag: "div", classes: [], children: [], data: { code: "test", language: "javascript", }, }, ], styles: [], assets: [], }, }); equalFragment(fragment, <$.CodeText lang="javascript" code="test" />); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { w-code-block { margin-top: unset; margin-right: unset; margin-bottom: unset; margin-left: unset } }" `); }); test("RichText", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "33178bae-2d99-7d1f-ffa5-101356769dad", type: "RichText", tag: "div", classes: [], children: [ "8418336b-918d-3160-1d76-8c9de474cb43", "63929533-a4de-17c6-3b13-351aa16c6e10", "7724e6e0-9e3c-4526-35a9-f11dcd628c35", "d760a6fd-5da7-7d24-4f4c-24e68a651758", "49c71aa2-c61b-4823-dfdb-dc3badea814b", "28784747-282c-fe17-a81b-c36c9212b78e", "6e4c4ff0-0ebe-1de0-d94d-6f18f08f0762", "88e7fd8c-98ff-49f7-aff1-3286e2e0e211", "63202791-9c33-bc97-b49e-71a2af805488", "b386cecd-1697-e7e8-6c7b-66f36261fec2", "98b880aa-7ba5-c754-8069-b83cb198ec32", "2e3bf379-e9ee-182c-9f92-1e1409ba1dd7", "43d15a61-5f5d-d4bd-4a8c-04b80371e808", "676d66b8-3df9-45a5-b2a4-62b75b623443", "57268510-0593-b1df-7786-f1f3b3d02830", "cd959e82-0fe8-a4a7-6b9f-c5fab19aa6ec", "10389bae-2b31-83d5-919f-45fc50689288", ], }, { _id: "8418336b-918d-3160-1d76-8c9de474cb43", type: "Heading", tag: "h1", classes: [], children: ["c1759a8a-6657-5c74-4e95-33c8a721caab"], }, { _id: "c1759a8a-6657-5c74-4e95-33c8a721caab", text: true, v: "Heading 1", }, { _id: "63929533-a4de-17c6-3b13-351aa16c6e10", type: "Heading", tag: "h2", classes: [], children: ["d04d84bb-f382-5af4-482a-e6a0aaab168b"], }, { _id: "d04d84bb-f382-5af4-482a-e6a0aaab168b", text: true, v: "Heading 2", }, { _id: "7724e6e0-9e3c-4526-35a9-f11dcd628c35", type: "Heading", tag: "h3", classes: [], children: ["409d9647-ea51-e048-2bfd-cbbee2efb0c6"], }, { _id: "409d9647-ea51-e048-2bfd-cbbee2efb0c6", text: true, v: "Heading 3", }, { _id: "d760a6fd-5da7-7d24-4f4c-24e68a651758", type: "Heading", tag: "h4", classes: [], children: ["4ce7fd9b-c7e9-186c-853a-931c507e5d1d"], }, { _id: "4ce7fd9b-c7e9-186c-853a-931c507e5d1d", text: true, v: "Heading 4", }, { _id: "49c71aa2-c61b-4823-dfdb-dc3badea814b", type: "Heading", tag: "h5", classes: [], children: ["f28abd5f-e21c-08f5-c6be-ec335166114d"], }, { _id: "f28abd5f-e21c-08f5-c6be-ec335166114d", text: true, v: "Heading 5", }, { _id: "28784747-282c-fe17-a81b-c36c9212b78e", type: "Heading", tag: "h6", classes: [], children: ["6f753bba-a1d9-5092-e605-2f1756fc4a3d"], }, { _id: "6f753bba-a1d9-5092-e605-2f1756fc4a3d", text: true, v: "Heading 6", }, { _id: "6e4c4ff0-0ebe-1de0-d94d-6f18f08f0762", type: "Paragraph", tag: "p", classes: [], children: ["27e847ff-962c-0782-1278-2f4ff342aaad"], }, { _id: "27e847ff-962c-0782-1278-2f4ff342aaad", text: true, v: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.", }, { _id: "88e7fd8c-98ff-49f7-aff1-3286e2e0e211", type: "Blockquote", tag: "blockquote", classes: [], children: ["9c1d38a3-512b-4f90-e6c4-cadf585cb608"], }, { _id: "9c1d38a3-512b-4f90-e6c4-cadf585cb608", text: true, v: "Block quote", }, { _id: "63202791-9c33-bc97-b49e-71a2af805488", type: "Paragraph", tag: "p", classes: [], children: ["3080a095-fc1a-9de8-e6d1-21c801aed169"], }, { _id: "3080a095-fc1a-9de8-e6d1-21c801aed169", text: true, v: "Ordered list", }, { _id: "b386cecd-1697-e7e8-6c7b-66f36261fec2", type: "List", tag: "ol", classes: [], children: [ "aafe8235-0ff4-c6f4-8382-40fc34900b75", "0fd98471-33b6-e8d2-76f1-6e373849e6ba", "2ccfda89-a200-d286-440a-f94699163329", ], }, { _id: "aafe8235-0ff4-c6f4-8382-40fc34900b75", type: "ListItem", tag: "li", classes: [], children: ["bf312343-69b6-bec7-5ee1-e19154d85b62"], }, { _id: "bf312343-69b6-bec7-5ee1-e19154d85b62", text: true, v: "Item 1", }, { _id: "0fd98471-33b6-e8d2-76f1-6e373849e6ba", type: "ListItem", tag: "li", classes: [], children: ["161683ac-eed6-7a68-64f7-706c42984c65"], }, { _id: "161683ac-eed6-7a68-64f7-706c42984c65", text: true, v: "Item 2", }, { _id: "2ccfda89-a200-d286-440a-f94699163329", type: "ListItem", tag: "li", classes: [], children: ["59f4270b-72e6-99b1-041a-5ab836481b5c"], }, { _id: "59f4270b-72e6-99b1-041a-5ab836481b5c", text: true, v: "Item 3", }, { _id: "98b880aa-7ba5-c754-8069-b83cb198ec32", type: "Paragraph", tag: "p", classes: [], children: ["b6755218-5a0d-c537-43c0-071245d7a472"], }, { _id: "b6755218-5a0d-c537-43c0-071245d7a472", text: true, v: "Unordered list", }, { _id: "2e3bf379-e9ee-182c-9f92-1e1409ba1dd7", type: "List", tag: "ul", classes: [], children: [ "a90c5835-61e2-e1e6-dfd3-0b02a34ad857", "748eb739-12d7-26fc-b8ee-f2c460f35170", "54178296-1686-9b14-a341-42ce51958ce5", ], }, { _id: "a90c5835-61e2-e1e6-dfd3-0b02a34ad857", type: "ListItem", tag: "li", classes: [], children: ["8557d8cf-09de-52c0-4631-c2123fc056d1"], }, { _id: "8557d8cf-09de-52c0-4631-c2123fc056d1", text: true, v: "Item A", }, { _id: "748eb739-12d7-26fc-b8ee-f2c460f35170", type: "ListItem", tag: "li", classes: [], children: ["666b664d-8023-50bb-453a-36017c7a3a38"], }, { _id: "666b664d-8023-50bb-453a-36017c7a3a38", text: true, v: "Item B", }, { _id: "54178296-1686-9b14-a341-42ce51958ce5", type: "ListItem", tag: "li", classes: [], children: ["15647123-fd1c-5928-3894-d666bc6f3c84"], }, { _id: "15647123-fd1c-5928-3894-d666bc6f3c84", text: true, v: "Item C", }, { _id: "43d15a61-5f5d-d4bd-4a8c-04b80371e808", type: "Paragraph", tag: "p", classes: [], children: ["db8cce1a-40ff-ccb7-5f18-ebb811777409"], }, { _id: "db8cce1a-40ff-ccb7-5f18-ebb811777409", type: "Link", tag: "a", classes: [], children: ["a8af1cd8-157c-4b92-c27e-bca1ae5e6668"], data: { button: false, block: "", link: { url: "https://university.webflow.com/lesson/add-and-nest-text-links-in-webflow", }, }, }, { _id: "a8af1cd8-157c-4b92-c27e-bca1ae5e6668", text: true, v: "Text link", }, { _id: "676d66b8-3df9-45a5-b2a4-62b75b623443", type: "Paragraph", tag: "p", classes: [], children: ["4f0b961b-5774-f441-e97f-e84a75efcf08"], }, { _id: "4f0b961b-5774-f441-e97f-e84a75efcf08", type: "Strong", tag: "strong", classes: [], children: ["5839c9b9-4281-f778-f7f9-c4844452295e"], }, { _id: "5839c9b9-4281-f778-f7f9-c4844452295e", text: true, v: "Bold text", }, { _id: "57268510-0593-b1df-7786-f1f3b3d02830", type: "Paragraph", tag: "p", classes: [], children: ["db4e59e1-b859-d2bc-ee4a-08c638f088db"], }, { _id: "db4e59e1-b859-d2bc-ee4a-08c638f088db", type: "Emphasized", tag: "em", classes: [], children: ["c2661284-144d-5052-447a-ffe78f3b8bbe"], }, { _id: "c2661284-144d-5052-447a-ffe78f3b8bbe", text: true, v: "Emphasis", }, { _id: "cd959e82-0fe8-a4a7-6b9f-c5fab19aa6ec", type: "Paragraph", tag: "p", classes: [], children: ["debd47f8-ddd2-8ead-af6c-5e42a16e15d0"], }, { _id: "debd47f8-ddd2-8ead-af6c-5e42a16e15d0", type: "Superscript", tag: "sup", classes: [], children: ["e6640190-f224-5a57-6181-55a793c00da8"], }, { _id: "e6640190-f224-5a57-6181-55a793c00da8", text: true, v: "Superscript", }, { _id: "10389bae-2b31-83d5-919f-45fc50689288", type: "Paragraph", tag: "p", classes: [], children: ["ec162f35-9226-0e8a-c0c2-32685ef444c1"], }, { _id: "ec162f35-9226-0e8a-c0c2-32685ef444c1", type: "Subscript", tag: "sub", classes: [], children: ["dcdc8f43-3cdc-1522-e397-60968e378b92"], }, { _id: "dcdc8f43-3cdc-1522-e397-60968e378b92", text: true, v: "Subscript", }, ], styles: [], assets: [], }, }); equalFragment( fragment, <$.Box> <$.Heading tag="h1">Heading 1 <$.Heading tag="h2">Heading 2 <$.Heading tag="h3">Heading 3 <$.Heading tag="h4">Heading 4 <$.Heading tag="h5">Heading 5 <$.Heading tag="h6">Heading 6 <$.Paragraph> Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. <$.Blockquote>Block quote <$.Paragraph>Ordered list <$.List ordered={true}> <$.ListItem>Item 1 <$.ListItem>Item 2 <$.ListItem>Item 3 <$.Paragraph>Unordered list <$.List> <$.ListItem>Item A <$.ListItem>Item B <$.ListItem>Item C <$.Paragraph> <$.Link href="https://university.webflow.com/lesson/add-and-nest-text-links-in-webflow"> Text link <$.Paragraph> <$.Bold>Bold text <$.Paragraph> <$.Italic>Emphasis <$.Paragraph> <$.Superscript>Superscript <$.Paragraph> <$.Subscript>Subscript ); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { h1 { margin-top: 20px; margin-right: 0; margin-bottom: 10px; margin-left: 0; font-size: 38px; font-weight: bold; line-height: 44px } h2 { margin-bottom: 10px; font-weight: bold; margin-top: 20px; font-size: 32px; line-height: 36px } h3 { margin-bottom: 10px; font-weight: bold; margin-top: 20px; font-size: 24px; line-height: 30px } h4 { margin-bottom: 10px; font-weight: bold; margin-top: 10px; font-size: 18px; line-height: 24px } h5 { margin-bottom: 10px; font-weight: bold; margin-top: 10px; font-size: 14px; line-height: 20px } h6 { margin-bottom: 10px; font-weight: bold; margin-top: 10px; font-size: 12px; line-height: 18px } p { margin-top: 0; margin-bottom: 10px } blockquote { margin-top: 0; margin-right: 0; margin-bottom: 10px; margin-left: 0; padding-top: 10px; padding-right: 20px; padding-bottom: 10px; padding-left: 20px; font-size: 18px; line-height: 22px; border-left: 5px solid rgba(226, 226, 226, 1) } ol { margin-top: 0; margin-bottom: 10px; padding-left: 40px } ul { margin-top: 0; margin-bottom: 10px; padding-left: 40px } a { background-color: rgba(0, 0, 0, 0) } a:active { outline: 0 none currentcolor } a:hover { outline: 0 none currentcolor } strong { font-weight: bold } sup { vertical-align: baseline; font-size: 75%; line-height: 0; position: relative; top: -0.5em } sub { vertical-align: baseline; font-size: 75%; line-height: 0; position: relative; bottom: -0.25em } }" `); }); test("Form", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "f19c775e-a9ef-fd0c-2d16-aa53c69e9da4", type: "FormWrapper", tag: "div", classes: [], children: [ "11c85957-89b5-e4f8-da11-f1acb4223a9f", "492633d9-7425-ba09-036f-e43213b9875b", "a0877638-7523-ac57-7ed9-19f5469e79d1", ], }, { _id: "11c85957-89b5-e4f8-da11-f1acb4223a9f", type: "FormForm", tag: "form", classes: [], children: ["eeb469a5-8b03-8002-7dec-586856365387"], }, { _id: "eeb469a5-8b03-8002-7dec-586856365387", type: "FormTextInput", tag: "input", classes: [], children: [], data: { attr: { id: "name", name: "name", maxlength: 256, placeholder: "", disabled: false, type: "text", required: false, autofocus: false, }, }, }, { _id: "492633d9-7425-ba09-036f-e43213b9875b", type: "FormSuccessMessage", tag: "div", classes: [], children: ["442d7e86-2c77-961c-ab1b-431bd34abcf7"], }, { _id: "442d7e86-2c77-961c-ab1b-431bd34abcf7", type: "Block", tag: "div", classes: [], children: ["875baf7f-6c98-b379-0cbe-59b5fdd2c112"], }, { _id: "875baf7f-6c98-b379-0cbe-59b5fdd2c112", text: true, v: "Thank you! Your submission has been received!", }, { _id: "a0877638-7523-ac57-7ed9-19f5469e79d1", type: "FormErrorMessage", tag: "div", classes: [], children: ["9eaf0a19-b2fa-7aef-4825-41f6319d790d"], }, { _id: "9eaf0a19-b2fa-7aef-4825-41f6319d790d", type: "Block", tag: "div", classes: [], children: ["a87685fe-24b2-31ff-e229-40c71f6155dd"], }, { _id: "a87685fe-24b2-31ff-e229-40c71f6155dd", text: true, v: "Oops! Something went wrong while submitting the form.", }, ], styles: [], assets: [], }, }); equalFragment( fragment, <$.Box> <$.Box> <$.Input id="name" name="name" maxLength={256} placeholder="" disabled={false} type="text" required={false} autoFocus={false} /> ); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { w-form { margin-top: 0; margin-right: 0; margin-bottom: 15px; margin-left: 0 } input { color: inherit; font-style: inherit; font-variant-caps: inherit; font-weight: inherit; font-stretch: inherit; font-size: inherit; line-height: normal; font-family: inherit; appearance: button; cursor: pointer; box-sizing: border-box; height: auto; margin: 0; padding: 0 } input::-moz-focus-inner { border: 0 none currentcolor; padding: 0 } w-input { color: rgba(51, 51, 51, 1); vertical-align: middle; background-color: rgba(238, 238, 238, 1); width: 100%; height: auto; margin-bottom: 10px; padding-top: 8px; padding-right: 12px; padding-bottom: 8px; padding-left: 12px; font-size: 14px; line-height: 1.42857; display: block; cursor: not-allowed; border: 1px solid rgba(204, 204, 204, 1) } w-input:-moz-placeholder { color: rgba(153, 153, 153, 1) } w-input::-moz-placeholder { color: rgba(153, 153, 153, 1); opacity: 1 } w-input::-webkit-input-placeholder { color: rgba(153, 153, 153, 1) } w-input:focus { border-top-color: rgba(56, 152, 236, 1); border-right-color: rgba(56, 152, 236, 1); border-bottom-color: rgba(56, 152, 236, 1); border-left-color: rgba(56, 152, 236, 1); outline: 0 none currentcolor } }" `); }); test("FormButton", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "b7d4b56c-77eb-a79d-4b73-7802d6c5f74a", type: "FormButton", tag: "input", classes: [], children: [], data: { attr: { value: "Submit", }, }, }, ], styles: [], assets: [], }, }); equalFragment(fragment, <$.Button>Submit); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { input { color: inherit; font-style: inherit; font-variant-caps: inherit; font-weight: inherit; font-stretch: inherit; font-size: inherit; line-height: normal; font-family: inherit; appearance: button; cursor: pointer; box-sizing: border-box; height: auto; margin: 0; padding: 0 } input::-moz-focus-inner { border: 0 none currentcolor; padding: 0 } w-button { color: rgba(255, 255, 255, 1); line-height: inherit; cursor: pointer; background-color: rgba(56, 152, 236, 1); border-top-left-radius: 0; border-top-right-radius: 0; border-bottom-right-radius: 0; padding-top: 9px; padding-right: 15px; padding-bottom: 9px; padding-left: 15px; text-decoration-line: none; text-decoration-style: solid; text-decoration-color: currentcolor; display: inline-block; appearance: button; border: 0 none currentcolor } }" `); }); test("FormTextInput", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "3b60c6c7-296c-f98b-0557-a55dcac084b3", type: "FormTextInput", tag: "input", classes: [], children: [], data: { attr: { id: "email", name: "email", maxlength: 256, placeholder: "", disabled: false, type: "email", required: true, autofocus: false, }, }, }, ], styles: [], assets: [], }, }); equalFragment( fragment, <$.Input id="email" name="email" maxLength={256} placeholder="" disabled={false} type="email" required={true} autoFocus={false} /> ); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { input { color: inherit; font-style: inherit; font-variant-caps: inherit; font-weight: inherit; font-stretch: inherit; font-size: inherit; line-height: normal; font-family: inherit; appearance: button; cursor: pointer; box-sizing: border-box; height: auto; margin: 0; padding: 0 } input::-moz-focus-inner { border: 0 none currentcolor; padding: 0 } w-input { color: rgba(51, 51, 51, 1); vertical-align: middle; background-color: rgba(238, 238, 238, 1); width: 100%; height: auto; margin-bottom: 10px; padding-top: 8px; padding-right: 12px; padding-bottom: 8px; padding-left: 12px; font-size: 14px; line-height: 1.42857; display: block; cursor: not-allowed; border: 1px solid rgba(204, 204, 204, 1) } w-input:-moz-placeholder { color: rgba(153, 153, 153, 1) } w-input::-moz-placeholder { color: rgba(153, 153, 153, 1); opacity: 1 } w-input::-webkit-input-placeholder { color: rgba(153, 153, 153, 1) } w-input:focus { border-top-color: rgba(56, 152, 236, 1); border-right-color: rgba(56, 152, 236, 1); border-bottom-color: rgba(56, 152, 236, 1); border-left-color: rgba(56, 152, 236, 1); outline: 0 none currentcolor } }" `); }); test("FormBlockLabel", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "0fdc679b-9891-dfb3-698a-0114d0c6c729", type: "FormBlockLabel", tag: "label", classes: [], children: ["7827c7aa-cd90-8bda-4570-605f2a1a0d61"], data: { attr: { for: "email", }, }, }, { _id: "7827c7aa-cd90-8bda-4570-605f2a1a0d61", text: true, v: "Email Address", }, ], styles: [], assets: [], }, }); equalFragment(fragment, <$.Label htmlFor="email">Email Address); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { label { margin-bottom: 5px; font-weight: bold; display: block } }" `); }); test("FormTextarea", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "ff81988e-1cc2-8c93-d264-d702d846c30a", type: "FormTextarea", tag: "textarea", classes: [], children: [], data: { attr: { id: "field", name: "field", maxlength: 5000, placeholder: "Example Text", required: false, autofocus: false, }, }, }, ], styles: [], assets: [], }, }); equalFragment( fragment, <$.Textarea id="field" name="field" maxLength={5000} placeholder="Example Text" required={false} autoFocus={false} /> ); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { textarea { color: inherit; font-style: inherit; font-variant-caps: inherit; font-weight: inherit; font-stretch: inherit; font-size: inherit; line-height: inherit; font-family: inherit; overflow-x: auto; overflow-y: auto; height: auto; margin: 0 } w-input { color: rgba(51, 51, 51, 1); vertical-align: middle; background-color: rgba(238, 238, 238, 1); width: 100%; height: auto; margin-bottom: 10px; padding-top: 8px; padding-right: 12px; padding-bottom: 8px; padding-left: 12px; font-size: 14px; line-height: 1.42857; display: block; cursor: not-allowed; border: 1px solid rgba(204, 204, 204, 1) } w-input:-moz-placeholder { color: rgba(153, 153, 153, 1) } w-input::-moz-placeholder { color: rgba(153, 153, 153, 1); opacity: 1 } w-input::-webkit-input-placeholder { color: rgba(153, 153, 153, 1) } w-input:focus { border-top-color: rgba(56, 152, 236, 1); border-right-color: rgba(56, 152, 236, 1); border-bottom-color: rgba(56, 152, 236, 1); border-left-color: rgba(56, 152, 236, 1); outline: 0 none currentcolor } }" `); }); test("FormBlockLabel", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "0fdc679b-9891-dfb3-698a-0114d0c6c729", type: "FormBlockLabel", tag: "label", classes: [], children: ["7827c7aa-cd90-8bda-4570-605f2a1a0d61"], data: { attr: { for: "email", }, }, }, { _id: "7827c7aa-cd90-8bda-4570-605f2a1a0d61", text: true, v: "Email Address", }, ], styles: [], assets: [], }, }); equalFragment(fragment, <$.Label htmlFor="email">Email Address); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { label { margin-bottom: 5px; font-weight: bold; display: block } }" `); }); test("FormCheckboxWrapper, FormCheckboxInput, FormInlineLabel", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "4b49f914-6011-9030-5727-232bbddc7d56", type: "FormCheckboxWrapper", tag: "div", classes: [], children: [ "2e32acc8-7027-1741-1be1-cc72606b57cf", "3f68e88d-5c03-698e-c4e2-a6ad6ff1a00c", ], }, { _id: "2e32acc8-7027-1741-1be1-cc72606b57cf", type: "FormCheckboxInput", tag: "input", classes: [], children: [], data: { attr: { type: "checkbox", name: "checkbox", id: "checkbox", required: false, checked: false, }, }, }, { _id: "3f68e88d-5c03-698e-c4e2-a6ad6ff1a00c", type: "FormInlineLabel", tag: "label", classes: [], children: ["18550f45-3a50-27d6-4524-b91a226ded60"], }, { _id: "18550f45-3a50-27d6-4524-b91a226ded60", text: true, v: "Checkbox", }, ], styles: [], assets: [], }, }); equalFragment( fragment, <$.Label ws:label="Checkbox Field"> <$.Checkbox id="checkbox" name="checkbox" required={false} defaultChecked={false} /> <$.Text tag="span" ws:label="Label"> Checkbox ); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { w-checkbox { margin-bottom: 5px; padding-left: 20px; display: block } w-checkbox:after { content: " "; clear: both; grid-row-start: 1; grid-column-start: 1; grid-row-end: 2; grid-column-end: 2; display: table } w-checkbox:before { content: " "; grid-row-start: 1; grid-column-start: 1; grid-row-end: 2; grid-column-end: 2; display: table } input { color: inherit; font-style: inherit; font-variant-caps: inherit; font-weight: inherit; font-stretch: inherit; font-size: inherit; line-height: normal; font-family: inherit; appearance: button; cursor: pointer; box-sizing: border-box; height: auto; margin: 0; padding: 0 } input::-moz-focus-inner { border: 0 none currentcolor; padding: 0 } w-checkbox-input { float: left; margin-top: 4px; margin-right: 0; margin-bottom: 0; margin-left: -20px; line-height: normal } label { margin-bottom: 5px; font-weight: bold; display: block } w-form-label { cursor: pointer; margin-bottom: 0; font-weight: normal; display: inline-block } }" `); }); test("FormRadioWrapper, FormRadioInput, FormInlineLabel", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "712915f3-84e7-6e7a-e1a1-050e12d3b401", type: "FormRadioWrapper", tag: "div", classes: [], children: [ "2ee7bcbb-e2ec-3c21-2cb0-c76171ff84e0", "99874ce0-e582-a5ea-9934-f6e42266a80d", ], }, { _id: "2ee7bcbb-e2ec-3c21-2cb0-c76171ff84e0", type: "FormRadioInput", tag: "input", classes: [], children: [], data: { attr: { type: "radio", name: "radio", id: "radio", value: "Radio", required: false, }, }, }, { _id: "99874ce0-e582-a5ea-9934-f6e42266a80d", type: "FormInlineLabel", tag: "label", classes: [], children: ["9e6a0451-a55e-a063-9acc-ee22ed5eac94"], }, { _id: "9e6a0451-a55e-a063-9acc-ee22ed5eac94", text: true, v: "Radio", }, ], styles: [], assets: [], }, }); equalFragment( fragment, <$.Label ws:label="Radio Field"> <$.RadioButton id="radio" name="radio" required={false} value="Radio" /> <$.Text tag="span" ws:label="Label"> Radio ); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { w-radio { margin-bottom: 5px; padding-left: 20px; display: block } w-radio:after { content: " "; grid-row-start: 1; grid-column-start: 1; grid-row-end: 2; grid-column-end: 2; display: table; clear: both } w-radio:before { content: " "; grid-row-start: 1; grid-column-start: 1; grid-row-end: 2; grid-column-end: 2; display: table } input { color: inherit; font-style: inherit; font-variant-caps: inherit; font-weight: inherit; font-stretch: inherit; font-size: inherit; line-height: normal; font-family: inherit; appearance: button; cursor: pointer; box-sizing: border-box; height: auto; margin: 0; padding: 0 } input::-moz-focus-inner { border: 0 none currentcolor; padding: 0 } w-radio-input { float: left; margin-top: 3px; margin-right: 0; margin-bottom: 0; margin-left: -20px; line-height: normal } label { margin-bottom: 5px; font-weight: bold; display: block } w-form-label { cursor: pointer; margin-bottom: 0; font-weight: normal; display: inline-block } }" `); }); test("FormSelect", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "a68c34ca-e4c3-3dbc-0036-2554387bd7a4", type: "FormSelect", tag: "select", classes: [], children: [], data: { form: { opts: [ { t: "Select one...", v: "", }, { t: "First choice", v: "First", }, { t: "Second choice", v: "Second", }, { t: "Third choice", v: "Third", }, ], }, attr: { id: "field-3", name: "field-3", required: false, multiple: false, }, }, }, ], styles: [], assets: [], }, }); equalFragment( fragment, <$.Input id="field-3" name="field-3" required={false} multiple={false} /> ); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { select { color: inherit; font-style: inherit; font-variant-caps: inherit; font-weight: inherit; font-stretch: inherit; font-size: inherit; line-height: inherit; font-family: inherit; text-transform: none; margin: 0 } }" `); }); test("Multiline text", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "da7b8e40-a038-735c-bde8-0016079a5502", type: "Block", tag: "div", classes: [], children: [ "629fa602-de2f-b70b-ba5e-a74de823cfff", "b6a2a455-8ece-d70b-c88b-2f7b7dd39445", "49a4d697-0430-6739-9179-e75c1dbec765", ], }, { _id: "629fa602-de2f-b70b-ba5e-a74de823cfff", text: true, v: "a", }, { _id: "b6a2a455-8ece-d70b-c88b-2f7b7dd39445", type: "LineBreak", tag: "br", classes: [], children: [], }, { _id: "49a4d697-0430-6739-9179-e75c1dbec765", text: true, v: "b", }, ], styles: [], assets: [], }, }); equalFragment( fragment, <$.Box> {"a"} {"\n"} {"b"} ); expect(toCss(fragment)).toMatchInlineSnapshot(`""`); }); describe("Custom attributes", () => { test("Basic", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "249f235e-91b6-bd0f-bc42-00993479e637", type: "Heading", tag: "h1", classes: [], children: [], data: { xattr: [ { name: "at", value: "b", }, ], }, }, ], styles: [], assets: [], }, }); equalFragment(fragment, <$.Heading tag="h1" at="b" />); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { h1 { margin-top: 20px; margin-right: 0; margin-bottom: 10px; margin-left: 0; font-size: 38px; font-weight: bold; line-height: 44px } }" `); }); }); test("Set show false when visibility's only condition is false", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "b35ac1a9-5a38-56c6-03ba-3196d421b95e", type: "Block", tag: "div", classes: [], children: [], data: { visibility: { conditions: [false], }, }, }, ], styles: [], assets: [], }, }); equalFragment(fragment, <$.Box data-ws-show={false} />); expect(toCss(fragment)).toMatchInlineSnapshot(`""`); }); describe("Styles", () => { test("Single class", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "97d91be2-3bba-d340-0f13-a84e975b7497", type: "Heading", tag: "h1", classes: ["a7bff598-b719-1edb-067b-a90a54d68605"], children: [], }, ], styles: [ { _id: "a7bff598-b719-1edb-067b-a90a54d68605", type: "class", name: "Heading", styleLess: "color: hsla(0, 80.00%, 47.78%, 1.00);", }, ], assets: [], }, }); expect(fragment.styleSources).toEqual([ { type: "token", id: expect.any(String), name: "h1", }, { type: "token", id: expect.any(String), name: "Heading", }, ]); expect(fragment.styleSourceSelections).toEqual([ { instanceId: expect.any(String), values: [expect.any(String), expect.any(String)], }, ]); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { h1 { margin-top: 20px; margin-right: 0; margin-bottom: 10px; margin-left: 0; font-size: 38px; font-weight: bold; line-height: 44px } Heading { color: rgba(219, 24, 24, 1) } }" `); }); test("Combo class", async () => { $styleSources.set( new Map([ [ "uuBw1PRC_uE8RhTmwxaH8", { id: "uuBw1PRC_uE8RhTmwxaH8", type: "token", name: "button", }, ], [ "uuORexg4BOrRXBJZgB80_", { id: "uuORexg4BOrRXBJZgB80_", type: "token", name: "button.is-small", }, ], ]) ); $styles.set( new Map([ [ "uuBw1PRC_uE8RhTmwxaH8", { styleSourceId: "uuBw1PRC_uE8RhTmwxaH8", breakpointId: "base", property: "color", value: { type: "keyword", value: "green" }, }, ], [ "uuORexg4BOrRXBJZgB80_0", { styleSourceId: "uuORexg4BOrRXBJZgB80_", breakpointId: "base", property: "paddingTop", value: { type: "unit", unit: "rem", value: 1 }, }, ], ]) ); const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "5f7ab979-89b3-c705-6ab9-35f77dfb209f", type: "Link", tag: "a", classes: [ "194e7d07-469d-6ffa-3925-1f51bdad7e44", "194e7d07-469d-6ffa-3925-1f51bdad7e47", "194e7d07-469d-6ffa-3925-1f51bdad7e46", ], children: [], data: { link: { url: "#", }, }, }, ], styles: [ { _id: "194e7d07-469d-6ffa-3925-1f51bdad7e44", type: "class", name: "button", styleLess: "text-align: center;", children: [ "194e7d07-469d-6ffa-3925-1f51bdad7e47", "194e7d07-469d-6ffa-3925-1f51bdad7e46", ], }, { _id: "194e7d07-469d-6ffa-3925-1f51bdad7e47", type: "class", name: "is-small", comb: "&", styleLess: "padding: 1rem;", }, { _id: "194e7d07-469d-6ffa-3925-1f51bdad7e46", type: "class", name: "is-secondary", comb: "&", styleLess: "background-color: transparent;", }, ], assets: [], }, }); expect(fragment.styleSources).toEqual([ { type: "token", id: "uu1p3Xdvlq_AZOxnzDvAv", name: "a", }, { type: "token", id: "uumXb7vHOnzTr-4SIW-wJ", name: "button.is-small.is-secondary", }, ]); expect(fragment.styleSourceSelections).toEqual([ { instanceId: expect.any(String), values: [ "uu1p3Xdvlq_AZOxnzDvAv", "uuBw1PRC_uE8RhTmwxaH8", "uumXb7vHOnzTr-4SIW-wJ", "uuORexg4BOrRXBJZgB80_", ], }, ]); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { a { background-color: rgba(0, 0, 0, 0) } a:active { outline: 0 none currentcolor } a:hover { outline: 0 none currentcolor } button.is-small.is-secondary { text-align: center; background-color: transparent; padding: 1rem } }" `); }); test("Skip empty combo class", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "56ede23d-6dcc-a320-0af4-3a803d8efd88", type: "Block", tag: "div", classes: ["08c3532a-4e88-1106-9fda-e4dcd66f93f0"], children: [ "d06c01f8-da59-b31e-a910-55a6d33898f0", "b67fb4c4-ce52-b441-1d89-55b165282c99", ], }, { _id: "d06c01f8-da59-b31e-a910-55a6d33898f0", type: "Block", tag: "div", classes: ["6569b562-7de4-bd09-3d2d-59d7c11ec988"], children: [], }, { _id: "b67fb4c4-ce52-b441-1d89-55b165282c99", type: "Block", tag: "div", classes: [ "08c3532a-4e88-1106-9fda-e4dcd66f93f0", "c4851546-799d-c6bf-fb17-0b5765f48d72", ], children: [], }, ], styles: [ { _id: "08c3532a-4e88-1106-9fda-e4dcd66f93f0", fake: false, type: "class", name: "d1", namespace: "", comb: "", styleLess: "color: hsla(0, 70.45%, 48.11%, 1.00);", variants: {}, children: ["c4851546-799d-c6bf-fb17-0b5765f48d72"], }, { _id: "6569b562-7de4-bd09-3d2d-59d7c11ec988", fake: false, type: "class", name: "d2", namespace: "", comb: "", styleLess: "font-size: 200px;", variants: {}, children: [], }, { _id: "c4851546-799d-c6bf-fb17-0b5765f48d72", fake: false, type: "class", name: "d2", namespace: "", comb: "&", styleLess: "", variants: {}, children: [], }, ], assets: [], }, }); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { d1 { color: rgba(209, 36, 36, 1) } d2 { font-size: 200px } }" `); }); test("Webflow @variable syntax used by Relume (legacy?)", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "5f7ab979-89b3-c705-6ab9-35f77dfb209f", type: "Block", tag: "div", classes: ["194e7d07-469d-6ffa-3925-1f51bdad7e44"], children: [], }, ], styles: [ { _id: "194e7d07-469d-6ffa-3925-1f51bdad7e44", type: "class", name: "block", styleLess: "color: @var_relume-variable-color-neutral-1", children: [], }, ], assets: [], }, }); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { block { color: unset } }" `); }); test("States", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "5c66aeeb-941d-c52f-8e14-c54312878021", type: "Link", tag: "a", classes: ["81cf331a-ed88-7a5f-3538-8915c7788aea"], children: [], data: { link: { url: "#", }, }, }, ], styles: [ { _id: "81cf331a-ed88-7a5f-3538-8915c7788aea", fake: false, type: "class", name: "x", namespace: "", comb: "", styleLess: "transform: translate3d(7px, 74px, 16px);", variants: { main_hover: { styleLess: "background-color: hsla(0, 85.19%, 42.12%, 1.00);", }, tiny_focus: { styleLess: "background-color: hsla(264, 73.75%, 38.37%, 1.00);", }, tiny_active: { styleLess: "background-color: hsla(0, 46.06%, 51.80%, 1.00);", }, "tiny_focus-visible": { styleLess: "background-color: hsla(264, 36.36%, 41.56%, 1.00);", }, tiny_visited: { styleLess: "background-color: hsla(0, 83.67%, 42.10%, 1.00);", }, tiny_hover: { styleLess: "background-color: hsla(0, 60.99%, 58.99%, 1.00);", }, }, }, ], assets: [], }, }); expect(fragment.styleSources).toEqual([ { type: "token", id: expect.any(String), name: "a", }, { type: "token", id: expect.any(String), name: "x", }, ]); expect(fragment.styleSourceSelections).toEqual([ { instanceId: expect.any(String), values: [expect.any(String), expect.any(String)], }, ]); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { a { background-color: rgba(0, 0, 0, 0) } a:active { outline: 0 none currentcolor } a:hover { outline: 0 none currentcolor } x { transform: translate3d(7px, 74px, 16px) } x:hover { background-color: rgba(199, 16, 16, 1) } } @media all and (max-width: 479px) { x:active { background-color: rgba(189, 75, 75, 1) } x:focus { background-color: rgba(83, 26, 170, 1) } x:focus-visible { background-color: rgba(98, 67, 145, 1) } x:hover { background-color: rgba(214, 87, 87, 1) } x:visited { background-color: rgba(197, 18, 18, 1) } }" `); }); test("Breakpoints", async () => { const fragment = await toWebstudioFragment({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "c06c94aa-e2cd-fa7a-d8f8-574b474a20fa", type: "Block", tag: "div", classes: ["81fbefba-d2de-9cc2-81bf-3a929d4eb219"], children: [], }, ], styles: [ { _id: "81fbefba-d2de-9cc2-81bf-3a929d4eb219", fake: false, type: "class", name: "Div Block 2", namespace: "", comb: "", styleLess: "background-color: hsla(191, 100.00%, 50.00%, 1.00);", variants: { large: { styleLess: "background-color: hsla(150, 100.00%, 50.00%, 1.00);", }, xl: { styleLess: "", }, medium: { styleLess: "background-color: hsla(256, 100.00%, 50.00%, 1.00);", }, small: { styleLess: "background-color: hsla(308, 100.00%, 50.00%, 1.00);", }, tiny: { styleLess: "background-color: hsla(359, 100.00%, 50.00%, 1.00);", }, }, }, ], assets: [], }, }); expect(fragment.breakpoints).toEqual([ { id: expect.any(String), label: "base" }, { id: expect.any(String), label: "large", minWidth: 1280 }, { id: expect.any(String), label: "medium", maxWidth: 991 }, { id: expect.any(String), label: "small", maxWidth: 767 }, { id: expect.any(String), label: "tiny", maxWidth: 479 }, ]); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { Div Block 2 { background-color: rgba(0, 208, 255, 1) } } @media all and (max-width: 991px) { Div Block 2 { background-color: rgba(68, 0, 255, 1) } } @media all and (max-width: 767px) { Div Block 2 { background-color: rgba(255, 0, 221, 1) } } @media all and (max-width: 479px) { Div Block 2 { background-color: rgba(255, 0, 4, 1) } } @media all and (min-width: 1280px) { Div Block 2 { background-color: rgba(0, 255, 128, 1) } }" `); }); test("background images", async () => { const input = WfData.parse({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "2e9842a4-ac18-9d21-894b-026c6eb20441", type: "Block", tag: "div", classes: ["98133834-439c-9a8c-7e9f-c3186f2fa45f"], children: [], data: { text: false, }, }, ], styles: [ { _id: "98133834-439c-9a8c-7e9f-c3186f2fa45f", fake: false, type: "class", name: "Div Block", namespace: "", comb: "", styleLess: "height: 400px; background-image: linear-gradient(180deg, hsla(0, 0.00%, 0.00%, 0.11), white), @img_667d0b7769e0cc3754b584f6, @img_667d0fe180995eadc1534a26, @img_example_bg; background-position: 0px 0px, 550px 0px, 0px 0px,0px 0px; background-size: auto, contain, auto, auto; background-repeat: repeat, no-repeat, repeat,repeat; background-attachment: scroll, fixed, scroll, fixed;", variants: {}, children: [], }, ], assets: [ { cdnUrl: "https://uploads-ssl.webflow.com/667c32290bd6159c18dca9a0/667d0b7769e0cc3754b584f6_IMG_2882%20(1).png", siteId: "667c32290bd6159c18dca9a0", width: 800, height: 600, fileName: "IMG_2882 (1).png", createdOn: "2024-06-27T06:49:27.100Z", origFileName: "IMG_2882 (1).png", fileHash: "36f49907757795f0a4ecfcfdfc483115", variants: [ { origFileName: "IMG_2882%20(1)-p-500.png", fileName: "667d0b7769e0cc3754b584f6_IMG_2882%20(1)-p-500.png", format: "png", size: 192728, width: 500, quality: 100, cdnUrl: "https://daks2k3a4ib2z.cloudfront.net/667c32290bd6159c18dca9a0/667d0b7769e0cc3754b584f6_IMG_2882%20(1)-p-500.png", s3Url: "https://s3.amazonaws.com/webflow-prod-assets/667c32290bd6159c18dca9a0/667d0b7769e0cc3754b584f6_IMG_2882%20(1)-p-500.png", }, ], mimeType: "image/png", s3Url: "https://s3.amazonaws.com/webflow-prod-assets/667c32290bd6159c18dca9a0/667d0b7769e0cc3754b584f6_IMG_2882%20(1).png", thumbUrl: "", _id: "667d0b7769e0cc3754b584f6", markedAsDeleted: false, fileSize: 862053, }, { cdnUrl: "https://uploads-ssl.webflow.com/667c32290bd6159c18dca9a0/667d0fe180995eadc1534a26_%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82%20%D0%9C%D0%B8%D1%80%20%3A%20%252F%20.webp", siteId: "667c32290bd6159c18dca9a0", width: 1024, height: 1024, fileName: "Привет Мир : %2F .webp", createdOn: "2024-06-27T07:08:17.010Z", origFileName: "Привет Мир : %2F .webp", fileHash: "d86e52a94c04120f455b276effa59046", variants: [ { origFileName: "%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82%20%D0%9C%D0%B8%D1%80%20%3A%20%252F%20-p-500.webp", fileName: "667d0fe180995eadc1534a26_%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82%20%D0%9C%D0%B8%D1%80%20%3A%20%252F%20-p-500.webp", format: "webp", size: 26992, width: 500, quality: 100, cdnUrl: "https://daks2k3a4ib2z.cloudfront.net/667c32290bd6159c18dca9a0/667d0fe180995eadc1534a26_%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82%20%D0%9C%D0%B8%D1%80%20%3A%20%252F%20-p-500.webp", s3Url: "https://s3.amazonaws.com/webflow-prod-assets/667c32290bd6159c18dca9a0/667d0fe180995eadc1534a26_%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82%20%D0%9C%D0%B8%D1%80%20%3A%20%252F%20-p-500.webp", }, { origFileName: "%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82%20%D0%9C%D0%B8%D1%80%20%3A%20%252F%20-p-800.webp", fileName: "667d0fe180995eadc1534a26_%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82%20%D0%9C%D0%B8%D1%80%20%3A%20%252F%20-p-800.webp", format: "webp", size: 45964, width: 800, quality: 100, cdnUrl: "https://daks2k3a4ib2z.cloudfront.net/667c32290bd6159c18dca9a0/667d0fe180995eadc1534a26_%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82%20%D0%9C%D0%B8%D1%80%20%3A%20%252F%20-p-800.webp", s3Url: "https://s3.amazonaws.com/webflow-prod-assets/667c32290bd6159c18dca9a0/667d0fe180995eadc1534a26_%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82%20%D0%9C%D0%B8%D1%80%20%3A%20%252F%20-p-800.webp", }, ], mimeType: "image/webp", s3Url: "https://s3.amazonaws.com/webflow-prod-assets/667c32290bd6159c18dca9a0/667d0fe180995eadc1534a26_%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82%20%D0%9C%D0%B8%D1%80%20%3A%20%252F%20.webp", thumbUrl: "", _id: "667d0fe180995eadc1534a26", markedAsDeleted: false, fileSize: 191270, }, ], }, }); const fragment = await toWebstudioFragment(input); const bgStyle = fragment.styles.find( (style) => style.property === "backgroundImage" ); expect(bgStyle).toBeDefined(); expect(bgStyle?.value.type).toEqual("layers"); const layers = bgStyle?.value; invariant(layers?.type === "layers"); const imgA = layers.value[1]; const imgB = layers.value[2]; const noneLayer = layers.value[3]; invariant(imgA.type === "image"); invariant(imgA.value.type === "url"); invariant(imgB.type === "image"); invariant(imgB.value.type === "url"); expect(imgA.value.url).toEqual(input.payload.assets[0].s3Url); expect(imgB.value.url).toEqual(input.payload.assets[1].s3Url); expect(noneLayer.type).toEqual("keyword"); invariant(noneLayer.type === "keyword"); expect(noneLayer.value).toEqual("none"); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { Div Block { height: 400px; background-image: linear-gradient(180deg,hsla(0,0.00%,0.00%,0.11),white), url("https://s3.amazonaws.com/webflow-prod-assets/667c32290bd6159c18dca9a0/667d0b7769e0cc3754b584f6_IMG_2882%20(1).png"), url("https://s3.amazonaws.com/webflow-prod-assets/667c32290bd6159c18dca9a0/667d0fe180995eadc1534a26_%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82%20%D0%9C%D0%B8%D1%80%20%3A%20%252F%20.webp"), none; background-size: auto, contain, auto, auto; background-repeat: repeat, no-repeat, repeat, repeat; background-attachment: scroll, fixed, scroll, fixed; background-position: 0px 0px, 550px 0px, 0px 0px, 0px 0px } }" `); }); test("@raw webflow custom properties", async () => { const input = WfData.parse({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "89ed49d8-2d32-5bab-22e1-264acb8d2d84", type: "Block", tag: "div", classes: ["393ac121-a395-7678-e598-994e30fb7880"], children: [], data: { text: false, tag: "div" }, }, ], styles: [ { _id: "393ac121-a395-7678-e598-994e30fb7880", fake: false, type: "class", name: "Div Block", namespace: "", comb: "", styleLess: "color: @raw<|black|>; background-color: @raw<|red|>;", variants: {}, children: [], }, ], assets: [], }, }); const fragment = await toWebstudioFragment(input); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { Div Block { color: black; background-color: red } }" `); }); test("append transparent color when background-clip is used", async () => { const input = WfData.parse({ type: "@webflow/XscpData", payload: { nodes: [ { _id: "15c3fb65-b871-abe4-f9a4-3747c8a882e0", type: "Heading", tag: "h2", classes: ["069649be-a33a-1a9a-3763-c0bd9d1f3a3d"], children: ["b69a5869-f046-5a0c-151e-9b134a6852aa"], data: { tag: "h2", devlink: { runtimeProps: {}, slot: "" }, displayName: "", attr: { id: "" }, xattr: [], search: { exclude: false }, visibility: { conditions: [] }, }, }, { _id: "b69a5869-f046-5a0c-151e-9b134a6852aa", text: true, v: "Protect your systems securely with Prism", }, ], styles: [ { _id: "069649be-a33a-1a9a-3763-c0bd9d1f3a3d", fake: false, type: "class", name: "H2 Heading 2", namespace: "", comb: "", styleLess: "background-image: linear-gradient(350deg, hsla(256.3636363636363, 72.13%, 23.92%, 0.00), hsla(256.2162162162162, 72.55%, 80.00%, 1.00) 49%, #bba7f1); color: hsla(0, 0.00%, 100.00%, 1.00); background-clip: text;", variants: {}, children: [], createdBy: "58b4b8186ceb395341fcf640", origin: null, selector: null, }, ], assets: [], }, }); const fragment = await toWebstudioFragment(input); expect(toCss(fragment)).toMatchInlineSnapshot(` "@media all { h2 { margin-bottom: 10px; font-weight: bold; margin-top: 20px; font-size: 32px; line-height: 36px } H2 Heading 2 { background-image: linear-gradient(350deg,hsla(256.3636363636363,72.13%,23.92%,0.00),hsla(256.2162162162162,72.55%,80.00%,1.00) 49%,#bba7f1); -webkit-background-clip: text; background-clip: text; color: transparent } }" `); }); });