From 4889a0047169b23acc42dfbff0e1cd96df3050ec Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Sat, 17 Oct 2026 18:17:33 +0200 Subject: [PATCH 1/2] test: Add drag and resize tests --- src/lib/tests/DragAndResize.test.js | 170 ++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 src/lib/tests/DragAndResize.test.js diff --git a/src/lib/tests/DragAndResize.test.js b/src/lib/tests/DragAndResize.test.js new file mode 100644 index 0000000000..1243dec5a6 --- /dev/null +++ b/src/lib/tests/DragAndResize.test.js @@ -0,0 +1,170 @@ +/** + * @jest-environment jsdom + */ +/* + * Copyright (c) 2016-present, Parse, LLC + * All rights reserved. + * + * This source code is licensed under the license found in the LICENSE file in + * the root directory of this source tree. + */ +import React, { act } from 'react'; +import { createRoot } from 'react-dom/client'; +import { ResizableBox } from 'react-resizable'; +import CanvasElement from '../../dashboard/Data/CustomDashboard/CanvasElement.react'; + +// The dashboard drags and resizes with react-draggable, through react-rnd +// (custom dashboard canvas elements) and react-resizable (data browser panels). +// These tests render with react-dom and dispatch native mouse events, because +// react-draggable listens on the document for the mouse moves of a drag. +global.IS_REACT_ACT_ENVIRONMENT = true; + +let container; +let root; + +beforeEach(() => { + container = document.createElement('div'); + document.body.appendChild(container); + root = createRoot(container); +}); + +afterEach(() => { + act(() => { + root.unmount(); + }); + container.remove(); +}); + +function render(element) { + act(() => { + root.render(element); + }); +} + +// Dispatch a native mouse event with the primary button +function dispatchMouseEvent(target, type, { x, y }) { + act(() => { + target.dispatchEvent( + new MouseEvent(type, { bubbles: true, cancelable: true, button: 0, clientX: x, clientY: y }) + ); + }); +} + +// A drag starts with a mousedown on the dragged node, then react-draggable +// tracks mousemove and mouseup on the document +function drag(target, from, to) { + dispatchMouseEvent(target, 'mousedown', from); + dispatchMouseEvent(document, 'mousemove', to); + dispatchMouseEvent(document, 'mouseup', to); +} + +describe('Canvas element drag', () => { + const element = { id: 'element-1', x: 100, y: 50, width: 300, height: 200 }; + + function renderCanvasElement() { + const props = { + onSelect: jest.fn(), + onPositionChange: jest.fn(), + onSizeChange: jest.fn(), + onDrag: jest.fn(), + onResize: jest.fn(), + }; + render( + +
Content
+
+ ); + return props; + } + + it('moves the element when dragged by the drag handle', () => { + const { onDrag, onPositionChange } = renderCanvasElement(); + drag(container.querySelector('.dragHandle'), { x: 10, y: 10 }, { x: 110, y: 60 }); + expect(onDrag).toHaveBeenCalledWith('element-1', 200, 100, 300, 200); + expect(onPositionChange).toHaveBeenCalledTimes(1); + expect(onPositionChange).toHaveBeenCalledWith('element-1', 200, 100); + }); + + it('snaps the position to the 50px drag grid', () => { + const { onPositionChange } = renderCanvasElement(); + // Moving 120px right and 20px down snaps to 100px right and 0px down + drag(container.querySelector('.dragHandle'), { x: 10, y: 10 }, { x: 130, y: 30 }); + expect(onPositionChange).toHaveBeenCalledWith('element-1', 200, 50); + }); + + it('does not move the element when dragged by its content', () => { + const { onDrag, onPositionChange } = renderCanvasElement(); + drag(container.querySelector('.elementContent'), { x: 10, y: 10 }, { x: 110, y: 60 }); + expect(onDrag).not.toHaveBeenCalled(); + expect(onPositionChange).not.toHaveBeenCalled(); + }); +}); + +describe('Data browser panel resize', () => { + // Same ResizableBox configuration as the aggregation and graph panels in + // DataBrowser.react.js: a west handle and a width between a min and a max + function renderPanel() { + const props = { + onResizeStart: jest.fn(), + onResize: jest.fn(), + onResizeStop: jest.fn(), + }; + render( + +
Panel
+
+ ); + return props; + } + + function dragWestHandle(fromX, toX) { + drag( + container.querySelector('.react-resizable-handle-w'), + { x: fromX, y: 10 }, + { x: toX, y: 10 } + ); + } + + // Only the width is checked, because the panels have no finite height + function lastWidth(callback) { + return callback.mock.lastCall[1].size.width; + } + + it('widens the panel when the west handle is dragged left', () => { + const { onResizeStart, onResize, onResizeStop } = renderPanel(); + dragWestHandle(400, 350); + expect(onResizeStart).toHaveBeenCalledTimes(1); + expect(lastWidth(onResize)).toBe(350); + expect(onResizeStop).toHaveBeenCalledTimes(1); + expect(lastWidth(onResizeStop)).toBe(350); + expect(container.querySelector('.react-resizable').style.width).toBe('350px'); + }); + + it('narrows the panel when the west handle is dragged right', () => { + const { onResize, onResizeStop } = renderPanel(); + dragWestHandle(400, 450); + expect(lastWidth(onResize)).toBe(250); + expect(lastWidth(onResizeStop)).toBe(250); + }); + + it('limits the width to the maximum', () => { + const { onResize, onResizeStop } = renderPanel(); + dragWestHandle(400, 100); + expect(lastWidth(onResize)).toBe(500); + expect(lastWidth(onResizeStop)).toBe(500); + }); + + it('limits the width to the minimum', () => { + const { onResize, onResizeStop } = renderPanel(); + dragWestHandle(400, 650); + expect(lastWidth(onResize)).toBe(100); + expect(lastWidth(onResizeStop)).toBe(100); + }); +}); From 0660630692941af7a76cf9dda5e6a7daddacc0b0 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Sat, 17 Oct 2026 18:18:20 +0200 Subject: [PATCH 2/2] refactor: Bump react-draggable from 4.7.0 to 4.7.2 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index e08f952e83..51ce83b0fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,7 +48,7 @@ "react-dnd": "16.0.1", "react-dnd-html5-backend": "16.0.1", "react-dom": "19.2.7", - "react-draggable": "4.7.0", + "react-draggable": "4.7.2", "react-helmet": "6.1.0", "react-popper-tooltip": "4.4.2", "react-resizable": "4.0.2", @@ -24874,9 +24874,9 @@ } }, "node_modules/react-draggable": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.7.0.tgz", - "integrity": "sha512-kTpANmKWVnFXiZ76Ag2ZowiFStuBYnJ606PI1TbUsOg29/400/JNIxI9+CuenhiAqFuXWJffz6F4UI3R51kUug==", + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.7.2.tgz", + "integrity": "sha512-pmqDx72EqhMGoJe/x42ZO2wJ9TIz7qJ5OPnFu6lVWWznEuJumlSjR+J0V4tNyDFhUo6dVvv56FeiTqN+arTgnw==", "license": "MIT", "dependencies": { "clsx": "^2.1.1", diff --git a/package.json b/package.json index 706ec02904..71779db361 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "react-dnd": "16.0.1", "react-dnd-html5-backend": "16.0.1", "react-dom": "19.2.7", - "react-draggable": "4.7.0", + "react-draggable": "4.7.2", "react-helmet": "6.1.0", "react-popper-tooltip": "4.4.2", "react-resizable": "4.0.2",