import React from "react";
import { vi } from "vitest";
import { CURSOR_TYPE, KEYS } from "@excalidraw/common";
import { Excalidraw } from "../index";
import { API } from "./helpers/api";
import { Keyboard, Pointer, UI } from "./helpers/ui";
import { getTextEditor, updateTextEditor } from "./queries/dom";
import {
act,
fireEvent,
render,
GlobalTestState,
mockBoundingClientRect,
restoreOriginalGetBoundingClientRect,
waitFor,
} from "./test-utils";
const { h } = window;
const queryContainer = (selector: string) =>
GlobalTestState.renderResult.container.querySelector(selector);
const mouse = new Pointer("mouse");
const touch = new Pointer("touch");
const pen = new Pointer("pen");
const pointerTypes = [mouse, touch, pen];
describe("view mode", () => {
beforeEach(async () => {
mockBoundingClientRect();
await render();
await waitFor(() => expect(h.state.width).toBe(200));
});
afterEach(() => {
restoreOriginalGetBoundingClientRect();
});
it("after switching to view mode – cursor type should be pointer", async () => {
API.setAppState({ viewModeEnabled: true });
expect(GlobalTestState.interactiveCanvas.style.cursor).toBe(
CURSOR_TYPE.GRAB,
);
});
it("after switching to view mode, moving, clicking, and pressing space key – cursor type should be pointer", async () => {
API.setAppState({ viewModeEnabled: true });
pointerTypes.forEach((pointerType) => {
const pointer = pointerType;
pointer.reset();
pointer.move(100, 100);
pointer.click();
Keyboard.keyPress(KEYS.SPACE);
expect(GlobalTestState.interactiveCanvas.style.cursor).toBe(
CURSOR_TYPE.GRAB,
);
});
});
it("dispatches pointer events to custom tools in view mode", () => {
const onPointerDown = vi.fn();
const onPointerUp = vi.fn();
GlobalTestState.renderResult.rerender(
,
);
API.setAppState({ viewModeEnabled: true });
act(() => {
h.app.setActiveTool({
type: "custom",
customType: "comment",
locked: true,
});
});
expect(h.app.isInteractionEnabled()).toBe(true);
const { scrollX, scrollY } = h.state;
mouse.reset();
mouse.downAt(50, 50);
mouse.moveTo(100, 100);
mouse.upAt(100, 100);
expect(onPointerDown).toHaveBeenCalledTimes(1);
expect(onPointerDown.mock.calls[0][0]).toMatchObject({
type: "custom",
customType: "comment",
});
expect(onPointerUp).toHaveBeenCalledTimes(1);
expect(onPointerUp.mock.calls[0][0]).toMatchObject({
type: "custom",
customType: "comment",
});
expect([h.state.scrollX, h.state.scrollY]).toEqual([scrollX, scrollY]);
});
it("cursor should stay as grabbing type when hovering over canvas elements", async () => {
// create a rectangle, then hover over it – cursor should be
// move type for mouse and grab for touch & pen
// then switch to view-mode and cursor should be grabbing type
UI.createElement("rectangle", { size: 100 });
pointerTypes.forEach((pointerType) => {
const pointer = pointerType;
pointer.moveTo(50, 50);
// eslint-disable-next-line dot-notation
if (pointerType["pointerType"] === "mouse") {
expect(GlobalTestState.interactiveCanvas.style.cursor).toBe(
CURSOR_TYPE.MOVE,
);
} else {
expect(GlobalTestState.interactiveCanvas.style.cursor).toBe(
CURSOR_TYPE.GRAB,
);
}
API.setAppState({ viewModeEnabled: true });
expect(GlobalTestState.interactiveCanvas.style.cursor).toBe(
CURSOR_TYPE.GRAB,
);
});
});
it("submits active text editing when entering view mode", async () => {
const text = API.createElement({
type: "text",
text: "before",
x: 20,
y: 20,
});
API.setElements([text]);
API.setSelectedElements([text]);
Keyboard.keyPress(KEYS.ENTER);
const editor = await getTextEditor();
updateTextEditor(editor, "committed before view mode");
GlobalTestState.renderResult.rerender(
,
);
await waitFor(() => {
expect(h.state.editingTextElement).toBe(null);
expect(queryContainer(".excalidraw-wysiwyg")).toBe(null);
});
expect(h.elements.find((element) => element.id === text.id)).toMatchObject({
originalText: "committed before view mode",
});
});
it("commits frame-name editing when entering view mode", async () => {
const frame = API.createElement({
type: "frame",
x: 20,
y: 30,
width: 80,
height: 50,
});
API.setElements([frame]);
API.updateElement(frame, { name: "before" });
API.setAppState({ editingFrame: frame.id });
const frameNameInput = await waitFor(() => {
const input = queryContainer(".frame-name input");
expect(input).not.toBe(null);
return input as HTMLInputElement;
});
fireEvent.change(frameNameInput, { target: { value: " committed " } });
API.setAppState({ viewModeEnabled: true });
await waitFor(() => {
expect(h.state.editingFrame).toBe(null);
expect(queryContainer(".frame-name input")).toBe(null);
});
expect(h.elements.find((element) => element.id === frame.id)).toMatchObject(
{ name: "committed" },
);
});
});