factor out test helpers (#2093)
This commit is contained in:
parent
4c2d34ffd7
commit
8b9e2a540d
32
src/tests/helpers/api.ts
Normal file
32
src/tests/helpers/api.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { ExcalidrawElement } from "../../element/types";
|
||||
|
||||
const { h } = window;
|
||||
|
||||
export class API {
|
||||
static getSelectedElements = (): ExcalidrawElement[] => {
|
||||
return h.elements.filter(
|
||||
(element) => h.state.selectedElementIds[element.id],
|
||||
);
|
||||
};
|
||||
|
||||
static getSelectedElement = (): ExcalidrawElement => {
|
||||
const selectedElements = API.getSelectedElements();
|
||||
if (selectedElements.length !== 1) {
|
||||
throw new Error(
|
||||
`expected 1 selected element; got ${selectedElements.length}`,
|
||||
);
|
||||
}
|
||||
return selectedElements[0];
|
||||
};
|
||||
|
||||
static getStateHistory = () => {
|
||||
// @ts-ignore
|
||||
return h.history.stateHistory;
|
||||
};
|
||||
|
||||
static clearSelection = () => {
|
||||
// @ts-ignore
|
||||
h.app.clearSelection(null);
|
||||
expect(API.getSelectedElements().length).toBe(0);
|
||||
};
|
||||
}
|
203
src/tests/helpers/ui.ts
Normal file
203
src/tests/helpers/ui.ts
Normal file
@ -0,0 +1,203 @@
|
||||
import { ToolName } from "../queries/toolQueries";
|
||||
import { fireEvent, GlobalTestState } from "../test-utils";
|
||||
import { KEYS, Key } from "../../keys";
|
||||
import { ExcalidrawElement } from "../../element/types";
|
||||
import { API } from "./api";
|
||||
|
||||
const { h } = window;
|
||||
|
||||
let altKey = false;
|
||||
let shiftKey = false;
|
||||
let ctrlKey = false;
|
||||
|
||||
export class Keyboard {
|
||||
static withModifierKeys = (
|
||||
modifiers: { alt?: boolean; shift?: boolean; ctrl?: boolean },
|
||||
cb: () => void,
|
||||
) => {
|
||||
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 hotkeyDown = (hotkey: Key) => {
|
||||
const key = KEYS[hotkey];
|
||||
if (typeof key !== "string") {
|
||||
throw new Error("must provide a hotkey, not a key code");
|
||||
}
|
||||
Keyboard.keyDown(key);
|
||||
};
|
||||
|
||||
static hotkeyUp = (hotkey: Key) => {
|
||||
const key = KEYS[hotkey];
|
||||
if (typeof key !== "string") {
|
||||
throw new Error("must provide a hotkey, not a key code");
|
||||
}
|
||||
Keyboard.keyUp(key);
|
||||
};
|
||||
|
||||
static keyDown = (key: string) => {
|
||||
fireEvent.keyDown(document, {
|
||||
key,
|
||||
ctrlKey,
|
||||
shiftKey,
|
||||
altKey,
|
||||
keyCode: key.toUpperCase().charCodeAt(0),
|
||||
which: key.toUpperCase().charCodeAt(0),
|
||||
});
|
||||
};
|
||||
|
||||
static keyUp = (key: string) => {
|
||||
fireEvent.keyUp(document, {
|
||||
key,
|
||||
ctrlKey,
|
||||
shiftKey,
|
||||
altKey,
|
||||
keyCode: key.toUpperCase().charCodeAt(0),
|
||||
which: key.toUpperCase().charCodeAt(0),
|
||||
});
|
||||
};
|
||||
|
||||
static hotkeyPress = (key: Key) => {
|
||||
Keyboard.hotkeyDown(key);
|
||||
Keyboard.hotkeyUp(key);
|
||||
};
|
||||
|
||||
static keyPress = (key: string) => {
|
||||
Keyboard.keyDown(key);
|
||||
Keyboard.keyUp(key);
|
||||
};
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
const mouse = new Pointer("mouse");
|
||||
|
||||
export class UI {
|
||||
static clickTool = (toolName: ToolName) => {
|
||||
fireEvent.click(GlobalTestState.renderResult.getByToolName(toolName));
|
||||
};
|
||||
|
||||
static createElement(
|
||||
type: ToolName,
|
||||
{
|
||||
x = 0,
|
||||
y = x,
|
||||
size = 10,
|
||||
}: {
|
||||
x?: number;
|
||||
y?: number;
|
||||
size?: number;
|
||||
},
|
||||
) {
|
||||
UI.clickTool(type);
|
||||
mouse.reset();
|
||||
mouse.down(x, y);
|
||||
mouse.reset();
|
||||
mouse.up(x + size, y + size);
|
||||
return h.elements[h.elements.length - 1];
|
||||
}
|
||||
|
||||
static group(elements: ExcalidrawElement[]) {
|
||||
mouse.select(elements);
|
||||
Keyboard.withModifierKeys({ ctrl: true }, () => {
|
||||
Keyboard.keyPress("g");
|
||||
});
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -19,14 +19,37 @@ type TestRenderFn = (
|
||||
options?: Omit<RenderOptions, "queries">,
|
||||
) => RenderResult<typeof customQueries>;
|
||||
|
||||
const renderApp: TestRenderFn = (ui, options) =>
|
||||
render(ui, {
|
||||
const renderApp: TestRenderFn = (ui, options) => {
|
||||
const renderResult = render(ui, {
|
||||
queries: customQueries,
|
||||
...options,
|
||||
});
|
||||
|
||||
GlobalTestState.renderResult = renderResult;
|
||||
GlobalTestState.canvas = renderResult.container.querySelector("canvas")!;
|
||||
|
||||
return renderResult;
|
||||
};
|
||||
|
||||
// re-export everything
|
||||
export * from "@testing-library/react";
|
||||
|
||||
// override render method
|
||||
export { renderApp as render };
|
||||
|
||||
/**
|
||||
* For state-sharing across test helpers.
|
||||
* NOTE: there shouldn't be concurrency issues as each test is running in its
|
||||
* own process and thus gets its own instance of this module when running
|
||||
* tests in parallel.
|
||||
*/
|
||||
export class GlobalTestState {
|
||||
/**
|
||||
* automatically updated on each call to render()
|
||||
*/
|
||||
static renderResult: RenderResult<typeof customQueries> = null!;
|
||||
/**
|
||||
* automatically updated on each call to render()
|
||||
*/
|
||||
static canvas: HTMLCanvasElement = null!;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user