|
1 | 1 | import ExcelJS from "exceljs" |
2 | | -import { formatWorkbook } from "../extract-text-from-xlsx" |
| 2 | +import { extractTextFromXLSX, formatWorkbook } from "../extract-text-from-xlsx" |
3 | 3 | import { extractTextFromFile } from "../extract-text" |
4 | 4 |
|
| 5 | +describe("XLSX buffer decoding", () => { |
| 6 | + it("preserves empty columns in a populated row from real XLSX bytes", async () => { |
| 7 | + const workbook = new ExcelJS.Workbook() |
| 8 | + const worksheet = workbook.addWorksheet("Sparse") |
| 9 | + worksheet.getCell("A1").value = null |
| 10 | + worksheet.getCell("B1").value = "middle" |
| 11 | + worksheet.getCell("C1").value = undefined |
| 12 | + worksheet.getCell("D1").value = "end" |
| 13 | + |
| 14 | + const expected = "--- Sheet: Sparse ---\n\tmiddle\t\tend" |
| 15 | + expect(formatWorkbook(workbook)).toBe(expected) |
| 16 | + const source = Buffer.from(await workbook.xlsx.writeBuffer()) |
| 17 | + expect(await extractTextFromXLSX(source)).toBe(expected) |
| 18 | + }) |
| 19 | + |
| 20 | + it("decodes only the selected buffer view when another workbook follows in the backing buffer", async () => { |
| 21 | + const approved = new ExcelJS.Workbook() |
| 22 | + const worksheet = approved.addWorksheet("Selected") |
| 23 | + worksheet.getCell("A1").value = "approved cell" |
| 24 | + worksheet.getCell("B1").value = 42 |
| 25 | + const approvedBytes = Buffer.from(await approved.xlsx.writeBuffer()) |
| 26 | + |
| 27 | + const unapproved = new ExcelJS.Workbook() |
| 28 | + unapproved.addWorksheet("Unapproved").getCell("A1").value = "outside selected bytes" |
| 29 | + const unapprovedBytes = Buffer.from(await unapproved.xlsx.writeBuffer()) |
| 30 | + const prefix = Buffer.from("unapproved prefix") |
| 31 | + const backing = Buffer.concat([prefix, approvedBytes, unapprovedBytes]) |
| 32 | + const source = backing.subarray(prefix.length, prefix.length + approvedBytes.length) |
| 33 | + |
| 34 | + // Passing source.buffer would expose the later, unapproved ZIP archive to the decoder. |
| 35 | + expect(source.byteOffset).toBeGreaterThan(0) |
| 36 | + expect(source.byteLength).toBeLessThan(source.buffer.byteLength) |
| 37 | + expect(await extractTextFromXLSX(source)).toBe("--- Sheet: Selected ---\napproved cell\t42") |
| 38 | + }) |
| 39 | + |
| 40 | + it("rejects invalid workbook bytes instead of returning empty workbook text", async () => { |
| 41 | + await expect(extractTextFromXLSX(Buffer.from("not an XLSX archive"))).rejects.toThrow() |
| 42 | + }) |
| 43 | +}) |
| 44 | + |
5 | 45 | describe("formatWorkbook", () => { |
6 | 46 | describe("basic functionality", () => { |
7 | 47 | it("should extract text with proper formatting", () => { |
@@ -177,6 +217,19 @@ describe("formatWorkbook", () => { |
177 | 217 | }) |
178 | 218 |
|
179 | 219 | describe("edge cases", () => { |
| 220 | + it("includes row 50000, marks row 50001 as truncated, and still formats the next sheet", () => { |
| 221 | + const workbook = new ExcelJS.Workbook() |
| 222 | + const worksheet = workbook.addWorksheet("Large") |
| 223 | + worksheet.getCell("A50000").value = "last included row" |
| 224 | + worksheet.getCell("A50001").value = "excluded row content" |
| 225 | + workbook.addWorksheet("Next").getCell("A1").value = "next sheet content" |
| 226 | + |
| 227 | + expect(formatWorkbook(workbook)).toBe( |
| 228 | + "--- Sheet: Large ---\nlast included row\n[... truncated at row 50001 ...]\n\n" + |
| 229 | + "--- Sheet: Next ---\nnext sheet content", |
| 230 | + ) |
| 231 | + }) |
| 232 | + |
180 | 233 | it("should handle empty workbook", () => { |
181 | 234 | const workbook = new ExcelJS.Workbook() |
182 | 235 | workbook.addWorksheet("Empty Sheet") |
|
0 commit comments