Write integration tests (#719)

* Scaffold a simple test case for debugging

* Set up Jest environment that works with React

- Install and set up react-testing-library
- "Unignore" roughjs and browser-nativejs transformations
- Separate App component from ReactDOM

* Write first passing test

- Mock canvas
- Remove App file and mount/unmount ReactDOM on import

* Add tests for drag create behavior

* Fix comments in dragCreate

* Pin jest-mock-canvas dependency

* Remove dependency range for testing library

* Add tests for multi point mode and selection element

* Fix all tests due to decrease in updates to canvas when changing tools

* Disable state updates if component is unmounted

- Remove all event listeners
- Disable storing scene in state if component is unmounted

* Add tests for move and element selection

* Merge branch 'master' into add-integration-tests

* Add tests for resizing rectangle

* move unmounted check to syncActionResult method

* Use a custom test renderer instead of default testing-library functions

* Add custom query for selecting tools

* move files around

Co-authored-by: David Luzar <luzar.david@gmail.com>
This commit is contained in:
Gasim Gasimzada 2020-02-12 02:19:43 +04:00 committed by GitHub
parent ad4ad238ef
commit 7183234895
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 1180 additions and 5 deletions

View file

@ -475,6 +475,9 @@ export class App extends React.Component<any, AppState> {
res: ActionResult,
commitToHistory: boolean = true,
) => {
if (this.unmounted) {
return;
}
if (res.elements) {
elements = res.elements;
if (commitToHistory) {
@ -515,6 +518,11 @@ export class App extends React.Component<any, AppState> {
this.saveDebounced.flush();
};
private disableEvent: EventHandlerNonNull = e => {
e.preventDefault();
};
private unmounted = false;
public async componentDidMount() {
document.addEventListener("copy", this.onCopy);
document.addEventListener("paste", this.pasteFromClipboard);
@ -526,28 +534,32 @@ export class App extends React.Component<any, AppState> {
window.addEventListener("resize", this.onResize, false);
window.addEventListener("unload", this.onUnload, false);
window.addEventListener("blur", this.onUnload, false);
window.addEventListener("dragover", e => e.preventDefault(), false);
window.addEventListener("drop", e => e.preventDefault(), false);
window.addEventListener("dragover", this.disableEvent, false);
window.addEventListener("drop", this.disableEvent, false);
const searchParams = new URLSearchParams(window.location.search);
const id = searchParams.get("id");
if (id) {
// Backwards compatibility with legacy url format
this.syncActionResult(await loadScene(id));
const scene = await loadScene(id);
this.syncActionResult(scene);
} else {
const match = window.location.hash.match(
/^#json=([0-9]+),([a-zA-Z0-9_-]+)$/,
);
if (match) {
this.syncActionResult(await loadScene(match[1], match[2]));
const scene = await loadScene(match[1], match[2]);
this.syncActionResult(scene);
} else {
this.syncActionResult(await loadScene(null));
const scene = await loadScene(null);
this.syncActionResult(scene);
}
}
}
public componentWillUnmount() {
this.unmounted = true;
document.removeEventListener("copy", this.onCopy);
document.removeEventListener("paste", this.pasteFromClipboard);
document.removeEventListener("cut", this.onCut);
@ -558,9 +570,12 @@ export class App extends React.Component<any, AppState> {
this.updateCurrentCursorPosition,
false,
);
document.removeEventListener("keyup", this.onKeyUp);
window.removeEventListener("resize", this.onResize, false);
window.removeEventListener("unload", this.onUnload, false);
window.removeEventListener("blur", this.onUnload, false);
window.removeEventListener("dragover", this.disableEvent, false);
window.removeEventListener("drop", this.disableEvent, false);
}
public state: AppState = getDefaultAppState();

8
src/setupTests.ts Normal file
View file

@ -0,0 +1,8 @@
import "@testing-library/jest-dom";
import "jest-canvas-mock";
// ReactDOM is located inside index.tsx file
// as a result, we need a place for it to render into
const element = document.createElement("div");
element.id = "root";
document.body.appendChild(element);

View file

@ -0,0 +1,277 @@
import React from "react";
import ReactDOM from "react-dom";
import { App } from "../index";
import * as Renderer from "../renderer/renderScene";
import { KEYS } from "../keys";
import { render, fireEvent } from "./test-utils";
// Unmount ReactDOM from root
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
const renderScene = jest.spyOn(Renderer, "renderScene");
beforeEach(() => {
localStorage.clear();
renderScene.mockClear();
});
describe("add element to the scene when mouse dragging long enough", () => {
it("rectangle", () => {
const { getByToolName, container } = render(<App />);
// select tool
const tool = getByToolName("rectangle");
fireEvent.click(tool);
const canvas = container.querySelector("canvas")!;
// start from (30, 20)
fireEvent.mouseDown(canvas, { clientX: 30, clientY: 20 });
// move to (60,70)
fireEvent.mouseMove(canvas, { clientX: 60, clientY: 70 });
// finish (position does not matter)
fireEvent.mouseUp(canvas);
expect(renderScene).toHaveBeenCalledTimes(4);
expect(renderScene.mock.calls[3][1]).toBeNull();
const elements = renderScene.mock.calls[3][0];
expect(elements.length).toEqual(1);
expect(elements[0].type).toEqual("rectangle");
expect(elements[0].x).toEqual(30);
expect(elements[0].y).toEqual(20);
expect(elements[0].width).toEqual(30); // 60 - 30
expect(elements[0].height).toEqual(50); // 70 - 20
});
it("ellipse", () => {
const { getByToolName, container } = render(<App />);
// select tool
const tool = getByToolName("ellipse");
fireEvent.click(tool);
const canvas = container.querySelector("canvas")!;
// start from (30, 20)
fireEvent.mouseDown(canvas, { clientX: 30, clientY: 20 });
// move to (60,70)
fireEvent.mouseMove(canvas, { clientX: 60, clientY: 70 });
// finish (position does not matter)
fireEvent.mouseUp(canvas);
expect(renderScene).toHaveBeenCalledTimes(4);
expect(renderScene.mock.calls[3][1]).toBeNull();
const elements = renderScene.mock.calls[3][0];
expect(elements.length).toEqual(1);
expect(elements[0].type).toEqual("ellipse");
expect(elements[0].x).toEqual(30);
expect(elements[0].y).toEqual(20);
expect(elements[0].width).toEqual(30); // 60 - 30
expect(elements[0].height).toEqual(50); // 70 - 20
});
it("diamond", () => {
const { getByToolName, container } = render(<App />);
// select tool
const tool = getByToolName("diamond");
fireEvent.click(tool);
const canvas = container.querySelector("canvas")!;
// start from (30, 20)
fireEvent.mouseDown(canvas, { clientX: 30, clientY: 20 });
// move to (60,70)
fireEvent.mouseMove(canvas, { clientX: 60, clientY: 70 });
// finish (position does not matter)
fireEvent.mouseUp(canvas);
expect(renderScene).toHaveBeenCalledTimes(4);
expect(renderScene.mock.calls[3][1]).toBeNull();
const elements = renderScene.mock.calls[3][0];
expect(elements.length).toEqual(1);
expect(elements[0].type).toEqual("diamond");
expect(elements[0].x).toEqual(30);
expect(elements[0].y).toEqual(20);
expect(elements[0].width).toEqual(30); // 60 - 30
expect(elements[0].height).toEqual(50); // 70 - 20
});
it("arrow", () => {
const { getByToolName, container } = render(<App />);
// select tool
const tool = getByToolName("arrow");
fireEvent.click(tool);
const canvas = container.querySelector("canvas")!;
// start from (30, 20)
fireEvent.mouseDown(canvas, { clientX: 30, clientY: 20 });
// move to (60,70)
fireEvent.mouseMove(canvas, { clientX: 60, clientY: 70 });
// finish (position does not matter)
fireEvent.mouseUp(canvas);
expect(renderScene).toHaveBeenCalledTimes(4);
expect(renderScene.mock.calls[3][1]).toBeNull();
const elements = renderScene.mock.calls[3][0];
expect(elements.length).toEqual(1);
expect(elements[0].type).toEqual("arrow");
expect(elements[0].x).toEqual(30);
expect(elements[0].y).toEqual(20);
expect(elements[0].points.length).toEqual(2);
expect(elements[0].points[0]).toEqual([0, 0]);
expect(elements[0].points[1]).toEqual([30, 50]); // (60 - 30, 70 - 20)
});
it("line", () => {
const { getByToolName, container } = render(<App />);
// select tool
const tool = getByToolName("line");
fireEvent.click(tool);
const canvas = container.querySelector("canvas")!;
// start from (30, 20)
fireEvent.mouseDown(canvas, { clientX: 30, clientY: 20 });
// move to (60,70)
fireEvent.mouseMove(canvas, { clientX: 60, clientY: 70 });
// finish (position does not matter)
fireEvent.mouseUp(canvas);
expect(renderScene).toHaveBeenCalledTimes(4);
expect(renderScene.mock.calls[3][1]).toBeNull();
const elements = renderScene.mock.calls[3][0];
expect(elements.length).toEqual(1);
expect(elements[0].type).toEqual("line");
expect(elements[0].x).toEqual(30);
expect(elements[0].y).toEqual(20);
expect(elements[0].points.length).toEqual(2);
expect(elements[0].points[0]).toEqual([0, 0]);
expect(elements[0].points[1]).toEqual([30, 50]); // (60 - 30, 70 - 20)
});
});
describe("do not add element to the scene if size is too small", () => {
it("rectangle", () => {
const { getByToolName, container } = render(<App />);
// select tool
const tool = getByToolName("rectangle");
fireEvent.click(tool);
const canvas = container.querySelector("canvas")!;
// start from (30, 20)
fireEvent.mouseDown(canvas, { clientX: 30, clientY: 20 });
// finish (position does not matter)
fireEvent.mouseUp(canvas);
expect(renderScene).toHaveBeenCalledTimes(3);
expect(renderScene.mock.calls[2][1]).toBeNull();
const elements = renderScene.mock.calls[2][0];
expect(elements.length).toEqual(0);
});
it("ellipse", () => {
const { getByToolName, container } = render(<App />);
// select tool
const tool = getByToolName("ellipse");
fireEvent.click(tool);
const canvas = container.querySelector("canvas")!;
// start from (30, 20)
fireEvent.mouseDown(canvas, { clientX: 30, clientY: 20 });
// finish (position does not matter)
fireEvent.mouseUp(canvas);
expect(renderScene).toHaveBeenCalledTimes(3);
expect(renderScene.mock.calls[2][1]).toBeNull();
const elements = renderScene.mock.calls[2][0];
expect(elements.length).toEqual(0);
});
it("diamond", () => {
const { getByToolName, container } = render(<App />);
// select tool
const tool = getByToolName("diamond");
fireEvent.click(tool);
const canvas = container.querySelector("canvas")!;
// start from (30, 20)
fireEvent.mouseDown(canvas, { clientX: 30, clientY: 20 });
// finish (position does not matter)
fireEvent.mouseUp(canvas);
expect(renderScene).toHaveBeenCalledTimes(3);
expect(renderScene.mock.calls[2][1]).toBeNull();
const elements = renderScene.mock.calls[2][0];
expect(elements.length).toEqual(0);
});
it("arrow", () => {
const { getByToolName, container } = render(<App />);
// select tool
const tool = getByToolName("arrow");
fireEvent.click(tool);
const canvas = container.querySelector("canvas")!;
// start from (30, 20)
fireEvent.mouseDown(canvas, { clientX: 30, clientY: 20 });
// finish (position does not matter)
fireEvent.mouseUp(canvas);
// we need to finalize it because arrows and lines enter multi-mode
fireEvent.keyDown(document, { key: KEYS.ENTER });
expect(renderScene).toHaveBeenCalledTimes(4);
expect(renderScene.mock.calls[3][1]).toBeNull();
const elements = renderScene.mock.calls[3][0];
expect(elements.length).toEqual(0);
});
it("line", () => {
const { getByToolName, container } = render(<App />);
// select tool
const tool = getByToolName("line");
fireEvent.click(tool);
const canvas = container.querySelector("canvas")!;
// start from (30, 20)
fireEvent.mouseDown(canvas, { clientX: 30, clientY: 20 });
// finish (position does not matter)
fireEvent.mouseUp(canvas);
// we need to finalize it because arrows and lines enter multi-mode
fireEvent.keyDown(document, { key: KEYS.ENTER });
expect(renderScene).toHaveBeenCalledTimes(4);
expect(renderScene.mock.calls[3][1]).toBeNull();
const elements = renderScene.mock.calls[3][0];
expect(elements.length).toEqual(0);
});
});

88
src/tests/move.test.tsx Normal file
View file

@ -0,0 +1,88 @@
import React from "react";
import ReactDOM from "react-dom";
import { render, fireEvent } from "./test-utils";
import { App } from "../index";
import * as Renderer from "../renderer/renderScene";
// Unmount ReactDOM from root
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
const renderScene = jest.spyOn(Renderer, "renderScene");
beforeEach(() => {
localStorage.clear();
renderScene.mockClear();
});
describe("move element", () => {
it("rectangle", () => {
const { getByToolName, container } = render(<App />);
const canvas = container.querySelector("canvas")!;
{
// create element
const tool = getByToolName("rectangle");
fireEvent.click(tool);
fireEvent.mouseDown(canvas, { clientX: 30, clientY: 20 });
fireEvent.mouseMove(canvas, { clientX: 60, clientY: 70 });
fireEvent.mouseUp(canvas);
expect(renderScene).toHaveBeenCalledTimes(4);
const elements = renderScene.mock.calls[3][0];
const selectionElement = renderScene.mock.calls[3][1];
expect(selectionElement).toBeNull();
expect(elements.length).toEqual(1);
expect(elements[0].isSelected).toBeTruthy();
expect([elements[0].x, elements[0].y]).toEqual([30, 20]);
renderScene.mockClear();
}
fireEvent.mouseDown(canvas, { clientX: 50, clientY: 20 });
fireEvent.mouseMove(canvas, { clientX: 20, clientY: 40 });
fireEvent.mouseUp(canvas);
expect(renderScene).toHaveBeenCalledTimes(3);
const elements = renderScene.mock.calls[2][0];
expect(renderScene.mock.calls[2][1]).toBeNull();
expect(elements.length).toEqual(1);
expect([elements[0].x, elements[0].y]).toEqual([0, 40]);
});
});
describe("duplicate element on move when ALT is clicked", () => {
it("rectangle", () => {
const { getByToolName, container } = render(<App />);
const canvas = container.querySelector("canvas")!;
{
// create element
const tool = getByToolName("rectangle");
fireEvent.click(tool);
fireEvent.mouseDown(canvas, { clientX: 30, clientY: 20 });
fireEvent.mouseMove(canvas, { clientX: 60, clientY: 70 });
fireEvent.mouseUp(canvas);
expect(renderScene).toHaveBeenCalledTimes(4);
const elements = renderScene.mock.calls[3][0];
const selectionElement = renderScene.mock.calls[3][1];
expect(selectionElement).toBeNull();
expect(elements.length).toEqual(1);
expect(elements[0].isSelected).toBeTruthy();
expect([elements[0].x, elements[0].y]).toEqual([30, 20]);
renderScene.mockClear();
}
fireEvent.mouseDown(canvas, { clientX: 50, clientY: 20, altKey: true });
fireEvent.mouseMove(canvas, { clientX: 20, clientY: 40 });
fireEvent.mouseUp(canvas);
expect(renderScene).toHaveBeenCalledTimes(3);
const elements = renderScene.mock.calls[2][0];
expect(renderScene.mock.calls[2][1]).toBeNull();
expect(elements.length).toEqual(2);
// previous element should stay intact
expect([elements[0].x, elements[0].y]).toEqual([30, 20]);
expect([elements[1].x, elements[1].y]).toEqual([0, 40]);
});
});

View file

@ -0,0 +1,140 @@
import React from "react";
import ReactDOM from "react-dom";
import { render, fireEvent } from "./test-utils";
import { App } from "../index";
import * as Renderer from "../renderer/renderScene";
import { KEYS } from "../keys";
// Unmount ReactDOM from root
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
const renderScene = jest.spyOn(Renderer, "renderScene");
beforeEach(() => {
localStorage.clear();
renderScene.mockClear();
});
describe("remove shape in non linear elements", () => {
it("rectangle", () => {
const { getByToolName, container } = render(<App />);
// select tool
const tool = getByToolName("rectangle");
fireEvent.click(tool);
const canvas = container.querySelector("canvas")!;
fireEvent.mouseDown(canvas, { clientX: 30, clientY: 20 });
fireEvent.mouseUp(canvas, { clientX: 30, clientY: 30 });
expect(renderScene).toHaveBeenCalledTimes(3);
const elements = renderScene.mock.calls[2][0];
expect(elements.length).toEqual(0);
});
it("ellipse", () => {
const { getByToolName, container } = render(<App />);
// select tool
const tool = getByToolName("ellipse");
fireEvent.click(tool);
const canvas = container.querySelector("canvas")!;
fireEvent.mouseDown(canvas, { clientX: 30, clientY: 20 });
fireEvent.mouseUp(canvas, { clientX: 30, clientY: 30 });
expect(renderScene).toHaveBeenCalledTimes(3);
const elements = renderScene.mock.calls[2][0];
expect(elements.length).toEqual(0);
});
it("diamond", () => {
const { getByToolName, container } = render(<App />);
// select tool
const tool = getByToolName("diamond");
fireEvent.click(tool);
const canvas = container.querySelector("canvas")!;
fireEvent.mouseDown(canvas, { clientX: 30, clientY: 20 });
fireEvent.mouseUp(canvas, { clientX: 30, clientY: 30 });
expect(renderScene).toHaveBeenCalledTimes(3);
const elements = renderScene.mock.calls[2][0];
expect(elements.length).toEqual(0);
});
});
describe("multi point mode in linear elements", () => {
it("arrow", () => {
const { getByToolName, container } = render(<App />);
// select tool
const tool = getByToolName("arrow");
fireEvent.click(tool);
const canvas = container.querySelector("canvas")!;
// first point is added on mouse down
fireEvent.mouseDown(canvas, { clientX: 30, clientY: 30 });
// second point, enable multi point
fireEvent.mouseUp(canvas, { clientX: 30, clientY: 30 });
fireEvent.mouseMove(canvas, { clientX: 50, clientY: 60 });
// third point
fireEvent.mouseDown(canvas, { clientX: 50, clientY: 60 });
fireEvent.mouseUp(canvas);
fireEvent.mouseMove(canvas, { clientX: 100, clientY: 140 });
// done
fireEvent.mouseDown(canvas);
fireEvent.mouseUp(canvas);
fireEvent.keyDown(document, { key: KEYS.ENTER });
expect(renderScene).toHaveBeenCalledTimes(8);
const elements = renderScene.mock.calls[7][0];
expect(elements.length).toEqual(1);
expect(elements[0].type).toEqual("arrow");
expect(elements[0].x).toEqual(30);
expect(elements[0].y).toEqual(30);
expect(elements[0].points).toEqual([
[0, 0],
[20, 30],
[70, 110],
]);
});
it("line", () => {
const { getByToolName, container } = render(<App />);
// select tool
const tool = getByToolName("line");
fireEvent.click(tool);
const canvas = container.querySelector("canvas")!;
// first point is added on mouse down
fireEvent.mouseDown(canvas, { clientX: 30, clientY: 30 });
// second point, enable multi point
fireEvent.mouseUp(canvas, { clientX: 30, clientY: 30 });
fireEvent.mouseMove(canvas, { clientX: 50, clientY: 60 });
// third point
fireEvent.mouseDown(canvas, { clientX: 50, clientY: 60 });
fireEvent.mouseUp(canvas);
fireEvent.mouseMove(canvas, { clientX: 100, clientY: 140 });
// done
fireEvent.mouseDown(canvas);
fireEvent.mouseUp(canvas);
fireEvent.keyDown(document, { key: KEYS.ENTER });
expect(renderScene).toHaveBeenCalledTimes(8);
const elements = renderScene.mock.calls[7][0];
expect(elements.length).toEqual(1);
expect(elements[0].type).toEqual("line");
expect(elements[0].x).toEqual(30);
expect(elements[0].y).toEqual(30);
expect(elements[0].points).toEqual([
[0, 0],
[20, 30],
[70, 110],
]);
});
});

View file

@ -0,0 +1,32 @@
import { queries, buildQueries } from "@testing-library/react";
const _getAllByToolName = (container: HTMLElement, tool: string) => {
const toolMap: { [propKey: string]: string } = {
selection: "Selection — S, 1",
rectangle: "Rectangle — R, 2",
diamond: "Diamond — D, 3",
ellipse: "Ellipse — E, 4",
arrow: "Arrow — A, 5",
line: "Line — L, 6",
};
const toolTitle = toolMap[tool as string];
return queries.getAllByTitle(container, toolTitle);
};
const getMultipleError = (c: any, tool: any) =>
`Found multiple elements with tool name: ${tool}`;
const getMissingError = (c: any, tool: any) =>
`Unable to find an element with tool name: ${tool}`;
export const [
queryByToolName,
getAllByToolName,
getByToolName,
findAllByToolName,
findByToolName,
] = buildQueries<string[]>(
_getAllByToolName,
getMultipleError,
getMissingError,
);

98
src/tests/resize.test.tsx Normal file
View file

@ -0,0 +1,98 @@
import React from "react";
import ReactDOM from "react-dom";
import { render, fireEvent } from "./test-utils";
import { App } from "../index";
import * as Renderer from "../renderer/renderScene";
// Unmount ReactDOM from root
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
const renderScene = jest.spyOn(Renderer, "renderScene");
beforeEach(() => {
localStorage.clear();
renderScene.mockClear();
});
describe("resize element", () => {
it("rectangle", () => {
const { getByToolName, container } = render(<App />);
const canvas = container.querySelector("canvas")!;
{
// create element
const tool = getByToolName("rectangle");
fireEvent.click(tool);
fireEvent.mouseDown(canvas, { clientX: 30, clientY: 20 });
fireEvent.mouseMove(canvas, { clientX: 60, clientY: 70 });
fireEvent.mouseUp(canvas);
expect(renderScene).toHaveBeenCalledTimes(4);
const elements = renderScene.mock.calls[3][0];
const selectionElement = renderScene.mock.calls[3][1];
expect(selectionElement).toBeNull();
expect(elements.length).toEqual(1);
expect(elements[0].isSelected).toBeTruthy();
expect([elements[0].x, elements[0].y]).toEqual([30, 20]);
renderScene.mockClear();
}
// select the element first
fireEvent.mouseDown(canvas, { clientX: 50, clientY: 20 });
fireEvent.mouseUp(canvas);
// select a handler rectangle (top-left)
fireEvent.mouseDown(canvas, { clientX: 21, clientY: 13 });
fireEvent.mouseMove(canvas, { clientX: 20, clientY: 40 });
fireEvent.mouseUp(canvas);
expect(renderScene).toHaveBeenCalledTimes(5);
const elements = renderScene.mock.calls[4][0];
expect(renderScene.mock.calls[4][1]).toBeNull();
expect(elements.length).toEqual(1);
expect([elements[0].x, elements[0].y]).toEqual([29, 47]);
expect([elements[0].width, elements[0].height]).toEqual([31, 23]);
});
});
describe("resize element with aspect ratio when SHIFT is clicked", () => {
it("rectangle", () => {
const { getByToolName, container } = render(<App />);
const canvas = container.querySelector("canvas")!;
{
// create element
const tool = getByToolName("rectangle");
fireEvent.click(tool);
fireEvent.mouseDown(canvas, { clientX: 30, clientY: 20 });
fireEvent.mouseMove(canvas, { clientX: 60, clientY: 70 });
fireEvent.mouseUp(canvas);
expect(renderScene).toHaveBeenCalledTimes(4);
const elements = renderScene.mock.calls[3][0];
const selectionElement = renderScene.mock.calls[3][1];
expect(selectionElement).toBeNull();
expect(elements.length).toEqual(1);
expect(elements[0].isSelected).toBeTruthy();
expect([elements[0].x, elements[0].y]).toEqual([30, 20]);
renderScene.mockClear();
}
// select the element first
fireEvent.mouseDown(canvas, { clientX: 50, clientY: 20 });
fireEvent.mouseUp(canvas);
// select a handler rectangle (top-left)
fireEvent.mouseDown(canvas, { clientX: 21, clientY: 13 });
fireEvent.mouseMove(canvas, { clientX: 20, clientY: 40, shiftKey: true });
fireEvent.mouseUp(canvas);
expect(renderScene).toHaveBeenCalledTimes(5);
const elements = renderScene.mock.calls[4][0];
expect(renderScene.mock.calls[4][1]).toBeNull();
expect(elements.length).toEqual(1);
expect([elements[0].x, elements[0].y]).toEqual([29, 39]);
expect([elements[0].width, elements[0].height]).toEqual([31, 31]);
});
});

View file

@ -0,0 +1,211 @@
import React from "react";
import ReactDOM from "react-dom";
import { render, fireEvent } from "./test-utils";
import { App } from "../index";
import * as Renderer from "../renderer/renderScene";
import { KEYS } from "../keys";
// Unmount ReactDOM from root
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
const renderScene = jest.spyOn(Renderer, "renderScene");
beforeEach(() => {
localStorage.clear();
renderScene.mockClear();
});
describe("selection element", () => {
it("create selection element on mouse down", () => {
const { getByToolName, container } = render(<App />);
// select tool
const tool = getByToolName("selection");
fireEvent.click(tool);
const canvas = container.querySelector("canvas")!;
fireEvent.mouseDown(canvas, { clientX: 60, clientY: 100 });
expect(renderScene).toHaveBeenCalledTimes(1);
const selectionElement = renderScene.mock.calls[0][1]!;
expect(selectionElement).not.toBeNull();
expect(selectionElement.type).toEqual("selection");
expect([selectionElement.x, selectionElement.y]).toEqual([60, 100]);
expect([selectionElement.width, selectionElement.height]).toEqual([0, 0]);
// TODO: There is a memory leak if mouse up is not triggered
fireEvent.mouseUp(canvas);
});
it("resize selection element on mouse move", () => {
const { getByToolName, container } = render(<App />);
// select tool
const tool = getByToolName("selection");
fireEvent.click(tool);
const canvas = container.querySelector("canvas")!;
fireEvent.mouseDown(canvas, { clientX: 60, clientY: 100 });
fireEvent.mouseMove(canvas, { clientX: 150, clientY: 30 });
expect(renderScene).toHaveBeenCalledTimes(2);
const selectionElement = renderScene.mock.calls[1][1]!;
expect(selectionElement).not.toBeNull();
expect(selectionElement.type).toEqual("selection");
expect([selectionElement.x, selectionElement.y]).toEqual([60, 30]);
expect([selectionElement.width, selectionElement.height]).toEqual([90, 70]);
// TODO: There is a memory leak if mouse up is not triggered
fireEvent.mouseUp(canvas);
});
it("remove selection element on mouse up", () => {
const { getByToolName, container } = render(<App />);
// select tool
const tool = getByToolName("selection");
fireEvent.click(tool);
const canvas = container.querySelector("canvas")!;
fireEvent.mouseDown(canvas, { clientX: 60, clientY: 100 });
fireEvent.mouseMove(canvas, { clientX: 150, clientY: 30 });
fireEvent.mouseUp(canvas);
expect(renderScene).toHaveBeenCalledTimes(3);
const selectionElement = renderScene.mock.calls[2][1];
expect(selectionElement).toBeNull();
});
});
describe("select single element on the scene", () => {
it("rectangle", () => {
const { getByToolName, container } = render(<App />);
const canvas = container.querySelector("canvas")!;
{
// create element
const tool = getByToolName("rectangle");
fireEvent.click(tool);
fireEvent.mouseDown(canvas, { clientX: 30, clientY: 20 });
fireEvent.mouseMove(canvas, { clientX: 60, clientY: 70 });
fireEvent.mouseUp(canvas);
fireEvent.keyDown(document, { key: KEYS.ESCAPE });
}
const tool = getByToolName("selection");
fireEvent.click(tool);
// click on a line on the rectangle
fireEvent.mouseDown(canvas, { clientX: 45, clientY: 20 });
fireEvent.mouseUp(canvas);
expect(renderScene).toHaveBeenCalledTimes(7);
const elements = renderScene.mock.calls[6][0];
const selectionElement = renderScene.mock.calls[6][1];
expect(selectionElement).toBeNull();
expect(elements.length).toEqual(1);
expect(elements[0].isSelected).toBeTruthy();
});
it("diamond", () => {
const { getByToolName, container } = render(<App />);
const canvas = container.querySelector("canvas")!;
{
// create element
const tool = getByToolName("diamond");
fireEvent.click(tool);
fireEvent.mouseDown(canvas, { clientX: 30, clientY: 20 });
fireEvent.mouseMove(canvas, { clientX: 60, clientY: 70 });
fireEvent.mouseUp(canvas);
fireEvent.keyDown(document, { key: KEYS.ESCAPE });
}
const tool = getByToolName("selection");
fireEvent.click(tool);
// click on a line on the rectangle
fireEvent.mouseDown(canvas, { clientX: 45, clientY: 20 });
fireEvent.mouseUp(canvas);
expect(renderScene).toHaveBeenCalledTimes(7);
const elements = renderScene.mock.calls[6][0];
const selectionElement = renderScene.mock.calls[6][1];
expect(selectionElement).toBeNull();
expect(elements.length).toEqual(1);
expect(elements[0].isSelected).toBeTruthy();
});
it("ellipse", () => {
const { getByToolName, container } = render(<App />);
const canvas = container.querySelector("canvas")!;
{
// create element
const tool = getByToolName("ellipse");
fireEvent.click(tool);
fireEvent.mouseDown(canvas, { clientX: 30, clientY: 20 });
fireEvent.mouseMove(canvas, { clientX: 60, clientY: 70 });
fireEvent.mouseUp(canvas);
fireEvent.keyDown(document, { key: KEYS.ESCAPE });
}
const tool = getByToolName("selection");
fireEvent.click(tool);
// click on a line on the rectangle
fireEvent.mouseDown(canvas, { clientX: 45, clientY: 20 });
fireEvent.mouseUp(canvas);
expect(renderScene).toHaveBeenCalledTimes(7);
const elements = renderScene.mock.calls[6][0];
const selectionElement = renderScene.mock.calls[6][1];
expect(selectionElement).toBeNull();
expect(elements.length).toEqual(1);
expect(elements[0].isSelected).toBeTruthy();
});
it("arrow", () => {
const { getByToolName, container } = render(<App />);
const canvas = container.querySelector("canvas")!;
{
// create element
const tool = getByToolName("arrow");
fireEvent.click(tool);
fireEvent.mouseDown(canvas, { clientX: 30, clientY: 20 });
fireEvent.mouseMove(canvas, { clientX: 60, clientY: 70 });
fireEvent.mouseUp(canvas);
fireEvent.keyDown(document, { key: KEYS.ESCAPE });
}
const tool = getByToolName("selection");
fireEvent.click(tool);
// click on a line on the rectangle
fireEvent.mouseDown(canvas, { clientX: 45, clientY: 20 });
fireEvent.mouseUp(canvas);
expect(renderScene).toHaveBeenCalledTimes(7);
const elements = renderScene.mock.calls[6][0];
const selectionElement = renderScene.mock.calls[6][1];
expect(selectionElement).toBeNull();
expect(elements.length).toEqual(1);
expect(elements[0].isSelected).toBeTruthy();
});
it("arrow", () => {
const { getByToolName, container } = render(<App />);
const canvas = container.querySelector("canvas")!;
{
// create element
const tool = getByToolName("line");
fireEvent.click(tool);
fireEvent.mouseDown(canvas, { clientX: 30, clientY: 20 });
fireEvent.mouseMove(canvas, { clientX: 60, clientY: 70 });
fireEvent.mouseUp(canvas);
fireEvent.keyDown(document, { key: KEYS.ESCAPE });
}
const tool = getByToolName("selection");
fireEvent.click(tool);
// click on a line on the rectangle
fireEvent.mouseDown(canvas, { clientX: 45, clientY: 20 });
fireEvent.mouseUp(canvas);
expect(renderScene).toHaveBeenCalledTimes(7);
const elements = renderScene.mock.calls[6][0];
const selectionElement = renderScene.mock.calls[6][1];
expect(selectionElement).toBeNull();
expect(elements.length).toEqual(1);
expect(elements[0].isSelected).toBeTruthy();
});
});

30
src/tests/test-utils.ts Normal file
View file

@ -0,0 +1,30 @@
import {
render,
queries,
RenderResult,
RenderOptions,
} from "@testing-library/react";
import * as toolQueries from "./queries/toolQueries";
const customQueries = {
...queries,
...toolQueries,
};
type TestRenderFn = (
ui: React.ReactElement,
options?: Omit<RenderOptions, "queries">,
) => RenderResult<typeof customQueries>;
const renderApp: TestRenderFn = (ui, options) =>
render(ui, {
queries: customQueries,
...options,
});
// re-export everything
export * from "@testing-library/react";
// override render method
export { renderApp as render };