Full document, spreadsheet, slideshow, and diagram tooling
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 610 lines 21 kB view raw
1/** 2 * Comprehensive tests for formula reference adjustment on row/col insert/delete. 3 * 4 * Issue #508: Formula refs must be adjusted when rows/columns are structurally 5 * modified. This includes absolute refs ($A$1) which are only "absolute" for 6 * copy/paste, not for structural changes. 7 */ 8 9import { describe, it, expect } from 'vitest'; 10import { 11 adjustFormulaRefs, 12 insertRow, 13 deleteRow, 14 insertColumn, 15 deleteColumn, 16} from '../src/sheets/row-col-ops.js'; 17 18// ============================================================ 19// Helper: simple in-memory cell map for integration tests 20// ============================================================ 21 22interface MockCellData { 23 v: unknown; 24 f: string; 25 s: unknown; 26} 27 28function createMockCellMap(initial: Record<string, MockCellData> = {}) { 29 const store = new Map<string, MockCell>(); 30 31 class MockCell { 32 private data = new Map<string, unknown>(); 33 constructor(cellData?: MockCellData) { 34 if (cellData) { 35 this.data.set('v', cellData.v); 36 this.data.set('f', cellData.f); 37 this.data.set('s', cellData.s ?? ''); 38 } 39 } 40 get(key: string) { return this.data.get(key); } 41 set(key: string, value: unknown) { this.data.set(key, value); } 42 } 43 44 for (const [id, data] of Object.entries(initial)) { 45 store.set(id, new MockCell(data)); 46 } 47 48 const cellMap = { 49 get(key: string) { return store.get(key); }, 50 set(key: string, value: unknown) { store.set(key, value as MockCell); }, 51 has(key: string) { return store.has(key); }, 52 delete(key: string) { store.delete(key); }, 53 forEach(cb: (value: unknown, key: string) => void) { store.forEach(cb); }, 54 }; 55 56 function getCells() { return cellMap; } 57 58 function setCellData(id: string, data: { v?: unknown; f?: string; s?: unknown }) { 59 let cell: MockCell; 60 if (store.has(id)) { 61 cell = store.get(id)!; 62 } else { 63 cell = new MockCell(); 64 store.set(id, cell); 65 } 66 if (data.v !== undefined) cell.set('v', data.v); 67 if (data.f !== undefined) cell.set('f', data.f); 68 if (data.s !== undefined) cell.set('s', typeof data.s === 'object' ? JSON.stringify(data.s) : data.s); 69 } 70 71 function getCellData(id: string): MockCellData | null { 72 const cell = store.get(id); 73 if (!cell) return null; 74 return { 75 v: cell.get('v') ?? '', 76 f: (cell.get('f') as string) ?? '', 77 s: cell.get('s') ?? '', 78 }; 79 } 80 81 return { getCells, setCellData, getCellData, store }; 82} 83 84// ============================================================ 85// Row insertion — relative refs 86// ============================================================ 87 88describe('Row insert — relative refs', () => { 89 it('shifts ref at insert point down by 1', () => { 90 expect(adjustFormulaRefs('A5', { type: 'row', index: 5, delta: 1 })).toBe('A6'); 91 }); 92 93 it('shifts ref below insert point down by 1', () => { 94 expect(adjustFormulaRefs('A10', { type: 'row', index: 5, delta: 1 })).toBe('A11'); 95 }); 96 97 it('does not shift ref above insert point', () => { 98 expect(adjustFormulaRefs('A2', { type: 'row', index: 5, delta: 1 })).toBe('A2'); 99 }); 100 101 it('shifts multiple refs in a formula', () => { 102 expect(adjustFormulaRefs('A5+B7+C3', { type: 'row', index: 5, delta: 1 })) 103 .toBe('A6+B8+C3'); 104 }); 105 106 it('shifts refs in SUM with range', () => { 107 expect(adjustFormulaRefs('SUM(A5:A10)', { type: 'row', index: 5, delta: 1 })) 108 .toBe('SUM(A6:A11)'); 109 }); 110 111 it('insert at row 1 shifts everything', () => { 112 expect(adjustFormulaRefs('A1+B1', { type: 'row', index: 1, delta: 1 })) 113 .toBe('A2+B2'); 114 }); 115}); 116 117// ============================================================ 118// Row insertion — absolute refs ($) 119// ============================================================ 120 121describe('Row insert — absolute refs', () => { 122 it('shifts A$5 down when inserting at row 5 (absolute row, structural change)', () => { 123 expect(adjustFormulaRefs('A$5', { type: 'row', index: 5, delta: 1 })).toBe('A$6'); 124 }); 125 126 it('shifts A$5 down when inserting above (row 3)', () => { 127 expect(adjustFormulaRefs('A$5', { type: 'row', index: 3, delta: 1 })).toBe('A$6'); 128 }); 129 130 it('does not shift A$5 when inserting below (row 10)', () => { 131 expect(adjustFormulaRefs('A$5', { type: 'row', index: 10, delta: 1 })).toBe('A$5'); 132 }); 133 134 it('shifts $A5 down (absolute col, relative row)', () => { 135 expect(adjustFormulaRefs('$A5', { type: 'row', index: 3, delta: 1 })).toBe('$A6'); 136 }); 137 138 it('shifts $A$5 down (fully absolute ref)', () => { 139 expect(adjustFormulaRefs('$A$5', { type: 'row', index: 3, delta: 1 })).toBe('$A$6'); 140 }); 141 142 it('preserves $ signs in the output', () => { 143 expect(adjustFormulaRefs('$B$10', { type: 'row', index: 5, delta: 1 })).toBe('$B$11'); 144 }); 145 146 it('shifts mixed refs correctly in one formula', () => { 147 expect(adjustFormulaRefs('A1+$B$5+C$3', { type: 'row', index: 3, delta: 1 })) 148 .toBe('A1+$B$6+C$4'); 149 }); 150}); 151 152// ============================================================ 153// Row deletion — relative and absolute refs 154// ============================================================ 155 156describe('Row delete — relative refs', () => { 157 it('ref to deleted row becomes #REF!', () => { 158 expect(adjustFormulaRefs('A5', { type: 'row', index: 5, delta: -1 })).toBe('#REF!'); 159 }); 160 161 it('ref below deleted row shifts up', () => { 162 expect(adjustFormulaRefs('A10', { type: 'row', index: 5, delta: -1 })).toBe('A9'); 163 }); 164 165 it('ref above deleted row unchanged', () => { 166 expect(adjustFormulaRefs('A2', { type: 'row', index: 5, delta: -1 })).toBe('A2'); 167 }); 168 169 it('multiple refs: deleted becomes #REF!, others shift', () => { 170 expect(adjustFormulaRefs('A5+A10+A2', { type: 'row', index: 5, delta: -1 })) 171 .toBe('#REF!+A9+A2'); 172 }); 173}); 174 175describe('Row delete — absolute refs', () => { 176 it('A$5 to deleted row becomes #REF!', () => { 177 expect(adjustFormulaRefs('A$5', { type: 'row', index: 5, delta: -1 })).toBe('#REF!'); 178 }); 179 180 it('$A$5 to deleted row becomes #REF!', () => { 181 expect(adjustFormulaRefs('$A$5', { type: 'row', index: 5, delta: -1 })).toBe('#REF!'); 182 }); 183 184 it('$A$10 shifts up when row 5 deleted', () => { 185 expect(adjustFormulaRefs('$A$10', { type: 'row', index: 5, delta: -1 })).toBe('$A$9'); 186 }); 187 188 it('A$10 shifts up when row 5 deleted, preserving $ on row', () => { 189 expect(adjustFormulaRefs('A$10', { type: 'row', index: 5, delta: -1 })).toBe('A$9'); 190 }); 191 192 it('$A$3 unchanged when row 5 deleted (above deleted row)', () => { 193 expect(adjustFormulaRefs('$A$3', { type: 'row', index: 5, delta: -1 })).toBe('$A$3'); 194 }); 195}); 196 197// ============================================================ 198// Column insertion — relative and absolute refs 199// ============================================================ 200 201describe('Column insert — relative refs', () => { 202 it('shifts ref at insert col right', () => { 203 expect(adjustFormulaRefs('C1', { type: 'col', index: 3, delta: 1 })).toBe('D1'); 204 }); 205 206 it('shifts ref right of insert col right', () => { 207 expect(adjustFormulaRefs('E1', { type: 'col', index: 3, delta: 1 })).toBe('F1'); 208 }); 209 210 it('does not shift ref left of insert col', () => { 211 expect(adjustFormulaRefs('A1', { type: 'col', index: 3, delta: 1 })).toBe('A1'); 212 }); 213}); 214 215describe('Column insert — absolute refs', () => { 216 it('shifts $C1 right on structural insert (absolute col)', () => { 217 expect(adjustFormulaRefs('$C1', { type: 'col', index: 3, delta: 1 })).toBe('$D1'); 218 }); 219 220 it('shifts C$1 right (relative col, absolute row)', () => { 221 expect(adjustFormulaRefs('C$1', { type: 'col', index: 3, delta: 1 })).toBe('D$1'); 222 }); 223 224 it('shifts $C$1 right (fully absolute)', () => { 225 expect(adjustFormulaRefs('$C$1', { type: 'col', index: 3, delta: 1 })).toBe('$D$1'); 226 }); 227 228 it('does not shift $B1 when insert is at col 3 (left of insert)', () => { 229 expect(adjustFormulaRefs('$B1', { type: 'col', index: 3, delta: 1 })).toBe('$B1'); 230 }); 231 232 it('shifts $E$5 right when insert is at col 3', () => { 233 expect(adjustFormulaRefs('$E$5', { type: 'col', index: 3, delta: 1 })).toBe('$F$5'); 234 }); 235}); 236 237// ============================================================ 238// Column deletion — relative and absolute refs 239// ============================================================ 240 241describe('Column delete — relative refs', () => { 242 it('ref to deleted col becomes #REF!', () => { 243 expect(adjustFormulaRefs('C1', { type: 'col', index: 3, delta: -1 })).toBe('#REF!'); 244 }); 245 246 it('ref right of deleted col shifts left', () => { 247 expect(adjustFormulaRefs('E1', { type: 'col', index: 3, delta: -1 })).toBe('D1'); 248 }); 249 250 it('ref left of deleted col unchanged', () => { 251 expect(adjustFormulaRefs('A1', { type: 'col', index: 3, delta: -1 })).toBe('A1'); 252 }); 253}); 254 255describe('Column delete — absolute refs', () => { 256 it('$C1 to deleted col becomes #REF!', () => { 257 expect(adjustFormulaRefs('$C1', { type: 'col', index: 3, delta: -1 })).toBe('#REF!'); 258 }); 259 260 it('$C$1 to deleted col becomes #REF!', () => { 261 expect(adjustFormulaRefs('$C$1', { type: 'col', index: 3, delta: -1 })).toBe('#REF!'); 262 }); 263 264 it('$E$1 shifts left when col 3 deleted', () => { 265 expect(adjustFormulaRefs('$E$1', { type: 'col', index: 3, delta: -1 })).toBe('$D$1'); 266 }); 267 268 it('$B$1 unchanged when col 3 deleted (left of deleted)', () => { 269 expect(adjustFormulaRefs('$B$1', { type: 'col', index: 3, delta: -1 })).toBe('$B$1'); 270 }); 271}); 272 273// ============================================================ 274// Range refs (A1:B5) 275// ============================================================ 276 277describe('Range refs — row operations', () => { 278 it('insert inside range expands the range', () => { 279 expect(adjustFormulaRefs('SUM(A1:A10)', { type: 'row', index: 5, delta: 1 })) 280 .toBe('SUM(A1:A11)'); 281 }); 282 283 it('insert above range shifts both ends', () => { 284 expect(adjustFormulaRefs('SUM(A5:A10)', { type: 'row', index: 2, delta: 1 })) 285 .toBe('SUM(A6:A11)'); 286 }); 287 288 it('insert below range does not change it', () => { 289 expect(adjustFormulaRefs('SUM(A1:A5)', { type: 'row', index: 10, delta: 1 })) 290 .toBe('SUM(A1:A5)'); 291 }); 292 293 it('delete row at range endpoint: endpoint becomes #REF!', () => { 294 expect(adjustFormulaRefs('SUM(A1:A10)', { type: 'row', index: 10, delta: -1 })) 295 .toBe('SUM(A1:#REF!)'); 296 }); 297 298 it('delete row above range shifts both endpoints up', () => { 299 expect(adjustFormulaRefs('SUM(A5:A10)', { type: 'row', index: 2, delta: -1 })) 300 .toBe('SUM(A4:A9)'); 301 }); 302 303 it('delete row inside range shifts lower endpoint', () => { 304 expect(adjustFormulaRefs('SUM(A3:A10)', { type: 'row', index: 5, delta: -1 })) 305 .toBe('SUM(A3:A9)'); 306 }); 307 308 it('multi-column range adjusts on row insert', () => { 309 expect(adjustFormulaRefs('SUM(A1:C10)', { type: 'row', index: 5, delta: 1 })) 310 .toBe('SUM(A1:C11)'); 311 }); 312}); 313 314describe('Range refs — column operations', () => { 315 it('insert inside column range shifts right endpoint', () => { 316 expect(adjustFormulaRefs('SUM(A1:E1)', { type: 'col', index: 3, delta: 1 })) 317 .toBe('SUM(A1:F1)'); 318 }); 319 320 it('insert left of range shifts both endpoints', () => { 321 expect(adjustFormulaRefs('SUM(C1:E1)', { type: 'col', index: 2, delta: 1 })) 322 .toBe('SUM(D1:F1)'); 323 }); 324 325 it('delete col at range endpoint becomes #REF!', () => { 326 expect(adjustFormulaRefs('SUM(C1:E1)', { type: 'col', index: 3, delta: -1 })) 327 .toBe('SUM(#REF!:D1)'); 328 }); 329}); 330 331describe('Range refs with absolute markers', () => { 332 it('shifts $A$1:$A$10 range on row insert', () => { 333 expect(adjustFormulaRefs('SUM($A$1:$A$10)', { type: 'row', index: 5, delta: 1 })) 334 .toBe('SUM($A$1:$A$11)'); 335 }); 336 337 it('shifts $A$5:$C$10 on row insert above range', () => { 338 expect(adjustFormulaRefs('SUM($A$5:$C$10)', { type: 'row', index: 2, delta: 1 })) 339 .toBe('SUM($A$6:$C$11)'); 340 }); 341 342 it('shifts $C$1:$E$1 on column insert', () => { 343 expect(adjustFormulaRefs('SUM($C$1:$E$1)', { type: 'col', index: 3, delta: 1 })) 344 .toBe('SUM($D$1:$F$1)'); 345 }); 346 347 it('absolute range endpoint on deleted row becomes #REF!', () => { 348 expect(adjustFormulaRefs('SUM($A$5:$A$5)', { type: 'row', index: 5, delta: -1 })) 349 .toBe('SUM(#REF!:#REF!)'); 350 }); 351}); 352 353// ============================================================ 354// Cross-sheet refs — should NOT be adjusted 355// ============================================================ 356 357describe('Cross-sheet refs are not adjusted', () => { 358 it('unquoted cross-sheet ref unchanged on row insert', () => { 359 expect(adjustFormulaRefs('Sheet2!A5', { type: 'row', index: 3, delta: 1 })) 360 .toBe('Sheet2!A5'); 361 }); 362 363 it('quoted cross-sheet ref unchanged on row insert', () => { 364 expect(adjustFormulaRefs("'My Sheet'!A5", { type: 'row', index: 3, delta: 1 })) 365 .toBe("'My Sheet'!A5"); 366 }); 367 368 it('cross-sheet ref unchanged but local ref shifts', () => { 369 expect(adjustFormulaRefs('Sheet2!A5+A5', { type: 'row', index: 3, delta: 1 })) 370 .toBe('Sheet2!A5+A6'); 371 }); 372 373 it('cross-sheet ref unchanged on column delete', () => { 374 expect(adjustFormulaRefs('Sheet1!C1+D1', { type: 'col', index: 3, delta: -1 })) 375 .toBe('Sheet1!C1+C1'); 376 }); 377}); 378 379// ============================================================ 380// Complex formula scenarios 381// ============================================================ 382 383describe('Complex formulas', () => { 384 it('IF formula with multiple refs', () => { 385 expect(adjustFormulaRefs('IF(A5>0,B5,C5)', { type: 'row', index: 3, delta: 1 })) 386 .toBe('IF(A6>0,B6,C6)'); 387 }); 388 389 it('nested SUM + single ref', () => { 390 expect(adjustFormulaRefs('SUM(A1:A10)+B5', { type: 'row', index: 5, delta: -1 })) 391 .toBe('SUM(A1:A9)+#REF!'); 392 }); 393 394 it('VLOOKUP formula adjusts table range', () => { 395 expect(adjustFormulaRefs('VLOOKUP(A1,B1:D10,3)', { type: 'row', index: 5, delta: 1 })) 396 .toBe('VLOOKUP(A1,B1:D11,3)'); 397 }); 398 399 it('INDEX/MATCH formula adjusts all refs', () => { 400 expect(adjustFormulaRefs('INDEX(B1:B10,MATCH(A1,A1:A10,0))', { type: 'row', index: 5, delta: 1 })) 401 .toBe('INDEX(B1:B11,MATCH(A1,A1:A11,0))'); 402 }); 403 404 it('formula with no cell refs unchanged', () => { 405 expect(adjustFormulaRefs('42+10', { type: 'row', index: 1, delta: 1 })).toBe('42+10'); 406 }); 407 408 it('empty formula unchanged', () => { 409 expect(adjustFormulaRefs('', { type: 'row', index: 1, delta: 1 })).toBe(''); 410 }); 411 412 it('formula with concatenation operator', () => { 413 expect(adjustFormulaRefs('A5&" - "&B5', { type: 'row', index: 3, delta: 1 })) 414 .toBe('A6&" - "&B6'); 415 }); 416 417 it('multiple deletions produce multiple #REF!', () => { 418 expect(adjustFormulaRefs('A3+A4+A5', { type: 'row', index: 3, delta: -1 })) 419 .toBe('#REF!+A3+A4'); 420 }); 421}); 422 423// ============================================================ 424// Integration: insertRow with absolute refs in formulas 425// ============================================================ 426 427describe('insertRow — absolute ref integration', () => { 428 it('adjusts absolute refs in formulas of non-shifted cells', () => { 429 const { getCells, setCellData, getCellData } = createMockCellMap({ 430 'A1': { v: '', f: '$A$3+$B$4', s: '' }, 431 'A3': { v: 100, f: '', s: '' }, 432 'B4': { v: 200, f: '', s: '' }, 433 }); 434 435 insertRow(getCells, setCellData, 2, 2); 436 437 expect(getCellData('A1')?.f).toBe('$A$4+$B$5'); 438 }); 439 440 it('adjusts absolute refs in formulas of shifted cells', () => { 441 const { getCells, setCellData, getCellData } = createMockCellMap({ 442 'A3': { v: '', f: '$A$1+$B$2', s: '' }, 443 'A1': { v: 10, f: '', s: '' }, 444 'B2': { v: 20, f: '', s: '' }, 445 }); 446 447 insertRow(getCells, setCellData, 3, 2); 448 449 expect(getCellData('A4')?.f).toBe('$A$1+$B$2'); 450 }); 451 452 it('adjusts mixed absolute/relative refs on insert', () => { 453 const { getCells, setCellData, getCellData } = createMockCellMap({ 454 'A1': { v: '', f: 'A5+$B$5+C$5', s: '' }, 455 'A5': { v: 10, f: '', s: '' }, 456 'B5': { v: 20, f: '', s: '' }, 457 'C5': { v: 30, f: '', s: '' }, 458 }); 459 460 insertRow(getCells, setCellData, 3, 3); 461 462 expect(getCellData('A1')?.f).toBe('A6+$B$6+C$6'); 463 }); 464}); 465 466// ============================================================ 467// Integration: deleteRow with absolute refs in formulas 468// ============================================================ 469 470describe('deleteRow — absolute ref integration', () => { 471 it('absolute ref to deleted row becomes #REF!', () => { 472 const { getCells, setCellData, getCellData } = createMockCellMap({ 473 'A1': { v: '', f: '$A$3*2', s: '' }, 474 'A3': { v: 100, f: '', s: '' }, 475 }); 476 477 deleteRow(getCells, setCellData, 3, 1); 478 479 expect(getCellData('A1')?.f).toBe('#REF!*2'); 480 }); 481 482 it('absolute ref below deleted row shifts up', () => { 483 const { getCells, setCellData, getCellData } = createMockCellMap({ 484 'A1': { v: '', f: '$A$5', s: '' }, 485 'A3': { v: 'delete me', f: '', s: '' }, 486 'A5': { v: 50, f: '', s: '' }, 487 }); 488 489 deleteRow(getCells, setCellData, 3, 1); 490 491 expect(getCellData('A1')?.f).toBe('$A$4'); 492 }); 493}); 494 495// ============================================================ 496// Integration: insertColumn with absolute refs in formulas 497// ============================================================ 498 499describe('insertColumn — absolute ref integration', () => { 500 it('adjusts absolute column refs in non-shifted cells', () => { 501 const { getCells, setCellData, getCellData } = createMockCellMap({ 502 'A1': { v: '', f: '$C$1+$D$1', s: '' }, 503 'C1': { v: 10, f: '', s: '' }, 504 'D1': { v: 20, f: '', s: '' }, 505 }); 506 507 insertColumn(getCells, setCellData, 2, 1); 508 509 expect(getCellData('A1')?.f).toBe('$D$1+$E$1'); 510 }); 511 512 it('mixed refs adjusted on column insert', () => { 513 const { getCells, setCellData, getCellData } = createMockCellMap({ 514 'A1': { v: '', f: 'C1+$C$1+$C1+C$1', s: '' }, 515 'C1': { v: 10, f: '', s: '' }, 516 }); 517 518 insertColumn(getCells, setCellData, 3, 1); 519 520 expect(getCellData('A1')?.f).toBe('D1+$D$1+$D1+D$1'); 521 }); 522}); 523 524// ============================================================ 525// Integration: deleteColumn with absolute refs in formulas 526// ============================================================ 527 528describe('deleteColumn — absolute ref integration', () => { 529 it('absolute ref to deleted column becomes #REF!', () => { 530 const { getCells, setCellData, getCellData } = createMockCellMap({ 531 'A1': { v: '', f: '$C$1+$D$1', s: '' }, 532 'C1': { v: 10, f: '', s: '' }, 533 'D1': { v: 20, f: '', s: '' }, 534 }); 535 536 deleteColumn(getCells, setCellData, 3, 1); 537 538 expect(getCellData('A1')?.f).toBe('#REF!+$C$1'); 539 }); 540 541 it('absolute ref right of deleted col shifts left', () => { 542 const { getCells, setCellData, getCellData } = createMockCellMap({ 543 'A1': { v: '', f: '$E$1', s: '' }, 544 'B1': { v: 'delete me', f: '', s: '' }, 545 'E1': { v: 50, f: '', s: '' }, 546 }); 547 548 deleteColumn(getCells, setCellData, 2, 1); 549 550 expect(getCellData('A1')?.f).toBe('$D$1'); 551 }); 552}); 553 554// ============================================================ 555// Edge cases 556// ============================================================ 557 558describe('Edge cases', () => { 559 it('row 1 insert at row 1 shifts all refs', () => { 560 expect(adjustFormulaRefs('A1+B1+C1', { type: 'row', index: 1, delta: 1 })) 561 .toBe('A2+B2+C2'); 562 }); 563 564 it('delete row 1 shifts everything up', () => { 565 expect(adjustFormulaRefs('A2+A3', { type: 'row', index: 1, delta: -1 })) 566 .toBe('A1+A2'); 567 }); 568 569 it('column A insert at col 1 shifts all column refs right', () => { 570 expect(adjustFormulaRefs('A1+B1+C1', { type: 'col', index: 1, delta: 1 })) 571 .toBe('B1+C1+D1'); 572 }); 573 574 it('delete column A shifts all refs left', () => { 575 expect(adjustFormulaRefs('B1+C1', { type: 'col', index: 1, delta: -1 })) 576 .toBe('A1+B1'); 577 }); 578 579 it('large row number shifts correctly', () => { 580 expect(adjustFormulaRefs('A999', { type: 'row', index: 500, delta: 1 })).toBe('A1000'); 581 }); 582 583 it('multi-letter column (AA) shifts correctly', () => { 584 expect(adjustFormulaRefs('AA1', { type: 'col', index: 27, delta: 1 })).toBe('AB1'); 585 }); 586 587 it('formula with only operators and numbers is unchanged', () => { 588 expect(adjustFormulaRefs('1+2*3/4', { type: 'row', index: 1, delta: 1 })).toBe('1+2*3/4'); 589 }); 590 591 it('sequential insert/delete operations', () => { 592 let formula = 'A1+A5+A10'; 593 594 formula = adjustFormulaRefs(formula, { type: 'row', index: 3, delta: 1 }); 595 expect(formula).toBe('A1+A6+A11'); 596 597 formula = adjustFormulaRefs(formula, { type: 'row', index: 6, delta: -1 }); 598 expect(formula).toBe('A1+#REF!+A10'); 599 }); 600 601 it('preserves all $ combinations through row adjustment', () => { 602 const result = adjustFormulaRefs('A5+$A5+A$5+$A$5', { type: 'row', index: 3, delta: 1 }); 603 expect(result).toBe('A6+$A6+A$6+$A$6'); 604 }); 605 606 it('preserves all $ combinations through column adjustment', () => { 607 const result = adjustFormulaRefs('C1+$C1+C$1+$C$1', { type: 'col', index: 3, delta: 1 }); 608 expect(result).toBe('D1+$D1+D$1+$D$1'); 609 }); 610});