import { size } from '@planview/pv-utilities' import { calculateHitAreas } from './calculate-hit-areas' import type { GridRowId, GridRowMeta } from '../types' type StructuredItem = { id: string expanded?: boolean children?: StructuredItem[] } function prepareGridData(items: StructuredItem[]) { const rootIds: GridRowId[] = [] const rowIds: GridRowId[] = [] const entities = new Map() const meta = new Map>() const levelMap = new Map() const collectItem = ( item: StructuredItem, level: number, isExpanded = true ) => { entities.set(item.id, item) if (level) { // Omitting 0 level items to cover a different code path levelMap.set(item.id, level) } if (isExpanded) { rowIds.push(item.id) } if (item.children?.length) { meta.set(item.id, { type: 'tree', children: item.children.map((childItem) => childItem.id), }) item.children.forEach((childItem) => collectItem(childItem, level + 1, item.expanded) ) } } items.forEach((item) => { collectItem(item, 0) rootIds.push(item.id) }) return { rowIds, rowCollection: { ids: rootIds, entities, meta }, levelMap } } describe('calculateHitAreas', () => { let result: ReturnType describe('flat grid', () => { beforeEach(() => { const { rowIds, rowCollection } = prepareGridData([ { id: '1' }, { id: '2' }, { id: '3' }, ]) result = calculateHitAreas({ isTreeGrid: false, rowHeight: size.medium, treeIndentSize: 'large', canConvertLeaf: false, draggingRowIds: new Set(['1']), mode: 'default', previewColumnOffset: size.small, rowIds, rowCollection, }) }) it('should generate hit areas between each row', () => { expect(result.collisions).toHaveLength(4) expect( result.collisions.filter((c) => c.operation === 'between') ).toHaveLength(4) expect(result.maxBottom).toEqual(161) }) it('should generate correctly', () => { expect(result.collisions).toMatchSnapshot() }) }) describe('tree grid', () => { const setup = prepareGridData([ { id: '1', children: [{ id: '11' }, { id: '12' }], }, { id: '2', children: [ { id: '21' }, { id: '22', children: [{ id: '222' }], }, ], }, { id: '3', }, ]) describe('no leaf conversion', () => { beforeEach(() => { result = calculateHitAreas({ isTreeGrid: true, rowHeight: size.medium, treeIndentSize: 'large', canConvertLeaf: false, draggingRowIds: new Set(['3']), mode: 'default', previewColumnOffset: size.small, ...setup, }) }) it('should generate hit areas between each row', () => { expect( result.collisions.filter((c) => c.operation === 'between') ).toHaveLength(12) }) it('should generate hit areas over each parent', () => { expect( result.collisions.filter((c) => c.operation === 'over') ).toHaveLength(3) }) it('should not generate hit areas over a selected parent', () => { const localResult = calculateHitAreas({ isTreeGrid: true, rowHeight: size.medium, treeIndentSize: 'large', canConvertLeaf: false, draggingRowIds: new Set(['2']), mode: 'default', previewColumnOffset: size.small, ...setup, rowIds: ['1', '11', '12', '2', '3'], }) expect( localResult.collisions.find((c) => c.parentId === '2') ).toBeUndefined() }) it('should generate multiple hit areas at seams', () => { expect( result.collisions.filter((c) => c.prevId === '222') ).toHaveLength(3) }) it('should generate correctly', () => { expect(result.collisions).toMatchSnapshot() }) }) describe('with leaf conversion', () => { beforeEach(() => { const { entities } = setup.rowCollection const makeOneLoading = new Map(entities) makeOneLoading.delete('11') result = calculateHitAreas({ isTreeGrid: true, rowHeight: size.medium, treeIndentSize: 'large', canConvertLeaf: true, draggingRowIds: new Set(['3']), mode: 'default', previewColumnOffset: size.small, ...setup, rowCollection: { ...setup.rowCollection, entities: makeOneLoading, }, }) }) it('should generate hit areas over each leaf item that is not loading', () => { expect( result.collisions.filter( (c) => c.operation === 'over' && ['11', '12', '21'].includes(c.parentId as string) ) ).toHaveLength(2) }) it('should generate correctly', () => { expect(result.collisions).toMatchSnapshot() }) }) }) describe('tree grid (no ordering)', () => { const setup = prepareGridData([ { id: '1', children: [{ id: '11' }, { id: '12' }], }, { id: '2', children: [ { id: '21' }, { id: '22', expanded: false, children: [{ id: '222' }], }, ], }, { id: '3', }, ]) describe('no leaf conversion', () => { beforeEach(() => { result = calculateHitAreas({ isTreeGrid: true, rowHeight: size.medium, treeIndentSize: 'large', canConvertLeaf: false, draggingRowIds: new Set(['3']), mode: 'parent', previewColumnOffset: size.small, ...setup, }) }) it('should generate hit areas over each parent', () => { expect(result.collisions).toHaveLength(4) expect( result.collisions.filter((c) => c.operation === 'between') ).toHaveLength(0) }) it('should generate a root level hit area', () => { expect(result.collisions[3]).toHaveProperty('top', -50) expect(result.collisions[3]).toHaveProperty('bottom', 372) }) it('should not provide a prevInsertId for any of the areas', () => { expect( result.collisions.every((c) => c.prevInsertId === null) ).toBe(true) }) it('should sort nested areas before their parent areas', () => { expect( result.collisions.findIndex((c) => c.parentId === '22') ).toBeLessThan( result.collisions.findIndex((c) => c.parentId === '2') ) }) it('should generate correctly', () => { expect(result.collisions).toMatchSnapshot() }) }) describe('with leaf conversion', () => { beforeEach(() => { result = calculateHitAreas({ isTreeGrid: true, rowHeight: size.medium, treeIndentSize: 'large', canConvertLeaf: true, draggingRowIds: new Set(['3']), mode: 'parent', previewColumnOffset: size.small, ...setup, }) }) it('should generate hit areas over each parent and leaf item', () => { expect(result.collisions).toHaveLength(7) expect( result.collisions.filter((c) => c.operation === 'between') ).toHaveLength(0) }) it('should generate a root level hit area', () => { expect(result.collisions[6]).toHaveProperty('top', -50) expect(result.collisions[6]).toHaveProperty('bottom', 372) }) it('should generate correctly', () => { expect(result.collisions).toMatchSnapshot() }) }) }) })