Skip to content
Snippets Groups Projects
Unverified Commit 955899fe authored by Ryan Laurie's avatar Ryan Laurie Committed by GitHub
Browse files

Fix drag + drop operations on collections pages (#30718)

* add patch to react-dropzone to work for us

* add drag-pin e2e test

* just wrap the event handlers from react-dropzone
parent 1de40475
No related branches found
No related tags found
No related merge requests found
......@@ -37,3 +37,11 @@ export function dragField(startIndex, dropIndex) {
});
});
}
export function dragAndDrop(subjectAlias, targetAlias) {
const dataTransfer = new DataTransfer();
cy.get("@" + subjectAlias).trigger("dragstart", { dataTransfer });
cy.get("@" + targetAlias).trigger("drop", { dataTransfer });
cy.get("@" + subjectAlias).trigger("dragend");
}
import {
popover,
restore,
dragAndDrop,
getPinnedSection,
openPinnedItemMenu,
openUnpinnedItemMenu,
......@@ -208,6 +209,38 @@ describe("scenarios > collection pinned items overview", () => {
cy.findByText("A question").should("be.visible");
});
});
it("should be able to pin a visualization by dragging it up", () => {
cy.request("PUT", "/api/card/2", {
collection_position: 1,
collection_preview: false,
});
openRootCollection();
cy.findByTestId("collection-table")
.findByText("Orders, Count, Grouped by Created At (year)")
.as("draggingViz");
cy.findByTestId("pinned-items").as("pinnedItems");
// this test can give us some degree of confidence, but its effectiveness is limited
// because we are manually firing events on the correct elements. It doesn't seem that there's
// a way to actually simulate the raw user interaction of dragging a certain distance in cypress.
// this will not guarantee that the drag and drop functionality will work in the real world, e.g
// when our various drag + drop libraries start interfering with events on one another.
// for example, this test would not have caught https://github.com/metabase/metabase/issues/30614
// even libraries like https://github.com/dmtrKovalenko/cypress-real-events rely on firing events
// on specific elements rather than truly simulating mouse movements across the screen
dragAndDrop("draggingViz", "pinnedItems");
cy.findByTestId("collection-table")
.findByText("Orders, Count, Grouped by Created At (year)")
.should("not.exist");
cy.findByTestId("pinned-items")
.findByText("Orders, Count, Grouped by Created At (year)")
.should("exist");
});
});
const openRootCollection = () => {
......
......@@ -11,6 +11,7 @@ import {
closeNavigationSidebar,
openCollectionMenu,
visitCollection,
dragAndDrop,
openUnpinnedItemMenu,
getPinnedSection,
} from "e2e/support/helpers";
......@@ -662,14 +663,6 @@ function moveOpenedCollectionTo(newParent) {
modal().should("not.exist");
}
function dragAndDrop(subjectAlias, targetAlias) {
const dataTransfer = new DataTransfer();
cy.get("@" + subjectAlias).trigger("dragstart", { dataTransfer });
cy.get("@" + targetAlias).trigger("drop", { dataTransfer });
cy.get("@" + subjectAlias).trigger("dragend");
}
function moveItemToCollection(itemName, collectionName) {
cy.request("GET", "/api/collection/root/items").then(resp => {
const ALL_ITEMS = resp.body.data;
......
......@@ -32,6 +32,7 @@ import { isSmallScreen } from "metabase/lib/dom";
import Databases from "metabase/entities/databases";
import UploadOverlay from "../components/UploadOverlay";
import { getComposedDragProps } from "./utils";
import {
CollectionEmptyContent,
......@@ -201,7 +202,7 @@ function CollectionContent({
const canUpload = uploadsEnabled && collection.can_write;
const dropzoneProps = canUpload ? getRootProps() : {};
const dropzoneProps = canUpload ? getComposedDragProps(getRootProps()) : {};
const unpinnedQuery = {
collection: collectionId,
......
import type { DropzoneRootProps } from "react-dropzone";
import type { DragEventHandler, DragEvent } from "react";
export const composeFileEventHandler =
(fn: DragEventHandler<HTMLElement> | undefined) =>
(event: DragEvent<HTMLElement>) => {
if (!event?.dataTransfer?.types.includes("Files")) {
return;
}
fn?.(event);
};
export const getComposedDragProps = (
props: DropzoneRootProps,
): DropzoneRootProps => {
return {
...props,
onDragEnter: composeFileEventHandler(props.onDragEnter),
onDragLeave: composeFileEventHandler(props.onDragLeave),
onDragOver: composeFileEventHandler(props.onDragOver),
onDrop: composeFileEventHandler(props.onDrop),
};
};
import type { DropzoneRootProps } from "react-dropzone";
import type { DragEvent } from "react";
import { getComposedDragProps, composeFileEventHandler } from "./utils";
describe("Collections > containers > utils", () => {
const testNonFileEvent = {
dataTransfer: {
types: ["text/plain"],
},
} as unknown as DragEvent<HTMLElement>;
const testFileEvent = {
dataTransfer: {
types: ["Files"],
},
} as unknown as DragEvent<HTMLElement>;
describe("getComposedDragProps", () => {
it("should compose all drag event handlers to ignore non-file events", () => {
const dragEventSpy = jest.fn();
const nonDragEventSpy = jest.fn();
const mockProps = {
onDragEnter: dragEventSpy,
onDragLeave: dragEventSpy,
onDragOver: dragEventSpy,
onDrop: dragEventSpy,
onClick: nonDragEventSpy,
} as DropzoneRootProps;
const composedProps = getComposedDragProps(mockProps);
composedProps.onDragEnter?.(testNonFileEvent);
composedProps.onDragLeave?.(testNonFileEvent);
composedProps.onDragOver?.(testNonFileEvent);
composedProps.onDrop?.(testNonFileEvent);
// this non-drag handler should not get composed
composedProps.onClick?.(testNonFileEvent);
expect(dragEventSpy).not.toHaveBeenCalled();
expect(nonDragEventSpy).toHaveBeenCalledTimes(1);
});
});
describe("composeFileEventHandler", () => {
it("should return a function that ignores non-file drag events", () => {
const testFn = jest.fn();
const composedFn = composeFileEventHandler(testFn);
composedFn(testNonFileEvent);
expect(testFn).not.toHaveBeenCalled();
composedFn(testFileEvent);
expect(testFn).toHaveBeenCalledTimes(1);
});
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment