| 1 | import { describe, expect, it } from "vite-plus/test"; |
| 2 | import { filePreviewDelimiter, parseDelimitedPreview } from "./delimitedPreview.ts"; |
| 3 | |
| 4 | describe("delimited file previews", () => { |
| 5 | it("respects a specific MIME type before the extension", () => { |
| 6 | expect(filePreviewDelimiter({ name: "data.CSV", mimeType: "text/plain" })).toBe(","); |
| 7 | expect(filePreviewDelimiter({ name: "data.csv", mimeType: "application/pdf" })).toBeNull(); |
| 8 | }); |
| 9 | it("preserves quoted separators, escaped quotes, blank cells and line breaks", () => { |
| 10 | expect( |
| 11 | parseDelimitedPreview('\ufeffname,notes,empty\r\n"A, B","say ""hi""\nagain",\r\n', ","), |
| 12 | ).toEqual({ |
| 13 | rows: [ |
| 14 | ["name", "notes", "empty"], |
| 15 | ["A, B", 'say "hi"\nagain', ""], |
| 16 | ], |
| 17 | truncated: false, |
| 18 | }); |
| 19 | expect(parseDelimitedPreview("one\ttwo\n\tthree", "\t").rows).toEqual([ |
| 20 | ["one", "two"], |
| 21 | ["", "three"], |
| 22 | ]); |
| 23 | }); |
| 24 | describe.each([",", "\t"] as const)("with delimiter %j", (delimiter) => { |
| 25 | it("preserves a final quoted empty record without requiring a line ending", () => { |
| 26 | for (const ending of ["", "\n", "\r\n"]) { |
| 27 | expect(parseDelimitedPreview(`name\r\nAlice\r\n""${ending}`, delimiter)).toEqual({ |
| 28 | rows: [["name"], ["Alice"], [""]], |
| 29 | truncated: false, |
| 30 | }); |
| 31 | } |
| 32 | expect(parseDelimitedPreview('""', delimiter)).toEqual({ |
| 33 | rows: [[""]], |
| 34 | truncated: false, |
| 35 | }); |
| 36 | }); |
| 37 | it("does not invent records for empty input or a trailing line ending", () => { |
| 38 | for (const prefix of ["", "\ufeff"]) { |
| 39 | expect(parseDelimitedPreview(prefix, delimiter).rows).toEqual([]); |
| 40 | expect(parseDelimitedPreview(`${prefix}name\r\n`, delimiter).rows).toEqual([["name"]]); |
| 41 | expect(parseDelimitedPreview(`${prefix}""`, delimiter).rows).toEqual([[""]]); |
| 42 | } |
| 43 | }); |
| 44 | it("keeps a quoted empty record at the row limit", () => { |
| 45 | const text = `${"name\n".repeat(99)}""`; |
| 46 | const preview = parseDelimitedPreview(text, delimiter); |
| 47 | expect(preview.rows).toHaveLength(100); |
| 48 | expect(preview.rows.at(-1)).toEqual([""]); |
| 49 | expect(preview.truncated).toBe(false); |
| 50 | expect(parseDelimitedPreview(`${text}\nextra`, delimiter).truncated).toBe(true); |
| 51 | }); |
| 52 | }); |
| 53 | it("bounds rows, columns and cell length and reports partial content", () => { |
| 54 | const table = parseDelimitedPreview( |
| 55 | Array.from({ length: 101 }, () => |
| 56 | Array.from({ length: 31 }, () => "x".repeat(2001)).join(","), |
| 57 | ).join("\n"), |
| 58 | ",", |
| 59 | ); |
| 60 | expect(table.truncated).toBe(true); |
| 61 | expect(table.rows).toHaveLength(100); |
| 62 | expect(table.rows[0]).toHaveLength(30); |
| 63 | expect(table.rows[0]?.[0]).toHaveLength(2000); |
| 64 | expect(parseDelimitedPreview('a,"unfinished', ",").truncated).toBe(true); |
| 65 | }); |
| 66 | }); |