mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
test: add more resizing tests (#7028)
Co-authored-by: dwelle <luzar.david@gmail.com>
This commit is contained in:
parent
b86184a849
commit
89a3bbddb7
7 changed files with 1305 additions and 302 deletions
|
@ -1,13 +1,37 @@
|
|||
import {
|
||||
import type { Point } from "../../types";
|
||||
import type {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawLinearElement,
|
||||
ExcalidrawTextElement,
|
||||
ExcalidrawArrowElement,
|
||||
ExcalidrawRectangleElement,
|
||||
ExcalidrawEllipseElement,
|
||||
ExcalidrawDiamondElement,
|
||||
ExcalidrawTextContainer,
|
||||
ExcalidrawTextElementWithContainer,
|
||||
} from "../../element/types";
|
||||
import {
|
||||
getTransformHandles,
|
||||
getTransformHandlesFromCoords,
|
||||
OMIT_SIDES_FOR_FRAME,
|
||||
OMIT_SIDES_FOR_MULTIPLE_ELEMENTS,
|
||||
TransformHandleType,
|
||||
type TransformHandle,
|
||||
type TransformHandleDirection,
|
||||
} from "../../element/transformHandles";
|
||||
import { KEYS } from "../../keys";
|
||||
import { ToolName } from "../queries/toolQueries";
|
||||
import { fireEvent, GlobalTestState } from "../test-utils";
|
||||
import { type ToolName } from "../queries/toolQueries";
|
||||
import { fireEvent, GlobalTestState, screen } from "../test-utils";
|
||||
import { mutateElement } from "../../element/mutateElement";
|
||||
import { API } from "./api";
|
||||
import {
|
||||
isFrameElement,
|
||||
isLinearElement,
|
||||
isFreeDrawElement,
|
||||
isTextElement,
|
||||
} from "../../element/typeChecks";
|
||||
import { getCommonBounds, getElementPointsCoords } from "../../element/bounds";
|
||||
import { rotatePoint } from "../../math";
|
||||
|
||||
const { h } = window;
|
||||
|
||||
|
@ -86,6 +110,29 @@ export class Keyboard {
|
|||
};
|
||||
}
|
||||
|
||||
const getElementPointForSelection = (element: ExcalidrawElement): Point => {
|
||||
const { x, y, width, height, angle } = element;
|
||||
const target: Point = [
|
||||
x +
|
||||
(isLinearElement(element) || isFreeDrawElement(element) ? 0 : width / 2),
|
||||
y,
|
||||
];
|
||||
let center: Point;
|
||||
|
||||
if (isLinearElement(element)) {
|
||||
const bounds = getElementPointsCoords(element, element.points);
|
||||
center = [(bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2];
|
||||
} else {
|
||||
center = [x + width / 2, y + height / 2];
|
||||
}
|
||||
|
||||
if (isTextElement(element)) {
|
||||
return center;
|
||||
}
|
||||
|
||||
return rotatePoint(target, center, angle);
|
||||
};
|
||||
|
||||
export class Pointer {
|
||||
public clientX = 0;
|
||||
public clientY = 0;
|
||||
|
@ -199,31 +246,120 @@ export class Pointer {
|
|||
elements: ExcalidrawElement | ExcalidrawElement[],
|
||||
) {
|
||||
API.clearSelection();
|
||||
|
||||
Keyboard.withModifierKeys({ shift: true }, () => {
|
||||
elements = Array.isArray(elements) ? elements : [elements];
|
||||
elements.forEach((element) => {
|
||||
this.reset();
|
||||
this.click(element.x, element.y);
|
||||
this.click(...getElementPointForSelection(element));
|
||||
});
|
||||
});
|
||||
|
||||
this.reset();
|
||||
}
|
||||
|
||||
clickOn(element: ExcalidrawElement) {
|
||||
this.reset();
|
||||
this.click(element.x, element.y);
|
||||
this.click(...getElementPointForSelection(element));
|
||||
this.reset();
|
||||
}
|
||||
|
||||
doubleClickOn(element: ExcalidrawElement) {
|
||||
this.reset();
|
||||
this.doubleClick(element.x, element.y);
|
||||
this.doubleClick(...getElementPointForSelection(element));
|
||||
this.reset();
|
||||
}
|
||||
}
|
||||
|
||||
const mouse = new Pointer("mouse");
|
||||
|
||||
const transform = (
|
||||
element: ExcalidrawElement | ExcalidrawElement[],
|
||||
handle: TransformHandleType,
|
||||
mouseMove: [deltaX: number, deltaY: number],
|
||||
keyboardModifiers: KeyboardModifiers = {},
|
||||
) => {
|
||||
const elements = Array.isArray(element) ? element : [element];
|
||||
mouse.select(elements);
|
||||
let handleCoords: TransformHandle | undefined;
|
||||
|
||||
if (elements.length === 1) {
|
||||
handleCoords = getTransformHandles(elements[0], h.state.zoom, "mouse")[
|
||||
handle
|
||||
];
|
||||
} else {
|
||||
const [x1, y1, x2, y2] = getCommonBounds(elements);
|
||||
const isFrameSelected = elements.some(isFrameElement);
|
||||
const transformHandles = getTransformHandlesFromCoords(
|
||||
[x1, y1, x2, y2, (x1 + x2) / 2, (y1 + y2) / 2],
|
||||
0,
|
||||
h.state.zoom,
|
||||
"mouse",
|
||||
isFrameSelected ? OMIT_SIDES_FOR_FRAME : OMIT_SIDES_FOR_MULTIPLE_ELEMENTS,
|
||||
);
|
||||
handleCoords = transformHandles[handle];
|
||||
}
|
||||
|
||||
if (!handleCoords) {
|
||||
throw new Error(`There is no "${handle}" handle for this selection`);
|
||||
}
|
||||
|
||||
const clientX = handleCoords[0] + handleCoords[2] / 2;
|
||||
const clientY = handleCoords[1] + handleCoords[3] / 2;
|
||||
|
||||
Keyboard.withModifierKeys(keyboardModifiers, () => {
|
||||
mouse.reset();
|
||||
mouse.down(clientX, clientY);
|
||||
mouse.move(mouseMove[0], mouseMove[1]);
|
||||
mouse.up();
|
||||
});
|
||||
};
|
||||
|
||||
const proxy = <T extends ExcalidrawElement>(
|
||||
element: T,
|
||||
): typeof element & {
|
||||
/** Returns the actual, current element from the elements array, instead of
|
||||
the proxy */
|
||||
get(): typeof element;
|
||||
} => {
|
||||
return new Proxy(
|
||||
{},
|
||||
{
|
||||
get(target, prop) {
|
||||
const currentElement = h.elements.find(
|
||||
({ id }) => id === element.id,
|
||||
) as any;
|
||||
if (prop === "get") {
|
||||
if (currentElement.hasOwnProperty("get")) {
|
||||
throw new Error(
|
||||
"trying to get `get` test property, but ExcalidrawElement seems to define its own",
|
||||
);
|
||||
}
|
||||
return () => currentElement;
|
||||
}
|
||||
return currentElement[prop];
|
||||
},
|
||||
},
|
||||
) as any;
|
||||
};
|
||||
|
||||
/** Tools that can be used to draw shapes */
|
||||
type DrawingToolName = Exclude<ToolName, "lock" | "selection" | "eraser">;
|
||||
|
||||
type Element<T extends DrawingToolName> = T extends "line" | "freedraw"
|
||||
? ExcalidrawLinearElement
|
||||
: T extends "arrow"
|
||||
? ExcalidrawArrowElement
|
||||
: T extends "text"
|
||||
? ExcalidrawTextElement
|
||||
: T extends "rectangle"
|
||||
? ExcalidrawRectangleElement
|
||||
: T extends "ellipse"
|
||||
? ExcalidrawEllipseElement
|
||||
: T extends "diamond"
|
||||
? ExcalidrawDiamondElement
|
||||
: ExcalidrawElement;
|
||||
|
||||
export class UI {
|
||||
static clickTool = (toolName: ToolName) => {
|
||||
fireEvent.click(GlobalTestState.renderResult.getByToolName(toolName));
|
||||
|
@ -246,6 +382,10 @@ export class UI {
|
|||
fireEvent.click(element);
|
||||
};
|
||||
|
||||
static clickByTitle = (title: string) => {
|
||||
fireEvent.click(screen.getByTitle(title));
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates an Excalidraw element, and returns a proxy that wraps it so that
|
||||
* accessing props will return the latest ones from the object existing in
|
||||
|
@ -255,16 +395,17 @@ export class UI {
|
|||
* If you need to get the actual element, not the proxy, call `get()` method
|
||||
* on the proxy object.
|
||||
*/
|
||||
static createElement<T extends ToolName>(
|
||||
static createElement<T extends DrawingToolName>(
|
||||
type: T,
|
||||
{
|
||||
position = 0,
|
||||
x = position,
|
||||
y = position,
|
||||
size = 10,
|
||||
width = size,
|
||||
height = width,
|
||||
width: initialWidth = size,
|
||||
height: initialHeight = initialWidth,
|
||||
angle = 0,
|
||||
points: initialPoints,
|
||||
}: {
|
||||
position?: number;
|
||||
x?: number;
|
||||
|
@ -273,25 +414,46 @@ export class UI {
|
|||
width?: number;
|
||||
height?: number;
|
||||
angle?: number;
|
||||
points?: T extends "line" | "arrow" | "freedraw" ? Point[] : never;
|
||||
} = {},
|
||||
): (T extends "arrow" | "line" | "freedraw"
|
||||
? ExcalidrawLinearElement
|
||||
: T extends "text"
|
||||
? ExcalidrawTextElement
|
||||
: ExcalidrawElement) & {
|
||||
): Element<T> & {
|
||||
/** Returns the actual, current element from the elements array, instead
|
||||
of the proxy */
|
||||
get(): T extends "arrow" | "line" | "freedraw"
|
||||
? ExcalidrawLinearElement
|
||||
: T extends "text"
|
||||
? ExcalidrawTextElement
|
||||
: ExcalidrawElement;
|
||||
get(): Element<T>;
|
||||
} {
|
||||
const width = initialWidth ?? initialHeight ?? size;
|
||||
const height = initialHeight ?? size;
|
||||
const points: Point[] = initialPoints ?? [
|
||||
[0, 0],
|
||||
[width, height],
|
||||
];
|
||||
|
||||
UI.clickTool(type);
|
||||
mouse.reset();
|
||||
mouse.down(x, y);
|
||||
mouse.reset();
|
||||
mouse.up(x + (width ?? height ?? size), y + (height ?? size));
|
||||
|
||||
if (type === "text") {
|
||||
mouse.reset();
|
||||
mouse.click(x, y);
|
||||
} else if ((type === "line" || type === "arrow") && points.length > 2) {
|
||||
points.forEach((point) => {
|
||||
mouse.reset();
|
||||
mouse.click(x + point[0], y + point[1]);
|
||||
});
|
||||
Keyboard.keyPress(KEYS.ESCAPE);
|
||||
} else if (type === "freedraw" && points.length > 2) {
|
||||
const firstPoint = points[0];
|
||||
mouse.reset();
|
||||
mouse.down(x + firstPoint[0], y + firstPoint[1]);
|
||||
points
|
||||
.slice(1)
|
||||
.forEach((point) => mouse.moveTo(x + point[0], y + point[1]));
|
||||
mouse.upAt();
|
||||
Keyboard.keyPress(KEYS.ESCAPE);
|
||||
} else {
|
||||
mouse.reset();
|
||||
mouse.down(x, y);
|
||||
mouse.reset();
|
||||
mouse.up(x + width, y + height);
|
||||
}
|
||||
|
||||
const origElement = h.elements[h.elements.length - 1] as any;
|
||||
|
||||
|
@ -299,25 +461,58 @@ export class UI {
|
|||
mutateElement(origElement, { angle });
|
||||
}
|
||||
|
||||
return new Proxy(
|
||||
{},
|
||||
{
|
||||
get(target, prop) {
|
||||
const currentElement = h.elements.find(
|
||||
(element) => element.id === origElement.id,
|
||||
) as any;
|
||||
if (prop === "get") {
|
||||
if (currentElement.hasOwnProperty("get")) {
|
||||
throw new Error(
|
||||
"trying to get `get` test property, but ExcalidrawElement seems to define its own",
|
||||
);
|
||||
}
|
||||
return () => currentElement;
|
||||
}
|
||||
return currentElement[prop];
|
||||
},
|
||||
},
|
||||
) as any;
|
||||
return proxy(origElement);
|
||||
}
|
||||
|
||||
static async editText<
|
||||
T extends ExcalidrawTextElement | ExcalidrawTextContainer,
|
||||
>(element: T, text: string) {
|
||||
const openedEditor = document.querySelector<HTMLTextAreaElement>(
|
||||
".excalidraw-textEditorContainer > textarea",
|
||||
);
|
||||
|
||||
if (!openedEditor) {
|
||||
mouse.select(element);
|
||||
Keyboard.keyPress(KEYS.ENTER);
|
||||
}
|
||||
|
||||
const editor =
|
||||
openedEditor ??
|
||||
document.querySelector<HTMLTextAreaElement>(
|
||||
".excalidraw-textEditorContainer > textarea",
|
||||
);
|
||||
if (!editor) {
|
||||
throw new Error("Can't find wysiwyg text editor in the dom");
|
||||
}
|
||||
|
||||
fireEvent.input(editor, { target: { value: text } });
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
editor.blur();
|
||||
|
||||
return isTextElement(element)
|
||||
? element
|
||||
: proxy(
|
||||
h.elements[
|
||||
h.elements.length - 1
|
||||
] as ExcalidrawTextElementWithContainer,
|
||||
);
|
||||
}
|
||||
|
||||
static resize(
|
||||
element: ExcalidrawElement | ExcalidrawElement[],
|
||||
handle: TransformHandleDirection,
|
||||
mouseMove: [deltaX: number, deltaY: number],
|
||||
keyboardModifiers: KeyboardModifiers = {},
|
||||
) {
|
||||
return transform(element, handle, mouseMove, keyboardModifiers);
|
||||
}
|
||||
|
||||
static rotate(
|
||||
element: ExcalidrawElement | ExcalidrawElement[],
|
||||
mouseMove: [deltaX: number, deltaY: number],
|
||||
keyboardModifiers: KeyboardModifiers = {},
|
||||
) {
|
||||
return transform(element, "rotation", mouseMove, keyboardModifiers);
|
||||
}
|
||||
|
||||
static group(elements: ExcalidrawElement[]) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue