| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265 |
- import { arrayToMap } from "@excalidraw/common";
- import { type GlobalPoint, type LocalPoint, pointFrom } from "@excalidraw/math";
- import { Excalidraw } from "@excalidraw/excalidraw";
- import { API } from "@excalidraw/excalidraw/tests/helpers/api";
- import { UI } from "@excalidraw/excalidraw/tests/helpers/ui";
- import "@excalidraw/utils/test-utils";
- import { render } from "@excalidraw/excalidraw/tests/test-utils";
- import * as distance from "../src/distance";
- import { hitElementItself } from "../src/collision";
- describe("check rotated elements can be hit:", () => {
- beforeEach(async () => {
- localStorage.clear();
- await render(<Excalidraw handleKeyboardGlobally={true} />);
- });
- it("arrow", () => {
- UI.createElement("arrow", {
- x: 0,
- y: 0,
- width: 124,
- height: 302,
- angle: 1.8700426423973724,
- points: [
- [0, 0],
- [120, -198],
- [-4, -302],
- ] as LocalPoint[],
- });
- const hit = hitElementItself({
- point: pointFrom<GlobalPoint>(88, -68),
- element: window.h.elements[0],
- threshold: 10,
- elementsMap: window.h.scene.getNonDeletedElementsMap(),
- });
- expect(hit).toBe(true);
- });
- });
- describe("frame hit testing", () => {
- it.each(["transparent", "#ffffff"])(
- "does not hit frame inside regardless of background color (%s)",
- (backgroundColor) => {
- const element = API.createElement({
- type: "frame",
- x: 0,
- y: 0,
- width: 100,
- height: 100,
- backgroundColor,
- });
- const elementsMap = arrayToMap([element]);
- expect(
- hitElementItself({
- point: pointFrom<GlobalPoint>(50, 50),
- element,
- threshold: 10,
- elementsMap,
- }),
- ).toBe(false);
- },
- );
- it("hits frame outline", () => {
- const element = API.createElement({
- type: "frame",
- x: 0,
- y: 0,
- width: 100,
- height: 100,
- backgroundColor: "#ffffff",
- });
- const elementsMap = arrayToMap([element]);
- expect(
- hitElementItself({
- point: pointFrom<GlobalPoint>(0, 50),
- element,
- threshold: 1,
- elementsMap,
- }),
- ).toBe(true);
- });
- });
- describe("hitElementItself cache", () => {
- beforeEach(async () => {
- // reset cache
- hitElementItself({
- point: pointFrom<GlobalPoint>(50, 50),
- element: API.createElement({
- type: "rectangle",
- x: 0,
- y: 0,
- width: 100,
- height: 100,
- backgroundColor: "#ffffff",
- }),
- threshold: Infinity,
- elementsMap: new Map([]),
- });
- localStorage.clear();
- await render(<Excalidraw handleKeyboardGlobally={true} />);
- });
- it("reuses cached result when threshold increases", () => {
- const element = API.createElement({
- type: "rectangle",
- x: 0,
- y: 0,
- width: 100,
- height: 100,
- backgroundColor: "#ffffff",
- });
- const elementsMap = arrayToMap([element]);
- const point = pointFrom<GlobalPoint>(100.5, 50);
- const distanceSpy = jest.spyOn(distance, "distanceToElement");
- expect(
- hitElementItself({
- point,
- element,
- threshold: 1,
- elementsMap,
- }),
- ).toBe(true);
- expect(distanceSpy).toHaveBeenCalledTimes(1);
- expect(
- hitElementItself({
- point,
- element,
- threshold: 10,
- elementsMap,
- }),
- ).toBe(true);
- expect(distanceSpy).toHaveBeenCalledTimes(1);
- distanceSpy.mockRestore();
- });
- it("does not reuse cache when threshold decreases", () => {
- const element = API.createElement({
- type: "rectangle",
- x: 0,
- y: 0,
- width: 100,
- height: 100,
- backgroundColor: "transparent",
- });
- const elementsMap = arrayToMap([element]);
- const point = pointFrom<GlobalPoint>(105, 50);
- const distanceSpy = jest.spyOn(distance, "distanceToElement");
- expect(
- hitElementItself({
- point,
- element,
- threshold: 10,
- elementsMap,
- }),
- ).toBe(true);
- expect(distanceSpy).toHaveBeenCalledTimes(1);
- expect(
- hitElementItself({
- point,
- element,
- threshold: 6,
- elementsMap,
- }),
- ).toBe(true);
- expect(distanceSpy).toHaveBeenCalledTimes(2);
- distanceSpy.mockRestore();
- });
- it("invalidates cache when element version changes", () => {
- const element = API.createElement({
- type: "rectangle",
- x: 0,
- y: 0,
- width: 100,
- height: 100,
- backgroundColor: "#ffffff",
- });
- const elementsMap = arrayToMap([element]);
- const point = pointFrom<GlobalPoint>(100.5, 50);
- const distanceSpy = jest.spyOn(distance, "distanceToElement");
- expect(
- hitElementItself({
- point,
- element,
- threshold: 1,
- elementsMap,
- }),
- ).toBe(true);
- expect(distanceSpy).toHaveBeenCalledTimes(1);
- const movedElement = {
- ...element,
- version: element.version + 1,
- versionNonce: element.versionNonce + 1,
- };
- expect(
- hitElementItself({
- point,
- element: movedElement,
- threshold: 1,
- elementsMap,
- }),
- ).toBe(true);
- expect(distanceSpy).toHaveBeenCalledTimes(2);
- distanceSpy.mockRestore();
- });
- it("override does not affect caching", () => {
- const element = API.createElement({
- type: "rectangle",
- x: 0,
- y: 0,
- width: 100,
- height: 100,
- backgroundColor: "transparent",
- });
- const elementsMap = arrayToMap([element]);
- const point = pointFrom<GlobalPoint>(50, 50);
- const distanceSpy = jest.spyOn(distance, "distanceToElement");
- expect(
- hitElementItself({
- point,
- element,
- threshold: 10,
- elementsMap,
- }),
- ).toBe(false);
- expect(distanceSpy).toHaveBeenCalledTimes(1);
- expect(
- hitElementItself({
- point,
- element,
- threshold: 10,
- elementsMap,
- overrideShouldTestInside: true,
- }),
- ).toBe(true);
- });
- });
|