import type { ClientRect, CollisionDetection, DroppableContainer, UniqueIdentifier, } from '@dnd-kit/core' import type { Store } from '../../state' import { createStore } from '../../state' import type { CollisionDetails } from './collision' import { collisionFactory } from './collision' import { DROPPABLE_TYPE_ROW_GROUP } from '../../constants' describe('collisionFactory', () => { let store: Store, collisionHandler: ReturnType beforeEach(() => { store = createStore() store.dispatch = jest.fn() jest.spyOn(store.selectors, 'selectDragMode') jest.spyOn(store.selectors, 'selectDraggingRowIds') jest.spyOn(store.selectors, 'selectCollisionAreas') jest.spyOn(store.selectors, 'selectRowIds') jest.spyOn(store.selectors, 'selectRowLevel') collisionHandler = collisionFactory(store) }) describe('basic', () => { let collisionParams: Parameters[0] beforeEach(() => { jest.mocked(store.selectors.selectDragMode).mockReturnValue( 'default' ) jest.mocked(store.selectors.selectDraggingRowIds).mockReturnValue( new Set() ) jest.mocked(store.selectors.selectCollisionAreas).mockReturnValue({ collisions: [ { top: -6, left: 0, bottom: 6, operation: 'between', level: 0, parentId: null, prevId: null, prevInsertId: null, }, { top: 6, left: 0, bottom: 40, operation: 'over', level: 0, parentId: '1', prevId: null, prevInsertId: null, }, { top: 40, left: 0, bottom: 52, operation: 'between', level: 0, parentId: null, prevId: '1', prevInsertId: '1', }, ], maxBottom: 52, maxTop: -6, }) collisionParams = { active: {} as any, collisionRect: { top: 0, left: 0, bottom: 0, right: 0, width: 0, height: 0, }, droppableContainers: [ { id: DROPPABLE_TYPE_ROW_GROUP, } as DroppableContainer, ], droppableRects: new Map([ [ DROPPABLE_TYPE_ROW_GROUP, { top: 100, left: 100, right: 500, bottom: 400, height: 300, width: 400, }, ], ]), pointerCoordinates: null, } }) it('should find the first collision when droppable is above row', () => { const result = collisionHandler({ ...collisionParams, collisionRect: { top: 100, left: 200, bottom: 146, right: 300, width: 100, height: 46, }, }) expect(result).toEqual([ { data: { level: 0, parentId: '1', prevId: null, prevInsertId: null, operation: 'over', } satisfies CollisionDetails, id: DROPPABLE_TYPE_ROW_GROUP, }, ]) }) it('should find the correct collision when over the list', () => { const result = collisionHandler({ ...collisionParams, collisionRect: { top: 550, left: 200, bottom: 96, right: 300, width: 100, height: 46, }, }) expect(result).toEqual([ { data: { level: 0, parentId: null, prevId: '1', prevInsertId: '1', operation: 'between', } satisfies CollisionDetails, id: DROPPABLE_TYPE_ROW_GROUP, }, ]) }) it('should find the last collision when droppable is below list', () => { const result = collisionHandler({ ...collisionParams, collisionRect: { top: 550, left: 200, bottom: 96, right: 300, width: 100, height: 46, }, }) expect(result).toEqual([ { data: { level: 0, parentId: null, prevId: '1', prevInsertId: '1', operation: 'between', } satisfies CollisionDetails, id: DROPPABLE_TYPE_ROW_GROUP, }, ]) }) }) describe('tree', () => { let collisionParams: Parameters[0] beforeEach(() => { jest.mocked(store.selectors.selectDragMode).mockReturnValue( 'default' ) jest.mocked(store.selectors.selectDraggingRowIds).mockReturnValue( new Set() ) jest.mocked(store.selectors.selectCollisionAreas).mockReturnValue({ collisions: [ { top: -6, left: 0, bottom: 6, operation: 'between', level: 0, parentId: null, prevId: null, prevInsertId: null, }, { top: 6, left: 0, bottom: 40, operation: 'over', level: 0, parentId: '1', prevId: null, prevInsertId: null, }, { top: 40, left: 0, bottom: 69, operation: 'between', level: 1, parentId: null, prevId: '1', prevInsertId: null, }, { top: 69, left: 0, bottom: 115, operation: 'between', level: 1, parentId: '1', prevId: '2', prevInsertId: '2', }, { top: 115, left: 73, bottom: 144, operation: 'between', level: 1, parentId: '1', prevId: '3', prevInsertId: '3', }, { top: 115, left: 0, bottom: 144, operation: 'between', level: 0, parentId: null, prevId: '3', prevInsertId: '1', }, { top: 144, left: 0, bottom: 178, operation: 'over', level: 0, parentId: '4', prevId: '3', prevInsertId: null, }, { top: 178, left: 0, bottom: 190, operation: 'between', level: 0, parentId: null, prevId: '4', prevInsertId: '4', }, ], maxBottom: 190, maxTop: -6, }) collisionParams = { active: {} as any, collisionRect: { top: 0, left: 0, bottom: 0, right: 0, width: 0, height: 0, }, droppableContainers: [ { id: DROPPABLE_TYPE_ROW_GROUP, } as DroppableContainer, ], droppableRects: new Map([ [ DROPPABLE_TYPE_ROW_GROUP, { top: 100, left: 100, right: 500, bottom: 400, height: 300, width: 400, }, ], ]), pointerCoordinates: null, } }) it('should report the correct location of insert', () => { const result = collisionHandler({ ...collisionParams, collisionRect: { top: 200, left: 200, bottom: 246, right: 300, width: 100, height: 46, }, }) expect(result).toEqual([ { data: { level: 1, parentId: '1', prevId: '3', prevInsertId: '3', operation: 'between', } satisfies CollisionDetails, id: DROPPABLE_TYPE_ROW_GROUP, }, ]) }) it('should take selected status into account when determining the prevInsertId', () => { jest.mocked(store.selectors.selectRowIds).mockReturnValue([ '1', '2', '3', '4', ]) jest.mocked(store.selectors.selectRowLevel).mockImplementation( (_, rowId) => { if (!rowId || rowId === '1' || rowId === '4') { return 0 } return 1 } ) jest.mocked(store.selectors.selectDraggingRowIds).mockReturnValue( new Set('3') ) const result = collisionHandler({ ...collisionParams, collisionRect: { top: 200, left: 200, bottom: 246, right: 300, width: 100, height: 46, }, }) expect(result).toEqual([ { data: { level: 1, parentId: '1', prevId: '3', prevInsertId: '2', operation: 'between', } satisfies CollisionDetails, id: DROPPABLE_TYPE_ROW_GROUP, }, ]) }) it('should stop traversing when a parent if found when looking for prevInsertId', () => { jest.mocked(store.selectors.selectRowIds).mockReturnValue([ '1', '2', '3', '4', ]) jest.mocked(store.selectors.selectRowLevel).mockImplementation( (_, rowId) => { if (!rowId || rowId === '1' || rowId === '4') { return 0 } return 1 } ) jest.mocked(store.selectors.selectDraggingRowIds).mockReturnValue( new Set(['2', '3']) ) const result = collisionHandler({ ...collisionParams, collisionRect: { top: 200, left: 200, bottom: 246, right: 300, width: 100, height: 46, }, }) expect(result).toEqual([ { data: { level: 1, parentId: '1', prevId: '3', prevInsertId: null, operation: 'between', } satisfies CollisionDetails, id: DROPPABLE_TYPE_ROW_GROUP, }, ]) }) it('should use the x position to determine which seam to drop at', () => { const result = collisionHandler({ ...collisionParams, collisionRect: { top: 200, left: 100, bottom: 246, right: 300, width: 100, height: 46, }, }) expect(result).toEqual([ { data: { level: 0, parentId: null, prevId: '3', prevInsertId: '1', operation: 'between', } satisfies CollisionDetails, id: DROPPABLE_TYPE_ROW_GROUP, }, ]) }) }) describe('parent mode', () => { let collisionParams: Parameters[0] beforeEach(() => { jest.mocked(store.selectors.selectDragMode).mockReturnValue( 'parent' ) jest.mocked(store.selectors.selectDraggingRowIds).mockReturnValue( new Set() ) jest.mocked(store.selectors.selectCollisionAreas).mockReturnValue({ collisions: [ { top: 46, left: 72, bottom: 92, operation: 'over', level: 1, parentId: '2', prevId: null, prevInsertId: null, }, { top: 0, left: 36, bottom: 230, operation: 'over', level: 0, parentId: '1', prevId: null, prevInsertId: null, }, { top: -50, left: 0, bottom: 280, operation: 'over', level: 0, parentId: null, prevId: null, prevInsertId: null, }, ], maxBottom: 280, maxTop: -50, }) collisionParams = { active: {} as any, collisionRect: { top: 0, left: 0, bottom: 0, right: 0, width: 0, height: 0, }, droppableContainers: [ { id: DROPPABLE_TYPE_ROW_GROUP, } as DroppableContainer, ], droppableRects: new Map([ [ DROPPABLE_TYPE_ROW_GROUP, { top: 100, left: 100, right: 500, bottom: 400, height: 300, width: 400, }, ], ]), pointerCoordinates: null, } }) it('should use the x position to determine which parent to drop on (deep parent)', () => { const result = collisionHandler({ ...collisionParams, collisionRect: { top: 130, left: 200, bottom: 176, right: 300, width: 100, height: 46, }, }) expect(result).toEqual([ { data: { level: 1, parentId: '2', prevId: null, prevInsertId: null, operation: 'over', } satisfies CollisionDetails, id: DROPPABLE_TYPE_ROW_GROUP, }, ]) }) it('should use the x position to determine which parent to drop on (shallow parent)', () => { const result = collisionHandler({ ...collisionParams, collisionRect: { top: 130, left: 160, bottom: 176, right: 300, width: 100, height: 46, }, }) expect(result).toEqual([ { data: { level: 0, parentId: '1', prevId: null, prevInsertId: null, operation: 'over', } satisfies CollisionDetails, id: DROPPABLE_TYPE_ROW_GROUP, }, ]) }) it('should report dropping on the root when mode is parent', () => { const result = collisionHandler({ ...collisionParams, collisionRect: { top: 130, left: 100, bottom: 96, right: 300, width: 100, height: 46, }, }) expect(result).toEqual([ { data: { level: 0, parentId: null, prevId: null, prevInsertId: null, operation: 'over', } satisfies CollisionDetails, id: DROPPABLE_TYPE_ROW_GROUP, }, ]) }) }) })