import { findClosestInsertable, getComponentTemplateData, getImageAssetFragment, insertWebstudioComponentAt, insertWebstudioFragmentAt, } from "./insert"; import { insertWebstudioElementAt } from "./insert"; import { enableMapSet } from "immer"; import { describe, test, expect, beforeEach, vi } from "vitest"; import { toast } from "@webstudio-is/design-system"; import { lintExpression } from "@webstudio-is/expression"; import type { Project } from "@webstudio-is/project"; import { createDefaultPages } from "@webstudio-is/project-build"; import { createTemplateComponentFixture, ws, css, expression, renderTemplate, renderData, token, type TemplateMeta, } from "@webstudio-is/template"; import * as defaultMetas from "@webstudio-is/sdk-components-react/metas"; import { coreTemplates } from "@webstudio-is/sdk-components-registry/core-templates"; import { componentIds } from "@webstudio-is/sdk-components-registry/components"; import type { Prop, WebstudioData, WebstudioFragment } from "@webstudio-is/sdk"; import { blockBodyComponent, blockComponent, coreMetas, elementComponent, encodeDataSourceVariable, executeExpression, isFormSubmission, } from "@webstudio-is/sdk"; import { $registeredComponentMetas, $registeredTemplates, } from "../nano-states"; import { $assets } from "~/shared/sync/data-stores"; import { $breakpoints, $dataSources, $instances, $pages, $project, $props, $styleSourceSelections, $styleSources, $styles, $resources, } from "~/shared/sync/data-stores"; import { registerContainers } from "../sync/sync-stores"; import { findAvailableVariables, getInstancePath, } from "@webstudio-is/project-build/runtime"; import { getInstanceKey, selectPage } from "../nano-states"; import { selectInstance } from "../nano-states"; import { $propValuesByInstanceSelector } from "../nano-states/props"; import { $dataSourceVariables } from "../nano-states/variables"; import { $selectedPageId } from "../nano-states/pages"; import { expectSlotsShareFragment } from "../slot-test-utils"; import { $selectedInstancePropsMetas } from "~/builder/features/settings-panel/shared"; import { $instanceTags } from "~/builder/features/style-panel/shared/model"; const Body = createTemplateComponentFixture("Body"); const Bold = createTemplateComponentFixture("Bold"); const Box = createTemplateComponentFixture("Box"); const Fragment = createTemplateComponentFixture("Fragment"); const Image = createTemplateComponentFixture("Image"); const ListItem = createTemplateComponentFixture("ListItem"); const Paragraph = createTemplateComponentFixture("Paragraph"); const Slot = createTemplateComponentFixture("Slot"); enableMapSet(); registerContainers(); $pages.set(createDefaultPages({ rootInstanceId: "" })); const defaultMetasMap = new Map( Object.entries({ ...defaultMetas, ...coreMetas }) ); $registeredComponentMetas.set(defaultMetasMap); const createFragment = ( fragment: Partial ): WebstudioFragment => ({ children: [], instances: [], styleSourceSelections: [], styleSources: [], breakpoints: [], styles: [], dataSources: [], resources: [], props: [], assets: [], ...fragment, }); const setDataStores = (data: Omit) => { $instances.set(data.instances); $breakpoints.set(data.breakpoints); $styleSources.set(data.styleSources); $styles.set(data.styles); $styleSourceSelections.set(data.styleSourceSelections); $dataSources.set(data.dataSources); $props.set(data.props); $assets.set(data.assets); $resources.set(data.resources); }; describe("insert webstudio element at", () => { beforeEach(() => { $pages.set( createDefaultPages({ homePageId: "homePageId", rootInstanceId: "bodyId" }) ); selectPage("homePageId"); $styleSourceSelections.set(new Map()); $styleSources.set(new Map()); $breakpoints.set(new Map()); $styles.set(new Map()); $dataSources.set(new Map()); $resources.set(new Map()); $props.set(new Map()); $assets.set(new Map()); }); test("insert element with div tag into body", async () => { $instances.set(renderData().instances); await insertWebstudioElementAt({ parentSelector: ["bodyId"], position: "end", }); const [_bodyId, newInstanceId] = $instances.get().keys(); expect($instances.get()).toEqual( renderData( ).instances ); }); test("insert element with li tag into ul", async () => { $instances.set( renderData( ).instances ); await insertWebstudioElementAt({ parentSelector: ["listId", "bodyId"], position: "end", }); const [_bodyId, _listId, newInstanceId] = $instances.get().keys(); expect($instances.get()).toEqual( renderData( ).instances ); }); test("insert element into selected instance", async () => { $pages.set( createDefaultPages({ homePageId: "homePageId", rootInstanceId: "bodyId" }) ); $instances.set( renderData( ).instances ); selectPage("homePageId"); selectInstance(["divId", "bodyId"]); await insertWebstudioElementAt(); const [_bodyId, _divId, newInstanceId] = $instances.get().keys(); expect($instances.get()).toEqual( renderData( ).instances ); }); test("insert element into selected legacy slot with direct children", async () => { $pages.set( createDefaultPages({ homePageId: "homePageId", rootInstanceId: "bodyId" }) ); $instances.set( renderData( ).instances ); selectPage("homePageId"); selectInstance(["slotId", "bodyId"]); await insertWebstudioElementAt(); const instances = $instances.get(); const fragmentId = instances.get("slotId")?.children[0]?.value; const newInstanceId = Array.from(instances.keys()).find( (id) => id !== "bodyId" && id !== "slotId" && id !== "boxId" && id !== fragmentId ); expect(instances).toEqual( renderData( ).instances ); }); test("insert element into shared slot content", async () => { $pages.set( createDefaultPages({ homePageId: "homePageId", rootInstanceId: "bodyId" }) ); $instances.set( renderData( {/* same ids */} ).instances ); selectPage("homePageId"); selectInstance(["slot1", "bodyId"]); await insertWebstudioElementAt(); const newInstanceId = $instances.get().get("fragment")?.children[1]?.value; expect($instances.get().get("slot1")?.children).toEqual([ { type: "id", value: "fragment" }, ]); expect($instances.get().get("slot2")?.children).toEqual([ { type: "id", value: "fragment" }, ]); expect($instances.get().get("fragment")?.children).toEqual([ { type: "id", value: "box" }, { type: "id", value: newInstanceId }, ]); expect($instances.get().get(newInstanceId ?? "")?.component).toBe( elementComponent ); }); test("insert element at start of shared slot content", async () => { $instances.set( renderData( {/* same ids */} ).instances ); await insertWebstudioElementAt({ parentSelector: ["slot1", "bodyId"], position: 0, }); const newInstanceId = $instances.get().get("fragment")?.children[0]?.value; expect($instances.get().get("slot1")?.children).toEqual([ { type: "id", value: "fragment" }, ]); expect($instances.get().get("slot2")?.children).toEqual([ { type: "id", value: "fragment" }, ]); expect($instances.get().get("fragment")?.children).toEqual([ { type: "id", value: newInstanceId }, { type: "id", value: "box" }, ]); expect($instances.get().get(newInstanceId ?? "")?.component).toBe( elementComponent ); }); test("insert element into nested shared slot content", async () => { $pages.set( createDefaultPages({ homePageId: "homePageId", rootInstanceId: "bodyId" }) ); $instances.set( renderData( {/* same ids */} ).instances ); selectPage("homePageId"); selectInstance(["div", "fragment", "slot1", "bodyId"]); await insertWebstudioElementAt(); const newInstanceId = $instances.get().get("div")?.children[0]?.value; expect($instances.get().get("slot1")?.children).toEqual([ { type: "id", value: "fragment" }, ]); expect($instances.get().get("slot2")?.children).toEqual([ { type: "id", value: "fragment" }, ]); expect($instances.get().get("fragment")?.children).toEqual([ { type: "id", value: "div" }, ]); expect($instances.get().get("div")?.children).toEqual([ { type: "id", value: newInstanceId }, ]); expect($instances.get().get(newInstanceId ?? "")?.component).toBe( elementComponent ); }); test("insert element into closest non-textual container", async () => { $pages.set( createDefaultPages({ homePageId: "homePageId", rootInstanceId: "bodyId" }) ); $instances.set( renderData( text ).instances ); selectPage("homePageId"); selectInstance(["divId", "bodyId"]); await insertWebstudioElementAt(); const [_bodyId, _divId, _spanId, newInstanceId] = $instances.get().keys(); expect($instances.get()).toEqual( renderData( text ).instances ); }); test("insert element into closest non-empty container", async () => { $pages.set( createDefaultPages({ homePageId: "homePageId", rootInstanceId: "bodyId" }) ); $instances.set( renderData( ).instances ); selectPage("homePageId"); selectInstance(["imgId", "bodyId"]); await insertWebstudioElementAt(); const [_bodyId, _imgId, _spanId, newInstanceId] = $instances.get().keys(); expect($instances.get()).toEqual( renderData( ).instances ); }); test("reports unresolved explicit element insert target", async () => { const toastError = vi.spyOn(toast, "error").mockImplementation(() => ""); $instances.set(renderData().instances); expect( await insertWebstudioElementAt({ parentSelector: ["missingId"], position: "end", }) ).toBe(false); expect(toastError).toHaveBeenCalledWith( "Cannot insert: the target no longer exists." ); expect($instances.get()).toEqual( renderData().instances ); toastError.mockRestore(); }); }); describe("insert webstudio fragment at", () => { beforeEach(() => { $project.set({ id: "current_project" } as Project); $styleSourceSelections.set(new Map()); $styleSources.set(new Map()); $breakpoints.set(new Map()); $styles.set(new Map()); $dataSources.set(new Map()); $resources.set(new Map()); $props.set(new Map()); $assets.set(new Map()); }); test("insert multiple instances", async () => { $instances.set(renderData().instances); await insertWebstudioFragmentAt( renderTemplate( <> ), { parentSelector: ["bodyId"], position: "end", } ); expect($instances.get()).toEqual( renderData( ).instances ); }); test("allows legacy fragment warnings through the internal paste context", async () => { $pages.set( createDefaultPages({ homePageId: "homePageId", rootInstanceId: "bodyId" }) ); selectPage("homePageId"); $instances.set(renderData().instances); const onContentModelWarnings = vi.fn(); const inserted = insertWebstudioFragmentAt( renderTemplate( ), { parentSelector: ["bodyId"], position: "end" }, undefined, { allowContentModelWarnings: true, onContentModelWarnings } ); expect(inserted).toBe(true); expect($instances.get().get("bodyId")?.children).toHaveLength(1); expect(onContentModelWarnings).toHaveBeenCalledWith([ expect.objectContaining({ message: "Placing

element inside a