import { act } from "react-dom/test-utils"; import { createRoot, type Root } from "react-dom/client"; import { EditorSelection } from "@codemirror/state"; import { EditorView } from "@codemirror/view"; import { afterEach, beforeEach, expect, test, vi } from "vitest"; import { userEvent } from "@vitest/browser/context"; import { cssVar } from "@webstudio-is/design-system"; import "@webstudio-is/design-system/global.css"; import { EditorContent, EditorDialogButton, EditorDialogControl, } from "./code-editor-base"; ( globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean } ).IS_REACT_ACT_ENVIRONMENT = true; let container: HTMLDivElement; let root: Root; const rangeGetClientRects = Object.getOwnPropertyDescriptor( Range.prototype, "getClientRects" ); beforeEach(() => { vi.stubGlobal( "ResizeObserver", class { observe() {} unobserve() {} disconnect() {} } ); Object.defineProperty(Range.prototype, "getClientRects", { configurable: true, value: () => [new DOMRect(0, 0, 20, 16)], }); container = document.createElement("div"); document.body.appendChild(container); root = createRoot(container); }); afterEach(() => { act(() => root.unmount()); container.remove(); document.documentElement.removeAttribute("data-color-scheme"); vi.unstubAllGlobals(); if (rangeGetClientRects === undefined) { delete (Range.prototype as { getClientRects?: unknown }).getClientRects; } else { Object.defineProperty( Range.prototype, "getClientRects", rangeGetClientRects ); } }); test.each(["light", "dark"] as const)( "uses the shared selection color in %s mode", async (mode) => { document.documentElement.dataset.colorScheme = mode; act(() => { root.render( {}} onChangeComplete={() => {}} /> ); }); const editor = container.querySelector('[role="textbox"]'); if (editor === null) { throw new Error("Expected the code editor"); } const view = EditorView.findFromDOM(editor); if (view === null) { throw new Error("Expected the CodeMirror view"); } act(() => { view.dispatch({ selection: EditorSelection.range(0, view.state.doc.length), }); view.focus(); }); await act( () => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())) ) ); const selection = container.querySelector( ".cm-selectionBackground" ); if (selection === null) { throw new Error("Expected a rendered selection"); } const reference = document.createElement("span"); reference.style.background = cssVar("--background-text-selection"); reference.style.color = "var(--foreground-on-text-selection)"; document.body.appendChild(reference); const selectionColor = getComputedStyle(selection).backgroundColor; const referenceColor = getComputedStyle(reference).backgroundColor; const content = container.querySelector(".cm-content"); if (content === null) { throw new Error("Expected editor content"); } const selectedTextColor = getComputedStyle(content, "::selection").color; const referenceTextColor = getComputedStyle(reference).color; reference.remove(); expect(selectionColor).toBe(referenceColor); expect(selectedTextColor).toBe(referenceTextColor); } ); test("scopes disabled styles to the Webstudio code editor", () => { act(() => { root.render(
{}} onChangeComplete={() => {}} />
); }); const editors = container.querySelectorAll(".cm-editor"); const foreignEditor = container.querySelector( "[data-foreign-editor]" ); if (editors[0] === undefined || foreignEditor === null) { throw new Error("Expected both code editors"); } expect(getComputedStyle(editors[0]).opacity).toBe("0.3"); expect(getComputedStyle(foreignEditor).opacity).toBe("1"); }); test("keeps the editor maximize button background transparent when hovered", async () => { act(() => { root.render(
{}} onChangeComplete={() => {}} />
); }); const maximizeButton = container.querySelector( '[aria-label="Expand"]' ); if (maximizeButton === null) { throw new Error("Expected the maximize button"); } await userEvent.hover(maximizeButton.parentElement!); await userEvent.hover(maximizeButton); expect(getComputedStyle(maximizeButton).backgroundColor).toBe( "rgba(0, 0, 0, 0)" ); });