import { beforeEach, describe, expect, test, vi } from "vitest"; import { createEmptyWebstudioFragment } from "@webstudio-is/project-build/runtime"; import { inspectWebstudioJsxFragmentSyntax } from "@webstudio-is/project-build/transfer"; import { isLikelyWebstudioJsx, jsx } from "./plugin-jsx"; import { $project } from "../sync/data-stores"; import { pasteHandled, pasteIgnored } from "./copy-paste"; const mocks = vi.hoisted(() => ({ createJsxFragment: vi.fn(), insertWebstudioFragmentAt: vi.fn(), toastError: vi.fn(), })); vi.mock("../trpc/trpc-client", async (importOriginal) => ({ ...(await importOriginal()), nativeClient: { build: { createJsxFragment: { query: mocks.createJsxFragment, }, }, }, })); vi.mock("../instance-utils/insert", async (importOriginal) => ({ ...(await importOriginal()), insertWebstudioFragmentAt: mocks.insertWebstudioFragmentAt, })); vi.mock("../builder-api", async (importOriginal) => ({ ...(await importOriginal()), builderApi: { toast: { error: mocks.toastError, warn: vi.fn(), }, }, })); describe("jsx paste plugin", () => { beforeEach(() => { vi.clearAllMocks(); $project.set({ id: "project-id" } as never); }); test("detects only Webstudio JSX", () => { expect(isLikelyWebstudioJsx("")).toBe(true); expect(isLikelyWebstudioJsx('')).toBe(true); expect(isLikelyWebstudioJsx("")).toBe(true); expect(isLikelyWebstudioJsx("")).toBe(true); expect(isLikelyWebstudioJsx("")).toBe(false); expect(isLikelyWebstudioJsx("
HTML
")).toBe(false); expect(isLikelyWebstudioJsx("## Markdown")).toBe(false); }); test("can import JSX syntax inspection without the Node evaluator", () => { expect(() => inspectWebstudioJsxFragmentSyntax("")).not.toThrow(); expect(() => inspectWebstudioJsxFragmentSyntax("") ).toThrow('Do not access "process" in JSX fragments'); }); test("converts and inserts Webstudio JSX", async () => { const fragment = createEmptyWebstudioFragment(); mocks.createJsxFragment.mockResolvedValue(fragment); mocks.insertWebstudioFragmentAt.mockReturnValue(true); await expect(jsx.onPaste?.("")).resolves.toEqual(pasteHandled); expect(mocks.createJsxFragment).toHaveBeenCalledWith({ projectId: "project-id", source: "", }); expect(mocks.insertWebstudioFragmentAt).toHaveBeenCalledWith( fragment, undefined, "theirs", expect.objectContaining({ onBreakpointLimitMerge: expect.any(Function), }) ); }); test("does not claim non-Webstudio JSX text", async () => { await expect(jsx.onPaste?.("
HTML
")).resolves.toEqual( pasteIgnored ); expect(mocks.createJsxFragment).not.toHaveBeenCalled(); expect(mocks.insertWebstudioFragmentAt).not.toHaveBeenCalled(); }); test("does not claim JSX without a current project", async () => { $project.set(undefined); await expect(jsx.onPaste?.("")).resolves.toEqual(pasteIgnored); expect(mocks.createJsxFragment).not.toHaveBeenCalled(); expect(mocks.insertWebstudioFragmentAt).not.toHaveBeenCalled(); }); test("reports conversion errors and stops paste fallback", async () => { mocks.createJsxFragment.mockRejectedValue(new Error("Invalid JSX")); await expect(jsx.onPaste?.('')).resolves.toEqual({ success: false, error: "Invalid JSX", }); expect(mocks.toastError).not.toHaveBeenCalled(); expect(mocks.insertWebstudioFragmentAt).not.toHaveBeenCalled(); }); });