import { fireEvent, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { Draggable } from './Draggable'; describe('Draggable', () => { beforeEach(() => { vi.clearAllMocks(); }); it('renders children correctly', () => { render(
Drag me
, ); expect(screen.getByTestId('ockDraggable')).toBeInTheDocument(); }); it('starts at the default position if no starting position is provided', () => { render(
Drag me
, ); const draggable = screen.getByTestId('ockDraggable'); expect(draggable).toHaveStyle({ left: '20px', top: '20px' }); }); it('starts at the specified position if starting position is provided', () => { render(
Drag me
, ); const draggable = screen.getByTestId('ockDraggable'); expect(draggable).toHaveStyle({ left: '100px', top: '100px' }); }); it('has correct cursor styles', () => { render(
Drag me
, ); const draggable = screen.getByTestId('ockDraggable'); expect(draggable).toHaveClass('cursor-grab active:cursor-grabbing'); }); it('snaps to grid when dragging ends if enableSnapToGrid is true', async () => { const user = userEvent.setup(); render(
Drag me
, ); const draggable = screen.getByTestId('ockDraggable'); await user.pointer([ { keys: '[MouseLeft>]', target: draggable }, { coords: { x: 14, y: 16 } }, { keys: '[/MouseLeft]' }, ]); expect(draggable).toHaveStyle({ left: '10px', top: '20px' }); }); it('does not snap to grid when dragging ends if enableSnapToGrid is false', async () => { const user = userEvent.setup(); render(
Drag me
, ); const draggable = screen.getByTestId('ockDraggable'); await user.pointer([ { keys: '[MouseLeft>]', target: draggable }, { coords: { x: 14, y: 16 } }, { keys: '[/MouseLeft]' }, ]); expect(draggable).toHaveStyle({ left: '14px', top: '16px' }); }); it('calculates drag offset correctly', async () => { const user = userEvent.setup(); render(
Drag me
, ); const draggable = screen.getByTestId('ockDraggable'); await user.pointer([ { keys: '[MouseLeft>]', target: draggable, coords: { x: 60, y: 70 } }, { coords: { x: 80, y: 90 } }, { keys: '[/MouseLeft]' }, ]); expect(draggable).toHaveStyle({ left: '70px', top: '70px' }); }); it('updates position during drag movement', async () => { const user = userEvent.setup(); render(
Drag me
, ); const draggable = screen.getByTestId('ockDraggable'); await user.pointer([ { keys: '[MouseLeft>]', target: draggable, coords: { x: 0, y: 0 } }, { coords: { x: 50, y: 50 } }, ]); expect(draggable).toHaveStyle({ left: '50px', top: '50px' }); await user.pointer([{ coords: { x: 100, y: 75 } }]); expect(draggable).toHaveStyle({ left: '100px', top: '75px' }); }); it('prevents click after dragging but allows normal clicks', async () => { const onClick = vi.fn(); const user = userEvent.setup(); render( , ); const clickable = screen.getByTestId('clickable'); await user.pointer([ { keys: '[MouseLeft>]', target: clickable, coords: { x: 0, y: 0 } }, { coords: { x: 50, y: 50 } }, { keys: '[/MouseLeft]' }, ]); expect(onClick).not.toHaveBeenCalled(); await user.pointer([ { keys: '[MouseLeft>]', target: clickable, coords: { x: 0, y: 0 } }, { keys: '[/MouseLeft]' }, ]); expect(onClick).toHaveBeenCalled(); }); it('cleans up event listeners when unmounted during drag', () => { const { unmount } = render(
Drag me
, ); const draggable = screen.getByTestId('ockDraggable'); const removeSpy = vi.spyOn(document, 'removeEventListener'); fireEvent.pointerDown(draggable, { clientX: 0, clientY: 0 }); unmount(); expect(removeSpy).toHaveBeenCalledWith('pointermove', expect.any(Function)); expect(removeSpy).toHaveBeenCalledWith('pointerup', expect.any(Function)); }); it('disables dragging and sets cursor display to default when disabled is true', async () => { const user = userEvent.setup(); render(
Drag me
, ); const draggable = screen.getByTestId('ockDraggable'); // Attempt to drag await user.pointer([ { keys: '[MouseLeft>]', target: draggable, coords: { x: 0, y: 0 } }, { coords: { x: 50, y: 50 } }, { keys: '[/MouseLeft]' }, ]); // Position should not change expect(draggable).toHaveStyle({ left: '0px', top: '0px' }); }); });