From 0c0429255f8d33cce8a655d4744fe2be60841425 Mon Sep 17 00:00:00 2001 From: ShaMan123 Date: Fri, 15 Sep 2023 11:17:17 +0530 Subject: [PATCH] import tests --- .../__snapshots__/eventData.test.ts.snap | 40 +- src/canvas/__tests__/eventData.test.ts | 731 +++++++++++++++++- 2 files changed, 728 insertions(+), 43 deletions(-) diff --git a/src/canvas/__tests__/__snapshots__/eventData.test.ts.snap b/src/canvas/__tests__/__snapshots__/eventData.test.ts.snap index a01f160254d..31a16045466 100644 --- a/src/canvas/__tests__/__snapshots__/eventData.test.ts.snap +++ b/src/canvas/__tests__/__snapshots__/eventData.test.ts.snap @@ -116,9 +116,7 @@ exports[`Canvas event data HTML event "dragend" should fire a corresponding canv "x": 50, "y": 50, }, - "subTargets": [ - "Drag Target", - ], + "subTargets": [], "target": "Drag Target", "transform": null, }, @@ -131,9 +129,7 @@ exports[`Canvas event data HTML event "dragend" should fire a corresponding canv "y": -13, }, "button": 1, - "currentSubTargets": [ - "Drag Target", - ], + "currentSubTargets": [], "currentTarget": "Drag Target", "e": MouseEvent { "isTrusted": false, @@ -143,9 +139,7 @@ exports[`Canvas event data HTML event "dragend" should fire a corresponding canv "x": 50, "y": 50, }, - "subTargets": [ - "Drag Target", - ], + "subTargets": [], "target": "Drag Target", "transform": null, }, @@ -188,9 +182,7 @@ exports[`Canvas event data HTML event "dragenter" should fire a corresponding ca "e": MouseEvent { "isTrusted": false, }, - "subTargets": [ - "Drag Target", - ], + "subTargets": [], "target": "Drag Target", }, ], @@ -211,9 +203,7 @@ exports[`Canvas event data HTML event "dragenter" should fire a corresponding ca "y": 50, }, "previousTarget": undefined, - "subTargets": [ - "Drag Target", - ], + "subTargets": [], "target": "Drag Target", }, ], @@ -265,9 +255,7 @@ exports[`Canvas event data HTML event "dragover" should fire a corresponding can "e": MouseEvent { "isTrusted": false, }, - "subTargets": [ - "Drag Target", - ], + "subTargets": [], "target": "Drag Target", }, ], @@ -290,9 +278,7 @@ exports[`Canvas event data HTML event "dragover" should fire a corresponding can "y": 50, }, "previousTarget": undefined, - "subTargets": [ - "Drag Target", - ], + "subTargets": [], "target": "Drag Target", }, ], @@ -327,9 +313,7 @@ exports[`Canvas event data HTML event "drop" should fire a corresponding canvas "x": 50, "y": 50, }, - "subTargets": [ - "Drag Target", - ], + "subTargets": [], "target": "Drag Target", }, ], @@ -350,9 +334,7 @@ exports[`Canvas event data HTML event "drop" should fire a corresponding canvas "x": 50, "y": 50, }, - "subTargets": [ - "Drag Target", - ], + "subTargets": [], "target": "Drag Target", }, ], @@ -373,9 +355,7 @@ exports[`Canvas event data HTML event "drop" should fire a corresponding canvas "x": 50, "y": 50, }, - "subTargets": [ - "Drag Target", - ], + "subTargets": [], "target": "Drag Target", }, ], diff --git a/src/canvas/__tests__/eventData.test.ts b/src/canvas/__tests__/eventData.test.ts index 75b3e8bce91..d30fb70b7a0 100644 --- a/src/canvas/__tests__/eventData.test.ts +++ b/src/canvas/__tests__/eventData.test.ts @@ -1,14 +1,24 @@ /* eslint-disable no-restricted-globals */ import '../../../jest.extend'; import { Point } from '../../Point'; +import { ActiveSelection } from '../../shapes/ActiveSelection'; +import { Circle } from '../../shapes/Circle'; import { Group } from '../../shapes/Group'; import { IText } from '../../shapes/IText/IText'; import { FabricObject } from '../../shapes/Object/FabricObject'; +import { Rect } from '../../shapes/Rect'; +import { Triangle } from '../../shapes/Triangle'; import type { TMat2D } from '../../typedefs'; import { Canvas } from '../Canvas'; const genericVpt = [2.3, 0, 0, 2.3, 120, 80] as TMat2D; +const registerTestObjects = (objects: Record) => { + Object.entries(objects).forEach(([key, object]) => { + jest.spyOn(object, 'toJSON').mockReturnValue(key); + }); +}; + describe('Canvas event data', () => { let canvas: Canvas; let spy: jest.SpyInstance; @@ -124,22 +134,717 @@ describe('Event targets', () => { expect(targetSpy).toHaveBeenCalledTimes(1); }); - test('searchPossibleTargets', () => { - const subTarget = new FabricObject(); - const target = new Group([subTarget], { + test('mouseover and mouseout with subTargetCheck', () => { + const rect1 = new FabricObject({ + width: 5, + height: 5, + left: 5, + top: 0, + strokeWidth: 0, + }); + const rect2 = new FabricObject({ + width: 5, + height: 5, + left: 5, + top: 5, + strokeWidth: 0, + }); + const rect3 = new FabricObject({ + width: 5, + height: 5, + left: 0, + top: 5, + strokeWidth: 0, + }); + const rect4 = new FabricObject({ + width: 5, + height: 5, + left: 0, + top: 0, + strokeWidth: 0, + }); + const rect5 = new FabricObject({ + width: 5, + height: 5, + left: 2.5, + top: 2.5, + strokeWidth: 0, + }); + const group1 = new Group([rect1, rect2], { subTargetCheck: true, }); - const parent = new Group([target], { + const group2 = new Group([rect3, rect4], { subTargetCheck: true, - interactive: true, }); - const canvas = new Canvas(null); - canvas.add(parent); - const targetSpy = jest.fn(); - target.on('mousedown', targetSpy); - jest.spyOn(canvas, '_checkTarget').mockReturnValue(true); - const found = canvas.searchPossibleTargets([parent], new Point()); - expect(found).toBe(target); - expect(canvas.targets).toEqual([subTarget, target, parent]); + // a group with 2 groups, with 2 rects each, one group left one group right + // each with 2 rects vertically aligned + const group = new Group([group1, group2], { + subTargetCheck: true, + }); + + const enter = jest.fn(); + const exit = jest.fn(); + + const getTargetsFromEventStream = (mock: jest.Mock) => + mock.mock.calls.map((args) => args[0].target); + + registerTestObjects({ + rect1, + rect2, + rect3, + rect4, + rect5, + group1, + group2, + group, + }); + + Object.values({ + rect1, + rect2, + rect3, + rect4, + rect5, + group1, + group2, + group, + }).forEach((object) => { + object.on('mouseover', enter); + object.on('mouseout', exit); + }); + + const canvas = new Canvas(); + canvas.add(group, rect5); + + const fire = (x: number, y: number) => { + enter.mockClear(); + exit.mockClear(); + canvas + .getSelectionElement() + .dispatchEvent(new MouseEvent('mousemove', { clientX: x, clientY: y })); + }; + + fire(1, 1); + expect(getTargetsFromEventStream(enter)).toEqual([group, rect4, group2]); + expect(getTargetsFromEventStream(exit)).toEqual([]); + + fire(5, 5); + expect(getTargetsFromEventStream(enter)).toEqual([rect5]); + expect(getTargetsFromEventStream(exit)).toEqual([group, rect4, group2]); + + fire(9, 9); + expect(getTargetsFromEventStream(enter)).toEqual([group, rect2, group1]); + expect(getTargetsFromEventStream(exit)).toEqual([rect5]); + + fire(9, 1); + expect(getTargetsFromEventStream(enter)).toEqual([rect1]); + expect(getTargetsFromEventStream(exit)).toEqual([rect2]); + }); + + describe('findTarget', () => { + const mockEvent = ({ + canvas, + ...init + }: MouseEventInit & { canvas: Canvas }) => { + const e = new MouseEvent('mousedown', { + ...init, + }); + jest + .spyOn(e, 'target', 'get') + .mockReturnValue(canvas.getSelectionElement()); + return e; + }; + + const findTarget = (canvas: Canvas, ev?: MouseEventInit) => { + const target = canvas.findTarget( + mockEvent({ canvas, clientX: 0, clientY: 0, ...ev }) + ); + const targets = canvas.targets; + canvas.targets = []; + return { target, targets }; + }; + + test.each([true, false])( + 'findTargetsTraversal: search all is %s', + (searchAll) => { + const subTarget1 = new FabricObject(); + const target1 = new Group([subTarget1], { + subTargetCheck: true, + interactive: true, + }); + const subTarget2 = new FabricObject(); + const target2 = new Group([subTarget2], { + subTargetCheck: true, + }); + const parent = new Group([target1, target2], { + subTargetCheck: true, + interactive: true, + }); + registerTestObjects({ + subTarget1, + target1, + subTarget2, + target2, + parent, + }); + + const canvas = new Canvas(null); + canvas.add(parent); + + jest.spyOn(canvas, '_checkTarget').mockReturnValue(true); + const found = canvas['findTargetsTraversal']([parent], new Point(), { + searchStrategy: searchAll ? 'search-all' : 'first-hit', + }); + expect(found).toEqual( + searchAll + ? [subTarget2, target2, subTarget1, target1, parent] + : [subTarget2, target2, parent] + ); + } + ); + + test('searchPossibleTargets', () => { + const subTarget = new FabricObject(); + const target = new Group([subTarget], { + subTargetCheck: true, + }); + const parent = new Group([target], { + subTargetCheck: true, + interactive: true, + }); + registerTestObjects({ subTarget, target, parent }); + + const canvas = new Canvas(null); + canvas.add(parent); + + jest.spyOn(canvas, '_checkTarget').mockReturnValue(true); + const found = canvas.searchPossibleTargets([parent], new Point()); + expect(found).toBe(target); + expect(canvas.targets).toEqual([subTarget]); + }); + + test('searchPossibleTargets with selection', () => { + const subTarget = new FabricObject(); + const target = new Group([subTarget], { + subTargetCheck: true, + }); + const other = new FabricObject(); + const activeSelection = new ActiveSelection(); + registerTestObjects({ subTarget, target, other, activeSelection }); + + const canvas = new Canvas(null, { activeSelection }); + canvas.add(other, target); + activeSelection.add(target, other); + canvas.setActiveObject(activeSelection); + + jest.spyOn(canvas, '_checkTarget').mockReturnValue(true); + const found = canvas.searchPossibleTargets( + [activeSelection], + new Point() + ); + expect(found).toBe(activeSelection); + expect(canvas.targets).toEqual([]); + }); + + test('findTarget clears prev targets', () => { + const canvas = new Canvas(); + canvas.targets = [new FabricObject()]; + expect(findTarget(canvas, { clientX: 0, clientY: 0 })).toEqual({ + target: undefined, + targets: [], + }); + }); + + test('findTarget preserveObjectStacking false', () => { + const rect = new FabricObject({ + left: 0, + top: 0, + width: 10, + height: 10, + controls: {}, + }); + const rectOver = new FabricObject({ + left: 0, + top: 0, + width: 10, + height: 10, + controls: {}, + }); + registerTestObjects({ rect, rectOver }); + + const canvas = new Canvas(null, { preserveObjectStacking: false }); + canvas.add(rect, rectOver); + canvas.setActiveObject(rect); + + expect(findTarget(canvas, { clientX: 5, clientY: 5 })).toEqual({ + target: rect, + targets: [], + }); + }); + + test('findTarget preserveObjectStacking true', () => { + const rect = new FabricObject({ left: 0, top: 0, width: 30, height: 30 }); + const rectOver = new FabricObject({ + left: 0, + top: 0, + width: 30, + height: 30, + }); + registerTestObjects({ rect, rectOver }); + + const canvas = new Canvas(null, { preserveObjectStacking: true }); + canvas.add(rect, rectOver); + + const e = { + clientX: 15, + clientY: 15, + shiftKey: true, + }; + const e2 = { clientX: 4, clientY: 4 }; + + expect(findTarget(canvas, e)).toEqual( + { target: rectOver, targets: [] } + // 'Should return the rectOver, rect is not considered' + ); + + canvas.setActiveObject(rect); + expect(findTarget(canvas, e)).toEqual( + { target: rectOver, targets: [] } + // 'Should still return rectOver because is above active object' + ); + + expect(findTarget(canvas, e2)).toEqual( + { target: rect, targets: [] } + // 'Should rect because a corner of the activeObject has been hit' + ); + + canvas.altSelectionKey = 'shiftKey'; + expect(findTarget(canvas, e)).toEqual( + { target: rect, targets: [] } + // 'Should rect because active and altSelectionKey is pressed' + ); + }); + + test('findTarget with subTargetCheck', () => { + const canvas = new Canvas(); + const rect = new FabricObject({ left: 0, top: 0, width: 10, height: 10 }); + const rect2 = new FabricObject({ + left: 30, + top: 30, + width: 10, + height: 10, + }); + const group = new Group([rect, rect2]); + registerTestObjects({ rect, rect2, group }); + canvas.add(group); + + expect(findTarget(canvas, { clientX: 5, clientY: 5 })).toEqual({ + target: group, + targets: [], + }); + + expect(findTarget(canvas, { clientX: 35, clientY: 35 })).toEqual({ + target: group, + targets: [], + }); + + group.subTargetCheck = true; + group.setCoords(); + + expect(findTarget(canvas, { clientX: 5, clientY: 5 })).toEqual({ + target: group, + targets: [rect], + }); + + expect(findTarget(canvas, { clientX: 15, clientY: 15 })).toEqual({ + target: group, + targets: [], + }); + + expect(findTarget(canvas, { clientX: 35, clientY: 35 })).toEqual({ + target: group, + targets: [rect2], + }); + }); + + test('findTarget with subTargetCheck and canvas zoom', () => { + const nested1 = new FabricObject({ + width: 100, + height: 100, + fill: 'yellow', + }); + const nested2 = new FabricObject({ + width: 100, + height: 100, + left: 100, + top: 100, + fill: 'purple', + }); + const nestedGroup = new Group([nested1, nested2], { + scaleX: 0.5, + scaleY: 0.5, + top: 100, + left: 0, + subTargetCheck: true, + }); + const rect1 = new FabricObject({ + width: 100, + height: 100, + fill: 'red', + }); + const rect2 = new FabricObject({ + width: 100, + height: 100, + left: 100, + top: 100, + fill: 'blue', + }); + const group = new Group([rect1, rect2, nestedGroup], { + top: -150, + left: -50, + subTargetCheck: true, + }); + registerTestObjects({ + rect1, + rect2, + nested1, + nested2, + nestedGroup, + group, + }); + + const canvas = new Canvas(null, { + viewportTransform: [0.1, 0, 0, 0.1, 100, 200], + }); + canvas.add(group); + + expect(findTarget(canvas, { clientX: 96, clientY: 186 })).toEqual({ + target: group, + targets: [rect1], + }); + + expect(findTarget(canvas, { clientX: 98, clientY: 188 })).toEqual({ + target: group, + targets: [rect1], + }); + + expect(findTarget(canvas, { clientX: 100, clientY: 190 })).toEqual({ + target: group, + targets: [rect1], + }); + + expect(findTarget(canvas, { clientX: 102, clientY: 192 })).toEqual({ + target: group, + targets: [rect1], + }); + + expect(findTarget(canvas, { clientX: 104, clientY: 194 })).toEqual({ + target: group, + targets: [rect1], + }); + + expect(findTarget(canvas, { clientX: 106, clientY: 196 })).toEqual({ + target: group, + targets: [rect2], + }); + }); + + test.each([true, false])( + 'findTarget on activeObject with subTargetCheck and preserveObjectStacking %s', + (preserveObjectStacking) => { + const rect = new FabricObject({ + left: 0, + top: 0, + width: 10, + height: 10, + }); + const rect2 = new FabricObject({ + left: 30, + top: 30, + width: 10, + height: 10, + }); + const group = new Group([rect, rect2], { subTargetCheck: true }); + registerTestObjects({ rect, rect2, group }); + + const canvas = new Canvas(null, { preserveObjectStacking }); + canvas.add(group); + canvas.setActiveObject(group); + + expect(findTarget(canvas, { clientX: 9, clientY: 9 })).toEqual({ + target: group, + targets: [rect], + }); + } + ); + + test('findTarget with perPixelTargetFind', () => { + const triangle = new Triangle({ width: 30, height: 30 }); + registerTestObjects({ triangle }); + + const canvas = new Canvas(); + canvas.add(triangle); + + expect(findTarget(canvas, { clientX: 5, clientY: 5 })).toEqual({ + target: triangle, + targets: [], + }); + + canvas.perPixelTargetFind = true; + + expect(findTarget(canvas, { clientX: 5, clientY: 5 })).toEqual({ + target: undefined, + targets: [], + }); + expect(findTarget(canvas, { clientX: 15, clientY: 15 })).toEqual({ + target: triangle, + targets: [], + }); + }); + + describe('findTarget with perPixelTargetFind in nested group', () => { + const prepareTest = () => { + const deepTriangle = new Triangle({ + left: 0, + top: 0, + width: 30, + height: 30, + fill: 'yellow', + }); + const triangle2 = new Triangle({ + left: 100, + top: 120, + width: 30, + height: 30, + angle: 100, + fill: 'pink', + }); + const deepCircle = new Circle({ + radius: 30, + top: 0, + left: 30, + fill: 'blue', + }); + const circle2 = new Circle({ + scaleX: 2, + scaleY: 2, + radius: 10, + top: 120, + left: -20, + fill: 'purple', + }); + const deepRect = new Rect({ + width: 50, + height: 30, + top: 10, + left: 110, + fill: 'red', + skewX: 40, + skewY: 20, + }); + const rect2 = new Rect({ + width: 100, + height: 80, + top: 50, + left: 60, + fill: 'green', + }); + const deepGroup = new Group([deepTriangle, deepCircle, deepRect], { + subTargetCheck: true, + }); + const group2 = new Group([deepGroup, circle2, rect2, triangle2], { + subTargetCheck: true, + }); + const group3 = new Group([group2], { subTargetCheck: true }); + + registerTestObjects({ + deepTriangle, + triangle2, + deepCircle, + circle2, + rect2, + deepRect, + deepGroup, + group2, + group3, + }); + + const canvas = new Canvas(null, { perPixelTargetFind: true }); + canvas.add(group3); + + return { + canvas, + deepTriangle, + triangle2, + deepCircle, + circle2, + rect2, + deepRect, + deepGroup, + group2, + group3, + }; + }; + + test.each([ + { x: 5, y: 5 }, + { x: 21, y: 9 }, + { x: 37, y: 7 }, + { x: 89, y: 47 }, + { x: 16, y: 122 }, + { x: 127, y: 37 }, + { x: 87, y: 139 }, + ])('transparent hit on %s', ({ x: clientX, y: clientY }) => { + const { canvas } = prepareTest(); + expect(findTarget(canvas, { clientX, clientY })).toEqual({ + target: undefined, + targets: [], + }); + }); + + test('findTarget with perPixelTargetFind in nested group', () => { + const { + canvas, + deepTriangle, + triangle2, + deepCircle, + circle2, + rect2, + deepRect, + deepGroup, + group2, + group3, + } = prepareTest(); + + expect(findTarget(canvas, { clientX: 15, clientY: 15 })).toEqual({ + target: group3, + targets: [deepTriangle, deepGroup, group2], + }); + + expect(findTarget(canvas, { clientX: 50, clientY: 20 })).toEqual({ + target: group3, + targets: [deepCircle, deepGroup, group2], + }); + + expect(findTarget(canvas, { clientX: 117, clientY: 16 })).toEqual({ + target: group3, + targets: [deepRect, deepGroup, group2], + }); + + expect(findTarget(canvas, { clientX: 100, clientY: 90 })).toEqual({ + target: group3, + targets: [rect2, group2], + }); + + expect(findTarget(canvas, { clientX: 9, clientY: 145 })).toEqual({ + target: group3, + targets: [circle2, group2], + }); + + expect(findTarget(canvas, { clientX: 66, clientY: 143 })).toEqual({ + target: group3, + targets: [triangle2, group2], + }); + }); + }); + + test('findTarget on active selection', () => { + const rect1 = new FabricObject({ + left: 0, + top: 0, + width: 10, + height: 10, + }); + const rect2 = new FabricObject({ + left: 20, + top: 20, + width: 10, + height: 10, + }); + const rect3 = new FabricObject({ + left: 20, + top: 0, + width: 10, + height: 10, + }); + const activeSelection = new ActiveSelection([rect1, rect2], { + subTargetCheck: true, + cornerSize: 2, + }); + registerTestObjects({ rect1, rect2, rect3, activeSelection }); + + const canvas = new Canvas(null, { activeSelection }); + canvas.add(rect1, rect2, rect3); + canvas.setActiveObject(activeSelection); + + expect(findTarget(canvas, { clientX: 5, clientY: 5 })).toEqual({ + target: activeSelection, + targets: [rect1], + }); + + expect(findTarget(canvas, { clientX: 40, clientY: 15 })).toEqual({ + target: undefined, + targets: [], + }); + expect(activeSelection.__corner).toBeUndefined(); + + expect(findTarget(canvas, { clientX: 0, clientY: 0 })).toEqual({ + target: activeSelection, + targets: [], + }); + expect(activeSelection.__corner).toBe('tl'); + + expect(findTarget(canvas, { clientX: 25, clientY: 5 })).toEqual( + { + target: activeSelection, + targets: [], + } + // 'Should not return the rect behind active selection' + ); + + canvas.discardActiveObject(); + expect(findTarget(canvas, { clientX: 25, clientY: 5 })).toEqual( + { + target: rect3, + targets: [], + } + // 'Should return the rect after clearing selection' + ); + }); + + test('findTarget on active selection with perPixelTargetFind', () => { + const rect1 = new Rect({ + left: 0, + top: 0, + width: 10, + height: 10, + }); + const rect2 = new Rect({ + left: 20, + top: 20, + width: 10, + height: 10, + }); + const activeSelection = new ActiveSelection([rect1, rect2]); + registerTestObjects({ rect1, rect2, activeSelection }); + + const canvas = new Canvas(null, { + activeSelection, + perPixelTargetFind: true, + preserveObjectStacking: true, + }); + canvas.add(rect1, rect2); + canvas.setActiveObject(activeSelection); + + expect(findTarget(canvas, { clientX: 8, clientY: 8 })).toEqual({ + target: activeSelection, + targets: [], + }); + + expect(findTarget(canvas, { clientX: 15, clientY: 15 })).toEqual({ + target: undefined, + targets: [], + }); + }); }); });