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

268
package-lock.json generated
View File

@ -1292,6 +1292,12 @@
"any-observable": "^0.3.0"
}
},
"@sheerun/mutationobserver-shim": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.2.tgz",
"integrity": "sha512-vTCdPp/T/Q3oSqwHmZ5Kpa9oI7iLtGl3RQaA/NyLHikvcrPxACkkKVr/XzkSPJWXHRhKGzVvb0urJsbMlRxi1Q==",
"dev": true
},
"@svgr/babel-plugin-add-jsx-attribute": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz",
@ -1401,6 +1407,189 @@
"loader-utils": "^1.2.3"
}
},
"@testing-library/dom": {
"version": "6.12.2",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-6.12.2.tgz",
"integrity": "sha512-KCnvHra5fV+wDxg3wJObGvZFxq7v1DJt829GNFLuRDjKxVNc/B5AdsylNF5PMHFbWMXDsHwM26d2NZcZO9KjbQ==",
"dev": true,
"requires": {
"@babel/runtime": "^7.6.2",
"@sheerun/mutationobserver-shim": "^0.3.2",
"@types/testing-library__dom": "^6.0.0",
"aria-query": "3.0.0",
"pretty-format": "^24.9.0",
"wait-for-expect": "^3.0.0"
}
},
"@testing-library/jest-dom": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.1.1.tgz",
"integrity": "sha512-7xnmBFcUmmUVAUhFiZ/u3CxFh1e46THAwra4SiiKNCW4By26RedCRwEk0rtleFPZG0wlTSNOKDvJjWYy93dp0w==",
"dev": true,
"requires": {
"@babel/runtime": "^7.8.3",
"@types/testing-library__jest-dom": "^5.0.0",
"chalk": "^3.0.0",
"css": "^2.2.4",
"css.escape": "^1.5.1",
"jest-diff": "^25.1.0",
"jest-matcher-utils": "^25.1.0",
"lodash": "^4.17.15",
"pretty-format": "^25.1.0",
"redent": "^3.0.0"
},
"dependencies": {
"@jest/types": {
"version": "25.1.0",
"resolved": "https://registry.npmjs.org/@jest/types/-/types-25.1.0.tgz",
"integrity": "sha512-VpOtt7tCrgvamWZh1reVsGADujKigBUFTi19mlRjqEGsE8qH4r3s+skY33dNdXOwyZIvuftZ5tqdF1IgsMejMA==",
"dev": true,
"requires": {
"@types/istanbul-lib-coverage": "^2.0.0",
"@types/istanbul-reports": "^1.1.1",
"@types/yargs": "^15.0.0",
"chalk": "^3.0.0"
}
},
"@types/yargs": {
"version": "15.0.3",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.3.tgz",
"integrity": "sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==",
"dev": true,
"requires": {
"@types/yargs-parser": "*"
}
},
"ansi-styles": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
"integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
"dev": true,
"requires": {
"@types/color-name": "^1.1.1",
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
"integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"diff-sequences": {
"version": "25.1.0",
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.1.0.tgz",
"integrity": "sha512-nFIfVk5B/NStCsJ+zaPO4vYuLjlzQ6uFvPxzYyHlejNZ/UGa7G/n7peOXVrVNvRuyfstt+mZQYGpjxg9Z6N8Kw==",
"dev": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"jest-diff": {
"version": "25.1.0",
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-25.1.0.tgz",
"integrity": "sha512-nepXgajT+h017APJTreSieh4zCqnSHEJ1iT8HDlewu630lSJ4Kjjr9KNzm+kzGwwcpsDE6Snx1GJGzzsefaEHw==",
"dev": true,
"requires": {
"chalk": "^3.0.0",
"diff-sequences": "^25.1.0",
"jest-get-type": "^25.1.0",
"pretty-format": "^25.1.0"
}
},
"jest-get-type": {
"version": "25.1.0",
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.1.0.tgz",
"integrity": "sha512-yWkBnT+5tMr8ANB6V+OjmrIJufHtCAqI5ic2H40v+tRqxDmE0PGnIiTyvRWFOMtmVHYpwRqyazDbTnhpjsGvLw==",
"dev": true
},
"jest-matcher-utils": {
"version": "25.1.0",
"resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-25.1.0.tgz",
"integrity": "sha512-KGOAFcSFbclXIFE7bS4C53iYobKI20ZWleAdAFun4W1Wz1Kkej8Ng6RRbhL8leaEvIOjGXhGf/a1JjO8bkxIWQ==",
"dev": true,
"requires": {
"chalk": "^3.0.0",
"jest-diff": "^25.1.0",
"jest-get-type": "^25.1.0",
"pretty-format": "^25.1.0"
}
},
"pretty-format": {
"version": "25.1.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.1.0.tgz",
"integrity": "sha512-46zLRSGLd02Rp+Lhad9zzuNZ+swunitn8zIpfD2B4OPCRLXbM87RJT2aBLBWYOznNUML/2l/ReMyWNC80PJBUQ==",
"dev": true,
"requires": {
"@jest/types": "^25.1.0",
"ansi-regex": "^5.0.0",
"ansi-styles": "^4.0.0",
"react-is": "^16.12.0"
}
},
"redent": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
"integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
"dev": true,
"requires": {
"indent-string": "^4.0.0",
"strip-indent": "^3.0.0"
}
},
"strip-indent": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
"integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
"dev": true,
"requires": {
"min-indent": "^1.0.0"
}
},
"supports-color": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
"integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
}
}
},
"@testing-library/react": {
"version": "9.4.0",
"resolved": "https://registry.npmjs.org/@testing-library/react/-/react-9.4.0.tgz",
"integrity": "sha512-XdhDWkI4GktUPsz0AYyeQ8M9qS/JFie06kcSnUVcpgOwFjAu9vhwR83qBl+lw9yZWkbECjL8Hd+n5hH6C0oWqg==",
"dev": true,
"requires": {
"@babel/runtime": "^7.7.6",
"@testing-library/dom": "^6.11.0",
"@types/testing-library__react": "^9.1.2"
}
},
"@types/babel__core": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.3.tgz",
@ -1668,6 +1857,34 @@
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
"integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw=="
},
"@types/testing-library__dom": {
"version": "6.12.0",
"resolved": "https://registry.npmjs.org/@types/testing-library__dom/-/testing-library__dom-6.12.0.tgz",
"integrity": "sha512-PQ/gzABzc53T68RldZ/sJHKCihtP9ofU8XIgOk+H7tlfoCRdg9mqICio5Fo8j3Z8wo+pOfuDsuPprWsn3YtVmA==",
"dev": true,
"requires": {
"pretty-format": "^24.3.0"
}
},
"@types/testing-library__jest-dom": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.0.1.tgz",
"integrity": "sha512-GiPXQBVF9O4DG9cssD2d266vozBJvC5Tnv6aeH5ujgYJgys1DYm9AFCz7YC+STR5ksGxq3zCt+yP8T1wbk2DFg==",
"dev": true,
"requires": {
"@types/jest": "*"
}
},
"@types/testing-library__react": {
"version": "9.1.2",
"resolved": "https://registry.npmjs.org/@types/testing-library__react/-/testing-library__react-9.1.2.tgz",
"integrity": "sha512-CYaMqrswQ+cJACy268jsLAw355DZtPZGt3Jwmmotlcu8O/tkoXBI6AeZ84oZBJsIsesozPKzWzmv/0TIU+1E9Q==",
"dev": true,
"requires": {
"@types/react-dom": "*",
"@types/testing-library__dom": "*"
}
},
"@types/yargs": {
"version": "13.0.8",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.8.tgz",
@ -3863,6 +4080,12 @@
"resolved": "https://registry.npmjs.org/css-what/-/css-what-3.2.1.tgz",
"integrity": "sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw=="
},
"css.escape": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
"integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=",
"dev": true
},
"cssdb": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/cssdb/-/cssdb-4.4.0.tgz",
@ -3873,6 +4096,12 @@
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="
},
"cssfontparser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/cssfontparser/-/cssfontparser-1.2.1.tgz",
"integrity": "sha1-9AIvyPlwDGgCnVQghK+69CWj8+M=",
"dev": true
},
"cssnano": {
"version": "4.1.10",
"resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz",
@ -7154,6 +7383,16 @@
}
}
},
"jest-canvas-mock": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/jest-canvas-mock/-/jest-canvas-mock-2.2.0.tgz",
"integrity": "sha512-DcJdchb7eWFZkt6pvyceWWnu3lsp5QWbUeXiKgEMhwB3sMm5qHM1GQhDajvJgBeiYpgKcojbzZ53d/nz6tXvJw==",
"dev": true,
"requires": {
"cssfontparser": "^1.2.1",
"parse-color": "^1.0.0"
}
},
"jest-changed-files": {
"version": "24.9.0",
"resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-24.9.0.tgz",
@ -9390,6 +9629,12 @@
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
},
"min-indent": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.0.tgz",
"integrity": "sha1-z8RcN+nsDY8KDsPdTvf3w6vjklY=",
"dev": true
},
"mini-css-extract-plugin": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz",
@ -10359,6 +10604,23 @@
"safe-buffer": "^5.1.1"
}
},
"parse-color": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/parse-color/-/parse-color-1.0.0.tgz",
"integrity": "sha1-e3SLlag/A/FqlPU15S1/PZRlhhk=",
"dev": true,
"requires": {
"color-convert": "~0.5.0"
},
"dependencies": {
"color-convert": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz",
"integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0=",
"dev": true
}
}
},
"parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
@ -14952,6 +15214,12 @@
"xml-name-validator": "^3.0.0"
}
},
"wait-for-expect": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/wait-for-expect/-/wait-for-expect-3.0.1.tgz",
"integrity": "sha512-3Ha7lu+zshEG/CeHdcpmQsZnnZpPj/UsG3DuKO8FskjuDbkx3jE3845H+CuwZjA2YWYDfKMU2KhnCaXMLd3wVw==",
"dev": true
},
"walker": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz",

View File

@ -16,6 +16,8 @@
},
"description": "",
"devDependencies": {
"@testing-library/jest-dom": "5.1.1",
"@testing-library/react": "9.4.0",
"@types/jest": "25.1.2",
"@types/nanoid": "2.1.0",
"@types/react": "16.9.19",
@ -24,12 +26,18 @@
"eslint-config-prettier": "6.10.0",
"eslint-plugin-prettier": "3.1.2",
"husky": "4.2.1",
"jest-canvas-mock": "2.2.0",
"lint-staged": "10.0.7",
"node-sass": "4.13.1",
"prettier": "1.19.1",
"rewire": "4.0.1",
"typescript": "3.7.5"
},
"jest": {
"transformIgnorePatterns": [
"node_modules/(?!(roughjs|browser-nativefs)/)"
]
},
"eslintConfig": {
"extends": [
"prettier",

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