import { isRangeEqual, getIdsFromRange, healSpannedRange } from './range' import type { GridRangeSelection } from '../types' import type { Store } from '../state' import { createStore } from '../state' describe('range utils', () => { describe('isRangeEqual', () => { it('should return true if both ranges are null', () => { expect(isRangeEqual(null, null)).toBe(true) }) it('should return false if only one range is null', () => { const range = { from: { rowId: '1', columnId: 'col1' }, to: { rowId: '2', columnId: 'col2' }, } as GridRangeSelection expect(isRangeEqual(range, null)).toBe(false) expect(isRangeEqual(null, range)).toBe(false) }) it('should return true if ranges have the same values', () => { const range1 = { from: { rowId: '1', columnId: 'col1' }, to: { rowId: '2', columnId: 'col2' }, } as GridRangeSelection const range2 = { from: { rowId: '1', columnId: 'col1' }, to: { rowId: '2', columnId: 'col2' }, } as GridRangeSelection expect(isRangeEqual(range1, range2)).toBe(true) }) it('should return false if ranges have different values', () => { const range1 = { from: { rowId: '1', columnId: 'col1' }, to: { rowId: '2', columnId: 'col2' }, } as GridRangeSelection const range2 = { from: { rowId: '1', columnId: 'col1' }, to: { rowId: '3', columnId: 'col2' }, } as GridRangeSelection expect(isRangeEqual(range1, range2)).toBe(false) }) }) describe('getIdsFromRange', () => { it('should return correct sets when "from" is before "to"', () => { const range = { from: { rowId: '1', columnId: 'col1' }, to: { rowId: '3', columnId: 'col3' }, } as GridRangeSelection const rowIds = ['1', '2', '3', '4'] const columnIds = ['col1', 'col2', 'col3', 'col4'] const result = getIdsFromRange(range, rowIds, columnIds) expect(result.rowIds).toEqual(new Set(['1', '2', '3'])) expect(result.columnIds).toEqual(new Set(['col1', 'col2', 'col3'])) expect(result.firstColumnId).toBe('col1') expect(result.firstRowId).toBe('1') expect(result.lastColumnId).toBe('col3') expect(result.lastRowId).toBe('3') }) it('should return correct sets when "to" is before "from"', () => { const range = { from: { rowId: '3', columnId: 'col3' }, to: { rowId: '1', columnId: 'col1' }, } as GridRangeSelection const rowIds = ['1', '2', '3', '4'] const columnIds = ['col1', 'col2', 'col3', 'col4'] const result = getIdsFromRange(range, rowIds, columnIds) expect(result.rowIds).toEqual(new Set(['1', '2', '3'])) expect(result.columnIds).toEqual(new Set(['col1', 'col2', 'col3'])) expect(result.firstColumnId).toBe('col1') expect(result.firstRowId).toBe('1') expect(result.lastColumnId).toBe('col3') expect(result.lastRowId).toBe('3') }) it('should handle single cell selections', () => { const range = { from: { rowId: '2', columnId: 'col2' }, to: { rowId: '2', columnId: 'col2' }, } as GridRangeSelection const rowIds = ['1', '2', '3', '4'] const columnIds = ['col1', 'col2', 'col3', 'col4'] const result = getIdsFromRange(range, rowIds, columnIds) expect(result.rowIds).toEqual(new Set(['2'])) expect(result.columnIds).toEqual(new Set(['col2'])) expect(result.firstColumnId).toBe('col2') expect(result.firstRowId).toBe('2') expect(result.lastColumnId).toBe('col2') expect(result.lastRowId).toBe('2') }) }) describe('healSpannedRange', () => { let store: Store beforeEach(() => { store = createStore() jest.spyOn(store.selectors, 'selectRowIds').mockReturnValue([ '1', '2', '3', ]) jest.spyOn(store.selectors, 'selectColumnIds').mockReturnValue([ 'col1', 'col2', 'col3', 'col4', ]) jest.spyOn( store.selectors, 'selectColumnSpanByRowId' ).mockReturnValue(null) }) it('should return the exact range if firstColumnId is null', () => { jest.spyOn(store.selectors, 'selectColumnIds').mockReturnValue([]) const range: GridRangeSelection = { from: { rowId: '1', columnId: 'col1' }, to: { rowId: '3', columnId: 'col3' }, } const result = healSpannedRange(range, store) expect(result).toBe(range) }) it('should return the same range when there are no column spans', () => { const range: GridRangeSelection = { from: { rowId: '1', columnId: 'col1' }, to: { rowId: '3', columnId: 'col3' }, } const result = healSpannedRange(range, store) expect(result).toEqual(range) expect(result).not.toBe(range) // Check it's a clone }) it('should expand range to include column spans on the left', () => { const range: GridRangeSelection = { from: { rowId: '1', columnId: 'col2' }, to: { rowId: '2', columnId: 'col3' }, } const colSpans = new Map([ [ 'col1', { id: 'col1', positionColumnIds: ['col1', 'col2'], }, ], [ 'col2', { id: 'col2', skip: true, }, ], ]) jest.mocked( store.selectors.selectColumnSpanByRowId ).mockReturnValue(colSpans) const result = healSpannedRange(range, store) expect(result).toEqual({ from: { rowId: '1', columnId: 'col1' }, to: { rowId: '2', columnId: 'col3' }, }) }) it('should expand range to include column spans on the left (inverted "from" and "to")', () => { const range: GridRangeSelection = { from: { rowId: '2', columnId: 'col3' }, to: { rowId: '1', columnId: 'col2' }, } const colSpans = new Map([ [ 'col1', { id: 'col1', positionColumnIds: ['col1', 'col2'], }, ], [ 'col2', { id: 'col2', skip: true, }, ], ]) jest.mocked( store.selectors.selectColumnSpanByRowId ).mockReturnValue(colSpans) const result = healSpannedRange(range, store) expect(result).toEqual({ from: { rowId: '2', columnId: 'col3' }, to: { rowId: '1', columnId: 'col1' }, }) }) it('should expand range to include column spans on the right', () => { const range: GridRangeSelection = { from: { rowId: '1', columnId: 'col1' }, to: { rowId: '2', columnId: 'col2' }, } const colSpans = new Map([ [ 'col2', { id: 'col2', positionColumnIds: ['col2', 'col3'], }, ], ]) jest.mocked( store.selectors.selectColumnSpanByRowId ).mockReturnValue(colSpans) const result = healSpannedRange(range, store) expect(result).toEqual({ from: { rowId: '1', columnId: 'col1' }, to: { rowId: '2', columnId: 'col3' }, }) }) it('should expand range to include column spans on the right (inverted "from" and "to")', () => { const range: GridRangeSelection = { to: { rowId: '1', columnId: 'col1' }, from: { rowId: '2', columnId: 'col2' }, } const colSpans = new Map([ [ 'col2', { id: 'col2', positionColumnIds: ['col2', 'col3'], }, ], ]) jest.mocked( store.selectors.selectColumnSpanByRowId ).mockReturnValue(colSpans) const result = healSpannedRange(range, store) expect(result).toEqual({ from: { rowId: '2', columnId: 'col3' }, to: { rowId: '1', columnId: 'col1' }, }) }) it('should handle reversed ranges (from right to left)', () => { const range: GridRangeSelection = { from: { rowId: '1', columnId: 'col3' }, to: { rowId: '2', columnId: 'col2' }, } const colSpans = new Map([ [ 'col1', { id: 'col1', positionColumnIds: ['col1', 'col2'], }, ], ]) jest.mocked( store.selectors.selectColumnSpanByRowId ).mockReturnValue(colSpans) const result = healSpannedRange(range, store) expect(result).toEqual({ from: { rowId: '1', columnId: 'col3' }, to: { rowId: '2', columnId: 'col1' }, }) }) it('should handle multiple iterations to heal all spans', () => { const range: GridRangeSelection = { from: { rowId: '1', columnId: 'col2' }, to: { rowId: '2', columnId: 'col3' }, } // First iteration will find col1-col2 span // Second iteration will find col0-col1 span const iteration1 = new Map([ [ 'col1', { id: 'col1', positionColumnIds: ['col1', 'col2'], }, ], ]) const iteration2 = new Map([ [ 'col0', { id: 'col0', positionColumnIds: ['col0', 'col1'], }, ], ]) jest.mocked(store.selectors.selectColumnIds).mockReturnValue([ 'col0', 'col1', 'col2', 'col3', 'col4', ]) jest.mocked(store.selectors.selectColumnSpanByRowId) .mockReturnValueOnce(iteration1) .mockReturnValueOnce(iteration2) .mockReturnValue(null) const result = healSpannedRange(range, store) expect(result).toEqual({ from: { rowId: '1', columnId: 'col0' }, to: { rowId: '2', columnId: 'col3' }, }) }) }) })