import type { ColumnState, HeaderHierarchy } from '../types' import { getLowestLeafs, constrainParentWidth, getChildGroups, getParentGroups, getGroupAccumulatedWidth, buildHeaderHierarchy, getInferredSticky, getInferredLockedLocation, getAdjustedColumnWidths, healPreferencesSplitGroups, healPreferencesSortedIds, } from './column-utils' const data: Record = { 'top-group': { id: 'top-group', actualWidth: 200, data: { label: 'top-group', id: 'top-group', }, parentId: null, childIds: ['group-1', 'group-2'], isParent: true, }, 'group-1': { id: 'group-1', actualWidth: 200, data: { label: 'group-1', id: 'group-1', }, parentId: 'top-group', childIds: ['1', '2'], isParent: true, }, 'group-2': { id: 'group-2', actualWidth: 200, data: { label: 'group-2', id: 'group-2', sticky: 'left', }, parentId: 'top-group', childIds: ['3', '4'], isParent: true, }, 1: { id: '1', actualWidth: 200, data: { label: 'test 1', id: '1', width: 200, maxWidth: 200, sticky: 'left', }, parentId: 'group-1', childIds: [], isParent: false, }, 2: { id: '2', actualWidth: 200, data: { label: 'test 2', id: '2', width: 200, maxWidth: 300, }, parentId: 'group-1', childIds: [], isParent: false, }, 3: { id: '3', actualWidth: 200, data: { label: 'test 3', id: '3', width: 200, minWidth: 200, }, parentId: 'group-2', childIds: [], isParent: false, }, 4: { id: '4', actualWidth: 180, data: { label: 'test 4', id: '4', width: 180, minWidth: 180, }, parentId: 'group-2', childIds: [], isParent: false, }, 5: { id: '5', actualWidth: 180, data: { label: 'test 5', id: '5', width: 180, minWidth: 180, }, parentId: null, childIds: [], isParent: false, }, } describe('column-utils', () => { describe('getLowestLeafs', () => { it('should get children from direct parent', () => { const parent = data['group-1'] expect(getLowestLeafs(parent.id, data, [])).toEqual([ data[1], data[2], ]) }) it('should get children from top parent', () => { const parent = data['top-group'] expect(getLowestLeafs(parent.id, data, [])).toEqual([ data[1], data[2], data[3], data[4], ]) }) it('should be able to get faulty id', () => { expect(getLowestLeafs('x', data, [])).toEqual([]) }) it('should not add hidden columns', () => { const parent = data['top-group'] const hiddenData = { ...data, 1: { ...data[1], data: { ...data[1].data, hidden: true, }, }, } expect(getLowestLeafs(parent.id, hiddenData, [data[1].id])).toEqual( [data[2], data[3], data[4]] ) }) describe('hidden columns', () => { it('should not return hidden columns', () => { const parent = data['top-group'] const hiddenData = { ...data, 1: { ...data[1], data: { ...data[1].data, hidden: true, }, }, } expect( getLowestLeafs(parent.id, hiddenData, [data[1].id]) ).toEqual([data[2], data[3], data[4]]) }) it('should return hidden columns', () => { const parent = data['top-group'] const hiddenData = { ...data, 1: { ...data[1], data: { ...data[1].data, hidden: true, }, }, } expect(getLowestLeafs(parent.id, hiddenData, [])).toEqual([ hiddenData[1], data[2], data[3], data[4], ]) }) }) }) describe('getChildGroups', () => { it('should return child groups', () => { const parent = data['top-group'] expect(getChildGroups(parent.id, data)).toEqual([ data['group-1'], data['group-2'], ]) }) it('should return empty if no child groups', () => { const parent = data['group-1'] expect(getChildGroups(parent.id, data)).toEqual([]) }) it('should return empty if passed column', () => { const parent = data[1] expect(getChildGroups(parent.id, data)) }) it('should be able to get faulty id', () => { expect(getChildGroups('x', data)).toEqual([]) }) }) describe('getParentGroups', () => { it('should return all parents from column', () => { const column = data[3] expect(getParentGroups(column.id, data)).toEqual([ data['group-2'], data['top-group'], ]) }) it('should return all parents from group', () => { const group = data['group-2'] expect(getParentGroups(group.id, data)).toEqual([data['top-group']]) }) it('should return empty if top group', () => { const group = data['top-group'] expect(getParentGroups(group.id, data)).toEqual([]) }) it('should return empty if no parents', () => { const column = data[5] expect(getParentGroups(column.id, data)).toEqual([]) }) it('should be able to get faulty id', () => { expect(getParentGroups('x', data)).toEqual([]) }) }) describe('getGroupAccumulatedWidth', () => { it('should return total width of all columns under top group', () => { const group = data['top-group'] expect(getGroupAccumulatedWidth(group.id, data, [])).toBe(780) }) it('should be able to get faulty id', () => { expect(getGroupAccumulatedWidth('x', data, [])).toBe(0) }) }) describe('constrainParentWidth', () => { it('should constrain parent max-width', () => { const parent = data['group-1'] const newWidth = 700 expect( constrainParentWidth( newWidth, getLowestLeafs(parent.id, data, []) ) ).toBe(500) }) it('should constrain parent min-width', () => { const parent = data['group-1'] const newWidth = 100 expect( constrainParentWidth( newWidth, getLowestLeafs(parent.id, data, []) ) ).toBe(200) }) }) describe('buildHeaderHierarchy', () => { it('should build header hierarchy', () => { expect(buildHeaderHierarchy(data, [])).toEqual({ groups: [ { childIds: ['group-1', 'group-2'], columnIds: ['1', '2', '3', '4'], id: 'top-group', level: 2, parentIds: [], width: 4, }, { childIds: ['1', '2'], columnIds: ['1', '2'], id: 'group-1', level: 1, parentIds: ['top-group'], width: 2, }, { childIds: ['3', '4'], columnIds: ['3', '4'], id: 'group-2', level: 1, parentIds: ['top-group'], width: 2, }, ], levels: 2, }) }) it('should build header hierarchy (no groups)', () => { const dataNoGroups = { 1: { id: '1', actualWidth: 200, data: { label: 'test 1', id: '1', width: 150, maxWidth: 200, }, parentId: null, childIds: [], isParent: false, }, 2: { id: '2', actualWidth: 200, data: { label: 'test 2', id: '2', width: 150, maxWidth: 300, }, parentId: null, childIds: [], isParent: false, }, 3: { id: '3', actualWidth: 200, data: { label: 'test 3', id: '3', width: 150, minWidth: 200, }, parentId: null, childIds: [], isParent: false, }, } expect(buildHeaderHierarchy(dataNoGroups, [])).toEqual({ groups: [], levels: 0, }) }) it('should remove illegal groups from header hierarchy ', () => { const illegalData = { 'illegal-group': { id: 'illegal-group', actualWidth: 200, data: { label: 'illegal-group', id: 'illegal-group', }, parentId: null, childIds: ['top-group'], isParent: true, }, ...data, } expect(buildHeaderHierarchy(illegalData, [])).toEqual({ groups: [ { childIds: ['group-1', 'group-2'], columnIds: ['1', '2', '3', '4'], id: 'top-group', level: 2, parentIds: [], width: 4, }, { childIds: ['1', '2'], columnIds: ['1', '2'], id: 'group-1', level: 1, parentIds: ['top-group'], width: 2, }, { childIds: ['3', '4'], columnIds: ['3', '4'], id: 'group-2', level: 1, parentIds: ['top-group'], width: 2, }, ], levels: 2, }) }) it('should warn if groups have duplicate ids', () => { const consoleWarnSpy = jest .spyOn(console, 'warn') .mockImplementation(() => {}) const duplicateData = { ...data, 'group-1': { ...data['group-1'], childIds: ['1', '2', '3'], }, } buildHeaderHierarchy(duplicateData, []) expect(consoleWarnSpy).toHaveBeenCalledWith( 'Duplicate child reference found in columnGroups! Columns or groups can only be children of a single parent.' ) consoleWarnSpy.mockRestore() }) it('should remove hidden columns from header hierarchy', () => { const hiddenData: Record = { 'top-group': { id: 'top-group', actualWidth: 200, data: { label: 'top-group', id: 'top-group', }, parentId: null, childIds: ['1', '2'], isParent: true, }, 1: { id: '1', actualWidth: 200, data: { label: 'test 1', id: '1', width: 200, maxWidth: 200, sticky: 'left', }, parentId: 'top-group', childIds: [], isParent: false, }, 2: { id: '2', actualWidth: 200, data: { label: 'test 2', id: '2', width: 200, maxWidth: 300, }, parentId: 'top-group', childIds: [], isParent: false, }, } const hierarchy = buildHeaderHierarchy(hiddenData, [data[1].id]) expect(hierarchy.groups).toEqual([ { childIds: ['2'], columnIds: ['2'], id: 'top-group', level: 1, parentIds: [], width: 1, }, ]) }) }) describe('getInferredSticky', () => { let hierarchy: HeaderHierarchy beforeEach(() => { hierarchy = buildHeaderHierarchy(data, []) }) it('should infer sticky if parents', () => { const dataNoTopGroup = { ...data, } delete dataNoTopGroup['top-group'] expect(data[3].data.sticky).toBe(undefined) expect( getInferredSticky(data[3].id, dataNoTopGroup, hierarchy) ).toBe('left') }) it('should override child if parent is not sticky', () => { expect(data[1].data.sticky).toBe('left') expect(getInferredSticky(data[1].id, data, hierarchy)).toBe( undefined ) }) }) describe('getInferredLockedLocation', () => { let hierarchy: HeaderHierarchy const dataLocked: Record = { 'top-group': { id: 'top-group', actualWidth: 200, data: { label: 'top-group', id: 'top-group', }, parentId: null, childIds: ['group-1', 'group-2'], isParent: true, }, 'group-1': { id: 'group-1', actualWidth: 200, data: { label: 'group-1', id: 'group-1', }, parentId: 'top-group', childIds: ['1', '2'], isParent: true, }, 'group-2': { id: 'group-2', actualWidth: 200, data: { label: 'group-2', id: 'group-2', lockedLocation: 'left', }, parentId: 'top-group', childIds: ['3', '4'], isParent: true, }, 1: { id: '1', actualWidth: 200, data: { label: 'test 1', id: '1', width: 150, maxWidth: 200, lockedLocation: 'left', }, parentId: 'group-1', childIds: [], isParent: false, }, 2: { id: '2', actualWidth: 200, data: { label: 'test 2', id: '2', width: 150, maxWidth: 300, }, parentId: 'group-1', childIds: [], isParent: false, }, 3: { id: '3', actualWidth: 200, data: { label: 'test 3', id: '3', width: 150, minWidth: 200, }, parentId: 'group-2', childIds: [], isParent: false, }, 4: { id: '4', actualWidth: 150, data: { label: 'test 4', id: '4', width: 180, minWidth: 180, }, parentId: 'group-2', childIds: [], isParent: false, }, 5: { id: '5', actualWidth: 150, data: { label: 'test 5', id: '5', width: 180, minWidth: 180, }, parentId: null, childIds: [], isParent: false, }, } beforeEach(() => { hierarchy = buildHeaderHierarchy(dataLocked, []) }) it('should infer locked location if parents', () => { const dataNoTopGroup = { ...dataLocked, } delete dataNoTopGroup['top-group'] expect(dataLocked[3].data.lockedLocation).toBe(undefined) expect( getInferredLockedLocation( dataLocked[3].id, dataNoTopGroup, hierarchy ) ).toBe('left') }) it('should override child if parent is no locked location', () => { expect(dataLocked[1].data.lockedLocation).toBe('left') expect( getInferredLockedLocation(data[1].id, dataLocked, hierarchy) ).toBe(undefined) }) }) describe('getAdjustedColumnWidths', () => { it('should adjust widths proportionally', () => { const children = [data[2], data[3]] const newTotalWidth = 600 const adjustedWidths = getAdjustedColumnWidths( children, newTotalWidth ) expect(adjustedWidths).toEqual([ { id: '2', width: 300 }, { id: '3', width: 300 }, ]) }) it('should respect minWidth and maxWidth', () => { const children = [data[1], data[3]] const newTotalWidth = 500 const adjustedWidths = getAdjustedColumnWidths( children, newTotalWidth ) expect(adjustedWidths).toEqual([ { id: '1', width: 200 }, // maxWidth is 200 { id: '3', width: 300 }, // remaining width ]) }) it('should handle no adjustment needed', () => { const children = [data[1], data[2]] const newTotalWidth = 400 const adjustedWidths = getAdjustedColumnWidths( children, newTotalWidth ) expect(adjustedWidths).toEqual([]) }) it('should handle width decrease', () => { const children = [data[1], data[2]] const newTotalWidth = 300 const adjustedWidths = getAdjustedColumnWidths( children, newTotalWidth ) expect(adjustedWidths).toEqual([ { id: '1', width: 150 }, { id: '2', width: 150 }, ]) }) it('should handle width increase', () => { const children = [data[1], data[2], data[3]] const newTotalWidth = 900 const adjustedWidths = getAdjustedColumnWidths( children, newTotalWidth ) expect(adjustedWidths).toEqual([ { id: '1', width: 200 }, { id: '2', width: 300 }, { id: '3', width: 400 }, ]) }) }) describe('healPreferencesSplitGroups', () => { const groups = [ { id: 'group-1', level: 1, width: 2, childIds: ['1', '2'], columnIds: ['1', '2'], parentIds: [], }, { id: 'group-2', level: 1, width: 2, childIds: ['3', '4'], columnIds: ['3', '4'], parentIds: [], }, ] it('should return null if any group is split', () => { const newIds = ['1', '3', '2', '4'] const result = healPreferencesSplitGroups(newIds, groups) expect(result).toBeNull() }) it('should return newIds if no group is split', () => { const newIds = ['1', '2', '3', '4'] const result = healPreferencesSplitGroups(newIds, groups) expect(result).toEqual(newIds) }) it('should handle empty newIds', () => { const newIds: string[] = [] const result = healPreferencesSplitGroups(newIds, groups) expect(result).toEqual(newIds) }) it('should handle null newIds', () => { const result = healPreferencesSplitGroups(null, groups) expect(result).toBeNull() }) }) describe('healPreferencesSortedIds', () => { it('should return null if new ids are added', () => { const newIds = ['1', '2', '3', '4'] const currentIds = ['1', '2', '3'] const result = healPreferencesSortedIds(newIds, currentIds) expect(result).toBeNull() }) it('should filter out removed ids', () => { const newIds = ['1', '2'] const currentIds = ['1', '2', '3'] const result = healPreferencesSortedIds(newIds, currentIds) expect(result).toEqual(['1', '2']) }) it('should return current ids if no ids are added or removed', () => { const newIds = ['1', '2', '3'] const currentIds = ['1', '2', '3'] const result = healPreferencesSortedIds(newIds, currentIds) expect(result).toEqual(currentIds) }) }) })