diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e74e609cc0..5a23e716a1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## [next] +- tests(): migrate target hit tests to jest and drag and drop test to playwright [#9333](https://github.com/fabricjs/fabric.js/pull/9333) - fix(SVGParser): avoid crashing on SVG that use @import css feature [#9602](https://github.com/fabricjs/fabric.js/pull/9602) - fix(): compositionEnd event handler is not registered correctly. (regression from f91362c ) [#9610](https://github.com/fabricjs/fabric.js/pull/9610) - ci(): Add a test case from the multiple selection use case for groups [#9599](https://github.com/fabricjs/fabric.js/pull/9599) diff --git a/e2e/tests/text/drag&drop/index.html b/e2e/tests/text/drag&drop/index.html new file mode 100644 index 00000000000..a7a979605e1 --- /dev/null +++ b/e2e/tests/text/drag&drop/index.html @@ -0,0 +1,2 @@ + + diff --git a/e2e/tests/text/drag&drop/index.spec.ts b/e2e/tests/text/drag&drop/index.spec.ts index bcebbfbf3a4..db7c957e305 100644 --- a/e2e/tests/text/drag&drop/index.spec.ts +++ b/e2e/tests/text/drag&drop/index.spec.ts @@ -1,16 +1,19 @@ import type { Locator, Page } from '@playwright/test'; import { expect, test } from '@playwright/test'; +import type { IText } from '../../../..'; import setup from '../../../setup'; +import { TextUtil } from '../../../utils/TextUtil'; import { binaryToBuffer } from '../../../utils/binaryToBuffer'; +import { CanvasUtil } from '../../../utils/CanvasUtil'; setup(); const dragA = 'fabric'; const dragB = 'em ipsum\ndolor\nsit Amet2\nconsectge'; -test('Drag & Drop', async ({ page }) => { +const selectFabricInA = (page: Page) => { const canvas = page.locator('canvas').nth(1); - await test.step(`select "${dragA}" in A`, async () => { + return test.step(`select "${dragA}" in A`, async () => { await canvas.click({ position: { x: 130, @@ -25,17 +28,65 @@ test('Drag & Drop', async ({ page }) => { }, }); }); - await test.step('drag & drop to end', async () => { +}; + +const readEventStream = async (page: Page) => { + const data = await new CanvasUtil(page).executeInBrowser((canvas) => + canvas.readEventStream() + ); + return JSON.stringify(data, null, 2); +}; + +test('Drag & Drop', async ({ page }) => { + const canvas = page.locator('canvas').nth(1); + const textarea = page.locator('#textarea'); + const a = new TextUtil(page, 'a'); + const b = new TextUtil(page, 'b'); + + await selectFabricInA(page); + + await test.step('click sets the cursor', async () => { + await page.mouse.move(130, 50); await page.mouse.down(); - await page.mouse.move(0, 140, { steps: 40 }); - await page.mouse.move(435, 55, { steps: 40 }); + expect( + await a.executeInBrowser((text) => [ + text['draggableTextDelegate'].isActive(), + text.shouldStartDragging(), + ]) + ).toEqual([true, true]); + expect(await a.isCursorActive()).toBeFalsy(); + await page.mouse.up(); + expect( + await a.executeInBrowser((text) => [ + text['draggableTextDelegate'].isActive(), + text.shouldStartDragging(), + ]) + ).toEqual([false, false]); + await a.expectObjectToMatch({ + selectionStart: 3, + selectionEnd: 3, + }); + expect(await a.isCursorActive()).toBeTruthy(); + }); + + await selectFabricInA(page); + + // clean the stream + await readEventStream(page); + + await test.step('drag A & drop on self at end', async () => { + await page.mouse.down(); + await page.mouse.move(0, 140, { steps: 10 }); + await page.mouse.move(435, 55, { steps: 10 }); expect( await canvas.screenshot(), `1. drag "${dragA}" over "lor|em" (A => B)` ).toMatchSnapshot({ name: '1.drag-fabric-over-lor|em.png', }); - await page.mouse.move(240, 140, { steps: 40 }); + await page.mouse.move(400, 70, { steps: 10 }); + await page.mouse.move(250, 130, { steps: 10 }); + await page.mouse.move(240, 140, { steps: 10 }); expect( await canvas.screenshot(), `2. before dropping "${dragA}" => "sandbox|" (A => A)` @@ -49,6 +100,9 @@ test('Drag & Drop', async ({ page }) => { ).toMatchSnapshot({ name: '3.drop-fabric-after-sandbox.png', }); + expect(await readEventStream(page)).toMatchSnapshot({ + name: '3.events.json', + }); }); await test.step(`drag & drop to B(3) = "lor|${dragA}|em"`, async () => { @@ -62,12 +116,18 @@ test('Drag & Drop', async ({ page }) => { y: 55, }, }); + expect(await page.evaluate(() => document.activeElement)).toBe( + await b.executeInBrowser((text) => text.hiddenTextarea) + ); expect( await canvas.screenshot(), `4. drag & drop "${dragA}" => "lor|${dragA}|em" (A => B(3))` ).toMatchSnapshot({ name: '4.drop--lor|fabric|em.png', }); + expect(await readEventStream(page)).toMatchSnapshot({ + name: '4.events.json', + }); }); await test.step('select B', async () => { @@ -80,6 +140,7 @@ test('Drag & Drop', async ({ page }) => { await page.mouse.down(); await page.mouse.move(580, 300, { steps: 40 }); await page.mouse.up(); + await readEventStream(page); }); await test.step(`drag & drop to A(4) = ".js |${dragB}|sandbox"`, async () => { @@ -93,16 +154,166 @@ test('Drag & Drop', async ({ page }) => { y: 55, }, }); + expect(await page.evaluate(() => document.activeElement)).toBe( + await a.executeInBrowser((text) => text.hiddenTextarea) + ); expect( await canvas.screenshot(), `5. drag & drop "${dragB}" => ".js |${dragB}|sandbox" (B => A(4))` ).toMatchSnapshot({ name: '5..js |em ips.png', }); + expect(await readEventStream(page)).toMatchSnapshot({ + name: '5.events.json', + }); + }); + + await test.step(`drag & drop to textarea = ".js |${dragB}|sandbox"`, async () => { + await canvas.dragTo(textarea, { + sourcePosition: { + x: 120, + y: 55, + }, + }); + // expect(await page.evaluate(() => document.activeElement)).toBe( + // await textarea.elementHandle() + // ); + expect( + await page.screenshot(), + `6. drag & drop ".js |${dragB}|sandbox" to textarea (A => textarea)` + ).toMatchSnapshot({ + name: '6.drop-textarea.png', + }); + expect(await readEventStream(page)).toMatchSnapshot({ + name: '6.events.json', + }); + }); + + await test.step(`drag & drop "dolor" from textarea to B = "lor|dolor|fabrictur"`, async () => { + await page.mouse.move(25, 527); + await page.mouse.dblclick(25, 527); + await page.mouse.down(); + await page.mouse.move(25, 527); + await page.mouse.move(25, 550, { steps: 10 }); + await page.mouse.move(435, 55, { steps: 10 }); + await canvas.hover({ position: { x: 435, y: 55 } }); + await page.mouse.up(); + expect(await page.evaluate(() => document.activeElement)).toBe( + await b.executeInBrowser((text) => text.hiddenTextarea) + ); + expect( + await page.screenshot(), + `7. drag & drop "dolor" to B = "lor|dolor|fabrictur" (textarea => B)` + ).toMatchSnapshot({ + name: '7.drop-textarea-to-B-lor|dolor|fabrictur.png', + }); + expect(await readEventStream(page)).toMatchSnapshot({ + name: '7.events.json', + }); + }); +}); + +for (const options of [ + { + disabled: 'onDragStart', + exec: (text: IText) => (text.onDragStart = () => false), + expected: { + text: 'fabric.js sandbox', + selectionStart: 0, + selectionEnd: 6, + }, + }, + { + disabled: 'draggableTextDelegate#start', + exec: (text: IText) => (text['draggableTextDelegate'].start = () => false), + expected: { + text: 'fabric.js sandbox', + selectionStart: 3, + selectionEnd: 17, + }, + }, + { + disabled: 'draggableTextDelegate#isActive', + exec: (text: IText) => + (text['draggableTextDelegate'].isActive = () => false), + expected: { + text: 'fabric.js sandbox', + selectionStart: 0, + selectionEnd: 6, + }, + }, +] as const) { + test(`Disabling Drag & Drop by disabling ${options.disabled}`, async ({ + page, + }) => { + const a = new TextUtil(page, 'a'); + await test.step('disable dragging', () => a.executeInBrowser(options.exec)); + await selectFabricInA(page); + await readEventStream(page); + + await test.step('drag to end of text', async () => { + await page.mouse.down(); + await page.mouse.move(240, 140, { steps: 40 }); + a.expectObjectToMatch(options.expected); + expect(await readEventStream(page)).toMatchSnapshot({ + name: `disabling-drag-${options.disabled}.events.json`, + }); + }); + }); +} + +test('Disabling Drop', async ({ page }) => { + const canvas = page.locator('canvas').nth(1); + const a = new TextUtil(page, 'a'); + const b = new TextUtil(page, 'b'); + await test.step('disable dropping', () => { + a.executeInBrowser((text) => (text.canDrop = () => false)); + b.executeInBrowser((text) => (text.canDrop = () => false)); + }); + + await test.step('drop A on self', async () => { + await selectFabricInA(page); + await readEventStream(page); + await canvas.dragTo(canvas, { + sourcePosition: { + x: 130, + y: 40, + }, + targetPosition: { + x: 240, + y: 140, + }, + }); + await a.expectObjectToMatch({ text: 'fabric.js sandbox' }); + expect(await readEventStream(page)).toMatchSnapshot({ + name: 'disabling-drop-A.events.json', + }); + }); + + await test.step('drop A on B', async () => { + await selectFabricInA(page); + await readEventStream(page); + await canvas.dragTo(canvas, { + sourcePosition: { + x: 130, + y: 40, + }, + targetPosition: { + x: 435, + y: 55, + }, + }); + await a.expectObjectToMatch({ text: 'fabric.js sandbox' }); + await b.expectObjectToMatch({ + text: 'lorem ipsum\ndolor\nsit Amet2\nconsectgetur', + }); + expect(await readEventStream(page)).toMatchSnapshot({ + name: 'disabling-drop-B.events.json', + }); }); }); -async function waitForDragImage( +async function waitForDataTransfer( page: Page, canvas: Locator, { x, y }: { x: number; y: number } @@ -116,11 +327,28 @@ async function waitForDragImage( }); await page.mouse.down(); const dataTransfer = await page.evaluateHandle(() => - Object.defineProperty(new DataTransfer(), 'setDragImage', { - value: (image, x, y) => - window.dispatchEvent( - new CustomEvent('drag:image', { detail: { image, x, y } }) - ), + Object.defineProperties(new DataTransfer(), { + __data: { + value: {}, + }, + setDragImage: { + value(image, x, y) { + window.dispatchEvent( + new CustomEvent('drag:data', { + detail: { image, x, y, data: this.__data }, + }) + ); + }, + }, + setData: { + value(type, value) { + let out = value; + try { + out = JSON.parse(value); + } catch (error) {} + this.__data[type] = out; + }, + }, }) ); return [ @@ -128,21 +356,25 @@ async function waitForDragImage( page .evaluate( () => - new Promise<{ image: string; x: number; y: number }>((resolve) => + new Promise<{ + image: string; + x: number; + y: number; + data: Record; + }>((resolve) => window.addEventListener( - 'drag:image', - ({ detail: { image, x, y } }) => + 'drag:data', + ({ detail: { image, ...rest } }) => resolve({ image: image.toDataURL(`image/png`, 1), - x, - y, + ...rest, }), { once: true } ) ) ) - .then(({ x, y, image }) => { - return [binaryToBuffer(image), { x, y }] as const; + .then(({ x, y, image, ...data }) => { + return [binaryToBuffer(image), { x, y }, data] as const; }), ] as const; }); @@ -162,18 +394,18 @@ test('Drag Image A', async ({ page }) => { }); await test.step('start dragging', async () => { - const [dragEvent, trigger] = await waitForDragImage(page, canvas, { + const [dragEvent, trigger] = await waitForDataTransfer(page, canvas, { x: 130, y: 40, }); await canvas.dispatchEvent('dragstart', dragEvent); - const [image, position] = await trigger; + const [image, position, data] = await trigger; expect(image, `drag image A: "${dragA}"`).toMatchSnapshot({ name: 'drag-image-fabric.png', maxDiffPixelRatio: 0.03, }); expect( - JSON.stringify(position, null, 2), + JSON.stringify({ position, ...data }, null, 2), `drag image A position: "${dragA}"` ).toMatchSnapshot({ name: 'drag-image-fabric.json', @@ -199,17 +431,17 @@ test('Drag Image B', async ({ page }) => { }); await test.step('start dragging', async () => { - const [dragEvent, trigger] = await waitForDragImage(page, canvas, { + const [dragEvent, trigger] = await waitForDataTransfer(page, canvas, { x: 500, y: 280, }); await canvas.dispatchEvent('dragstart', dragEvent); - const [image, position] = await trigger; + const [image, position, data] = await trigger; expect(image, `drag image B: "${dragB}"`).toMatchSnapshot({ name: 'drag-image-em---tge.png', }); expect( - JSON.stringify(position, null, 2), + JSON.stringify({ position, ...data }, null, 2), `drag image B position: "${dragB}"` ).toMatchSnapshot({ name: 'drag-image-em---tge.json', diff --git a/e2e/tests/text/drag&drop/index.spec.ts-snapshots/3-events.json b/e2e/tests/text/drag&drop/index.spec.ts-snapshots/3-events.json new file mode 100644 index 00000000000..c349f283871 --- /dev/null +++ b/e2e/tests/text/drag&drop/index.spec.ts-snapshots/3-events.json @@ -0,0 +1,338 @@ +[ + [ + "dragstart", + { + "e": { + "isTrusted": true + }, + "target": "a" + }, + "canvas" + ], + [ + "dragstart", + { + "e": { + "isTrusted": true + }, + "target": "a" + }, + "a" + ], + [ + "dragenter", + { + "e": { + "isTrusted": true + }, + "target": "a", + "subTargets": [], + "dragSource": "a" + }, + "canvas" + ], + [ + "dragenter", + { + "subTargets": [], + "dragSource": "a", + "e": { + "isTrusted": true + }, + "target": "a", + "viewportPoint": { + "x": 117, + "y": 50 + }, + "scenePoint": { + "x": 117, + "y": 50 + }, + "pointer": { + "x": 117, + "y": 50 + }, + "absolutePointer": { + "x": 117, + "y": 50 + } + }, + "a" + ], + [ + "dragleave", + { + "subTargets": [], + "dragSource": "a", + "canDrop": false, + "e": { + "isTrusted": true + }, + "target": "a", + "viewportPoint": { + "x": 39, + "y": 110 + }, + "scenePoint": { + "x": 39, + "y": 110 + }, + "pointer": { + "x": 39, + "y": 110 + }, + "absolutePointer": { + "x": 39, + "y": 110 + } + }, + "a" + ], + [ + "dragenter", + { + "subTargets": [], + "dragSource": "a", + "canDrop": false, + "e": { + "isTrusted": true + }, + "target": "a", + "viewportPoint": { + "x": 87, + "y": 123 + }, + "scenePoint": { + "x": 87, + "y": 123 + }, + "pointer": { + "x": 87, + "y": 123 + }, + "absolutePointer": { + "x": 87, + "y": 123 + } + }, + "a" + ], + [ + "dragleave", + { + "subTargets": [], + "dragSource": "a", + "canDrop": false, + "e": { + "isTrusted": true + }, + "target": "a", + "viewportPoint": { + "x": 261, + "y": 89 + }, + "scenePoint": { + "x": 261, + "y": 89 + }, + "pointer": { + "x": 261, + "y": 89 + }, + "absolutePointer": { + "x": 261, + "y": 89 + } + }, + "a" + ], + [ + "dragenter", + { + "subTargets": [], + "dragSource": "a", + "canDrop": false, + "e": { + "isTrusted": true + }, + "target": "b", + "viewportPoint": { + "x": 435, + "y": 55 + }, + "scenePoint": { + "x": 435, + "y": 55 + }, + "pointer": { + "x": 435, + "y": 55 + }, + "absolutePointer": { + "x": 435, + "y": 55 + } + }, + "b" + ], + [ + "dragleave", + { + "subTargets": [], + "dragSource": "a", + "canDrop": false, + "e": { + "isTrusted": true + }, + "target": "b", + "viewportPoint": { + "x": 385, + "y": 76 + }, + "scenePoint": { + "x": 385, + "y": 76 + }, + "pointer": { + "x": 385, + "y": 76 + }, + "absolutePointer": { + "x": 385, + "y": 76 + } + }, + "b" + ], + [ + "dragenter", + { + "subTargets": [], + "dragSource": "a", + "canDrop": false, + "e": { + "isTrusted": true + }, + "target": "a", + "viewportPoint": { + "x": 250, + "y": 130 + }, + "scenePoint": { + "x": 250, + "y": 130 + }, + "pointer": { + "x": 250, + "y": 130 + }, + "absolutePointer": { + "x": 250, + "y": 130 + } + }, + "a" + ], + [ + "drop", + { + "e": { + "isTrusted": true + }, + "target": "a", + "subTargets": [], + "dragSource": "a", + "viewportPoint": { + "x": 240, + "y": 140 + }, + "scenePoint": { + "x": 240, + "y": 140 + }, + "didDrop": false, + "pointer": { + "x": 240, + "y": 140 + }, + "absolutePointer": { + "x": 240, + "y": 140 + } + }, + "canvas" + ], + [ + "changed", + { + "index": 11, + "action": "drop" + }, + "a" + ], + [ + "text:changed", + { + "target": "a" + }, + "canvas" + ], + [ + "drop", + { + "e": { + "isTrusted": true + }, + "target": "a", + "subTargets": [], + "dragSource": "a", + "viewportPoint": { + "x": 240, + "y": 140 + }, + "scenePoint": { + "x": 240, + "y": 140 + }, + "didDrop": true, + "dropTarget": "a", + "pointer": { + "x": 240, + "y": 140 + }, + "absolutePointer": { + "x": 240, + "y": 140 + } + }, + "a" + ], + [ + "dragend", + { + "e": { + "isTrusted": true + }, + "target": "a", + "subTargets": [], + "dragSource": "a", + "didDrop": true, + "dropTarget": "a" + }, + "canvas" + ], + [ + "dragend", + { + "e": { + "isTrusted": true + }, + "target": "a", + "subTargets": [], + "dragSource": "a", + "didDrop": true, + "dropTarget": "a" + }, + "a" + ] +] \ No newline at end of file diff --git a/e2e/tests/text/drag&drop/index.spec.ts-snapshots/4-events.json b/e2e/tests/text/drag&drop/index.spec.ts-snapshots/4-events.json new file mode 100644 index 00000000000..103c7bc437e --- /dev/null +++ b/e2e/tests/text/drag&drop/index.spec.ts-snapshots/4-events.json @@ -0,0 +1,221 @@ +[ + [ + "dragstart", + { + "e": { + "isTrusted": true + }, + "target": "a" + }, + "canvas" + ], + [ + "dragstart", + { + "e": { + "isTrusted": true + }, + "target": "a" + }, + "a" + ], + [ + "dragenter", + { + "e": { + "isTrusted": true + }, + "target": "b", + "subTargets": [], + "dragSource": "a" + }, + "canvas" + ], + [ + "dragleave", + { + "subTargets": [], + "dragSource": "a", + "e": { + "isTrusted": true + }, + "target": "a", + "nextTarget": "b", + "viewportPoint": { + "x": 435, + "y": 55 + }, + "scenePoint": { + "x": 435, + "y": 55 + }, + "pointer": { + "x": 435, + "y": 55 + }, + "absolutePointer": { + "x": 435, + "y": 55 + } + }, + "a" + ], + [ + "dragenter", + { + "subTargets": [], + "dragSource": "a", + "e": { + "isTrusted": true + }, + "target": "b", + "previousTarget": "a", + "viewportPoint": { + "x": 435, + "y": 55 + }, + "scenePoint": { + "x": 435, + "y": 55 + }, + "pointer": { + "x": 435, + "y": 55 + }, + "absolutePointer": { + "x": 435, + "y": 55 + } + }, + "b" + ], + [ + "drop", + { + "e": { + "isTrusted": true + }, + "target": "b", + "subTargets": [], + "dragSource": "a", + "viewportPoint": { + "x": 435, + "y": 55 + }, + "scenePoint": { + "x": 435, + "y": 55 + }, + "didDrop": false, + "pointer": { + "x": 435, + "y": 55 + }, + "absolutePointer": { + "x": 435, + "y": 55 + } + }, + "canvas" + ], + [ + "selection:changed", + {}, + "b" + ], + [ + "text:selection:changed", + { + "target": "b" + }, + "canvas" + ], + [ + "changed", + { + "index": 3, + "action": "drop" + }, + "b" + ], + [ + "text:changed", + { + "target": "b" + }, + "canvas" + ], + [ + "drop", + { + "e": { + "isTrusted": true + }, + "target": "b", + "subTargets": [], + "dragSource": "a", + "viewportPoint": { + "x": 435, + "y": 55 + }, + "scenePoint": { + "x": 435, + "y": 55 + }, + "didDrop": true, + "dropTarget": "b", + "pointer": { + "x": 435, + "y": 55 + }, + "absolutePointer": { + "x": 435, + "y": 55 + } + }, + "b" + ], + [ + "dragend", + { + "e": { + "isTrusted": true + }, + "target": "a", + "subTargets": [], + "dragSource": "a", + "didDrop": true, + "dropTarget": "b" + }, + "canvas" + ], + [ + "changed", + { + "index": 11, + "action": "dragend" + }, + "a" + ], + [ + "text:changed", + { + "target": "a" + }, + "canvas" + ], + [ + "dragend", + { + "e": { + "isTrusted": true + }, + "target": "a", + "subTargets": [], + "dragSource": "a", + "didDrop": true, + "dropTarget": "b" + }, + "a" + ] +] \ No newline at end of file diff --git a/e2e/tests/text/drag&drop/index.spec.ts-snapshots/5-events.json b/e2e/tests/text/drag&drop/index.spec.ts-snapshots/5-events.json new file mode 100644 index 00000000000..4688bb9b7fc --- /dev/null +++ b/e2e/tests/text/drag&drop/index.spec.ts-snapshots/5-events.json @@ -0,0 +1,221 @@ +[ + [ + "dragstart", + { + "e": { + "isTrusted": true + }, + "target": "b" + }, + "canvas" + ], + [ + "dragstart", + { + "e": { + "isTrusted": true + }, + "target": "b" + }, + "b" + ], + [ + "dragenter", + { + "e": { + "isTrusted": true + }, + "target": "a", + "subTargets": [], + "dragSource": "b" + }, + "canvas" + ], + [ + "dragleave", + { + "subTargets": [], + "dragSource": "b", + "e": { + "isTrusted": true + }, + "target": "b", + "nextTarget": "a", + "viewportPoint": { + "x": 120, + "y": 55 + }, + "scenePoint": { + "x": 120, + "y": 55 + }, + "pointer": { + "x": 120, + "y": 55 + }, + "absolutePointer": { + "x": 120, + "y": 55 + } + }, + "b" + ], + [ + "dragenter", + { + "subTargets": [], + "dragSource": "b", + "e": { + "isTrusted": true + }, + "target": "a", + "previousTarget": "b", + "viewportPoint": { + "x": 120, + "y": 55 + }, + "scenePoint": { + "x": 120, + "y": 55 + }, + "pointer": { + "x": 120, + "y": 55 + }, + "absolutePointer": { + "x": 120, + "y": 55 + } + }, + "a" + ], + [ + "drop", + { + "e": { + "isTrusted": true + }, + "target": "a", + "subTargets": [], + "dragSource": "b", + "viewportPoint": { + "x": 120, + "y": 55 + }, + "scenePoint": { + "x": 120, + "y": 55 + }, + "didDrop": false, + "pointer": { + "x": 120, + "y": 55 + }, + "absolutePointer": { + "x": 120, + "y": 55 + } + }, + "canvas" + ], + [ + "selection:changed", + {}, + "a" + ], + [ + "text:selection:changed", + { + "target": "a" + }, + "canvas" + ], + [ + "changed", + { + "index": 4, + "action": "drop" + }, + "a" + ], + [ + "text:changed", + { + "target": "a" + }, + "canvas" + ], + [ + "drop", + { + "e": { + "isTrusted": true + }, + "target": "a", + "subTargets": [], + "dragSource": "b", + "viewportPoint": { + "x": 120, + "y": 55 + }, + "scenePoint": { + "x": 120, + "y": 55 + }, + "didDrop": true, + "dropTarget": "a", + "pointer": { + "x": 120, + "y": 55 + }, + "absolutePointer": { + "x": 120, + "y": 55 + } + }, + "a" + ], + [ + "dragend", + { + "e": { + "isTrusted": true + }, + "target": "b", + "subTargets": [], + "dragSource": "b", + "didDrop": true, + "dropTarget": "a" + }, + "canvas" + ], + [ + "changed", + { + "index": 9, + "action": "dragend" + }, + "b" + ], + [ + "text:changed", + { + "target": "b" + }, + "canvas" + ], + [ + "dragend", + { + "e": { + "isTrusted": true + }, + "target": "b", + "subTargets": [], + "dragSource": "b", + "didDrop": true, + "dropTarget": "a" + }, + "b" + ] +] \ No newline at end of file diff --git a/e2e/tests/text/drag&drop/index.spec.ts-snapshots/6-drop-textarea.png b/e2e/tests/text/drag&drop/index.spec.ts-snapshots/6-drop-textarea.png new file mode 100644 index 00000000000..d0c22431fab Binary files /dev/null and b/e2e/tests/text/drag&drop/index.spec.ts-snapshots/6-drop-textarea.png differ diff --git a/e2e/tests/text/drag&drop/index.spec.ts-snapshots/6-events.json b/e2e/tests/text/drag&drop/index.spec.ts-snapshots/6-events.json new file mode 100644 index 00000000000..a32dee8a559 --- /dev/null +++ b/e2e/tests/text/drag&drop/index.spec.ts-snapshots/6-events.json @@ -0,0 +1,50 @@ +[ + [ + "dragstart", + { + "e": { + "isTrusted": true + }, + "target": "a" + }, + "canvas" + ], + [ + "dragstart", + { + "e": { + "isTrusted": true + }, + "target": "a" + }, + "a" + ], + [ + "dragend", + { + "e": { + "isTrusted": true + }, + "target": "a", + "subTargets": [], + "dragSource": "a", + "didDrop": true, + "dropTarget": "a" + }, + "canvas" + ], + [ + "dragend", + { + "e": { + "isTrusted": true + }, + "target": "a", + "subTargets": [], + "dragSource": "a", + "didDrop": true, + "dropTarget": "a" + }, + "a" + ] +] \ No newline at end of file diff --git a/e2e/tests/text/drag&drop/index.spec.ts-snapshots/7-drop-textarea-to-B-lor-dolor-fabrictur.png b/e2e/tests/text/drag&drop/index.spec.ts-snapshots/7-drop-textarea-to-B-lor-dolor-fabrictur.png new file mode 100644 index 00000000000..2727eaa1eee Binary files /dev/null and b/e2e/tests/text/drag&drop/index.spec.ts-snapshots/7-drop-textarea-to-B-lor-dolor-fabrictur.png differ diff --git a/e2e/tests/text/drag&drop/index.spec.ts-snapshots/7-events.json b/e2e/tests/text/drag&drop/index.spec.ts-snapshots/7-events.json new file mode 100644 index 00000000000..fd6d197556b --- /dev/null +++ b/e2e/tests/text/drag&drop/index.spec.ts-snapshots/7-events.json @@ -0,0 +1,207 @@ +[ + [ + "dragenter", + { + "e": { + "isTrusted": true + }, + "subTargets": [] + }, + "canvas" + ], + [ + "dragleave", + { + "subTargets": [], + "e": { + "isTrusted": true + }, + "target": "a", + "viewportPoint": { + "x": 107, + "y": 451 + }, + "scenePoint": { + "x": 107, + "y": 451 + }, + "pointer": { + "x": 107, + "y": 451 + }, + "absolutePointer": { + "x": 107, + "y": 451 + } + }, + "a" + ], + [ + "dragenter", + { + "subTargets": [], + "canDrop": false, + "e": { + "isTrusted": true + }, + "target": "a", + "viewportPoint": { + "x": 189, + "y": 352 + }, + "scenePoint": { + "x": 189, + "y": 352 + }, + "pointer": { + "x": 189, + "y": 352 + }, + "absolutePointer": { + "x": 189, + "y": 352 + } + }, + "a" + ], + [ + "dragleave", + { + "subTargets": [], + "canDrop": false, + "e": { + "isTrusted": true + }, + "target": "a", + "viewportPoint": { + "x": 271, + "y": 253 + }, + "scenePoint": { + "x": 271, + "y": 253 + }, + "pointer": { + "x": 271, + "y": 253 + }, + "absolutePointer": { + "x": 271, + "y": 253 + } + }, + "a" + ], + [ + "dragenter", + { + "subTargets": [], + "canDrop": false, + "e": { + "isTrusted": true + }, + "target": "b", + "viewportPoint": { + "x": 435, + "y": 55 + }, + "scenePoint": { + "x": 435, + "y": 55 + }, + "pointer": { + "x": 435, + "y": 55 + }, + "absolutePointer": { + "x": 435, + "y": 55 + } + }, + "b" + ], + [ + "drop", + { + "e": { + "isTrusted": true + }, + "target": "b", + "subTargets": [], + "viewportPoint": { + "x": 435, + "y": 55 + }, + "scenePoint": { + "x": 435, + "y": 55 + }, + "didDrop": false, + "pointer": { + "x": 435, + "y": 55 + }, + "absolutePointer": { + "x": 435, + "y": 55 + } + }, + "canvas" + ], + [ + "selection:changed", + {}, + "b" + ], + [ + "text:selection:changed", + { + "target": "b" + }, + "canvas" + ], + [ + "changed", + { + "index": 3, + "action": "drop" + }, + "b" + ], + [ + "text:changed", + { + "target": "b" + }, + "canvas" + ], + [ + "drop", + { + "e": { + "isTrusted": true + }, + "target": "b", + "subTargets": [], + "viewportPoint": { + "x": 435, + "y": 55 + }, + "scenePoint": { + "x": 435, + "y": 55 + }, + "didDrop": true, + "dropTarget": "b", + "pointer": { + "x": 435, + "y": 55 + }, + "absolutePointer": { + "x": 435, + "y": 55 + } + }, + "b" + ] +] \ No newline at end of file diff --git a/e2e/tests/text/drag&drop/index.spec.ts-snapshots/disabling-drag-draggableTextDelegate-isActive-events.json b/e2e/tests/text/drag&drop/index.spec.ts-snapshots/disabling-drag-draggableTextDelegate-isActive-events.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/e2e/tests/text/drag&drop/index.spec.ts-snapshots/disabling-drag-draggableTextDelegate-isActive-events.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/e2e/tests/text/drag&drop/index.spec.ts-snapshots/disabling-drag-draggableTextDelegate-start-events.json b/e2e/tests/text/drag&drop/index.spec.ts-snapshots/disabling-drag-draggableTextDelegate-start-events.json new file mode 100644 index 00000000000..94c5979446d --- /dev/null +++ b/e2e/tests/text/drag&drop/index.spec.ts-snapshots/disabling-drag-draggableTextDelegate-start-events.json @@ -0,0 +1,74 @@ +[ + [ + "selection:changed", + {}, + "a" + ], + [ + "text:selection:changed", + { + "target": "a" + }, + "canvas" + ], + [ + "selection:changed", + {}, + "a" + ], + [ + "text:selection:changed", + { + "target": "a" + }, + "canvas" + ], + [ + "selection:changed", + {}, + "a" + ], + [ + "text:selection:changed", + { + "target": "a" + }, + "canvas" + ], + [ + "selection:changed", + {}, + "a" + ], + [ + "text:selection:changed", + { + "target": "a" + }, + "canvas" + ], + [ + "selection:changed", + {}, + "a" + ], + [ + "text:selection:changed", + { + "target": "a" + }, + "canvas" + ], + [ + "selection:changed", + {}, + "a" + ], + [ + "text:selection:changed", + { + "target": "a" + }, + "canvas" + ] +] \ No newline at end of file diff --git a/e2e/tests/text/drag&drop/index.spec.ts-snapshots/disabling-drag-onDragStart-events.json b/e2e/tests/text/drag&drop/index.spec.ts-snapshots/disabling-drag-onDragStart-events.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/e2e/tests/text/drag&drop/index.spec.ts-snapshots/disabling-drag-onDragStart-events.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/e2e/tests/text/drag&drop/index.spec.ts-snapshots/disabling-drop-A-events.json b/e2e/tests/text/drag&drop/index.spec.ts-snapshots/disabling-drop-A-events.json new file mode 100644 index 00000000000..268b0f549d1 --- /dev/null +++ b/e2e/tests/text/drag&drop/index.spec.ts-snapshots/disabling-drop-A-events.json @@ -0,0 +1,128 @@ +[ + [ + "dragstart", + { + "e": { + "isTrusted": true + }, + "target": "a" + }, + "canvas" + ], + [ + "dragstart", + { + "e": { + "isTrusted": true + }, + "target": "a" + }, + "a" + ], + [ + "dragenter", + { + "e": { + "isTrusted": true + }, + "target": "a", + "subTargets": [], + "dragSource": "a" + }, + "canvas" + ], + [ + "dragenter", + { + "subTargets": [], + "dragSource": "a", + "e": { + "isTrusted": true + }, + "target": "a", + "viewportPoint": { + "x": 240, + "y": 140 + }, + "scenePoint": { + "x": 240, + "y": 140 + }, + "pointer": { + "x": 240, + "y": 140 + }, + "absolutePointer": { + "x": 240, + "y": 140 + } + }, + "a" + ], + [ + "dragleave", + { + "e": { + "isTrusted": true + }, + "target": "a", + "subTargets": [], + "dragSource": "a" + }, + "canvas" + ], + [ + "dragleave", + { + "subTargets": [], + "dragSource": "a", + "e": { + "isTrusted": true + }, + "target": "a", + "viewportPoint": { + "x": 240, + "y": 140 + }, + "scenePoint": { + "x": 240, + "y": 140 + }, + "pointer": { + "x": 240, + "y": 140 + }, + "absolutePointer": { + "x": 240, + "y": 140 + } + }, + "a" + ], + [ + "dragend", + { + "e": { + "isTrusted": true + }, + "target": "a", + "subTargets": [], + "dragSource": "a", + "didDrop": false + }, + "canvas" + ], + [ + "dragend", + { + "e": { + "isTrusted": true + }, + "target": "a", + "subTargets": [], + "dragSource": "a", + "didDrop": false + }, + "a" + ] +] \ No newline at end of file diff --git a/e2e/tests/text/drag&drop/index.spec.ts-snapshots/disabling-drop-B-events.json b/e2e/tests/text/drag&drop/index.spec.ts-snapshots/disabling-drop-B-events.json new file mode 100644 index 00000000000..7e274f07829 --- /dev/null +++ b/e2e/tests/text/drag&drop/index.spec.ts-snapshots/disabling-drop-B-events.json @@ -0,0 +1,128 @@ +[ + [ + "dragstart", + { + "e": { + "isTrusted": true + }, + "target": "a" + }, + "canvas" + ], + [ + "dragstart", + { + "e": { + "isTrusted": true + }, + "target": "a" + }, + "a" + ], + [ + "dragenter", + { + "e": { + "isTrusted": true + }, + "target": "b", + "subTargets": [], + "dragSource": "a" + }, + "canvas" + ], + [ + "dragenter", + { + "subTargets": [], + "dragSource": "a", + "e": { + "isTrusted": true + }, + "target": "b", + "viewportPoint": { + "x": 435, + "y": 55 + }, + "scenePoint": { + "x": 435, + "y": 55 + }, + "pointer": { + "x": 435, + "y": 55 + }, + "absolutePointer": { + "x": 435, + "y": 55 + } + }, + "b" + ], + [ + "dragleave", + { + "e": { + "isTrusted": true + }, + "target": "b", + "subTargets": [], + "dragSource": "a" + }, + "canvas" + ], + [ + "dragleave", + { + "subTargets": [], + "dragSource": "a", + "e": { + "isTrusted": true + }, + "target": "b", + "viewportPoint": { + "x": 435, + "y": 55 + }, + "scenePoint": { + "x": 435, + "y": 55 + }, + "pointer": { + "x": 435, + "y": 55 + }, + "absolutePointer": { + "x": 435, + "y": 55 + } + }, + "b" + ], + [ + "dragend", + { + "e": { + "isTrusted": true + }, + "target": "a", + "subTargets": [], + "dragSource": "a", + "didDrop": false + }, + "canvas" + ], + [ + "dragend", + { + "e": { + "isTrusted": true + }, + "target": "a", + "subTargets": [], + "dragSource": "a", + "didDrop": false + }, + "a" + ] +] \ No newline at end of file diff --git a/e2e/tests/text/drag&drop/index.spec.ts-snapshots/drag-image-em---tge.json b/e2e/tests/text/drag&drop/index.spec.ts-snapshots/drag-image-em---tge.json index c6bae5b5a0a..f0147530031 100644 --- a/e2e/tests/text/drag&drop/index.spec.ts-snapshots/drag-image-em---tge.json +++ b/e2e/tests/text/drag&drop/index.spec.ts-snapshots/drag-image-em---tge.json @@ -1,4 +1,508 @@ { - "x": -400, - "y": -20 + "position": { + "x": -400, + "y": -20 + }, + "data": { + "text/plain": "em ipsum\ndolor\nsit Amet2\nconsectge", + "application/fabric": { + "value": "em ipsum\ndolor\nsit Amet2\nconsectge", + "styles": [ + { + "fontSize": 50, + "fontWeight": "normal", + "fontFamily": "Arial", + "fontStyle": "normal", + "underline": false, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "red", + "deltaY": 0, + "textBackgroundColor": "" + }, + { + "fontSize": 60, + "fontWeight": "normal", + "fontFamily": "Arial", + "fontStyle": "normal", + "underline": false, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "red", + "deltaY": 0, + "textBackgroundColor": "" + }, + { + "fontSize": 40, + "fontWeight": "normal", + "fontFamily": "Arial", + "fontStyle": "normal", + "underline": false, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "rgb(0,0,0)", + "deltaY": 0, + "textBackgroundColor": "" + }, + { + "fontSize": 40, + "fontWeight": "normal", + "fontFamily": "Arial", + "fontStyle": "normal", + "underline": false, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "rgb(0,0,0)", + "deltaY": 0, + "textBackgroundColor": "yellow" + }, + { + "fontSize": 40, + "fontWeight": "normal", + "fontFamily": "Arial", + "fontStyle": "normal", + "underline": false, + "overline": false, + "linethrough": true, + "stroke": null, + "strokeWidth": 1, + "fill": "rgb(0,0,0)", + "deltaY": 0, + "textBackgroundColor": "yellow", + "textDecoration": " line-through" + }, + { + "fontSize": 40, + "fontWeight": "normal", + "fontFamily": "Arial", + "fontStyle": "normal", + "underline": false, + "overline": false, + "linethrough": true, + "stroke": null, + "strokeWidth": 1, + "fill": "rgb(0,0,0)", + "deltaY": 0, + "textBackgroundColor": "yellow", + "textDecoration": " line-through" + }, + { + "fontSize": 40, + "fontWeight": "normal", + "fontFamily": "Arial", + "fontStyle": "normal", + "underline": false, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "rgb(0,0,0)", + "deltaY": 0, + "textBackgroundColor": "yellow" + }, + { + "fontSize": 40, + "fontWeight": "normal", + "fontFamily": "Arial", + "fontStyle": "normal", + "underline": false, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "rgb(0,0,0)", + "deltaY": 0, + "textBackgroundColor": "" + }, + { + "fontSize": 40, + "fontWeight": "normal", + "fontFamily": "Arial", + "fontStyle": "normal", + "underline": false, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "rgb(0,0,0)", + "deltaY": 0, + "textBackgroundColor": "" + }, + { + "fontSize": 40, + "fontWeight": "normal", + "fontFamily": "Arial", + "fontStyle": "normal", + "underline": false, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "rgb(0,0,0)", + "deltaY": 0, + "textBackgroundColor": "", + "textDecoration": "underline" + }, + { + "fontSize": 40, + "fontWeight": "normal", + "fontFamily": "Arial", + "fontStyle": "normal", + "underline": false, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "rgb(0,0,0)", + "deltaY": 0, + "textBackgroundColor": "", + "textDecoration": "underline" + }, + { + "fontSize": 40, + "fontWeight": "normal", + "fontFamily": "Arial", + "fontStyle": "italic", + "underline": false, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "green", + "deltaY": 0, + "textBackgroundColor": "", + "textDecoration": "underline" + }, + { + "fontSize": 40, + "fontWeight": "normal", + "fontFamily": "Arial", + "fontStyle": "italic", + "underline": false, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "green", + "deltaY": 0, + "textBackgroundColor": "", + "textDecoration": "underline" + }, + { + "fontSize": 40, + "fontWeight": "normal", + "fontFamily": "Arial", + "fontStyle": "italic", + "underline": false, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "green", + "deltaY": 0, + "textBackgroundColor": "", + "textDecoration": "underline" + }, + { + "fontSize": 40, + "fontWeight": "normal", + "fontFamily": "Arial", + "fontStyle": "normal", + "underline": false, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "rgb(0,0,0)", + "deltaY": 0, + "textBackgroundColor": "" + }, + { + "fontSize": 40, + "fontWeight": "bold", + "fontFamily": "Arial", + "fontStyle": "normal", + "underline": false, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "blue", + "deltaY": 0, + "textBackgroundColor": "" + }, + { + "fontSize": 40, + "fontWeight": "bold", + "fontFamily": "Arial", + "fontStyle": "normal", + "underline": false, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "blue", + "deltaY": 0, + "textBackgroundColor": "" + }, + { + "fontSize": 63, + "fontWeight": "bold", + "fontFamily": "Arial", + "fontStyle": "normal", + "underline": false, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "blue", + "deltaY": 0, + "textBackgroundColor": "" + }, + { + "fontSize": 40, + "fontWeight": "normal", + "fontFamily": "Arial", + "fontStyle": "normal", + "underline": false, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "rgb(0,0,0)", + "deltaY": 0, + "textBackgroundColor": "" + }, + { + "fontSize": 40, + "fontWeight": "normal", + "fontFamily": "Arial", + "fontStyle": "normal", + "underline": true, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "rgb(0,0,0)", + "deltaY": 0, + "textBackgroundColor": "", + "textDecoration": " underline" + }, + { + "fontSize": 40, + "fontWeight": "normal", + "fontFamily": "Arial", + "fontStyle": "normal", + "underline": true, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "rgb(0,0,0)", + "deltaY": 0, + "textBackgroundColor": "", + "textDecoration": " underline" + }, + { + "fontSize": 40, + "fontWeight": "normal", + "fontFamily": "Arial", + "fontStyle": "normal", + "underline": false, + "overline": true, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "rgb(0,0,0)", + "deltaY": 0, + "textBackgroundColor": "", + "textDecoration": " overline" + }, + { + "fontSize": 40, + "fontWeight": "normal", + "fontFamily": "Arial", + "fontStyle": "normal", + "underline": false, + "overline": true, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "rgb(0,0,0)", + "deltaY": 0, + "textBackgroundColor": "", + "textDecoration": " overline" + }, + { + "fontSize": 40, + "fontWeight": "normal", + "fontFamily": "Arial", + "fontStyle": "normal", + "underline": false, + "overline": true, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "rgb(0,0,0)", + "deltaY": 0, + "textBackgroundColor": "", + "textDecoration": " overline" + }, + { + "fontSize": 40, + "fontWeight": "normal", + "fontFamily": "Arial", + "fontStyle": "normal", + "underline": false, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "rgb(0,0,0)", + "deltaY": 0, + "textBackgroundColor": "" + }, + { + "fontSize": 40, + "fontWeight": "normal", + "fontFamily": "Arial", + "fontStyle": "normal", + "underline": false, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "#666", + "deltaY": 0, + "textBackgroundColor": "", + "textDecoration": "line-through" + }, + { + "fontSize": 40, + "fontWeight": "normal", + "fontFamily": "Arial", + "fontStyle": "normal", + "underline": false, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "#666", + "deltaY": 0, + "textBackgroundColor": "", + "textDecoration": "line-through" + }, + { + "fontSize": 40, + "fontWeight": "normal", + "fontFamily": "Arial", + "fontStyle": "normal", + "underline": false, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "#666", + "deltaY": 0, + "textBackgroundColor": "", + "textDecoration": "line-through" + }, + { + "fontSize": 40, + "fontWeight": "normal", + "fontFamily": "Arial", + "fontStyle": "normal", + "underline": false, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "#666", + "deltaY": 0, + "textBackgroundColor": "", + "textDecoration": "line-through" + }, + { + "fontSize": 40, + "fontWeight": "normal", + "fontFamily": "Arial", + "fontStyle": "normal", + "underline": false, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "#666", + "deltaY": 0, + "textBackgroundColor": "", + "textDecoration": "line-through" + }, + { + "fontSize": 40, + "fontWeight": "normal", + "fontFamily": "Arial", + "fontStyle": "normal", + "underline": false, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "rgb(0,0,0)", + "deltaY": 0, + "textBackgroundColor": "" + }, + { + "fontSize": 40, + "fontWeight": "normal", + "fontFamily": "Arial", + "fontStyle": "normal", + "underline": false, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "rgb(0,0,0)", + "deltaY": 0, + "textBackgroundColor": "" + }, + { + "fontSize": 40, + "fontWeight": "normal", + "fontFamily": "Arial", + "fontStyle": "normal", + "underline": true, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "rgb(0,0,0)", + "deltaY": 0, + "textBackgroundColor": "", + "textDecoration": " underline" + }, + { + "fontSize": 40, + "fontWeight": "normal", + "fontFamily": "Arial", + "fontStyle": "normal", + "underline": false, + "overline": false, + "linethrough": false, + "stroke": "#ff1e15", + "strokeWidth": 2, + "fill": "rgb(0,0,0)", + "deltaY": 0, + "textBackgroundColor": "" + } + ] + } + } } \ No newline at end of file diff --git a/e2e/tests/text/drag&drop/index.spec.ts-snapshots/drag-image-fabric.json b/e2e/tests/text/drag&drop/index.spec.ts-snapshots/drag-image-fabric.json index a6e1452e1b0..5ef7acfb8bb 100644 --- a/e2e/tests/text/drag&drop/index.spec.ts-snapshots/drag-image-fabric.json +++ b/e2e/tests/text/drag&drop/index.spec.ts-snapshots/drag-image-fabric.json @@ -1,4 +1,98 @@ { - "x": -44.5, - "y": -20 + "position": { + "x": -44.5, + "y": -20 + }, + "data": { + "text/plain": "fabric", + "application/fabric": { + "value": "fabric", + "styles": [ + { + "fontSize": 64, + "fontWeight": "bold", + "fontFamily": "Times New Roman", + "fontStyle": "normal", + "underline": false, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "rgb(0,0,0)", + "deltaY": 0, + "textBackgroundColor": "" + }, + { + "fontSize": 64, + "fontWeight": "bold", + "fontFamily": "Times New Roman", + "fontStyle": "normal", + "underline": false, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "rgb(0,0,0)", + "deltaY": 0, + "textBackgroundColor": "" + }, + { + "fontSize": 64, + "fontWeight": "bold", + "fontFamily": "Times New Roman", + "fontStyle": "normal", + "underline": false, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "rgb(0,0,0)", + "deltaY": 0, + "textBackgroundColor": "" + }, + { + "fontSize": 64, + "fontWeight": "bold", + "fontFamily": "Times New Roman", + "fontStyle": "normal", + "underline": false, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "rgb(0,0,0)", + "deltaY": 0, + "textBackgroundColor": "" + }, + { + "fontSize": 64, + "fontWeight": "bold", + "fontFamily": "Times New Roman", + "fontStyle": "normal", + "underline": false, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "rgb(0,0,0)", + "deltaY": 0, + "textBackgroundColor": "" + }, + { + "fontSize": 64, + "fontWeight": "bold", + "fontFamily": "Times New Roman", + "fontStyle": "normal", + "underline": false, + "overline": false, + "linethrough": false, + "stroke": null, + "strokeWidth": 1, + "fill": "rgb(0,0,0)", + "deltaY": 0, + "textBackgroundColor": "" + } + ] + } + } } \ No newline at end of file diff --git a/e2e/tests/text/drag&drop/index.ts b/e2e/tests/text/drag&drop/index.ts index 5bc16e0845b..2c9dc950f46 100644 --- a/e2e/tests/text/drag&drop/index.ts +++ b/e2e/tests/text/drag&drop/index.ts @@ -1,5 +1,5 @@ import * as fabric from 'fabric'; -import { beforeAll } from '../../test'; +import { before } from '../../test'; const styles = { 0: { @@ -77,41 +77,100 @@ const styles = { }, }; -beforeAll( - (canvas) => { - const textValue = 'fabric.js sandbox'; - const a = new fabric.Textbox(textValue, { - originX: 'center', - width: 210, - left: 150, - top: 20, - splitByGrapheme: true, - styles: fabric.util.stylesFromArray( - [ - { - style: { - fontWeight: 'bold', - fontSize: 64, - }, - start: 0, - end: 9, +const roundPoint = (point: fabric.Point) => ({ + x: Math.round(point.x), + y: Math.round(point.y), +}); + +const parseEvent = ( + type: string, + { pointer, absolutePointer, ...ev } = {}, + caller: fabric.Textbox | fabric.Canvas +) => + JSON.parse( + JSON.stringify([ + type, + { + ...ev, + ...(pointer ? { pointer: roundPoint(pointer) } : {}), + ...(absolutePointer + ? { absolutePointer: roundPoint(absolutePointer) } + : {}), + }, + caller, + ]) + ); + +class TestCanvas extends fabric.Canvas { + eventStream = []; + readEventStream() { + const data = this.eventStream; + this.eventStream = []; + return data; + } + toJSON() { + return 'canvas'; + } +} + +const commonEvents = [ + 'dragstart', + // 'dragover', + // 'drag', + 'dragenter', + 'dragleave', + 'drop', + 'dragend', +] as const; + +before('#canvas', (el) => { + const canvas = new TestCanvas(el, { width: 800, height: 500 }); + const textValue = 'fabric.js sandbox'; + const a = new fabric.Textbox(textValue, { + originX: 'center', + width: 210, + left: 150, + top: 20, + splitByGrapheme: true, + styles: fabric.util.stylesFromArray( + [ + { + style: { + fontWeight: 'bold', + fontSize: 64, }, - ], - textValue - ), + start: 0, + end: 9, + }, + ], + textValue + ), + }); + const b = new fabric.Textbox('lorem ipsum\ndolor\nsit Amet2\nconsectgetur', { + left: 400, + top: 20, + objectCaching: false, + fontFamily: 'Arial', + styles, + }); + + ( + [...commonEvents, 'text:selection:changed', 'text:changed'] as const + ).forEach((type) => { + canvas.on(type, (ev) => { + canvas.eventStream.push(parseEvent(type, ev, canvas)); }); - const b = new fabric.Textbox( - 'lorem ipsum\ndolor\nsit Amet2\nconsectgetur', - { - left: 400, - top: 20, - objectCaching: false, - fontFamily: 'Arial', - styles, - } - ); - canvas.add(a, b); - return { a, b }; - }, - { width: 800, height: 500 } -); + }); + ([...commonEvents, 'selection:changed', 'changed'] as const).forEach( + (type) => { + a.on(type, (ev) => canvas.eventStream.push(parseEvent(type, ev, a))); + b.on(type, (ev) => canvas.eventStream.push(parseEvent(type, ev, b))); + } + ); + Object.entries({ a, b }).forEach( + ([key, object]) => (object.toJSON = () => key) + ); + + canvas.add(a, b); + return { canvas, objects: { a, b } }; +}); diff --git a/e2e/utils/ObjectUtil.ts b/e2e/utils/ObjectUtil.ts index 99a32e905fd..0203cb93a31 100644 --- a/e2e/utils/ObjectUtil.ts +++ b/e2e/utils/ObjectUtil.ts @@ -44,7 +44,7 @@ export class ObjectUtil { ); } - async expectObjectToMatch>(expected: T) { + async expectObjectToMatch(expected: Partial) { const snapshot = await this.executeInBrowser((object) => object); expect(snapshot).toMatchObject(expected); } diff --git a/e2e/utils/TextUtil.ts b/e2e/utils/TextUtil.ts index 2f1f454b213..058e86a3bd4 100644 --- a/e2e/utils/TextUtil.ts +++ b/e2e/utils/TextUtil.ts @@ -19,4 +19,13 @@ export class TextUtil extends ObjectUtil { { index } ); } + + isCursorActive() { + return this.executeInBrowser((object) => { + return [ + object['_currentTickState'], + object['_currentTickCompleteState'], + ].some((cursorAnimation) => cursorAnimation && !cursorAnimation.isDone()); + }); + } } diff --git a/src/canvas/__tests__/__snapshots__/eventData.test.ts.snap b/src/canvas/__tests__/__snapshots__/eventData.test.ts.snap index cdf36d9eeea..f0e81fa324d 100644 --- a/src/canvas/__tests__/__snapshots__/eventData.test.ts.snap +++ b/src/canvas/__tests__/__snapshots__/eventData.test.ts.snap @@ -700,7 +700,7 @@ exports[`Canvas event data HTML event "wheel" should fire a corresponding canvas ] `; -exports[`should fire mouse over/out events on target 1`] = ` +exports[`Event targets should fire mouse over/out events on target 1`] = ` [ [ "mouseover", @@ -784,7 +784,7 @@ exports[`should fire mouse over/out events on target 1`] = ` ] `; -exports[`should fire mouse over/out events on target 2`] = ` +exports[`Event targets should fire mouse over/out events on target 2`] = ` [ [ "mouse:move:before", diff --git a/src/canvas/__tests__/eventData.test.ts b/src/canvas/__tests__/eventData.test.ts index 611549538d5..3d32723cdc6 100644 --- a/src/canvas/__tests__/eventData.test.ts +++ b/src/canvas/__tests__/eventData.test.ts @@ -1,15 +1,24 @@ /* eslint-disable no-restricted-globals */ import '../../../jest.extend'; -import type { TPointerEvent } from '../../EventTypeDefs'; 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; @@ -131,38 +140,755 @@ describe('Canvas event data', () => { }); }); -it('A selected subtarget should not fire an event twice', () => { - const target = new FabricObject(); - const group = new Group([target], { - subTargetCheck: true, - interactive: true, +describe('Event targets', () => { + it('A selected subtarget should not fire an event twice', () => { + const target = new FabricObject(); + const group = new Group([target], { + subTargetCheck: true, + interactive: true, + }); + const canvas = new Canvas(); + canvas.add(group); + const targetSpy = jest.fn(); + target.on('mousedown', targetSpy); + jest.spyOn(canvas, '_checkTarget').mockReturnValue(true); + canvas.getSelectionElement().dispatchEvent( + new MouseEvent('mousedown', { + clientX: 50, + clientY: 50, + }) + ); + expect(targetSpy).toHaveBeenCalledTimes(1); }); - const canvas = new Canvas(); - canvas.add(group); - const targetSpy = jest.fn(); - target.on('mousedown', targetSpy); - jest.spyOn(canvas, '_checkTarget').mockReturnValue(true); - canvas.__onMouseDown({ - target: canvas.getSelectionElement(), - clientX: 0, - clientY: 0, - } as unknown as TPointerEvent); - expect(targetSpy).toHaveBeenCalledTimes(1); -}); -it('should fire mouse over/out events on target', () => { - const target = new FabricObject({ width: 10, height: 10 }); - const canvas = new Canvas(); - canvas.add(target); - - jest.spyOn(target, 'toJSON').mockReturnValue('target'); - - const targetSpy = jest.spyOn(target, 'fire'); - const canvasSpy = jest.spyOn(canvas, 'fire'); - const enter = new MouseEvent('mousemove', { clientX: 5, clientY: 5 }); - const exit = new MouseEvent('mousemove', { clientX: 20, clientY: 20 }); - canvas._onMouseMove(enter); - canvas._onMouseMove(exit); - expect(targetSpy.mock.calls).toMatchSnapshot(); - expect(canvasSpy.mock.calls).toMatchSnapshot(); + 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 group2 = new Group([rect3, rect4], { + subTargetCheck: true, + }); + // 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.skip.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(); + 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.failing('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(); + 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, target, parent]); + }); + + 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(undefined, { 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(undefined, { 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(undefined, { 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(undefined, { + 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(undefined, { 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(undefined, { 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(undefined, { 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(undefined, { + 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: [], + }); + }); + }); + + it('should fire mouse over/out events on target', () => { + const target = new FabricObject({ width: 10, height: 10 }); + const canvas = new Canvas(); + canvas.add(target); + + jest.spyOn(target, 'toJSON').mockReturnValue('target'); + + const targetSpy = jest.spyOn(target, 'fire'); + const canvasSpy = jest.spyOn(canvas, 'fire'); + const enter = new MouseEvent('mousemove', { clientX: 5, clientY: 5 }); + const exit = new MouseEvent('mousemove', { clientX: 20, clientY: 20 }); + canvas._onMouseMove(enter); + canvas._onMouseMove(exit); + expect(targetSpy.mock.calls).toMatchSnapshot(); + expect(canvasSpy.mock.calls).toMatchSnapshot(); + }); }); diff --git a/test/unit/canvas.js b/test/unit/canvas.js index a5737d76b52..3ac67d4e7b3 100644 --- a/test/unit/canvas.js +++ b/test/unit/canvas.js @@ -771,392 +771,6 @@ canvas.remove(rect); }); - QUnit.test('findTarget preserveObjectStacking false', function(assert) { - canvas.preserveObjectStacking = false; - var rect = makeRect({ left: 0, top: 0 }), - rectOver = makeRect({ left: 0, top: 0 }), - target, - e = { clientX: 5, clientY: 5, target: canvas.upperCanvasEl }; - canvas.add(rect); - canvas.add(rectOver); - canvas.setActiveObject(rect); - canvas.renderAll(); - target = canvas.findTarget(e); - assert.equal(target, rect, 'Should return the rect'); - }); - - QUnit.test('findTarget preserveObjectStacking true', function(assert) { - assert.ok(typeof canvas.findTarget === 'function'); - canvas.preserveObjectStacking = true; - var rect = makeRect({ left: 0, top: 0, width: 30, height: 30 }), - rectOver = makeRect({ left: 0, top: 0, width: 30, height: 30 }), - target, - e1 = { clientX: 15, clientY: 15, shiftKey: true, target: canvas.upperCanvasEl }, - e2 = { clientX: 4, clientY: 4, target: canvas.upperCanvasEl }; - canvas.add(rect); - canvas.add(rectOver); - target = canvas.findTarget(e1); - assert.equal(target, rectOver, 'Should return the rectOver, rect is not considered'); - canvas.setActiveObject(rect); - target = canvas.findTarget(e1); - assert.equal(target, rectOver, 'Should still return rectOver because is above active object'); - target = canvas.findTarget(e2); - assert.equal(target, rect, 'Should rect because a corner of the activeObject has been hit'); - canvas.altSelectionKey = 'shiftKey'; - target = canvas.findTarget(e1); - assert.equal(target, rect, 'Should rect because active and altSelectionKey is pressed'); - canvas.preserveObjectStacking = false; - }); - - QUnit.test('findTarget with subTargetCheck', function(assert) { - var rect = makeRect({ left: 0, top: 0 }), - rect2 = makeRect({ left: 30, top: 30}), target, - group = new fabric.Group([rect, rect2]); - - canvas.add(group); - - target = canvas.findTarget({ - clientX: 5, clientY: 5, target: canvas.upperCanvasEl - }); - assert.equal(target, group, 'Should return the group'); - assert.equal(canvas.targets[0], undefined, 'no subtarget should return'); - - target = canvas.findTarget({ - clientX: 30, clientY: 30, target: canvas.upperCanvasEl - }); - assert.equal(target, group, 'Should return the group'); - group.subTargetCheck = true; - group.setCoords(); - target = canvas.findTarget({ - clientX: 5, clientY: 5, target: canvas.upperCanvasEl - }); - assert.equal(target, group, 'Should return the group'); - assert.equal(canvas.targets[0], rect, 'should return the rect'); - - target = canvas.findTarget({ - clientX: 15, clientY: 15, target: canvas.upperCanvasEl - }); - assert.equal(target, group, 'Should return the group'); - assert.equal(canvas.targets[0], undefined, 'no subtarget should return'); - - target = canvas.findTarget({ - clientX: 32, clientY: 32, target: canvas.upperCanvasEl - }); - assert.equal(target, group, 'Should return the group'); - assert.equal(canvas.targets[0], rect2, 'should return the rect2'); - canvas.remove(group); - }); - - QUnit.test('findTarget with subTargetCheck and canvas zoom', function(assert) { - var rect3 = new fabric.Rect({ - width: 100, - height: 100, - fill: 'yellow' - }); - var rect4 = new fabric.Rect({ - width: 100, - height: 100, - left: 100, - top: 100, - fill: 'purple' - }); - var group3 = new fabric.Group( - [rect3, rect4], - { scaleX: 0.5, scaleY: 0.5, top: 100, left: 0 }); - group3.subTargetCheck = true; - group3.setCoords(); - var rect1 = new fabric.Rect({ - width: 100, - height: 100, - fill: 'red' - }); - var rect2 = new fabric.Rect({ - width: 100, - height: 100, - left: 100, - top: 100, - fill: 'blue' - }); - var g = new fabric.Group([rect1, rect2, group3], { top: -150, left: -50 }); - g.subTargetCheck = true; - canvas.viewportTransform = [0.1, 0, 0, 0.1, 100, 200]; - canvas.add(g); - - var target = canvas.findTarget({ - clientX: 96, clientY: 186, target: canvas.upperCanvasEl - }); - assert.equal(target, g, 'Should return the group 96'); - assert.equal(canvas.targets[0], rect1, 'should find the target rect 96'); - canvas.targets = []; - - target = canvas.findTarget({ - clientX: 98, clientY: 188, target: canvas.upperCanvasEl - }); - assert.equal(target, g, 'Should return the group 98'); - assert.equal(canvas.targets[0], rect1, 'should find the target rect1 98'); - canvas.targets = []; - - target = canvas.findTarget({ - clientX: 100, clientY: 190, target: canvas.upperCanvasEl - }); - assert.equal(target, g, 'Should return the group 100'); - assert.equal(canvas.targets[0], rect1, 'should find the target rect1 100'); - canvas.targets = []; - - target = canvas.findTarget({ - clientX: 102, clientY: 192, target: canvas.upperCanvasEl - }); - assert.equal(target, g, 'Should return the group 102'); - assert.equal(canvas.targets[0], rect1, 'should find the target rect 102'); - canvas.targets = []; - - target = canvas.findTarget({ - clientX: 104, clientY: 194, target: canvas.upperCanvasEl - }); - assert.equal(target, g, 'Should return the group 104'); - assert.equal(canvas.targets[0], rect1, 'should find the target rect 104'); - canvas.targets = []; - - target = canvas.findTarget({ - clientX: 106, clientY: 196, target: canvas.upperCanvasEl - }); - assert.equal(target, g, 'Should return the group 106'); - assert.equal(canvas.targets[0], rect2, 'should find the target rect2 106'); - canvas.targets = []; - }); - - QUnit.test('findTarget with subTargetCheck on activeObject', function(assert) { - var rect = makeRect({ left: 0, top: 0 }), - rect2 = makeRect({ left: 30, top: 30}), target, - group = new fabric.Group([rect, rect2]); - - - group.subTargetCheck = true; - canvas.add(group); - canvas.setActiveObject(group); - target = canvas.findTarget({ - clientX: 9, clientY: 9, target: canvas.upperCanvasEl - }); - assert.equal(target, group, 'Should return the group'); - assert.equal(canvas.targets[0], rect, 'should return the rect'); - - target = canvas.findTarget({ - clientX: 9, clientY: 9, target: canvas.upperCanvasEl - }); - - target = canvas.findTarget({ - clientX: 9, clientY: 9, target: canvas.upperCanvasEl - }); - - target = canvas.findTarget({ - clientX: 9, clientY: 9, target: canvas.upperCanvasEl - }); - - assert.equal(canvas.targets.length, 1, 'multiple calls to subtarget should not add more to targets'); - - canvas.remove(group); - }); - - QUnit.test('findTarget with subTargetCheck on activeObject and preserveObjectStacking true', function(assert) { - var rect = makeRect({ left: 0, top: 0 }), - rect2 = makeRect({ left: 30, top: 30}), target, - group = new fabric.Group([rect, rect2]); - canvas.preserveObjectStacking = true; - group.subTargetCheck = true; - canvas.add(group); - canvas.setActiveObject(group); - target = canvas.findTarget({ - clientX: 9, clientY: 9, target: canvas.upperCanvasEl - }); - assert.equal(target, group, 'Should return the group'); - assert.equal(canvas.targets[0], rect, 'should return the rect'); - - target = canvas.findTarget({ - clientX: 9, clientY: 9, target: canvas.upperCanvasEl - }); - - target = canvas.findTarget({ - clientX: 9, clientY: 9, target: canvas.upperCanvasEl - }); - - target = canvas.findTarget({ - clientX: 9, clientY: 9, target: canvas.upperCanvasEl - }); - - assert.equal(canvas.targets.length, 1, 'multiple calls to subtarget should not add more to targets'); - - canvas.remove(group); - }); - - QUnit.test('findTarget with perPixelTargetFind', function(assert) { - assert.ok(typeof canvas.findTarget === 'function'); - var triangle = makeTriangle({ left: 0, top: 0 }), target; - canvas.add(triangle); - target = canvas.findTarget({ - clientX: 5, clientY: 5, target: canvas.upperCanvasEl - }); - assert.equal(target, triangle, 'Should return the triangle by bounding box'); - //TODO find out why this stops the tests - canvas.perPixelTargetFind = true; - target = canvas.findTarget({ - clientX: 5, clientY: 5, target: canvas.upperCanvasEl - }); - assert.equal(target, null, 'Should return null because of transparency checks'); - target = canvas.findTarget({ - clientX: 15, clientY: 15, target: canvas.upperCanvasEl - }); - assert.equal(target, triangle, 'Should return the triangle now'); - canvas.perPixelTargetFind = false; - canvas.remove(triangle); - }); - - QUnit.test('findTarget with perPixelTargetFind in nested group', function(assert) { - assert.ok(typeof canvas.findTarget === 'function'); - var triangle = makeTriangle({ left: 0, top: 0, width: 30, height: 30, fill: 'yellow' }), - triangle2 = makeTriangle({ left: 100, top: 120, width: 30, height: 30, angle: 100, fill: 'pink' }), - circle = new fabric.Circle({ radius: 30, top: 0, left: 30, fill: 'blue' }), - circle2 = new fabric.Circle({ scaleX: 2, scaleY: 2, radius: 10, top: 120, left: -20, fill: 'purple' }), - rect = new fabric.Rect({ width: 100, height: 80, top: 50, left: 60, fill: 'green' }), - rect2 = new fabric.Rect({ width: 50, height: 30, top: 10, left: 110, fill: 'red', skewX: 40, skewY: 20 }), - group1 = new fabric.Group([triangle, circle, rect2], { subTargetCheck: true }), - group2 = new fabric.Group([group1, circle2, rect, triangle2], { subTargetCheck: true }), - group3 = new fabric.Group([group2], { subTargetCheck: true }), - target; - - canvas.add(group3); - canvas.perPixelTargetFind = true; - target = canvas.findTarget({ - clientX: 5, clientY: 5, target: canvas.upperCanvasEl - }); - assert.equal(target, null, 'Should return null because of transparency checks case 1'); - target = canvas.findTarget({ - clientX: 21, clientY: 9, target: canvas.upperCanvasEl - }); - assert.equal(target, null, 'Should return null because of transparency checks case 2'); - target = canvas.findTarget({ - clientX: 37, clientY: 7, target: canvas.upperCanvasEl - }); - assert.equal(target, null, 'Should return null because of transparency checks case 3'); - target = canvas.findTarget({ - clientX: 89, clientY: 47, target: canvas.upperCanvasEl - }); - assert.equal(target, null, 'Should return null because of transparency checks case 4'); - target = canvas.findTarget({ - clientX: 16, clientY: 122, target: canvas.upperCanvasEl - }); - assert.equal(target, null, 'Should return null because of transparency checks case 5'); - target = canvas.findTarget({ - clientX: 127, clientY: 37, target: canvas.upperCanvasEl - }); - assert.equal(target, null, 'Should return null because of transparency checks case 6'); - target = canvas.findTarget({ - clientX: 87, clientY: 139, target: canvas.upperCanvasEl - }); - assert.equal(target, null, 'Should return null because of transparency checks case 7'); - target = canvas.findTarget({ - clientX: 15, clientY: 15, target: canvas.upperCanvasEl - }); - assert.equal(target, group3, 'Should return the group3 now'); - assert.equal(canvas.targets.length, 3, 'Subtargets length should be 3'); - assert.equal(canvas.targets[0], triangle, 'The deepest target should be triangle'); - target = canvas.findTarget({ - clientX: 50, clientY: 20, target: canvas.upperCanvasEl - }); - assert.equal(target, group3, 'Should return the group3 now'); - assert.equal(canvas.targets.length, 3, 'Subtargets length should be 3'); - assert.equal(canvas.targets[0], circle, 'The deepest target should be circle'); - target = canvas.findTarget({ - clientX: 117, clientY: 16, target: canvas.upperCanvasEl - }); - assert.equal(target, group3, 'Should return the group3 now'); - assert.equal(canvas.targets.length, 3, 'Subtargets length should be 2'); - assert.equal(canvas.targets[0], rect2, 'The deepest target should be rect2'); - target = canvas.findTarget({ - clientX: 100, clientY: 90, target: canvas.upperCanvasEl - }); - assert.equal(target, group3, 'Should return the group3 now'); - assert.equal(canvas.targets.length, 2, 'Subtargets length should be 2'); - assert.equal(canvas.targets[0], rect, 'The deepest target should be rect'); - target = canvas.findTarget({ - clientX: 9, clientY: 145, target: canvas.upperCanvasEl - }); - assert.equal(target, group3, 'Should return the group3 now'); - assert.equal(canvas.targets.length, 2, 'Subtargets length should be 2'); - assert.equal(canvas.targets[0], circle2, 'The deepest target should be circle2'); - target = canvas.findTarget({ - clientX: 66, clientY: 143, target: canvas.upperCanvasEl - }); - assert.equal(target, group3, 'Should return the group3 now'); - assert.equal(canvas.targets.length, 2, 'Subtargets length should be 2'); - assert.equal(canvas.targets[0], triangle2, 'The deepest target should be triangle2'); - canvas.remove(group3); - }); - - QUnit.test('findTarget on active selection', function(assert) { - var rect1 = makeRect({ left: 0, top: 0 }), target; - var rect2 = makeRect({ left: 20, top: 20 }); - var rect3 = makeRect({ left: 20, top: 0 }); - canvas.add(rect1); - canvas.add(rect2); - canvas.add(rect3); - const group = new fabric.ActiveSelection(); - group.subTargetCheck = true; - group.add(rect1, rect2); - group.cornerSize = 2; - group.setCoords(); - canvas.setActiveObject(group); - target = canvas.findTarget({ - clientX: 5, clientY: 5, target: canvas.upperCanvasEl - }); - assert.equal(target, group, 'Should return the activegroup'); - target = canvas.findTarget({ - clientX: 40, clientY: 15, target: canvas.upperCanvasEl - }); - assert.equal(target, null, 'Should miss the activegroup'); - assert.ok(!group.__corner, 'not over control'); - target = canvas.findTarget({ - clientX: 0, clientY: 0, target: canvas.upperCanvasEl - }); - assert.equal(group.__corner, 'tl', 'over control'); - assert.ok(target, group, 'should return active selection if over control'); - target = canvas.findTarget({ - clientX: 5, clientY: 5, target: canvas.upperCanvasEl - }); - assert.ok(target, group, 'should return active selection'); - assert.equal(canvas.targets[0], rect1, 'Should return the rect inside active selection'); - target = canvas.findTarget({ - clientX: 25, clientY: 5, target: canvas.upperCanvasEl - }); - assert.equal(target, group, 'Should return the active selection'); - assert.deepEqual(canvas.targets, [], 'Should not return the rect behind active selection'); - canvas.discardActiveObject(); - target = canvas.findTarget({ - clientX: 25, clientY: 5, target: canvas.upperCanvasEl - }); - assert.equal(target, rect3, 'Should return the rect3 now that active selection has been cleared'); - }); - - QUnit.test('findTarget on active selection with perPixelTargetFind', function(assert) { - var rect1 = makeRect({ left: 0, top: 0 }), target; - var rect2 = makeRect({ left: 20, top: 20 }); - canvas.perPixelTargetFind = true; - canvas.preserveObjectStacking = true; - canvas.add(rect1); - canvas.add(rect2); - const group = new fabric.ActiveSelection(); - group.add(rect1, rect2); - canvas.setActiveObject(group); - target = canvas.findTarget({ - clientX: 8, clientY: 8, target: canvas.upperCanvasEl - }); - assert.equal(target, group, 'Should return the activegroup'); - - target = canvas.findTarget({ - clientX: 15, clientY: 15, target: canvas.upperCanvasEl - }); - assert.equal(target, null, 'Should miss the activegroup'); - }); - QUnit.test('toDataURL', function(assert) { assert.ok(typeof canvas.toDataURL === 'function'); var dataURL = canvas.toDataURL(); diff --git a/test/unit/canvas_events.js b/test/unit/canvas_events.js index 48c14d79b34..c7b6d260868 100644 --- a/test/unit/canvas_events.js +++ b/test/unit/canvas_events.js @@ -630,70 +630,6 @@ assert.deepEqual(targetControl, [], 'no target should be referenced'); }); - QUnit.test('mouseover and mouseout with subtarget check', function(assert) { - var rect1 = new fabric.Rect({ width: 5, height: 5, left: 5, top: 0, strokeWidth: 0, name: 'rect1' }); - var rect2 = new fabric.Rect({ width: 5, height: 5, left: 5, top: 5, strokeWidth: 0, name: 'rect2' }); - var rect3 = new fabric.Rect({ width: 5, height: 5, left: 0, top: 5, strokeWidth: 0, name: 'rect3' }); - var rect4 = new fabric.Rect({ width: 5, height: 5, left: 0, top: 0, strokeWidth: 0, name: 'rect4' }); - var rect5 = new fabric.Rect({ width: 5, height: 5, left: 2.5, top: 2.5, strokeWidth: 0, name: 'rect5' }); - var group1 = new fabric.Group([rect1, rect2], { subTargetCheck: true, name: 'group1' }); - var group2 = new fabric.Group([rect3, rect4], { subTargetCheck: true, name: 'group2' }); - // a group with 2 groups, with 2 rects each, one group left one group right - // each with 2 rects vertically aligned - var group = new fabric.Group([group1, group2], { subTargetCheck: true, name: 'group' }); - var c = new fabric.Canvas(); - var targetArray = []; - var targetOutArray = []; - [rect1, rect2, rect3, rect4, rect5, group1, group2, group].forEach(function(t) { - t.on('mouseover', function(opt) { - targetArray.push(opt.target); - }); - t.on('mouseout', function(opt) { - targetOutArray.push(opt.target); - }); - }); - c.add(group, rect5); - simulateEvent(c.upperCanvasEl, 'mousemove', { - pointerX: 1, pointerY: 1 - }); - assert.equal(targetArray[0], group, 'first hit is group'); - assert.equal(targetArray[2], group2, 'then hit group2'); - assert.equal(targetArray[1], rect4, 'then hit rect4'); - assert.equal(targetOutArray.length, 0, 'no target out'); - - targetArray = []; - targetOutArray = []; - simulateEvent(c.upperCanvasEl, 'mousemove', { - pointerX: 5, pointerY: 5 - }); - assert.equal(targetArray[0], rect5, 'first hit is target5'); - assert.equal(targetArray.length, 1, 'only one target'); - assert.equal(targetOutArray[0], group, 'first targetOutArray is group'); - assert.equal(targetOutArray[2], group2, 'then targetOutArray group2'); - assert.equal(targetOutArray[1], rect4, 'then targetOutArray rect4'); - - targetArray = []; - targetOutArray = []; - simulateEvent(c.upperCanvasEl, 'mousemove', { - pointerX: 9, pointerY: 9 - }); - assert.equal(targetArray[0], group, 'first hit is group'); - assert.equal(targetArray[2], group1, 'then hit group1'); - assert.equal(targetArray[1], rect2, 'then hit rect2'); - assert.equal(targetOutArray.length, 1, 'only one target out when moving away from rect 5'); - assert.equal(targetOutArray[0], rect5, 'rect5 fires out'); - - targetArray = []; - targetOutArray = []; - simulateEvent(c.upperCanvasEl, 'mousemove', { - pointerX: 9, pointerY: 1 - }); - assert.equal(targetArray[0], rect1, 'the only target changing is rect1'); - assert.equal(targetArray.length, 1, 'only one target entering '); - assert.equal(targetOutArray.length, 1, 'one target out'); - assert.equal(targetOutArray[0], rect2, 'the only target out is rect2'); - }); - QUnit.test('Fabric mouseover, mouseout events fire for subTargets when subTargetCheck is enabled', function(assert){ var done = assert.async(); var counterOver = 0, counterOut = 0, canvas = new fabric.Canvas(); diff --git a/test/unit/draggable_text.js b/test/unit/draggable_text.js deleted file mode 100644 index 8f9cf427f7a..00000000000 --- a/test/unit/draggable_text.js +++ /dev/null @@ -1,613 +0,0 @@ -function assertDragEventStream(name, a, b) { - QUnit.assert.equal(a.length, b.length, `${name} event stream should be same in size`); - a.forEach(({ target, dragSource, dropTarget, ..._a }, index) => { - const { target: targetB, dragSource: dragSourceB, dropTarget: dropTargetB, ..._b } = b[index]; - QUnit.assert.equal(target, targetB, `target should match ${index}`); - QUnit.assert.equal(dragSource, dragSourceB, `dragSource should match ${index}`); - QUnit.assert.equal(dropTarget, dropTargetB, `dropTarget should match ${index}`); - QUnit.assert.deepEqual(_a, _b, `event ${index} should match`); - }); -} - -(isNode() ? QUnit.module.skip : QUnit.module)('draggable text', function (hooks) { - let canvas; - hooks.before(function () { - canvas = new fabric.Canvas(null, { - enableRetinaScaling: false - }); - }); - hooks.after(() => canvas.dispose()); - hooks.afterEach(function () { - canvas.clear(); - canvas.cancelRequestedRender(); - }); - - function assertCursorAnimation(assert, text, active = false) { - const cursorState = [text._currentTickState, text._currentTickCompleteState].some( - (cursorAnimation) => cursorAnimation && !cursorAnimation.isDone() - ); - assert.equal(cursorState, active, `cursor animation state should be ${active}`); - } - - function wait(ms = 32) { - return new Promise(resolve => setTimeout(resolve, ms)); - } - - [true, false].forEach(enableRetinaScaling => { - QUnit.module(`enableRetinaScaling = ${enableRetinaScaling}`, function (hooks) { - let canvas, eventData, iText, iText2, eventStream, renderEffects; - let count = 0, countCanvas = 0; - hooks.before(() => { - fabric.config.configure({ devicePixelRatio: 2 }); - }); - hooks.after(() => { - fabric.config.restoreDefaults(); - }); - hooks.beforeEach(() => { - canvas = new fabric.Canvas(null, { - enableRetinaScaling, - }); - eventData = { - which: 1, - target: canvas.upperCanvasEl, - preventDefault() { - this.defaultPrevented = true; - }, - stopPropagation() { - this.propagationStopped = true; - }, - dataTransfer: { - data: {}, - get types() { - return Object.keys(this.data); - }, - dropEffect: 'none', - getData(type) { - return this.data[type]; - }, - setData(type, value) { - this.data[type] = value; - }, - setDragImage(img, x, y) { - this.dragImageData = { img, x, y }; - }, - }, - ...(enableRetinaScaling ? { - clientX: 60, - clientY: 30 - } : { - clientX: 30, - clientY: 15 - }) - }; - iText = new fabric.IText('test test'); - iText2 = new fabric.IText('test2 test2', { left: 200 }); - canvas.add(iText, iText2); - canvas.setActiveObject(iText); - iText.enterEditing(); - iText.selectionStart = 0; - iText.selectionEnd = 4; - count = 0; - countCanvas = 0; - canvas.on('text:selection:changed', () => { - countCanvas++; - }); - iText.on('selection:changed', () => { - count++; - }); - eventStream = { - canvas: [], - source: [], - target: [], - }; - ['dragstart', 'dragover', 'drag', 'dragenter', 'dragleave', 'drop', 'dragend', 'changed', 'text:changed'].forEach(type => { - canvas.on(type, (ev) => { - eventStream.canvas.push({ ...ev, type }); - }); - iText.on(type, (ev) => { - eventStream.source.push({ ...ev, type }); - }); - iText2.on(type, (ev) => { - eventStream.target.push({ ...ev, type }); - }); - }); - renderEffects = []; - canvas._renderDragEffects = (e, source, target) => renderEffects.push({ e, source, target }); - }); - hooks.afterEach(() => canvas.dispose()); - - function startDragging(eventData) { - const e = { ...eventData }; - canvas._onMouseDown({ ...eventData }); - canvas._onDragStart(e); - return e; - } - - function createDragEvent(x = eventData.clientX, y = eventData.clientY, dataTransfer = {}) { - return { - ...eventData, - defaultPrevented: false, - clientX: x, - clientY: y, - dataTransfer: { - ...eventData.dataTransfer, - ...dataTransfer - } - }; - } - - QUnit.test('click sets cursor', async function (assert) { - assert.equal(count, 0, 'selection:changed fired'); - assert.equal(countCanvas, 0, 'text:selection:changed fired'); - let called = 0; - // sinon spy!! - // iText.setCursorByClick = () => called++; - canvas._onMouseDown(eventData); - assert.ok(iText.draggableTextDelegate.isActive(), 'flagged as dragging'); - assert.ok(iText.shouldStartDragging(), 'flagged as dragging'); - - await wait(); - assertCursorAnimation(assert, iText); - // assert.equal(called, 0, 'should not set cursor on mouse up'); - canvas._onMouseUp(eventData); - assert.ok(!iText.draggableTextDelegate.isActive(), 'unflagged as dragging'); - assert.ok(!iText.shouldStartDragging(), 'unflagged as dragging'); - // assert.equal(called, 1, 'should set cursor on mouse up'); - assert.equal(iText.selectionStart, 2, 'set the selectionStart'); - assert.equal(iText.selectionEnd, 2, 'set the selectionend'); - assertCursorAnimation(assert, iText, true); - assert.equal(count, 1, 'selection:changed fired'); - assert.equal(countCanvas, 1, 'text:selection:changed fired'); - }); - - QUnit.test('drag end over selection focuses hiddenTextarea', function (assert) { - startDragging(eventData); - iText.hiddenTextarea.blur(); - canvas._onDragEnd(createDragEvent()); - assert.equal(fabric.getFabricDocument().activeElement, iText.hiddenTextarea, 'should have focused hiddenTextarea'); - }); - - QUnit.test('drag start', function (assert) { - const e = startDragging(eventData); - const charStyle = { - "stroke": null, - "strokeWidth": 1, - "fill": "rgb(0,0,0)", - "fontFamily": "Times New Roman", - "fontSize": 40, - "fontWeight": "normal", - "fontStyle": "normal", - "underline": false, - "overline": false, - "linethrough": false, - "deltaY": 0, - "textBackgroundColor": "" - }; - assert.equal(e.dataTransfer.data['text/plain'], 'test', 'should set text/plain'); - assert.deepEqual(JSON.parse(e.dataTransfer.data['application/fabric']), { - value: 'test', - styles: [charStyle, charStyle, charStyle, charStyle] - }, 'should set application/fabric'); - assert.equal(e.dataTransfer.effectAllowed, 'copyMove', 'should set effectAllowed'); - assert.ok(e.dataTransfer.dragImageData.img instanceof HTMLCanvasElement, 'drag image was set'); - assert.equal(e.dataTransfer.dragImageData.x, 30, 'drag image position'); - assert.equal(e.dataTransfer.dragImageData.y, 15, 'drag image position'); - assert.deepEqual(renderEffects, [], 'not rendered effects yet'); - canvas._onDragEnd(eventData); - assert.deepEqual(eventStream.source, [ - { - e, - target: iText, - type: 'dragstart' - }, { - e: eventData, - target: iText, - type: 'dragend', - subTargets: [], - dragSource: iText, - dropTarget: undefined, - didDrop: false - } - ], 'events should match'); - assert.deepEqual(eventStream.canvas, eventStream.source, 'events should match'); - }); - - QUnit.test('disable drag start: onDragStart', async function (assert) { - iText.onDragStart = () => false; - const e = startDragging(eventData); - assert.equal(iText.shouldStartDragging(), true, 'should flag dragging'); - assert.equal(iText.selectionStart, 0, 'selectionStart is kept'); - assert.equal(iText.selectionEnd, 4, 'selectionEnd is kept'); - assert.deepEqual(e.dataTransfer.data, {}, 'should not set dataTransfer'); - assert.equal(e.dataTransfer.effectAllowed, undefined, 'should not set effectAllowed'); - assert.deepEqual(e.dataTransfer.dragImageData, undefined, 'should not set dataTransfer'); - }); - - QUnit.test('disable drag start: start', async function (assert) { - iText.draggableTextDelegate.start = () => false; - const e = startDragging(eventData); - assert.equal(iText.shouldStartDragging(), false, 'should not flag dragging'); - assert.equal(iText.selectionStart, 2, 'selectionStart is set'); - assert.equal(iText.selectionEnd, 2, 'selectionEnd is set'); - assert.deepEqual(e.dataTransfer.data, {}, 'should not set dataTransfer'); - assert.equal(e.dataTransfer.effectAllowed, undefined, 'should not set effectAllowed'); - assert.deepEqual(e.dataTransfer.dragImageData, undefined, 'should not set dataTransfer'); - }); - - QUnit.test('disable drag start: isActive', async function (assert) { - iText.draggableTextDelegate.isActive = () => false; - const e = startDragging(eventData); - assert.equal(iText.shouldStartDragging(), false, 'should not flag dragging'); - assert.equal(iText.selectionStart, 0, 'selectionStart is kept'); - assert.equal(iText.selectionEnd, 4, 'selectionEnd is kept'); - assertCursorAnimation(assert, iText); - assert.deepEqual(e.dataTransfer.data, {}, 'should not set dataTransfer'); - assert.equal(e.dataTransfer.effectAllowed, undefined, 'should not set effectAllowed'); - assert.deepEqual(e.dataTransfer.dragImageData, undefined, 'should not set dataTransfer'); - }); - - QUnit.test('drag over: source', function (assert) { - const e = startDragging(eventData); - const dragEvents = []; - let index; - for (index = 0; index < 100; index++) { - const dragOverEvent = createDragEvent(eventData.clientX + index * canvas.getRetinaScaling()); - canvas._onDragOver(dragOverEvent); - dragEvents.push(dragOverEvent); - } - const dragEnd = createDragEvent(eventData.clientX + index * canvas.getRetinaScaling()); - canvas._onDragEnd(dragEnd); - assertDragEventStream('source', eventStream.source, [ - { e, target: iText, type: 'dragstart' }, - { - e: dragEvents[0], - target: iText, - type: 'dragenter', - subTargets: [], - dragSource: iText, - dropTarget: undefined, - canDrop: false, - pointer: new fabric.Point(30, 15), - absolutePointer: new fabric.Point(30, 15), - viewportPoint: new fabric.Point(30, 15), - scenePoint: new fabric.Point(30, 15), - previousTarget: undefined - }, - ...dragEvents.slice(0, 32).map(e => ({ - e, - target: iText, - type: 'dragover', - subTargets: [], - dragSource: iText, - dropTarget: undefined, - canDrop: false - })), - ...dragEvents.slice(32, 93).map(e => ({ - e, - target: iText, - type: 'dragover', - subTargets: [], - dragSource: iText, - dropTarget: iText, - canDrop: true - })), - { - e: dragEvents[93], - target: iText, - type: 'dragleave', - subTargets: [], - dragSource: iText, - dropTarget: undefined, - canDrop: false, - pointer: new fabric.Point(123, 15), - absolutePointer: new fabric.Point(123, 15), - viewportPoint: new fabric.Point(123, 15), - scenePoint: new fabric.Point(123, 15), - nextTarget: undefined - }, - { - e: dragEnd, - target: iText, - type: 'dragend', - subTargets: [], - dragSource: iText, - dropTarget: undefined, - didDrop: false, - } - ]); - assert.deepEqual(renderEffects, [ - ...dragEvents.slice(0, 32).map(e => ({ e, source: iText, target: undefined })), - ...dragEvents.slice(32, 93).map(e => ({ e, source: iText, target: iText })), - ...dragEvents.slice(93).map(e => ({ e, source: iText, target: undefined })), - ], 'render effects'); - assert.equal(fabric.getFabricDocument().activeElement, iText.hiddenTextarea, 'should have focused hiddenTextarea'); - }); - - QUnit.test('drag over: target', function (assert) { - const e = startDragging(eventData); - const dragEvents = []; - let index; - for (index = 180; index < 190; index = index + 5) { - const dragOverEvent = createDragEvent(eventData.clientX + index * canvas.getRetinaScaling()); - canvas._onDragOver(dragOverEvent); - dragEvents.push(dragOverEvent); - } - for (index = 0; index <= 20; index = index + 5) { - const dragOverEvent = createDragEvent(eventData.clientX + 190 * canvas.getRetinaScaling(), eventData.clientY - index * canvas.getRetinaScaling()); - canvas._onDragOver(dragOverEvent); - dragEvents.push(dragOverEvent); - } - const dragEnd = createDragEvent(eventData.clientX + index * canvas.getRetinaScaling()); - canvas._onDragEnd(dragEnd); - assertDragEventStream('source in target test', eventStream.source, [ - { e, target: iText, type: 'dragstart' }, - { - e: dragEnd, - target: iText, - type: 'dragend', - subTargets: [], - dragSource: iText, - dropTarget: undefined, - didDrop: false, - } - ]); - assertDragEventStream('target', eventStream.target, [ - { - e: dragEvents[0], - target: iText2, - type: 'dragenter', - subTargets: [], - dragSource: iText, - dropTarget: undefined, - canDrop: false, - pointer: new fabric.Point(210, 15), - absolutePointer: new fabric.Point(210, 15), - viewportPoint: new fabric.Point(210, 15), - scenePoint: new fabric.Point(210, 15), - previousTarget: undefined - }, - ...dragEvents.slice(0, 6).map(e => ({ - e, - target: iText2, - type: 'dragover', - subTargets: [], - dragSource: iText, - dropTarget: iText2, - canDrop: true - })), - { - e: dragEvents[6], - target: iText2, - type: 'dragleave', - subTargets: [], - dragSource: iText, - dropTarget: undefined, - canDrop: false, - pointer: new fabric.Point(220, -5), - absolutePointer: new fabric.Point(220, -5), - viewportPoint: new fabric.Point(220, -5), - scenePoint: new fabric.Point(220, -5), - nextTarget: undefined - }, - ]); - assert.deepEqual(renderEffects, [ - ...dragEvents.slice(0, 6).map(e => ({ e, source: iText, target: iText2 })), - ...dragEvents.slice(6).map(e => ({ e, source: iText, target: undefined })), - ], 'render effects'); - assert.equal(fabric.getFabricDocument().activeElement, iText.hiddenTextarea, 'should have focused hiddenTextarea'); - }); - - QUnit.test('drag over: canvas', function (assert) { - const e = startDragging(eventData); - const dragEvents = []; - let index; - for (index = 0; index < 10; index++) { - const dragOverEvent = createDragEvent(eventData.clientX + index * canvas.getRetinaScaling()); - canvas._onDragOver(dragOverEvent); - dragEvents.push(dragOverEvent); - } - // canvas._onDrop(dragEvent); - const dragEnd = createDragEvent(eventData.clientX + index * canvas.getRetinaScaling()); - canvas._onDragEnd(dragEnd); - assertDragEventStream('canvas', eventStream.canvas, [ - { e, target: iText, type: 'dragstart' }, - ...dragEvents.map(e => ({ - e, - target: iText, - type: 'dragover', - subTargets: [], - dragSource: iText, - dropTarget: undefined, - canDrop: false - })), - { - e: dragEnd, - target: iText, - type: 'dragend', - subTargets: [], - dragSource: iText, - dropTarget: undefined, - didDrop: false, - } - ]); - assert.equal(fabric.getFabricDocument().activeElement, iText.hiddenTextarea, 'should have focused hiddenTextarea'); - }); - - QUnit.test('drop on drag source', function (assert) { - const e = startDragging(eventData); - const dragEvents = []; - let index; - for (index = 70; index < 80; index = index + 5) { - const dragOverEvent = createDragEvent(eventData.clientX + index * canvas.getRetinaScaling()); - canvas._onDragOver(dragOverEvent); - dragEvents.push(dragOverEvent); - } - const drop = createDragEvent(eventData.clientX + index * canvas.getRetinaScaling(), undefined, { dropEffect: 'move' }); - canvas._onDrop(drop); - canvas._onDragEnd(drop); - assert.equal(iText.text, ' testestt', 'text after drop'); - assert.equal(iText.selectionStart, 4, 'selection after drop'); - assert.equal(iText.selectionEnd, 8, 'selection after drop'); - assertDragEventStream('drop on drag source', eventStream.source, [ - { e, target: iText, type: 'dragstart' }, - { - e: dragEvents[0], - target: iText, - type: 'dragenter', - subTargets: [], - dragSource: iText, - dropTarget: undefined, - canDrop: false, - pointer: new fabric.Point(100, 15), - absolutePointer: new fabric.Point(100, 15), - viewportPoint: new fabric.Point(100, 15), - scenePoint: new fabric.Point(100, 15), - previousTarget: undefined - }, - ...dragEvents.slice(0, 2).map(e => ({ - e, - target: iText, - type: 'dragover', - subTargets: [], - dragSource: iText, - dropTarget: iText, - canDrop: true - })), - { - action: 'drop', - index: 4, - type: 'changed' - }, - { - e: drop, - target: iText, - type: 'drop', - subTargets: [], - dragSource: iText, - dropTarget: iText, - didDrop: true, - pointer: new fabric.Point(110, 15), - absolutePointer: new fabric.Point(110, 15), - viewportPoint: new fabric.Point(110, 15), - scenePoint: new fabric.Point(110, 15), - }, - { - e: drop, - target: iText, - type: 'dragend', - subTargets: [], - dragSource: iText, - dropTarget: iText, - didDrop: true, - } - ]); - assert.equal(fabric.getFabricDocument().activeElement, iText.hiddenTextarea, 'should have focused hiddenTextarea'); - }); - - QUnit.test('drop', function (assert) { - const e = startDragging(eventData); - const dragEvents = []; - let index; - for (index = 200; index < 210; index = index + 5) { - const dragOverEvent = createDragEvent(eventData.clientX + index * canvas.getRetinaScaling()); - canvas._onDragOver(dragOverEvent); - dragEvents.push(dragOverEvent); - } - const drop = createDragEvent(eventData.clientX + index * canvas.getRetinaScaling(), undefined, { dropEffect: 'move' }); - canvas._onDrop(drop); - canvas._onDragEnd(drop); - assert.equal(iText2.text, 'testestt2 test2', 'text after drop'); - assert.equal(iText2.selectionStart, 3, 'selection after drop'); - assert.equal(iText2.selectionEnd, 7, 'selection after drop'); - assertDragEventStream('drop', eventStream.target, [ - { - e: dragEvents[0], - target: iText2, - type: 'dragenter', - subTargets: [], - dragSource: iText, - dropTarget: undefined, - canDrop: false, - pointer: new fabric.Point(230, 15), - absolutePointer: new fabric.Point(230, 15), - viewportPoint: new fabric.Point(230, 15), - scenePoint: new fabric.Point(230, 15), - previousTarget: undefined - }, - ...dragEvents.slice(0, 2).map(e => ({ - e, - target: iText2, - type: 'dragover', - subTargets: [], - dragSource: iText, - dropTarget: iText2, - canDrop: true - })), - { - action: 'drop', - index: 3, - type: 'changed' - }, - { - e: drop, - target: iText2, - type: 'drop', - subTargets: [], - dragSource: iText, - dropTarget: iText2, - didDrop: true, - absolutePointer: new fabric.Point(240, 15), - pointer: new fabric.Point(240, 15), - viewportPoint: new fabric.Point(240, 15), - scenePoint: new fabric.Point(240, 15), - }, - ]); - assert.equal(fabric.getFabricDocument().activeElement, iText2.hiddenTextarea, 'should have focused hiddenTextarea'); - }); - - QUnit.test('disable drop', function (assert) { - iText2.canDrop = () => false; - const e = startDragging(eventData); - const dragEvents = []; - let index; - for (index = 200; index < 210; index = index + 5) { - const dragOverEvent = createDragEvent(eventData.clientX + index * canvas.getRetinaScaling()); - canvas._onDragOver(dragOverEvent); - dragEvents.push(dragOverEvent); - } - const drop = createDragEvent(eventData.clientX + index * canvas.getRetinaScaling(), undefined, { dropEffect: 'none' }); - // the window will not invoke a drop event so we call drag end to simulate correctly - canvas._onDragEnd(drop); - assert.equal(iText2.text, 'test2 test2', 'text after drop'); - assert.equal(iText2.selectionStart, 0, 'selection after drop'); - assert.equal(iText2.selectionEnd, 0, 'selection after drop'); - assertDragEventStream('drop', eventStream.target, [ - { - e: dragEvents[0], - target: iText2, - type: 'dragenter', - subTargets: [], - dragSource: iText, - dropTarget: undefined, - canDrop: false, - pointer: new fabric.Point(230, 15), - absolutePointer: new fabric.Point(230, 15), - viewportPoint: new fabric.Point(230, 15), - scenePoint: new fabric.Point(230, 15), - previousTarget: undefined - }, - ...dragEvents.slice(0, 2).map(e => ({ - e, - target: iText2, - type: 'dragover', - subTargets: [], - dragSource: iText, - dropTarget: undefined, - canDrop: false - })), - ]); - assert.equal(fabric.getFabricDocument().activeElement, iText.hiddenTextarea, 'should have focused hiddenTextarea'); - }); - }); - }); -}); \ No newline at end of file