Spaces:
Sleeping
Sleeping
| import { describe, it, expect } from 'vitest'; | |
| import { buildInsertTableWithDataRequests } from './insertTableWithData.js'; | |
| describe('buildInsertTableWithDataRequests', () => { | |
| describe('table structure', () => { | |
| it('should create an insertTable request with correct dimensions', () => { | |
| const requests = buildInsertTableWithDataRequests( | |
| [ | |
| ['A', 'B'], | |
| ['C', 'D'], | |
| ], | |
| 1, | |
| false | |
| ); | |
| const tableReq = requests.find((r) => r.insertTable); | |
| expect(tableReq).toBeDefined(); | |
| expect(tableReq!.insertTable!.rows).toBe(2); | |
| expect(tableReq!.insertTable!.columns).toBe(2); | |
| expect(tableReq!.insertTable!.location!.index).toBe(1); | |
| }); | |
| it('should create a single-cell table', () => { | |
| const requests = buildInsertTableWithDataRequests([['Only']], 1, false); | |
| const tableReq = requests.find((r) => r.insertTable); | |
| expect(tableReq!.insertTable!.rows).toBe(1); | |
| expect(tableReq!.insertTable!.columns).toBe(1); | |
| const insertTexts = requests.filter((r) => r.insertText); | |
| expect(insertTexts).toHaveLength(1); | |
| expect(insertTexts[0].insertText!.text).toBe('Only'); | |
| }); | |
| it('should pad ragged rows to the widest row', () => { | |
| const requests = buildInsertTableWithDataRequests([['A', 'B', 'C'], ['D']], 1, false); | |
| const tableReq = requests.find((r) => r.insertTable); | |
| expect(tableReq!.insertTable!.rows).toBe(2); | |
| expect(tableReq!.insertTable!.columns).toBe(3); | |
| // Only non-empty cells get insertText: A, B, C, D | |
| const insertTexts = requests.filter((r) => r.insertText); | |
| expect(insertTexts).toHaveLength(4); | |
| }); | |
| }); | |
| describe('cell index math', () => { | |
| // For a 2x2 table at T=1, numCols=2: | |
| // baseCellIndex(1, r, c, 2) = 1 + 4 + r * (1 + 4) + 2*c = 5 + 5r + 2c | |
| // cell(0,0) base = 5, cell(0,1) base = 7 | |
| // cell(1,0) base = 10, cell(1,1) base = 12 | |
| it('should insert text at correct indices for a 2x2 table', () => { | |
| const requests = buildInsertTableWithDataRequests( | |
| [ | |
| ['A', 'B'], | |
| ['C', 'D'], | |
| ], | |
| 1, | |
| false | |
| ); | |
| const insertTexts = requests.filter((r) => r.insertText); | |
| expect(insertTexts).toHaveLength(4); | |
| // cell(0,0): base=5, cumulative=0 → index=5 | |
| expect(insertTexts[0].insertText!.text).toBe('A'); | |
| expect(insertTexts[0].insertText!.location!.index).toBe(5); | |
| // cell(0,1): base=7, cumulative=1 (len of "A") → index=8 | |
| expect(insertTexts[1].insertText!.text).toBe('B'); | |
| expect(insertTexts[1].insertText!.location!.index).toBe(8); | |
| // cell(1,0): base=10, cumulative=2 (len of "A"+"B") → index=12 | |
| expect(insertTexts[2].insertText!.text).toBe('C'); | |
| expect(insertTexts[2].insertText!.location!.index).toBe(12); | |
| // cell(1,1): base=12, cumulative=3 (len of "A"+"B"+"C") → index=15 | |
| expect(insertTexts[3].insertText!.text).toBe('D'); | |
| expect(insertTexts[3].insertText!.location!.index).toBe(15); | |
| }); | |
| it('should handle multi-character cell text correctly', () => { | |
| const requests = buildInsertTableWithDataRequests( | |
| [ | |
| ['Hello', 'World'], | |
| ['Foo', 'Bar'], | |
| ], | |
| 1, | |
| false | |
| ); | |
| const insertTexts = requests.filter((r) => r.insertText); | |
| // cell(0,0): base=5, cumulative=0 → 5 | |
| expect(insertTexts[0].insertText!.location!.index).toBe(5); | |
| // cell(0,1): base=7, cumulative=5 ("Hello") → 12 | |
| expect(insertTexts[1].insertText!.location!.index).toBe(12); | |
| // cell(1,0): base=10, cumulative=10 ("Hello"+"World") → 20 | |
| expect(insertTexts[2].insertText!.location!.index).toBe(20); | |
| // cell(1,1): base=12, cumulative=13 ("Hello"+"World"+"Foo") → 25 | |
| expect(insertTexts[3].insertText!.location!.index).toBe(25); | |
| }); | |
| it('should work with a different starting index', () => { | |
| const requests = buildInsertTableWithDataRequests([['X']], 50, false); | |
| const tableReq = requests.find((r) => r.insertTable); | |
| expect(tableReq!.insertTable!.location!.index).toBe(50); | |
| const insertTexts = requests.filter((r) => r.insertText); | |
| // cell(0,0): 50 + 4 + 0 + 0 = 54 | |
| expect(insertTexts[0].insertText!.location!.index).toBe(54); | |
| }); | |
| it('should handle a 3x3 table correctly', () => { | |
| const requests = buildInsertTableWithDataRequests( | |
| [ | |
| ['a', 'b', 'c'], | |
| ['d', 'e', 'f'], | |
| ['g', 'h', 'i'], | |
| ], | |
| 1, | |
| false | |
| ); | |
| const insertTexts = requests.filter((r) => r.insertText); | |
| expect(insertTexts).toHaveLength(9); | |
| // For 3x3 at T=1: baseCellIndex = 1 + 4 + r*(1+6) + 2c = 5 + 7r + 2c | |
| // row 0: bases = 5, 7, 9 | |
| // row 1: bases = 12, 14, 16 | |
| // row 2: bases = 19, 21, 23 | |
| // cell(0,0): base=5, cum=0 → 5 | |
| expect(insertTexts[0].insertText!.location!.index).toBe(5); | |
| // cell(0,1): base=7, cum=1 → 8 | |
| expect(insertTexts[1].insertText!.location!.index).toBe(8); | |
| // cell(0,2): base=9, cum=2 → 11 | |
| expect(insertTexts[2].insertText!.location!.index).toBe(11); | |
| // cell(1,0): base=12, cum=3 → 15 | |
| expect(insertTexts[3].insertText!.location!.index).toBe(15); | |
| // cell(1,1): base=14, cum=4 → 18 | |
| expect(insertTexts[4].insertText!.location!.index).toBe(18); | |
| // cell(1,2): base=16, cum=5 → 21 | |
| expect(insertTexts[5].insertText!.location!.index).toBe(21); | |
| // cell(2,0): base=19, cum=6 → 25 | |
| expect(insertTexts[6].insertText!.location!.index).toBe(25); | |
| // cell(2,1): base=21, cum=7 → 28 | |
| expect(insertTexts[7].insertText!.location!.index).toBe(28); | |
| // cell(2,2): base=23, cum=8 → 31 | |
| expect(insertTexts[8].insertText!.location!.index).toBe(31); | |
| }); | |
| }); | |
| describe('empty cells', () => { | |
| it('should skip insertText for empty string cells', () => { | |
| const requests = buildInsertTableWithDataRequests( | |
| [ | |
| ['A', ''], | |
| ['', 'D'], | |
| ], | |
| 1, | |
| false | |
| ); | |
| const insertTexts = requests.filter((r) => r.insertText); | |
| expect(insertTexts).toHaveLength(2); | |
| expect(insertTexts[0].insertText!.text).toBe('A'); | |
| expect(insertTexts[1].insertText!.text).toBe('D'); | |
| }); | |
| it('should compute correct indices when cells are skipped', () => { | |
| // 2x2 at T=1, but cell(0,1) is empty | |
| const requests = buildInsertTableWithDataRequests( | |
| [ | |
| ['A', ''], | |
| ['C', 'D'], | |
| ], | |
| 1, | |
| false | |
| ); | |
| const insertTexts = requests.filter((r) => r.insertText); | |
| expect(insertTexts).toHaveLength(3); | |
| // cell(0,0): base=5, cum=0 → 5 | |
| expect(insertTexts[0].insertText!.location!.index).toBe(5); | |
| // cell(0,1) skipped | |
| // cell(1,0): base=10, cum=1 ("A") → 11 | |
| expect(insertTexts[1].insertText!.location!.index).toBe(11); | |
| // cell(1,1): base=12, cum=2 ("A"+"C") → 14 | |
| expect(insertTexts[2].insertText!.location!.index).toBe(14); | |
| }); | |
| }); | |
| describe('header row bolding', () => { | |
| it('should add updateTextStyle requests for header cells when hasHeaderRow=true', () => { | |
| const requests = buildInsertTableWithDataRequests( | |
| [ | |
| ['Name', 'Age'], | |
| ['Alice', '30'], | |
| ], | |
| 1, | |
| true | |
| ); | |
| const styleReqs = requests.filter((r) => r.updateTextStyle); | |
| expect(styleReqs).toHaveLength(2); | |
| // "Name" bold: range starts at 5 (base for cell(0,0), cum=0), ends at 5+4=9 | |
| expect(styleReqs[0].updateTextStyle!.textStyle!.bold).toBe(true); | |
| expect(styleReqs[0].updateTextStyle!.range!.startIndex).toBe(5); | |
| expect(styleReqs[0].updateTextStyle!.range!.endIndex).toBe(9); | |
| // "Age" bold: base for cell(0,1)=7, cum=4 ("Name") → 11, ends at 11+3=14 | |
| expect(styleReqs[1].updateTextStyle!.textStyle!.bold).toBe(true); | |
| expect(styleReqs[1].updateTextStyle!.range!.startIndex).toBe(11); | |
| expect(styleReqs[1].updateTextStyle!.range!.endIndex).toBe(14); | |
| }); | |
| it('should not add style requests when hasHeaderRow=false', () => { | |
| const requests = buildInsertTableWithDataRequests( | |
| [ | |
| ['Name', 'Age'], | |
| ['Alice', '30'], | |
| ], | |
| 1, | |
| false | |
| ); | |
| const styleReqs = requests.filter((r) => r.updateTextStyle); | |
| expect(styleReqs).toHaveLength(0); | |
| }); | |
| it('should not bold empty header cells', () => { | |
| const requests = buildInsertTableWithDataRequests( | |
| [ | |
| ['Name', ''], | |
| ['Alice', '30'], | |
| ], | |
| 1, | |
| true | |
| ); | |
| const styleReqs = requests.filter((r) => r.updateTextStyle); | |
| expect(styleReqs).toHaveLength(1); | |
| expect(styleReqs[0].updateTextStyle!.range!.startIndex).toBe(5); | |
| }); | |
| it('should not bold non-header rows', () => { | |
| const requests = buildInsertTableWithDataRequests( | |
| [ | |
| ['H1', 'H2'], | |
| ['A', 'B'], | |
| ['C', 'D'], | |
| ], | |
| 1, | |
| true | |
| ); | |
| const styleReqs = requests.filter((r) => r.updateTextStyle); | |
| // Only 2 bold requests (for H1 and H2), not for A, B, C, D | |
| expect(styleReqs).toHaveLength(2); | |
| }); | |
| }); | |
| describe('tabId propagation', () => { | |
| it('should include tabId in all request locations when provided', () => { | |
| const tabId = 'tab123'; | |
| const requests = buildInsertTableWithDataRequests( | |
| [ | |
| ['A', 'B'], | |
| ['C', 'D'], | |
| ], | |
| 1, | |
| true, | |
| tabId | |
| ); | |
| const tableReq = requests.find((r) => r.insertTable); | |
| expect((tableReq!.insertTable!.location as any).tabId).toBe(tabId); | |
| const insertTexts = requests.filter((r) => r.insertText); | |
| for (const req of insertTexts) { | |
| expect((req.insertText!.location as any).tabId).toBe(tabId); | |
| } | |
| const styleReqs = requests.filter((r) => r.updateTextStyle); | |
| for (const req of styleReqs) { | |
| expect(req.updateTextStyle!.range!.tabId).toBe(tabId); | |
| } | |
| }); | |
| it('should not include tabId when not provided', () => { | |
| const requests = buildInsertTableWithDataRequests([['A']], 1, false); | |
| const tableReq = requests.find((r) => r.insertTable); | |
| expect((tableReq!.insertTable!.location as any).tabId).toBeUndefined(); | |
| const insertTexts = requests.filter((r) => r.insertText); | |
| expect((insertTexts[0].insertText!.location as any).tabId).toBeUndefined(); | |
| }); | |
| }); | |
| describe('error handling', () => { | |
| it('should throw on empty data array', () => { | |
| expect(() => buildInsertTableWithDataRequests([], 1, false)).toThrow(); | |
| }); | |
| it('should throw on data with only empty rows', () => { | |
| expect(() => buildInsertTableWithDataRequests([[]], 1, false)).toThrow(); | |
| }); | |
| }); | |
| }); | |