Files
superset2/superset-frontend/src/explore/components/controls/DndColumnSelectControl/dndTestUtils.ts
2026-06-07 20:00:03 -07:00

121 lines
4.1 KiB
TypeScript

/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import {
DroppableData,
resolveDragEnd,
} from 'src/explore/components/ExploreContainer/ExploreDndContext';
import { DndItemType } from 'src/explore/components/DndItemType';
import { DndItemValue } from 'src/explore/components/DatasourcePanel/types';
/**
* @dnd-kit's PointerSensor only reacts to real pointer events, which jsdom
* cannot meaningfully dispatch (it has no layout). To exercise drop behavior in
* unit tests we capture the `data` object a control registers via
* `useDroppable` and feed it through the same `resolveDragEnd` dispatcher the
* live `ExploreDndContextProvider` runs on drag end.
*
* Usage: spy on `useDroppable` with `captureDroppableData`, render the control,
* then call `simulateDrop` with the dragged item.
*/
export type CapturedDroppable = { current: DroppableData | undefined };
/**
* Returns a `jest.fn` mock implementation for `@dnd-kit/core`'s `useDroppable`
* that records the most recently registered droppable data into `captured`,
* while returning an inert droppable shape so the control still renders.
*/
export function captureDroppableData(captured: CapturedDroppable) {
return (args: { data?: DroppableData }) => {
captured.current = args?.data;
return {
setNodeRef: () => {},
isOver: false,
active: null,
rect: { current: null },
node: { current: null },
over: null,
};
};
}
/**
* Drives a single drag-and-drop of `item` onto the captured droppable through
* the production `resolveDragEnd` dispatcher.
*/
export function simulateDrop(
captured: CapturedDroppable,
item: { type: DndItemType; value: DndItemValue },
) {
resolveDragEnd(
{ id: 'drag-source', data: { current: item } },
{ id: 'dropzone', data: { current: captured.current ?? {} } },
);
}
export type SortableItemData = {
type: string;
dragIndex: number;
onShiftOptions?: (dragIndex: number, hoverIndex: number) => void;
onMoveLabel?: (dragIndex: number, hoverIndex: number) => void;
onDropLabel?: () => void;
};
export type CapturedSortables = { items: SortableItemData[] };
/**
* Returns a `jest.fn` implementation for `@dnd-kit/sortable`'s `useSortable`
* that records each sortable item's registered data (carrying the reorder
* callbacks) into `captured`, while returning an inert sortable shape so the
* control still renders.
*/
export function captureSortableData(captured: CapturedSortables) {
return (args: { data?: SortableItemData }) => {
if (args?.data) {
captured.items.push(args.data);
}
return {
attributes: {},
listeners: {},
setNodeRef: () => {},
transform: null,
transition: undefined,
isDragging: false,
setActivatorNodeRef: () => {},
};
};
}
/**
* Drives an in-list reorder (drag item at `fromIndex` over item at `toIndex`)
* through the production `resolveDragEnd` dispatcher, using the reorder
* callbacks the control registered on its sortable items.
*/
export function simulateReorder(
captured: CapturedSortables,
fromIndex: number,
toIndex: number,
) {
const from = captured.items.find(i => i.dragIndex === fromIndex);
const to = captured.items.find(i => i.dragIndex === toIndex);
resolveDragEnd(
{ id: `from-${fromIndex}`, data: { current: from } },
{ id: `to-${toIndex}`, data: { current: to } },
);
}