import { getByTestId, render } from '@testing-library/react'; import useFlowStore from '../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores'; import VisProgPage from '../../../../../src/pages/VisProgPage/VisProg'; import userEvent from '@testing-library/user-event'; class ResizeObserver { observe() {} unobserve() {} disconnect() {} } window.ResizeObserver = ResizeObserver; jest.mock('@neodrag/react', () => ({ useDraggable: (ref: React.RefObject, options: any) => { // We access the real useEffect from React to attach a listener // This bridges the gap between the test's userEvent and the component's logic const { useEffect } = jest.requireActual('react'); useEffect(() => { const element = ref.current; if (!element) return; // When the test fires a "pointerup" (end of click/drag), // we manually trigger the library's onDragEnd callback. const handlePointerUp = (e: PointerEvent) => { if (options.onDragEnd) { options.onDragEnd({ event: e }); } }; element.addEventListener('pointerup', handlePointerUp as EventListener); return () => { element.removeEventListener('pointerup', handlePointerUp as EventListener); }; }, [ref, options]); }, })); // We will mock @xyflow/react so we control screenToFlowPosition jest.mock('@xyflow/react', () => { const actual = jest.requireActual('@xyflow/react'); return { ...actual, useReactFlow: () => ({ screenToFlowPosition: ({ x, y }: { x: number; y: number }) => ({ x: x - 100, y: y - 100, }), }), }; }); // Reset Zustand state helper function resetStore() { useFlowStore.setState({ nodes: [], edges: [] }); } describe("Drag & drop node creation", () => { beforeEach(() => resetStore()); test("drops a phase node inside the canvas and adds it with transformed position", async () => { const user = userEvent.setup(); const { container } = render(); // --- Mock ReactFlow bounding box --- // Your DndToolbar checks these values: const flowEl = container.querySelector('.react-flow'); jest.spyOn(flowEl!, 'getBoundingClientRect').mockReturnValue({ left: 0, right: 800, top: 0, bottom: 600, width: 800, height: 600, x: 0, y: 0, toJSON: () => {}, }); const phaseLabel = getByTestId(container, 'draggable-phase') await user.pointer([ // touch the screen at element1 {keys: '[TouchA>]', target: phaseLabel}, // move the touch pointer to element2 {pointerName: 'TouchA', coords: {x: 300, y: 250}}, // release the touch pointer at the last position (element2) {keys: '[/TouchA]'}, ]); // Read the Zustand store const { nodes } = useFlowStore.getState(); // --- Assertions --- expect(nodes.length).toBe(1); const node = nodes[0]; expect(node.type).toBe("phase"); expect(node.id).toBe("phase-1"); // screenToFlowPosition was mocked to subtract 100 expect(node.position).toEqual({ x: 200, y: 150, }); }); });