2020-03-23 16:38:41 -07:00
|
|
|
import { reseed } from "../random";
|
|
|
|
import React from "react";
|
|
|
|
import ReactDOM from "react-dom";
|
|
|
|
import * as Renderer from "../renderer/renderScene";
|
|
|
|
import { render, fireEvent } from "./test-utils";
|
2020-04-12 16:24:52 +05:30
|
|
|
import App from "../components/App";
|
2020-03-23 16:38:41 -07:00
|
|
|
import { ToolName } from "./queries/toolQueries";
|
|
|
|
import { KEYS, Key } from "../keys";
|
|
|
|
import { setDateTimeForTests } from "../utils";
|
|
|
|
import { ExcalidrawElement } from "../element/types";
|
|
|
|
import { handlerRectangles } from "../element";
|
|
|
|
|
|
|
|
const { h } = window;
|
|
|
|
|
|
|
|
const renderScene = jest.spyOn(Renderer, "renderScene");
|
|
|
|
let getByToolName: (name: string) => HTMLElement = null!;
|
|
|
|
let canvas: HTMLCanvasElement = null!;
|
|
|
|
|
2020-05-20 16:21:37 +03:00
|
|
|
const clickTool = (toolName: ToolName) => {
|
2020-03-23 16:38:41 -07:00
|
|
|
fireEvent.click(getByToolName(toolName));
|
2020-05-20 16:21:37 +03:00
|
|
|
};
|
2020-03-23 16:38:41 -07:00
|
|
|
|
|
|
|
let lastClientX = 0;
|
|
|
|
let lastClientY = 0;
|
|
|
|
let pointerType: "mouse" | "pen" | "touch" = "mouse";
|
|
|
|
|
2020-05-20 16:21:37 +03:00
|
|
|
const pointerDown = (
|
2020-03-23 16:38:41 -07:00
|
|
|
clientX: number = lastClientX,
|
|
|
|
clientY: number = lastClientY,
|
|
|
|
altKey: boolean = false,
|
|
|
|
shiftKey: boolean = false,
|
2020-05-20 16:21:37 +03:00
|
|
|
) => {
|
2020-03-23 16:38:41 -07:00
|
|
|
lastClientX = clientX;
|
|
|
|
lastClientY = clientY;
|
|
|
|
fireEvent.pointerDown(canvas, {
|
|
|
|
clientX,
|
|
|
|
clientY,
|
|
|
|
altKey,
|
|
|
|
shiftKey,
|
|
|
|
pointerId: 1,
|
|
|
|
pointerType,
|
|
|
|
});
|
2020-05-20 16:21:37 +03:00
|
|
|
};
|
2020-03-23 16:38:41 -07:00
|
|
|
|
2020-05-20 16:21:37 +03:00
|
|
|
const pointer2Down = (clientX: number, clientY: number) => {
|
2020-03-23 16:38:41 -07:00
|
|
|
fireEvent.pointerDown(canvas, {
|
|
|
|
clientX,
|
|
|
|
clientY,
|
|
|
|
pointerId: 2,
|
|
|
|
pointerType,
|
|
|
|
});
|
2020-05-20 16:21:37 +03:00
|
|
|
};
|
2020-03-23 16:38:41 -07:00
|
|
|
|
2020-05-20 16:21:37 +03:00
|
|
|
const pointer2Move = (clientX: number, clientY: number) => {
|
2020-03-23 16:38:41 -07:00
|
|
|
fireEvent.pointerMove(canvas, {
|
|
|
|
clientX,
|
|
|
|
clientY,
|
|
|
|
pointerId: 2,
|
|
|
|
pointerType,
|
|
|
|
});
|
2020-05-20 16:21:37 +03:00
|
|
|
};
|
2020-03-23 16:38:41 -07:00
|
|
|
|
2020-05-20 16:21:37 +03:00
|
|
|
const pointer2Up = (clientX: number, clientY: number) => {
|
2020-03-23 16:38:41 -07:00
|
|
|
fireEvent.pointerUp(canvas, {
|
|
|
|
clientX,
|
|
|
|
clientY,
|
|
|
|
pointerId: 2,
|
|
|
|
pointerType,
|
|
|
|
});
|
2020-05-20 16:21:37 +03:00
|
|
|
};
|
2020-03-23 16:38:41 -07:00
|
|
|
|
2020-05-20 16:21:37 +03:00
|
|
|
const pointerMove = (
|
2020-03-23 16:38:41 -07:00
|
|
|
clientX: number = lastClientX,
|
|
|
|
clientY: number = lastClientY,
|
|
|
|
altKey: boolean = false,
|
|
|
|
shiftKey: boolean = false,
|
2020-05-20 16:21:37 +03:00
|
|
|
) => {
|
2020-03-23 16:38:41 -07:00
|
|
|
lastClientX = clientX;
|
|
|
|
lastClientY = clientY;
|
|
|
|
fireEvent.pointerMove(canvas, {
|
|
|
|
clientX,
|
|
|
|
clientY,
|
|
|
|
altKey,
|
|
|
|
shiftKey,
|
|
|
|
pointerId: 1,
|
|
|
|
pointerType,
|
|
|
|
});
|
2020-05-20 16:21:37 +03:00
|
|
|
};
|
2020-03-23 16:38:41 -07:00
|
|
|
|
2020-05-20 16:21:37 +03:00
|
|
|
const pointerUp = (
|
2020-03-23 16:38:41 -07:00
|
|
|
clientX: number = lastClientX,
|
|
|
|
clientY: number = lastClientY,
|
|
|
|
altKey: boolean = false,
|
|
|
|
shiftKey: boolean = false,
|
2020-05-20 16:21:37 +03:00
|
|
|
) => {
|
2020-03-23 16:38:41 -07:00
|
|
|
lastClientX = clientX;
|
|
|
|
lastClientY = clientY;
|
|
|
|
fireEvent.pointerUp(canvas, { pointerId: 1, pointerType, shiftKey, altKey });
|
2020-05-20 16:21:37 +03:00
|
|
|
};
|
2020-03-23 16:38:41 -07:00
|
|
|
|
2020-05-20 16:21:37 +03:00
|
|
|
const hotkeyDown = (key: Key) => {
|
2020-03-23 16:38:41 -07:00
|
|
|
fireEvent.keyDown(document, { key: KEYS[key] });
|
2020-05-20 16:21:37 +03:00
|
|
|
};
|
2020-03-23 16:38:41 -07:00
|
|
|
|
2020-05-20 16:21:37 +03:00
|
|
|
const hotkeyUp = (key: Key) => {
|
2020-03-23 16:38:41 -07:00
|
|
|
fireEvent.keyUp(document, {
|
|
|
|
key: KEYS[key],
|
|
|
|
});
|
2020-05-20 16:21:37 +03:00
|
|
|
};
|
2020-03-23 16:38:41 -07:00
|
|
|
|
2020-05-20 16:21:37 +03:00
|
|
|
const keyDown = (
|
2020-03-23 16:38:41 -07:00
|
|
|
key: string,
|
|
|
|
ctrlKey: boolean = false,
|
|
|
|
shiftKey: boolean = false,
|
2020-05-20 16:21:37 +03:00
|
|
|
) => {
|
2020-03-23 16:38:41 -07:00
|
|
|
fireEvent.keyDown(document, { key, ctrlKey, shiftKey });
|
2020-05-20 16:21:37 +03:00
|
|
|
};
|
2020-03-23 16:38:41 -07:00
|
|
|
|
2020-05-20 16:21:37 +03:00
|
|
|
const keyUp = (
|
2020-03-23 16:38:41 -07:00
|
|
|
key: string,
|
|
|
|
ctrlKey: boolean = false,
|
|
|
|
shiftKey: boolean = false,
|
2020-05-20 16:21:37 +03:00
|
|
|
) => {
|
2020-03-23 16:38:41 -07:00
|
|
|
fireEvent.keyUp(document, {
|
|
|
|
key,
|
|
|
|
ctrlKey,
|
|
|
|
shiftKey,
|
|
|
|
});
|
2020-05-20 16:21:37 +03:00
|
|
|
};
|
2020-03-23 16:38:41 -07:00
|
|
|
|
2020-05-20 16:21:37 +03:00
|
|
|
const hotkeyPress = (key: Key) => {
|
2020-03-23 16:38:41 -07:00
|
|
|
hotkeyDown(key);
|
|
|
|
hotkeyUp(key);
|
2020-05-20 16:21:37 +03:00
|
|
|
};
|
2020-03-23 16:38:41 -07:00
|
|
|
|
2020-05-20 16:21:37 +03:00
|
|
|
const keyPress = (
|
2020-03-23 16:38:41 -07:00
|
|
|
key: string,
|
|
|
|
ctrlKey: boolean = false,
|
|
|
|
shiftKey: boolean = false,
|
2020-05-20 16:21:37 +03:00
|
|
|
) => {
|
2020-03-23 16:38:41 -07:00
|
|
|
keyDown(key, ctrlKey, shiftKey);
|
|
|
|
keyUp(key, ctrlKey, shiftKey);
|
2020-05-20 16:21:37 +03:00
|
|
|
};
|
2020-03-23 16:38:41 -07:00
|
|
|
|
2020-05-20 16:21:37 +03:00
|
|
|
const clickLabeledElement = (label: string) => {
|
2020-03-23 16:38:41 -07:00
|
|
|
const element = document.querySelector(`[aria-label='${label}']`);
|
|
|
|
if (!element) {
|
|
|
|
throw new Error(`No labeled element found: ${label}`);
|
|
|
|
}
|
|
|
|
fireEvent.click(element);
|
2020-05-20 16:21:37 +03:00
|
|
|
};
|
2020-03-23 16:38:41 -07:00
|
|
|
|
2020-05-20 16:21:37 +03:00
|
|
|
const getSelectedElement = (): ExcalidrawElement => {
|
2020-03-23 16:38:41 -07:00
|
|
|
const selectedElements = h.elements.filter(
|
|
|
|
(element) => h.state.selectedElementIds[element.id],
|
|
|
|
);
|
|
|
|
if (selectedElements.length !== 1) {
|
|
|
|
throw new Error(
|
|
|
|
`expected 1 selected element; got ${selectedElements.length}`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return selectedElements[0];
|
2020-05-20 16:21:37 +03:00
|
|
|
};
|
2020-03-23 16:38:41 -07:00
|
|
|
|
2020-04-07 17:49:59 +09:00
|
|
|
type HandlerRectanglesRet = keyof ReturnType<typeof handlerRectangles>;
|
2020-05-20 16:21:37 +03:00
|
|
|
const getResizeHandles = () => {
|
2020-04-14 12:30:58 +03:00
|
|
|
const rects = handlerRectangles(
|
|
|
|
getSelectedElement(),
|
|
|
|
h.state.zoom,
|
|
|
|
pointerType,
|
|
|
|
) as {
|
|
|
|
[T in HandlerRectanglesRet]: [number, number, number, number];
|
|
|
|
};
|
2020-03-23 16:38:41 -07:00
|
|
|
|
|
|
|
const rv: { [K in keyof typeof rects]: [number, number] } = {} as any;
|
|
|
|
|
|
|
|
for (const handlePos in rects) {
|
|
|
|
const [x, y, width, height] = rects[handlePos as keyof typeof rects];
|
|
|
|
|
|
|
|
rv[handlePos as keyof typeof rects] = [x + width / 2, y + height / 2];
|
|
|
|
}
|
|
|
|
|
|
|
|
return rv;
|
2020-05-20 16:21:37 +03:00
|
|
|
};
|
2020-03-23 16:38:41 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* This is always called at the end of your test, so usually you don't need to call it.
|
|
|
|
* However, if you have a long test, you might want to call it during the test so it's easier
|
|
|
|
* to debug where a test failure came from.
|
|
|
|
*/
|
2020-05-20 16:21:37 +03:00
|
|
|
const checkpoint = (name: string) => {
|
2020-03-23 16:38:41 -07:00
|
|
|
expect(renderScene.mock.calls.length).toMatchSnapshot(
|
|
|
|
`[${name}] number of renders`,
|
|
|
|
);
|
|
|
|
expect(h.state).toMatchSnapshot(`[${name}] appState`);
|
|
|
|
expect(h.history.getSnapshotForTest()).toMatchSnapshot(`[${name}] history`);
|
|
|
|
expect(h.elements.length).toMatchSnapshot(`[${name}] number of elements`);
|
|
|
|
h.elements.forEach((element, i) =>
|
|
|
|
expect(element).toMatchSnapshot(`[${name}] element ${i}`),
|
|
|
|
);
|
2020-05-20 16:21:37 +03:00
|
|
|
};
|
2020-03-23 16:38:41 -07:00
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
// Unmount ReactDOM from root
|
|
|
|
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
|
|
|
|
|
|
|
|
localStorage.clear();
|
|
|
|
renderScene.mockClear();
|
|
|
|
h.history.clear();
|
|
|
|
reseed(7);
|
|
|
|
setDateTimeForTests("201933152653");
|
|
|
|
pointerType = "mouse";
|
|
|
|
|
|
|
|
const renderResult = render(<App />);
|
|
|
|
|
|
|
|
getByToolName = renderResult.getByToolName;
|
|
|
|
canvas = renderResult.container.querySelector("canvas")!;
|
|
|
|
});
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
checkpoint("end of test");
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("regression tests", () => {
|
|
|
|
it("draw every type of shape", () => {
|
|
|
|
clickTool("rectangle");
|
|
|
|
pointerDown(10, 10);
|
|
|
|
pointerMove(20, 20);
|
|
|
|
pointerUp();
|
|
|
|
|
|
|
|
clickTool("diamond");
|
|
|
|
pointerDown(30, 10);
|
|
|
|
pointerMove(40, 20);
|
|
|
|
pointerUp();
|
|
|
|
|
|
|
|
clickTool("ellipse");
|
|
|
|
pointerDown(50, 10);
|
|
|
|
pointerMove(60, 20);
|
|
|
|
pointerUp();
|
|
|
|
|
|
|
|
clickTool("arrow");
|
|
|
|
pointerDown(70, 10);
|
|
|
|
pointerMove(80, 20);
|
|
|
|
pointerUp();
|
|
|
|
|
|
|
|
clickTool("line");
|
|
|
|
pointerDown(90, 10);
|
|
|
|
pointerMove(100, 20);
|
|
|
|
pointerUp();
|
|
|
|
|
|
|
|
clickTool("arrow");
|
|
|
|
pointerDown(10, 30);
|
|
|
|
pointerUp();
|
|
|
|
pointerMove(20, 40);
|
|
|
|
pointerUp();
|
|
|
|
pointerMove(10, 50);
|
|
|
|
pointerUp();
|
|
|
|
hotkeyPress("ENTER");
|
|
|
|
|
|
|
|
clickTool("line");
|
|
|
|
pointerDown(30, 30);
|
|
|
|
pointerUp();
|
|
|
|
pointerMove(40, 40);
|
|
|
|
pointerUp();
|
|
|
|
pointerMove(30, 50);
|
|
|
|
pointerUp();
|
|
|
|
hotkeyPress("ENTER");
|
2020-05-12 20:10:11 +01:00
|
|
|
|
|
|
|
clickTool("draw");
|
|
|
|
pointerDown(30, 10);
|
|
|
|
pointerMove(40, 20);
|
|
|
|
pointerUp();
|
2020-03-23 16:38:41 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
it("click to select a shape", () => {
|
|
|
|
clickTool("rectangle");
|
|
|
|
pointerDown(10, 10);
|
|
|
|
pointerMove(20, 20);
|
|
|
|
pointerUp();
|
|
|
|
|
|
|
|
clickTool("rectangle");
|
|
|
|
pointerDown(30, 10);
|
|
|
|
pointerMove(40, 20);
|
|
|
|
pointerUp();
|
|
|
|
|
|
|
|
const prevSelectedId = getSelectedElement().id;
|
|
|
|
pointerDown(10, 10);
|
|
|
|
pointerUp();
|
|
|
|
expect(getSelectedElement().id).not.toEqual(prevSelectedId);
|
|
|
|
});
|
|
|
|
|
|
|
|
for (const [keys, shape] of [
|
|
|
|
["2r", "rectangle"],
|
|
|
|
["3d", "diamond"],
|
|
|
|
["4e", "ellipse"],
|
|
|
|
["5a", "arrow"],
|
|
|
|
["6l", "line"],
|
2020-05-12 20:10:11 +01:00
|
|
|
["7x", "draw"],
|
2020-03-23 16:38:41 -07:00
|
|
|
] as [string, ExcalidrawElement["type"]][]) {
|
|
|
|
for (const key of keys) {
|
|
|
|
it(`hotkey ${key} selects ${shape} tool`, () => {
|
|
|
|
keyPress(key);
|
|
|
|
|
|
|
|
pointerDown(10, 10);
|
|
|
|
pointerMove(20, 20);
|
|
|
|
pointerUp();
|
|
|
|
|
|
|
|
expect(getSelectedElement().type).toBe(shape);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
it("change the properties of a shape", () => {
|
|
|
|
clickTool("rectangle");
|
|
|
|
pointerDown(10, 10);
|
|
|
|
pointerMove(20, 20);
|
|
|
|
pointerUp();
|
|
|
|
|
|
|
|
clickLabeledElement("Background");
|
|
|
|
clickLabeledElement("#fa5252");
|
|
|
|
clickLabeledElement("Stroke");
|
|
|
|
clickLabeledElement("#5f3dc4");
|
|
|
|
expect(getSelectedElement().backgroundColor).toBe("#fa5252");
|
|
|
|
expect(getSelectedElement().strokeColor).toBe("#5f3dc4");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("resize an element, trying every resize handle", () => {
|
|
|
|
clickTool("rectangle");
|
|
|
|
pointerDown(10, 10);
|
|
|
|
pointerMove(20, 20);
|
|
|
|
pointerUp();
|
|
|
|
|
|
|
|
const resizeHandles = getResizeHandles();
|
2020-04-02 17:40:26 +09:00
|
|
|
delete resizeHandles.rotation; // exclude rotation handle
|
2020-03-23 16:38:41 -07:00
|
|
|
for (const handlePos in resizeHandles) {
|
|
|
|
const [x, y] = resizeHandles[handlePos as keyof typeof resizeHandles];
|
|
|
|
const { width: prevWidth, height: prevHeight } = getSelectedElement();
|
|
|
|
pointerDown(x, y);
|
|
|
|
pointerMove(x - 5, y - 5);
|
|
|
|
pointerUp();
|
|
|
|
const {
|
|
|
|
width: nextWidthNegative,
|
|
|
|
height: nextHeightNegative,
|
|
|
|
} = getSelectedElement();
|
|
|
|
expect(
|
|
|
|
prevWidth !== nextWidthNegative || prevHeight !== nextHeightNegative,
|
|
|
|
).toBeTruthy();
|
|
|
|
checkpoint(`resize handle ${handlePos} (-5, -5)`);
|
|
|
|
|
|
|
|
pointerDown();
|
|
|
|
pointerMove(x, y);
|
|
|
|
pointerUp();
|
|
|
|
const { width, height } = getSelectedElement();
|
|
|
|
expect(width).toBe(prevWidth);
|
|
|
|
expect(height).toBe(prevHeight);
|
|
|
|
checkpoint(`unresize handle ${handlePos} (-5, -5)`);
|
|
|
|
|
|
|
|
pointerDown(x, y);
|
|
|
|
pointerMove(x + 5, y + 5);
|
|
|
|
pointerUp();
|
|
|
|
const {
|
|
|
|
width: nextWidthPositive,
|
|
|
|
height: nextHeightPositive,
|
|
|
|
} = getSelectedElement();
|
|
|
|
expect(
|
|
|
|
prevWidth !== nextWidthPositive || prevHeight !== nextHeightPositive,
|
|
|
|
).toBeTruthy();
|
|
|
|
checkpoint(`resize handle ${handlePos} (+5, +5)`);
|
|
|
|
|
|
|
|
pointerDown();
|
|
|
|
pointerMove(x, y);
|
|
|
|
pointerUp();
|
|
|
|
const { width: finalWidth, height: finalHeight } = getSelectedElement();
|
|
|
|
expect(finalWidth).toBe(prevWidth);
|
|
|
|
expect(finalHeight).toBe(prevHeight);
|
|
|
|
|
|
|
|
checkpoint(`unresize handle ${handlePos} (+5, +5)`);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
it("click on an element and drag it", () => {
|
|
|
|
clickTool("rectangle");
|
|
|
|
pointerDown(10, 10);
|
|
|
|
pointerMove(20, 20);
|
|
|
|
pointerUp();
|
|
|
|
|
|
|
|
const { x: prevX, y: prevY } = getSelectedElement();
|
|
|
|
pointerDown(10, 10);
|
|
|
|
pointerMove(20, 20);
|
|
|
|
pointerUp();
|
|
|
|
|
|
|
|
const { x: nextX, y: nextY } = getSelectedElement();
|
|
|
|
expect(nextX).toBeGreaterThan(prevX);
|
|
|
|
expect(nextY).toBeGreaterThan(prevY);
|
|
|
|
|
|
|
|
checkpoint("dragged");
|
|
|
|
|
|
|
|
pointerDown();
|
|
|
|
pointerMove(10, 10);
|
|
|
|
pointerUp();
|
|
|
|
|
|
|
|
const { x, y } = getSelectedElement();
|
|
|
|
expect(x).toBe(prevX);
|
|
|
|
expect(y).toBe(prevY);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("alt-drag duplicates an element", () => {
|
|
|
|
clickTool("rectangle");
|
|
|
|
pointerDown(10, 10);
|
|
|
|
pointerMove(20, 20);
|
|
|
|
pointerUp();
|
|
|
|
|
|
|
|
expect(
|
|
|
|
h.elements.filter((element) => element.type === "rectangle").length,
|
|
|
|
).toBe(1);
|
|
|
|
pointerDown(10, 10, true);
|
|
|
|
pointerMove(20, 20, true);
|
|
|
|
pointerUp(20, 20, true);
|
|
|
|
expect(
|
|
|
|
h.elements.filter((element) => element.type === "rectangle").length,
|
|
|
|
).toBe(2);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("click-drag to select a group", () => {
|
|
|
|
clickTool("rectangle");
|
|
|
|
pointerDown(10, 10);
|
|
|
|
pointerMove(20, 20);
|
|
|
|
pointerUp();
|
|
|
|
|
|
|
|
clickTool("rectangle");
|
|
|
|
pointerDown(30, 10);
|
|
|
|
pointerMove(40, 20);
|
|
|
|
pointerUp();
|
|
|
|
|
|
|
|
clickTool("rectangle");
|
|
|
|
pointerDown(50, 10);
|
|
|
|
pointerMove(60, 20);
|
|
|
|
pointerUp();
|
|
|
|
|
|
|
|
pointerDown(0, 0);
|
|
|
|
pointerMove(45, 25);
|
|
|
|
pointerUp();
|
|
|
|
|
|
|
|
expect(
|
|
|
|
h.elements.filter((element) => h.state.selectedElementIds[element.id])
|
|
|
|
.length,
|
|
|
|
).toBe(2);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("shift-click to select a group, then drag", () => {
|
|
|
|
clickTool("rectangle");
|
|
|
|
pointerDown(10, 10);
|
|
|
|
pointerMove(20, 20);
|
|
|
|
pointerUp();
|
|
|
|
|
|
|
|
clickTool("rectangle");
|
|
|
|
pointerDown(30, 10);
|
|
|
|
pointerMove(40, 20);
|
|
|
|
pointerUp();
|
|
|
|
|
|
|
|
const prevRectsXY = h.elements
|
|
|
|
.filter((element) => element.type === "rectangle")
|
|
|
|
.map((element) => ({ x: element.x, y: element.y }));
|
|
|
|
pointerDown(10, 10);
|
|
|
|
pointerUp();
|
|
|
|
pointerDown(30, 10, false, true);
|
|
|
|
pointerUp();
|
|
|
|
pointerDown(30, 10);
|
|
|
|
pointerMove(40, 20);
|
|
|
|
pointerUp();
|
|
|
|
h.elements
|
|
|
|
.filter((element) => element.type === "rectangle")
|
|
|
|
.forEach((element, i) => {
|
|
|
|
expect(element.x).toBeGreaterThan(prevRectsXY[i].x);
|
|
|
|
expect(element.y).toBeGreaterThan(prevRectsXY[i].y);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it("pinch-to-zoom works", () => {
|
|
|
|
expect(h.state.zoom).toBe(1);
|
|
|
|
pointerType = "touch";
|
|
|
|
pointerDown(50, 50);
|
|
|
|
pointer2Down(60, 50);
|
|
|
|
pointerMove(40, 50);
|
|
|
|
pointer2Move(60, 50);
|
|
|
|
expect(h.state.zoom).toBeGreaterThan(1);
|
|
|
|
const zoomed = h.state.zoom;
|
|
|
|
pointerMove(45, 50);
|
|
|
|
pointer2Move(55, 50);
|
|
|
|
expect(h.state.zoom).toBeLessThan(zoomed);
|
|
|
|
pointerUp(45, 50);
|
|
|
|
pointer2Up(55, 50);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("two-finger scroll works", () => {
|
|
|
|
const startScrollY = h.state.scrollY;
|
|
|
|
pointerDown(50, 50);
|
|
|
|
pointer2Down(60, 50);
|
|
|
|
pointerMove(50, 40);
|
|
|
|
pointer2Move(60, 40);
|
|
|
|
pointerUp(50, 40);
|
|
|
|
pointer2Up(60, 40);
|
|
|
|
expect(h.state.scrollY).toBeLessThan(startScrollY);
|
|
|
|
|
|
|
|
const startScrollX = h.state.scrollX;
|
|
|
|
pointerDown(50, 50);
|
|
|
|
pointer2Down(50, 60);
|
|
|
|
pointerMove(60, 50);
|
|
|
|
pointer2Move(60, 60);
|
|
|
|
pointerUp(60, 50);
|
|
|
|
pointer2Up(60, 60);
|
|
|
|
expect(h.state.scrollX).toBeGreaterThan(startScrollX);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("spacebar + drag scrolls the canvas", () => {
|
|
|
|
const { scrollX: startScrollX, scrollY: startScrollY } = h.state;
|
|
|
|
hotkeyDown("SPACE");
|
|
|
|
pointerDown(50, 50);
|
|
|
|
pointerMove(60, 60);
|
|
|
|
pointerUp();
|
|
|
|
hotkeyUp("SPACE");
|
|
|
|
const { scrollX, scrollY } = h.state;
|
|
|
|
expect(scrollX).not.toEqual(startScrollX);
|
|
|
|
expect(scrollY).not.toEqual(startScrollY);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("arrow keys", () => {
|
|
|
|
clickTool("rectangle");
|
|
|
|
pointerDown(10, 10);
|
|
|
|
pointerMove(20, 20);
|
|
|
|
pointerUp();
|
|
|
|
hotkeyPress("ARROW_LEFT");
|
|
|
|
hotkeyPress("ARROW_LEFT");
|
|
|
|
hotkeyPress("ARROW_RIGHT");
|
|
|
|
hotkeyPress("ARROW_UP");
|
|
|
|
hotkeyPress("ARROW_UP");
|
|
|
|
hotkeyPress("ARROW_DOWN");
|
|
|
|
});
|
|
|
|
|
|
|
|
it("undo/redo drawing an element", () => {
|
|
|
|
clickTool("rectangle");
|
|
|
|
pointerDown(10, 10);
|
|
|
|
pointerMove(20, 20);
|
|
|
|
pointerUp();
|
|
|
|
|
|
|
|
clickTool("rectangle");
|
|
|
|
pointerDown(30, 10);
|
|
|
|
pointerMove(40, 20);
|
|
|
|
pointerUp();
|
|
|
|
|
2020-04-20 14:05:39 +01:00
|
|
|
clickTool("arrow");
|
|
|
|
pointerDown(10, 30);
|
2020-03-23 16:38:41 -07:00
|
|
|
pointerUp();
|
2020-04-20 14:05:39 +01:00
|
|
|
pointerMove(20, 40);
|
|
|
|
pointerDown(20, 40);
|
|
|
|
pointerUp();
|
|
|
|
pointerMove(10, 50);
|
|
|
|
pointerDown(10, 50);
|
|
|
|
pointerUp();
|
|
|
|
hotkeyPress("ENTER");
|
2020-03-23 16:38:41 -07:00
|
|
|
|
|
|
|
expect(h.elements.filter((element) => !element.isDeleted).length).toBe(3);
|
2020-04-20 14:05:39 +01:00
|
|
|
keyPress("z", true); // press twice for multi arrow
|
2020-03-23 16:38:41 -07:00
|
|
|
keyPress("z", true);
|
|
|
|
expect(h.elements.filter((element) => !element.isDeleted).length).toBe(2);
|
|
|
|
keyPress("z", true);
|
|
|
|
expect(h.elements.filter((element) => !element.isDeleted).length).toBe(1);
|
|
|
|
keyPress("z", true, true);
|
|
|
|
expect(h.elements.filter((element) => !element.isDeleted).length).toBe(2);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("zoom hotkeys", () => {
|
|
|
|
expect(h.state.zoom).toBe(1);
|
|
|
|
fireEvent.keyDown(document, { code: "Equal", ctrlKey: true });
|
|
|
|
fireEvent.keyUp(document, { code: "Equal", ctrlKey: true });
|
|
|
|
expect(h.state.zoom).toBeGreaterThan(1);
|
|
|
|
fireEvent.keyDown(document, { code: "Minus", ctrlKey: true });
|
|
|
|
fireEvent.keyUp(document, { code: "Minus", ctrlKey: true });
|
|
|
|
expect(h.state.zoom).toBe(1);
|
|
|
|
});
|
|
|
|
});
|