269 lines
6.0 KiB
TypeScript
Raw Normal View History

2020-08-29 14:16:40 +02:00
import {
ExcalidrawElement,
ExcalidrawLinearElement,
ExcalidrawTextElement,
} from "../../element/types";
import { CODES } from "../../keys";
import { ToolName } from "../queries/toolQueries";
import { fireEvent, GlobalTestState } from "../test-utils";
import { mutateElement } from "../../element/mutateElement";
2020-08-28 10:15:29 +02:00
import { API } from "./api";
const { h } = window;
let altKey = false;
let shiftKey = false;
let ctrlKey = false;
export type KeyboardModifiers = {
alt?: boolean;
shift?: boolean;
ctrl?: boolean;
};
2020-08-28 10:15:29 +02:00
export class Keyboard {
static withModifierKeys = (modifiers: KeyboardModifiers, cb: () => void) => {
2020-08-28 10:15:29 +02:00
const prevAltKey = altKey;
const prevShiftKey = shiftKey;
const prevCtrlKey = ctrlKey;
altKey = !!modifiers.alt;
shiftKey = !!modifiers.shift;
ctrlKey = !!modifiers.ctrl;
try {
cb();
} finally {
altKey = prevAltKey;
shiftKey = prevShiftKey;
ctrlKey = prevCtrlKey;
}
};
static keyDown = (key: string) => {
fireEvent.keyDown(document, {
key,
ctrlKey,
shiftKey,
altKey,
});
};
static keyUp = (key: string) => {
fireEvent.keyUp(document, {
key,
ctrlKey,
shiftKey,
altKey,
});
};
static keyPress = (key: string) => {
Keyboard.keyDown(key);
Keyboard.keyUp(key);
};
static codeDown = (code: string) => {
fireEvent.keyDown(document, {
code,
ctrlKey,
shiftKey,
altKey,
});
};
static codeUp = (code: string) => {
fireEvent.keyUp(document, {
code,
ctrlKey,
shiftKey,
altKey,
});
};
static codePress = (code: string) => {
Keyboard.codeDown(code);
Keyboard.codeUp(code);
};
2020-08-28 10:15:29 +02:00
}
export class Pointer {
private clientX = 0;
private clientY = 0;
constructor(
private readonly pointerType: "mouse" | "touch" | "pen",
private readonly pointerId = 1,
) {}
reset() {
this.clientX = 0;
this.clientY = 0;
}
getPosition() {
return [this.clientX, this.clientY];
}
restorePosition(x = 0, y = 0) {
this.clientX = x;
this.clientY = y;
fireEvent.pointerMove(GlobalTestState.canvas, this.getEvent());
}
private getEvent() {
return {
clientX: this.clientX,
clientY: this.clientY,
pointerType: this.pointerType,
pointerId: this.pointerId,
altKey,
shiftKey,
ctrlKey,
};
}
move(dx: number, dy: number) {
if (dx !== 0 || dy !== 0) {
this.clientX += dx;
this.clientY += dy;
fireEvent.pointerMove(GlobalTestState.canvas, this.getEvent());
}
}
down(dx = 0, dy = 0) {
this.move(dx, dy);
fireEvent.pointerDown(GlobalTestState.canvas, this.getEvent());
}
up(dx = 0, dy = 0) {
this.move(dx, dy);
fireEvent.pointerUp(GlobalTestState.canvas, this.getEvent());
}
click(dx = 0, dy = 0) {
this.down(dx, dy);
this.up();
}
doubleClick(dx = 0, dy = 0) {
this.move(dx, dy);
fireEvent.doubleClick(GlobalTestState.canvas, this.getEvent());
}
select(
/** if multiple elements supplied, they're shift-selected */
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.reset();
}
clickOn(element: ExcalidrawElement) {
this.reset();
this.click(element.x, element.y);
this.reset();
}
doubleClickOn(element: ExcalidrawElement) {
this.reset();
this.doubleClick(element.x, element.y);
this.reset();
}
2020-08-28 10:15:29 +02:00
}
const mouse = new Pointer("mouse");
export class UI {
static clickTool = (toolName: ToolName) => {
fireEvent.click(GlobalTestState.renderResult.getByToolName(toolName));
};
/**
* 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
* the app's elements array. This is because across the app lifecycle we tend
* to recreate element objects and the returned reference will become stale.
*
* If you need to get the actual element, not the proxy, call `get()` method
* on the proxy object.
*/
2020-08-29 14:16:40 +02:00
static createElement<T extends ToolName>(
type: T,
2020-08-28 10:15:29 +02:00
{
position = 0,
x = position,
y = position,
2020-08-28 10:15:29 +02:00
size = 10,
2020-08-29 14:16:40 +02:00
width = size,
height = width,
angle = 0,
2020-08-28 10:15:29 +02:00
}: {
position?: number;
2020-08-28 10:15:29 +02:00
x?: number;
y?: number;
size?: number;
2020-08-29 14:16:40 +02:00
width?: number;
height?: number;
angle?: number;
2020-09-09 21:08:06 +02:00
} = {},
): (T extends "arrow" | "line" | "draw"
2020-08-29 14:16:40 +02:00
? ExcalidrawLinearElement
: T extends "text"
? ExcalidrawTextElement
: ExcalidrawElement) & {
/** Returns the actual, current element from the elements array, instead
of the proxy */
get(): T extends "arrow" | "line" | "draw"
? ExcalidrawLinearElement
: T extends "text"
? ExcalidrawTextElement
: ExcalidrawElement;
} {
2020-08-28 10:15:29 +02:00
UI.clickTool(type);
mouse.reset();
mouse.down(x, y);
mouse.reset();
2020-08-29 14:16:40 +02:00
mouse.up(x + (width ?? height ?? size), y + (height ?? size));
const origElement = h.elements[h.elements.length - 1] as any;
if (angle !== 0) {
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;
2020-08-28 10:15:29 +02:00
}
static group(elements: ExcalidrawElement[]) {
mouse.select(elements);
Keyboard.withModifierKeys({ ctrl: true }, () => {
Keyboard.codePress(CODES.G);
2020-08-28 10:15:29 +02:00
});
}
}