From aa527ae2e973169fb6d62db19b75786947aa72dd Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Sat, 8 Feb 2025 19:06:22 +0400 Subject: [PATCH] refactor: rewrite more copy paste tests with jsx template (#4845) Ref https://github.com/webstudio-is/webstudio/issues/4093 1. rewrote tests with jsx template 2. fixed copying prop resources with slots --- apps/builder/app/shared/copy-paste.test.tsx | 398 +++++++++- .../app/shared/instance-utils.test.tsx | 724 ------------------ apps/builder/app/shared/instance-utils.ts | 19 +- 3 files changed, 392 insertions(+), 749 deletions(-) diff --git a/apps/builder/app/shared/copy-paste.test.tsx b/apps/builder/app/shared/copy-paste.test.tsx index 0c9fe34dbdeb..c4fe0b053176 100644 --- a/apps/builder/app/shared/copy-paste.test.tsx +++ b/apps/builder/app/shared/copy-paste.test.tsx @@ -3,6 +3,7 @@ import stripIndent from "strip-indent"; import { createRegularStyleSheet } from "@webstudio-is/css-engine"; import { createDefaultPages } from "@webstudio-is/project-build"; import { + encodeDataVariableId, getStyleDeclKey, ROOT_INSTANCE_ID, type WebstudioData, @@ -17,6 +18,8 @@ import { Variable, ResourceValue, ActionValue, + renderTemplate, + Parameter, } from "@webstudio-is/template"; import type { Project } from "@webstudio-is/project"; import { @@ -26,11 +29,6 @@ import { } from "./instance-utils"; import { $project } from "./nano-states"; -const pages = createDefaultPages({ - rootInstanceId: "bodyId", - systemDataSourceId: "", -}); - $project.set({ id: "current_project" } as Project); const createStub = (element: JSX.Element) => { @@ -91,6 +89,78 @@ const insertStyles = ({ } }; +test("extract the instance by id and all its descendants including slot instances", () => { + const data = renderData( + <$.Body ws:id="bodyId"> + <$.Box ws:id="boxId"> + <$.Slot> + <$.Fragment> + + + <$.Text ws:id="textId"> + + ); + const { instances } = extractWebstudioFragment(data, "boxId"); + expect(instances).toEqual([ + expect.objectContaining({ component: "Box" }), + expect.objectContaining({ component: "Slot" }), + expect.objectContaining({ component: "Fragment" }), + ]); +}); + +test("insert instances with slots", () => { + const data = renderData(<$.Body ws:id="bodyId">); + const fragment = renderTemplate( + <$.Slot ws:id="slotId"> + <$.Fragment ws:id="fragmentId"> + <$.Box ws:id="boxId"> + + + ); + expect(data.instances.size).toEqual(1); + insertWebstudioFragmentCopy({ + data, + fragment, + availableDataSources: new Set(), + }); + expect(data.instances.size).toEqual(4); + insertWebstudioFragmentCopy({ + data, + fragment, + availableDataSources: new Set(), + }); + expect(data.instances.size).toEqual(5); + expect(Array.from(data.instances.values())).toEqual([ + expect.objectContaining({ component: "Body" }), + // id of slot instances are preserved + expect.objectContaining({ component: "Fragment", id: "fragmentId" }), + expect.objectContaining({ component: "Box", id: "boxId" }), + expect.objectContaining({ component: "Slot" }), + expect.objectContaining({ component: "Slot" }), + ]); +}); + +test("insert instances with multiple roots", () => { + const data = renderData(<$.Body ws:id="bodyId">); + const fragment = renderTemplate( + <> + <$.Box> + <$.Text> + + <$.Box> + <$.Text> + + + ); + expect(data.instances.size).toEqual(1); + insertWebstudioFragmentCopy({ + data, + fragment, + availableDataSources: new Set(), + }); + expect(data.instances.size).toEqual(5); +}); + test("should add :root local styles", () => { const oldProject = createStub( { ); }); +describe("props", () => { + test("extract all props bound to fragment instances", () => { + const data = renderData( + <$.Body ws:id="bodyId" data-body=""> + <$.Box ws:id="boxId" data-box=""> + <$.Text ws:id="textId" data-text=""> + + + ); + const { props } = extractWebstudioFragment(data, "boxId"); + expect(props).toEqual([ + expect.objectContaining({ name: "data-box" }), + expect.objectContaining({ name: "data-text" }), + ]); + }); + + test("insert props with new ids", () => { + const data = renderData(<$.Body ws:id="bodyId">); + const fragment = renderTemplate( + <$.Box ws:id="boxId" data-box=""> + <$.Text ws:id="textId" data-text=""> + + ); + insertWebstudioFragmentCopy({ + data, + fragment, + availableDataSources: new Set(), + }); + expect(Array.from(data.props.values())).toEqual([ + expect.objectContaining({ + id: expect.toSatisfy((value) => value !== fragment.props[0].id), + name: "data-box", + }), + expect.objectContaining({ + id: expect.toSatisfy((value) => value !== fragment.props[1].id), + name: "data-text", + }), + ]); + }); + + test("preserve ids when insert props from slots", () => { + const data = renderData(<$.Body ws:id="bodyId">); + const fragment = renderTemplate( + <$.Slot> + <$.Fragment> + <$.Box ws:id="boxId" data-box=""> + <$.Text ws:id="textId" data-text=""> + + + + ); + insertWebstudioFragmentCopy({ + data, + fragment, + availableDataSources: new Set(), + }); + expect(Array.from(data.props.values())).toEqual([ + expect.objectContaining({ + id: fragment.props[0].id, + name: "data-box", + }), + expect.objectContaining({ + id: fragment.props[1].id, + name: "data-text", + }), + ]); + }); +}); + describe("variables", () => { test("extract variable", () => { const boxVariable = new Variable("Box Variable", ""); @@ -221,7 +360,7 @@ describe("variables", () => { <$.Box ws:id="boxId" vars={expression`${boxVariable}`}> ); - const fragment = extractWebstudioFragment({ pages, ...data }, "boxId"); + const fragment = extractWebstudioFragment(data, "boxId"); expect(fragment.dataSources).toEqual([ expect.objectContaining({ id: "0", type: "variable" }), ]); @@ -248,7 +387,7 @@ describe("variables", () => { ); - const fragment = extractWebstudioFragment({ pages, ...data }, "boxId"); + const fragment = extractWebstudioFragment(data, "boxId"); expect(fragment.dataSources).toEqual([]); expect(fragment.props).toEqual([ expect.objectContaining({ @@ -274,6 +413,103 @@ describe("variables", () => { ]); }); + test("insert variables with new ids", () => { + const boxParameter = new Parameter("My Parameter"); + const data = renderData(<$.Body ws:id="bodyId">); + const fragment = renderTemplate( + <$.Box + ws:id="boxId" + vars={expression`${boxParameter}`} + action={new ActionValue([], expression`${boxParameter}`)} + parameter={boxParameter} + > + {expression`${boxParameter}`} + + ); + insertWebstudioFragmentCopy({ + data, + fragment, + availableDataSources: new Set(), + }); + const [newDataSourceId] = data.dataSources.keys(); + expect(Array.from(data.dataSources.values())).toEqual([ + expect.objectContaining({ + id: expect.toSatisfy((value) => value !== fragment.dataSources[0].id), + name: "My Parameter", + }), + ]); + expect(Array.from(data.props.values())).toEqual([ + expect.objectContaining({ + name: "vars", + value: encodeDataVariableId(newDataSourceId), + }), + expect.objectContaining({ + name: "action", + value: [ + { + type: "execute", + args: [], + code: encodeDataVariableId(newDataSourceId), + }, + ], + }), + expect.objectContaining({ + name: "parameter", + value: newDataSourceId, + }), + ]); + }); + + test("preserve ids when insert variables from portals", () => { + const boxParameter = new Parameter("My Parameter"); + const data = renderData(<$.Body ws:id="bodyId">); + const fragment = renderTemplate( + <$.Slot> + <$.Fragment> + <$.Box + ws:id="boxId" + vars={expression`${boxParameter}`} + action={new ActionValue([], expression`${boxParameter}`)} + parameter={boxParameter} + > + {expression`${boxParameter}`} + + + + ); + insertWebstudioFragmentCopy({ + data, + fragment, + availableDataSources: new Set(), + }); + expect(Array.from(data.dataSources.values())).toEqual([ + expect.objectContaining({ + id: fragment.dataSources[0].id, + name: "My Parameter", + }), + ]); + expect(Array.from(data.props.values())).toEqual([ + expect.objectContaining({ + name: "vars", + value: encodeDataVariableId(fragment.dataSources[0].id), + }), + expect.objectContaining({ + name: "action", + value: [ + { + type: "execute", + args: [], + code: encodeDataVariableId(fragment.dataSources[0].id), + }, + ], + }), + expect.objectContaining({ + name: "parameter", + value: fragment.dataSources[0].id, + }), + ]); + }); + test("restore unset variables when insert fragment", () => { const bodyVariable = new Variable("Body Variable", ""); const data = renderData( @@ -289,9 +525,9 @@ describe("variables", () => { ); - const fragment = extractWebstudioFragment({ pages, ...data }, "boxId"); + const fragment = extractWebstudioFragment(data, "boxId"); insertWebstudioFragmentCopy({ - data: { pages, ...data }, + data, fragment, availableDataSources: findAvailableDataSources( data.dataSources, @@ -333,7 +569,7 @@ describe("resources", () => { <$.Box ws:id="boxId" vars={expression`${resourceVariable}`}> ); - const fragment = extractWebstudioFragment({ pages, ...data }, "boxId"); + const fragment = extractWebstudioFragment(data, "boxId"); expect(fragment.dataSources).toEqual([ expect.objectContaining({ id: "1", type: "variable" }), expect.objectContaining({ id: "0", type: "resource" }), @@ -360,7 +596,7 @@ describe("resources", () => { <$.Box ws:id="boxId" vars={expression`${resourceVariable}`}> ); - const fragment = extractWebstudioFragment({ pages, ...data }, "boxId"); + const fragment = extractWebstudioFragment(data, "boxId"); expect(fragment.dataSources).toEqual([ expect.objectContaining({ id: "1", type: "resource" }), ]); @@ -386,9 +622,9 @@ describe("resources", () => { <$.Box ws:id="boxId" vars={expression`${resourceVariable}`}> ); - const fragment = extractWebstudioFragment({ pages, ...data }, "boxId"); + const fragment = extractWebstudioFragment(data, "boxId"); insertWebstudioFragmentCopy({ - data: { pages, ...data }, + data, fragment, availableDataSources: findAvailableDataSources( data.dataSources, @@ -425,7 +661,7 @@ describe("resources", () => { <$.Box ws:id="boxId" resource={resourceProp}> ); - const fragment = extractWebstudioFragment({ pages, ...data }, "boxId"); + const fragment = extractWebstudioFragment(data, "boxId"); expect(fragment.dataSources).toEqual([ expect.objectContaining({ id: "1", type: "variable" }), ]); @@ -451,7 +687,7 @@ describe("resources", () => { <$.Box ws:id="boxId" resource={resourceProp}> ); - const fragment = extractWebstudioFragment({ pages, ...data }, "boxId"); + const fragment = extractWebstudioFragment(data, "boxId"); expect(fragment.dataSources).toEqual([]); expect(fragment.resources).toEqual([ expect.objectContaining({ @@ -475,9 +711,9 @@ describe("resources", () => { <$.Box ws:id="boxId" resource={resourceProp}> ); - const fragment = extractWebstudioFragment({ pages, ...data }, "boxId"); + const fragment = extractWebstudioFragment(data, "boxId"); insertWebstudioFragmentCopy({ - data: { pages, ...data }, + data, fragment, availableDataSources: findAvailableDataSources( data.dataSources, @@ -500,4 +736,132 @@ describe("resources", () => { }), ]); }); + + test("insert resources with new ids", () => { + const boxVariable = new Variable("Box Variable", ""); + const resourceProp = new ResourceValue("Box Resource", { + url: expression`${boxVariable}`, + method: "get", + headers: [{ name: "auth", value: expression`${boxVariable}` }], + body: expression`${boxVariable}`, + }); + const resourceVariable = new ResourceValue("Box Resource", { + url: expression`${boxVariable}`, + method: "get", + headers: [{ name: "auth", value: expression`${boxVariable}` }], + body: expression`${boxVariable}`, + }); + const data = renderData(<$.Body ws:id="bodyId">); + const fragment = renderTemplate( + <$.Box + ws:id="boxId" + action={resourceProp} + vars={expression`${resourceVariable}`} + > + ); + insertWebstudioFragmentCopy({ + data, + fragment, + availableDataSources: new Set(), + }); + const [newPropResourceId, newVariableResourceId] = data.resources.keys(); + const [newBoxVariableId] = data.dataSources.keys(); + const newVariableIdentifier = encodeDataVariableId(newBoxVariableId); + expect(Array.from(data.dataSources.values())).toEqual([ + expect.objectContaining({ + name: "Box Variable", + }), + expect.objectContaining({ + name: "Box Resource", + resourceId: newVariableResourceId, + }), + ]); + expect(Array.from(data.resources.values())).toEqual([ + expect.objectContaining({ + id: expect.toSatisfy((value) => value !== fragment.resources[0].id), + url: newVariableIdentifier, + headers: [{ name: "auth", value: newVariableIdentifier }], + body: newVariableIdentifier, + }), + expect.objectContaining({ + id: expect.toSatisfy((value) => value !== fragment.resources[1].id), + url: newVariableIdentifier, + headers: [{ name: "auth", value: newVariableIdentifier }], + body: newVariableIdentifier, + }), + ]); + expect(Array.from(data.props.values())).toEqual([ + expect.objectContaining({ + name: "action", + value: newPropResourceId, + }), + expect.objectContaining({ name: "vars" }), + ]); + }); + + test("preserve ids when insert resource from slot", () => { + const boxVariable = new Variable("Box Variable", ""); + const resourceProp = new ResourceValue("Box Resource", { + url: expression`${boxVariable}`, + method: "get", + headers: [{ name: "auth", value: expression`${boxVariable}` }], + body: expression`${boxVariable}`, + }); + const resourceVariable = new ResourceValue("Box Resource", { + url: expression`${boxVariable}`, + method: "get", + headers: [{ name: "auth", value: expression`${boxVariable}` }], + body: expression`${boxVariable}`, + }); + const data = renderData(<$.Body ws:id="bodyId">); + const fragment = renderTemplate( + <$.Slot ws:id="slotId"> + <$.Fragment ws:id="fragmentId"> + <$.Box + ws:id="boxId" + action={resourceProp} + vars={expression`${resourceVariable}`} + > + + + ); + insertWebstudioFragmentCopy({ + data, + fragment, + availableDataSources: new Set(), + }); + expect(Array.from(data.dataSources.values())).toEqual([ + expect.objectContaining({ + name: "Box Variable", + }), + expect.objectContaining({ + name: "Box Resource", + resourceId: fragment.resources[1].id, + }), + ]); + const oldVariableIdentifier = encodeDataVariableId( + fragment.dataSources[0].id + ); + expect(Array.from(data.resources.values())).toEqual([ + expect.objectContaining({ + id: fragment.resources[0].id, + url: oldVariableIdentifier, + headers: [{ name: "auth", value: oldVariableIdentifier }], + body: oldVariableIdentifier, + }), + expect.objectContaining({ + id: fragment.resources[1].id, + url: oldVariableIdentifier, + headers: [{ name: "auth", value: oldVariableIdentifier }], + body: oldVariableIdentifier, + }), + ]); + expect(Array.from(data.props.values())).toEqual([ + expect.objectContaining({ + name: "action", + value: fragment.resources[0].id, + }), + expect.objectContaining({ name: "vars" }), + ]); + }); }); diff --git a/apps/builder/app/shared/instance-utils.test.tsx b/apps/builder/app/shared/instance-utils.test.tsx index 6b23621f0dd5..f0b7f088b1d5 100644 --- a/apps/builder/app/shared/instance-utils.test.tsx +++ b/apps/builder/app/shared/instance-utils.test.tsx @@ -26,7 +26,6 @@ import type { WsComponentMeta, } from "@webstudio-is/sdk"; import { - encodeDataSourceVariable, coreMetas, portalComponent, collectionComponent, @@ -143,14 +142,6 @@ const createStyleDeclPair = ( createStyleDecl(styleSourceId, breakpointId, property, value), ]; -const createProp = (instanceId: string, id: string, name: string): Prop => ({ - id, - instanceId, - name, - type: "string", - value: id, -}); - const createImageAsset = (id: string, name = "", projectId = ""): Asset => { return { id, @@ -880,36 +871,6 @@ describe("delete instance", () => { }); describe("extract webstudio fragment", () => { - test("collect the instance by id and all its descendants including portal instances", () => { - // body - // bodyChild1 - // slot - // slotChild - // bodyChild2 - $instances.set( - toMap([ - createInstance("body", "Body", [ - { type: "id", value: "bodyChild1" }, - { type: "id", value: "bodyChild2" }, - ]), - createInstance("bodyChild1", "Box", [{ type: "id", value: "slot" }]), - createInstance("slot", "Slot", [{ type: "id", value: "slotChild" }]), - createInstance("slotChild", "Box", []), - createInstance("bodyChild2", "Box", []), - ]) - ); - const { instances } = extractWebstudioFragment( - getWebstudioData(), - "bodyChild1" - ); - - expect(instances).toEqual([ - createInstance("bodyChild1", "Box", [{ type: "id", value: "slot" }]), - createInstance("slot", "Slot", [{ type: "id", value: "slotChild" }]), - createInstance("slotChild", "Box", []), - ]); - }); - test("collect all styles and breakpoints bound to fragment instances", () => { // body // box1 @@ -973,32 +934,6 @@ describe("extract webstudio fragment", () => { expect(breakpoints).toEqual([{ id: "base", label: "base" }]); }); - test("collect all props bound to fragment instances", () => { - // body - // box1 - // box2 - $instances.set( - toMap([ - createInstance("body", "Body", [{ type: "id", value: "box1" }]), - createInstance("box1", "Box", [{ type: "id", value: "box2" }]), - createInstance("box2", "Box", []), - ]) - ); - $props.set( - toMap([ - createProp("body", "bodyProp", "data-body"), - createProp("box1", "box1Prop", "data-box1"), - createProp("box2", "box2Prop", "data-box2"), - ]) - ); - const { props } = extractWebstudioFragment(getWebstudioData(), "box1"); - - expect(props).toEqual([ - createProp("box1", "box1Prop", "data-box1"), - createProp("box2", "box2Prop", "data-box2"), - ]); - }); - test("collect assets from props and styles withiin fragment instances", () => { // body // box1 @@ -1085,109 +1020,6 @@ describe("extract webstudio fragment", () => { createFontAsset("asset6", "font2"), ]); }); - - test("collect resources within instances", () => { - // body - // box1 - // box2 - $instances.set( - toMap([ - createInstance("body", "Body", [ - { type: "id", value: "box1" }, - { type: "id", value: "box2" }, - ]), - createInstance("box1", "Box", []), - createInstance("box2", "Box", []), - ]) - ); - $resources.set( - toMap([ - { - id: "resource1", - name: "resource1", - url: `""`, - method: "get", - headers: [], - }, - { - id: "resource2", - name: "resource2", - url: `""`, - method: "get", - headers: [], - }, - { - id: "resource3", - name: "resource3", - url: `""`, - method: "get", - headers: [], - }, - ]) - ); - $dataSources.set( - toMap([ - { - id: "body$state", - scopeInstanceId: "box1", - name: "data1", - type: "resource", - resourceId: "resource1", - }, - { - id: "box1$state", - scopeInstanceId: "box1", - name: "data2", - type: "resource", - resourceId: "resource2", - }, - { - id: "box2$state", - scopeInstanceId: "box2", - type: "resource", - name: "data3", - resourceId: "resource3", - }, - ]) - ); - const { resources, dataSources } = extractWebstudioFragment( - getWebstudioData(), - "box1" - ); - - expect(resources).toEqual([ - { - id: "resource1", - name: "resource1", - url: `""`, - method: "get", - headers: [], - }, - { - id: "resource2", - name: "resource2", - url: `""`, - method: "get", - headers: [], - }, - ]); - expect(dataSources).toEqual([ - { - id: "body$state", - scopeInstanceId: "box1", - name: "data1", - type: "resource", - resourceId: "resource1", - }, - { - id: "box1$state", - scopeInstanceId: "box1", - name: "data2", - type: "resource", - resourceId: "resource2", - }, - ]); - }); }); describe("insert webstudio fragment copy", () => { @@ -1339,409 +1171,6 @@ describe("insert webstudio fragment copy", () => { ]); }); - test("insert instances", () => { - const data = getWebstudioDataStub(); - insertWebstudioFragmentCopy({ - data, - fragment: { - ...emptyFragment, - instances: [ - { - type: "instance", - id: "box", - component: "Box", - children: [], - }, - ], - }, - availableDataSources: new Set(), - }); - expect(Array.from(data.instances.keys())).toEqual([ - expect.not.stringMatching("box"), - ]); - }); - - test("insert instances with portals", () => { - // portal - // fragment - // box - const instancesWithPortals: Instance[] = [ - { - type: "instance", - id: "portal", - component: portalComponent, - children: [{ type: "id", value: "fragment" }], - }, - { - type: "instance", - id: "fragment", - component: "Fragment", - children: [{ type: "id", value: "box" }], - }, - { - type: "instance", - id: "box", - component: "Box", - children: [{ type: "text", value: "First" }], - }, - ]; - const data = getWebstudioDataStub(); - - insertWebstudioFragmentCopy({ - data, - fragment: { - ...emptyFragment, - instances: instancesWithPortals, - }, - availableDataSources: new Set(), - }); - - expect(Array.from(data.instances.values())).toEqual([ - { - type: "instance", - id: "fragment", - component: "Fragment", - children: [{ type: "id", value: "box" }], - }, - { - type: "instance", - id: "box", - component: "Box", - children: [{ type: "text", value: "First" }], - }, - { - type: "instance", - id: expect.not.stringMatching("portal"), - component: portalComponent, - children: [{ type: "id", value: "fragment" }], - }, - ]); - - // change portal content and make sure it does not break - // when stale portal with same id is inserted - data.instances.delete("box"); - data.instances.set("fragment", { - type: "instance", - id: "fragment", - component: "Fragment", - children: [], - }); - insertWebstudioFragmentCopy({ - data, - fragment: { - ...emptyFragment, - instances: instancesWithPortals, - }, - availableDataSources: new Set(), - }); - - expect(Array.from(data.instances.values())).toEqual([ - { - type: "instance", - id: "fragment", - component: "Fragment", - children: [], - }, - { - type: "instance", - id: expect.not.stringMatching("portal"), - component: portalComponent, - children: [{ type: "id", value: "fragment" }], - }, - { - type: "instance", - id: expect.not.stringMatching("portal"), - component: portalComponent, - children: [{ type: "id", value: "fragment" }], - }, - ]); - }); - - test("insert props with new ids", () => { - const data = getWebstudioDataStub(); - insertWebstudioFragmentCopy({ - data, - fragment: { - ...emptyFragment, - props: [ - { - id: "prop1", - instanceId: "body", - name: "myProp1", - type: "string", - value: "", - }, - ], - }, - availableDataSources: new Set(), - }); - expect(Array.from(data.props.values())).toEqual([ - { - id: expect.not.stringMatching("prop1"), - instanceId: expect.not.stringMatching("body"), - name: "myProp1", - type: "string", - value: "", - }, - ]); - }); - - test("insert props from portals with old ids", () => { - const data = getWebstudioDataStub(); - insertWebstudioFragmentCopy({ - data, - fragment: { - ...emptyFragment, - // portal - // fragment - instances: [ - { - type: "instance", - id: "portal", - component: portalComponent, - children: [{ type: "id", value: "fragment" }], - }, - { - type: "instance", - id: "fragment", - component: "Fragment", - children: [], - }, - ], - props: [ - { - id: "prop1", - instanceId: "fragment", - name: "myProp1", - type: "string", - value: "", - }, - ], - }, - availableDataSources: new Set(), - }); - expect(Array.from(data.props.values())).toEqual([ - { - id: "prop1", - instanceId: "fragment", - name: "myProp1", - type: "string", - value: "", - }, - ]); - }); - - test("insert data sources with new ids", () => { - const data = getWebstudioDataStub(); - insertWebstudioFragmentCopy({ - data, - fragment: { - ...emptyFragment, - dataSources: [ - { - id: "variableId", - scopeInstanceId: "body", - type: "variable", - name: "myVariable", - value: { type: "string", value: "" }, - }, - ], - instances: [ - createInstance("body", "Body", [ - { type: "expression", value: "$ws$dataSource$variableId" }, - ]), - ], - props: [ - { - id: "expressionId", - instanceId: "body", - name: "myProp1", - type: "expression", - value: "$ws$dataSource$variableId", - }, - { - id: "actionId", - instanceId: "body", - name: "myProp2", - type: "action", - value: [ - { - type: "execute", - args: [], - code: `$ws$dataSource$variableId = ""`, - }, - ], - }, - { - id: "parameterId", - instanceId: "body", - name: "myProp3", - type: "parameter", - value: "variableId", - }, - ], - }, - availableDataSources: new Set(), - }); - const [newVariableId] = data.dataSources.keys(); - const [newInstanceId] = data.instances.keys(); - expect(newVariableId).not.toEqual("variableId"); - expect(Array.from(data.dataSources.values())).toEqual([ - { - id: newVariableId, - scopeInstanceId: newInstanceId, - type: "variable", - name: "myVariable", - value: { type: "string", value: "" }, - }, - ]); - expect(Array.from(data.instances.values())).toEqual([ - createInstance(newInstanceId, "Body", [ - { type: "expression", value: encodeDataSourceVariable(newVariableId) }, - ]), - ]); - expect(Array.from(data.props.values())).toEqual([ - { - id: expect.not.stringMatching("expressionId"), - instanceId: newInstanceId, - name: "myProp1", - type: "expression", - value: encodeDataSourceVariable(newVariableId), - }, - { - id: expect.not.stringMatching("actionId"), - instanceId: newInstanceId, - name: "myProp2", - type: "action", - value: [ - { - type: "execute", - args: [], - code: `${encodeDataSourceVariable(newVariableId)} = ""`, - }, - ], - }, - { - id: expect.not.stringMatching("parameterId"), - instanceId: newInstanceId, - name: "myProp3", - type: "parameter", - value: newVariableId, - }, - ]); - }); - - test("insert data sources from portals with old ids", () => { - const data = getWebstudioDataStub(); - insertWebstudioFragmentCopy({ - data, - fragment: { - ...emptyFragment, - // portal - // fragment - instances: [ - { - type: "instance", - id: "portal", - component: portalComponent, - children: [{ type: "id", value: "fragment" }], - }, - { - type: "instance", - id: "fragment", - component: "Fragment", - children: [ - { type: "expression", value: "$ws$dataSource$variableId" }, - ], - }, - ], - dataSources: [ - { - id: "variableId", - scopeInstanceId: "fragment", - type: "variable", - name: "myVariable", - value: { type: "string", value: "" }, - }, - ], - props: [ - { - id: "expressionId", - instanceId: "fragment", - name: "myProp1", - type: "expression", - value: "$ws$dataSource$variableId", - }, - { - id: "actionId", - instanceId: "fragment", - name: "myProp2", - type: "action", - value: [ - { - type: "execute", - args: [], - code: `$ws$dataSource$variableId = ""`, - }, - ], - }, - { - id: "parameterId", - instanceId: "fragment", - name: "myProp3", - type: "parameter", - value: "variableId", - }, - ], - }, - availableDataSources: new Set(), - }); - expect(Array.from(data.dataSources.values())).toEqual([ - { - id: "variableId", - scopeInstanceId: "fragment", - type: "variable", - name: "myVariable", - value: { type: "string", value: "" }, - }, - ]); - expect(data.instances.get("fragment")).toEqual({ - type: "instance", - id: "fragment", - component: "Fragment", - children: [{ type: "expression", value: "$ws$dataSource$variableId" }], - }); - expect(Array.from(data.props.values())).toEqual([ - { - id: "expressionId", - instanceId: "fragment", - name: "myProp1", - type: "expression", - value: `$ws$dataSource$variableId`, - }, - { - id: "actionId", - instanceId: "fragment", - name: "myProp2", - type: "action", - value: [ - { - type: "execute", - args: [], - code: `$ws$dataSource$variableId = ""`, - }, - ], - }, - { - id: "parameterId", - instanceId: "fragment", - name: "myProp3", - type: "parameter", - value: `variableId`, - }, - ]); - }); - test("insert local styles with new ids and use merged breakpoint ids", () => { const breakpoints = toMap([{ id: "base", label: "base" }]); const data = getWebstudioDataStub({ breakpoints }); @@ -1860,159 +1289,6 @@ describe("insert webstudio fragment copy", () => { }, ]); }); - - test("insert resources with new ids", () => { - const data = getWebstudioDataStub(); - insertWebstudioFragmentCopy({ - data, - fragment: { - ...emptyFragment, - resources: [ - { - id: "resourceId", - name: "", - url: `$ws$dataSource$paramVariableId`, - method: "post", - headers: [ - { name: "auth", value: "$ws$dataSource$paramVariableId" }, - ], - body: `$ws$dataSource$paramVariableId`, - }, - ], - dataSources: [ - { - id: "paramVariableId", - scopeInstanceId: "body", - name: "myParam", - type: "variable", - value: { type: "string", value: "myParam" }, - }, - { - id: "resourceVariableId", - scopeInstanceId: "body", - name: "myResource", - type: "resource", - resourceId: "resourceId", - }, - ], - }, - availableDataSources: new Set(), - }); - const [newInstanceId] = data.instances.keys(); - const [newParamId, newResourceVariableId] = data.dataSources.keys(); - const [newResourceId] = data.resources.keys(); - expect(newResourceId).not.toEqual("resourceId"); - expect(Array.from(data.dataSources.values())).toEqual([ - { - id: newParamId, - scopeInstanceId: newInstanceId, - name: "myParam", - type: "variable", - value: { type: "string", value: "myParam" }, - }, - { - id: newResourceVariableId, - scopeInstanceId: newInstanceId, - name: "myResource", - type: "resource", - resourceId: newResourceId, - }, - ]); - expect(Array.from(data.resources.values())).toEqual([ - { - id: newResourceId, - name: "", - url: encodeDataSourceVariable(newParamId), - method: "post", - headers: [ - { name: "auth", value: encodeDataSourceVariable(newParamId) }, - ], - body: encodeDataSourceVariable(newParamId), - }, - ]); - }); - - test("insert resources from portals with old ids", () => { - const data = getWebstudioDataStub(); - insertWebstudioFragmentCopy({ - data, - fragment: { - ...emptyFragment, - // portal - // fragment - instances: [ - createInstance("portal", portalComponent, [ - { type: "id", value: "fragment" }, - ]), - createInstance("fragment", "Fragment", []), - ], - resources: [ - { - id: "resourceId", - name: "", - url: `""`, - method: "get", - headers: [], - }, - ], - dataSources: [ - { - id: "variableId", - scopeInstanceId: "fragment", - name: "myVariable", - type: "resource", - resourceId: "resourceId", - }, - ], - }, - availableDataSources: new Set(), - }); - expect(Array.from(data.dataSources.values())).toEqual([ - { - id: "variableId", - scopeInstanceId: "fragment", - name: "myVariable", - type: "resource", - resourceId: "resourceId", - }, - ]); - expect(Array.from(data.resources.values())).toEqual([ - { - id: "resourceId", - name: "", - url: `""`, - method: "get", - headers: [], - }, - ]); - }); - - test("insert instances with multiple roots", () => { - const data = getWebstudioDataStub(); - const { newInstanceIds } = insertWebstudioFragmentCopy({ - data, - fragment: { - ...emptyFragment, - // body1 - // box1 - // body2 - // box2 - // explicily define box first and then body - // to check first instance is not used as root - instances: [ - createInstance("box1", "Box", []), - createInstance("body1", "Body", [{ type: "id", value: "box1" }]), - createInstance("body2", "Body", [{ type: "id", value: "box2" }]), - createInstance("box2", "Box", []), - ], - resources: [], - dataSources: [], - }, - availableDataSources: new Set(), - }); - expect(data.instances.size).toEqual(4); - expect(newInstanceIds.size).toEqual(5); - }); }); describe("find closest insertable", () => { diff --git a/apps/builder/app/shared/instance-utils.ts b/apps/builder/app/shared/instance-utils.ts index 766af1c5e53d..cf36cc04cd23 100644 --- a/apps/builder/app/shared/instance-utils.ts +++ b/apps/builder/app/shared/instance-utils.ts @@ -520,7 +520,7 @@ const traverseStyleValue = ( }; export const extractWebstudioFragment = ( - data: WebstudioData, + data: Omit, rootInstanceId: string ): WebstudioFragment => { const { @@ -764,7 +764,7 @@ export const insertWebstudioFragmentCopy = ({ fragment, availableDataSources, }: { - data: WebstudioData; + data: Omit; fragment: WebstudioFragment; availableDataSources: Set; }) => { @@ -901,6 +901,15 @@ export const insertWebstudioFragmentCopy = ({ } } + for (const prop of fragment.props) { + if (instanceIds.has(prop.instanceId)) { + props.set(prop.id, prop); + if (prop.type === "resource") { + usedResourceIds.add(prop.value); + } + } + } + for (const resource of fragment.resources) { if (usedResourceIds.has(resource.id)) { resources.set(resource.id, resource); @@ -913,12 +922,6 @@ export const insertWebstudioFragmentCopy = ({ } } - for (const prop of fragment.props) { - if (instanceIds.has(prop.instanceId)) { - props.set(prop.id, prop); - } - } - // insert local style sources with their styles const instanceStyleSourceIds = new Set();