factor out test helpers (#2093)

This commit is contained in:
David Luzar 2020-08-28 10:15:29 +02:00 committed by GitHub
parent 4c2d34ffd7
commit 8b9e2a540d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 613 additions and 503 deletions

32
src/tests/helpers/api.ts Normal file
View 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
View 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

View File

@ -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!;
}