|
1 | 1 | import { type AxisRange, type BoundedAxisRange } from './GridAxisRange'; |
2 | 2 | import { type ModelIndex, type MoveOperation } from './GridMetrics'; |
3 | 3 | import type GridMetrics from './GridMetrics'; |
| 4 | +import GridModel from './GridModel'; |
4 | 5 | import GridRange, { type GridRangeIndex } from './GridRange'; |
5 | 6 | import GridUtils, { type Token, type TokenBox } from './GridUtils'; |
6 | 7 |
|
@@ -1067,3 +1068,192 @@ describe('translateTokenBox', () => { |
1067 | 1068 | expect(GridUtils.translateTokenBox(input, metrics)).toEqual(expectedValue); |
1068 | 1069 | }); |
1069 | 1070 | }); |
| 1071 | + |
| 1072 | +describe('getColumnSeparatorIndex', () => { |
| 1073 | + const mockTheme = { |
| 1074 | + ...GridTheme, |
| 1075 | + allowColumnResize: true, |
| 1076 | + headerSeparatorHandleSize: 10, |
| 1077 | + columnHeaderHeight: 30, |
| 1078 | + }; |
| 1079 | + |
| 1080 | + const createMockMetrics = ( |
| 1081 | + columnHeaderMaxDepth = 1 |
| 1082 | + ): Partial<GridMetrics> => ({ |
| 1083 | + rowHeaderWidth: 50, |
| 1084 | + columnHeaderHeight: 30, |
| 1085 | + columnHeaderMaxDepth, |
| 1086 | + floatingColumns: [], |
| 1087 | + floatingLeftWidth: 0, |
| 1088 | + visibleColumns: [0, 1, 2, 3], |
| 1089 | + allColumnXs: new Map([ |
| 1090 | + [0, 0], |
| 1091 | + [1, 100], |
| 1092 | + [2, 200], |
| 1093 | + [3, 300], |
| 1094 | + ]), |
| 1095 | + allColumnWidths: new Map([ |
| 1096 | + [0, 100], |
| 1097 | + [1, 100], |
| 1098 | + [2, 100], |
| 1099 | + [3, 100], |
| 1100 | + ]), |
| 1101 | + modelColumns: new Map([ |
| 1102 | + [0, 0], |
| 1103 | + [1, 1], |
| 1104 | + [2, 2], |
| 1105 | + [3, 3], |
| 1106 | + ]), |
| 1107 | + // Additional properties needed for getRowAtY (called by getColumnHeaderDepthAtY) |
| 1108 | + gridY: 30, |
| 1109 | + floatingTopRowCount: 0, |
| 1110 | + floatingBottomRowCount: 0, |
| 1111 | + rowCount: 100, |
| 1112 | + visibleRows: [], |
| 1113 | + allRowYs: new Map(), |
| 1114 | + allRowHeights: new Map(), |
| 1115 | + }); |
| 1116 | + |
| 1117 | + class MockGroupedGridModel extends GridModel { |
| 1118 | + private headerGroups: Map<number, Map<number, string>>; |
| 1119 | + |
| 1120 | + constructor(headerGroups: Map<number, Map<number, string>>) { |
| 1121 | + super(); |
| 1122 | + this.headerGroups = headerGroups; |
| 1123 | + } |
| 1124 | + |
| 1125 | + // eslint-disable-next-line class-methods-use-this |
| 1126 | + get rowCount(): number { |
| 1127 | + return 100; |
| 1128 | + } |
| 1129 | + |
| 1130 | + // eslint-disable-next-line class-methods-use-this |
| 1131 | + get columnCount(): number { |
| 1132 | + return 4; |
| 1133 | + } |
| 1134 | + |
| 1135 | + // eslint-disable-next-line class-methods-use-this |
| 1136 | + get columnHeaderMaxDepth(): number { |
| 1137 | + return 2; |
| 1138 | + } |
| 1139 | + |
| 1140 | + // eslint-disable-next-line class-methods-use-this |
| 1141 | + textForCell(column: ModelIndex, row: ModelIndex): string { |
| 1142 | + return `${column},${row}`; |
| 1143 | + } |
| 1144 | + |
| 1145 | + textForColumnHeader(column: ModelIndex, depth = 0): string | undefined { |
| 1146 | + return this.headerGroups.get(depth)?.get(column); |
| 1147 | + } |
| 1148 | + } |
| 1149 | + |
| 1150 | + it('detects separator at column boundary', () => { |
| 1151 | + const metrics = createMockMetrics() as GridMetrics; |
| 1152 | + const x = 150; // At boundary between column 0 and 1 (100 + 50) |
| 1153 | + const y = 15; // In header area |
| 1154 | + |
| 1155 | + const headerGroups = new Map([ |
| 1156 | + [ |
| 1157 | + 0, |
| 1158 | + new Map([ |
| 1159 | + [0, 'A'], |
| 1160 | + [1, 'B'], |
| 1161 | + [2, 'C'], |
| 1162 | + [3, 'D'], |
| 1163 | + ]), |
| 1164 | + ], |
| 1165 | + ]); |
| 1166 | + const model = new MockGroupedGridModel(headerGroups); |
| 1167 | + |
| 1168 | + const result = GridUtils.getColumnSeparatorIndex( |
| 1169 | + x, |
| 1170 | + y, |
| 1171 | + metrics, |
| 1172 | + mockTheme, |
| 1173 | + model |
| 1174 | + ); |
| 1175 | + |
| 1176 | + expect(result).toBe(0); |
| 1177 | + }); |
| 1178 | + |
| 1179 | + it('should return null at depth 1 when no separator exists (columns in same group)', () => { |
| 1180 | + // Depth 0 (base columns): A, B, C, D |
| 1181 | + // Depth 1 (groups): Group1, Group1, Group2, Group2 |
| 1182 | + // This is the core bug fix: hovering at group level where separator doesn't exist |
| 1183 | + const headerGroups = new Map([ |
| 1184 | + [ |
| 1185 | + 0, |
| 1186 | + new Map([ |
| 1187 | + [0, 'A'], |
| 1188 | + [1, 'B'], |
| 1189 | + [2, 'C'], |
| 1190 | + [3, 'D'], |
| 1191 | + ]), |
| 1192 | + ], |
| 1193 | + [ |
| 1194 | + 1, |
| 1195 | + new Map([ |
| 1196 | + [0, 'Group1'], |
| 1197 | + [1, 'Group1'], |
| 1198 | + [2, 'Group2'], |
| 1199 | + [3, 'Group2'], |
| 1200 | + ]), |
| 1201 | + ], |
| 1202 | + ]); |
| 1203 | + const model = new MockGroupedGridModel(headerGroups); |
| 1204 | + const metrics = createMockMetrics(2) as GridMetrics; |
| 1205 | + |
| 1206 | + const x = 150; // Between column 0 and 1 |
| 1207 | + const y = 15; // At depth 1 (0 + 15) |
| 1208 | + |
| 1209 | + const result = GridUtils.getColumnSeparatorIndex( |
| 1210 | + x, |
| 1211 | + y, |
| 1212 | + metrics, |
| 1213 | + mockTheme, |
| 1214 | + model |
| 1215 | + ); |
| 1216 | + |
| 1217 | + expect(result).toBeNull(); // No separator at depth 1 (both in Group1) |
| 1218 | + }); |
| 1219 | + |
| 1220 | + it('should detect separator at depth 1 when groups differ', () => { |
| 1221 | + // Depth 0 (base columns): A, B, C, D |
| 1222 | + // Depth 1 (groups): Group1, Group1, Group2, Group2 |
| 1223 | + const headerGroups = new Map([ |
| 1224 | + [ |
| 1225 | + 0, |
| 1226 | + new Map([ |
| 1227 | + [0, 'A'], |
| 1228 | + [1, 'B'], |
| 1229 | + [2, 'C'], |
| 1230 | + [3, 'D'], |
| 1231 | + ]), |
| 1232 | + ], |
| 1233 | + [ |
| 1234 | + 1, |
| 1235 | + new Map([ |
| 1236 | + [0, 'Group1'], |
| 1237 | + [1, 'Group1'], |
| 1238 | + [2, 'Group2'], |
| 1239 | + [3, 'Group2'], |
| 1240 | + ]), |
| 1241 | + ], |
| 1242 | + ]); |
| 1243 | + const model = new MockGroupedGridModel(headerGroups); |
| 1244 | + const metrics = createMockMetrics(2) as GridMetrics; |
| 1245 | + |
| 1246 | + const x = 250; // Between column 1 (Group1) and 2 (Group2) |
| 1247 | + const y = 15; // At depth 1 |
| 1248 | + |
| 1249 | + const result = GridUtils.getColumnSeparatorIndex( |
| 1250 | + x, |
| 1251 | + y, |
| 1252 | + metrics, |
| 1253 | + mockTheme, |
| 1254 | + model |
| 1255 | + ); |
| 1256 | + |
| 1257 | + expect(result).toBe(1); // Separator exists at depth 1 (Group1 vs Group2) |
| 1258 | + }); |
| 1259 | +}); |
0 commit comments