| 1 | import { describe, it, expect } from 'vitest'
|
| 2 | import { serializeCsv } from '@app/services/export/csv.serializer'
|
| 3 | import { buildManifest } from '@app/services/export/manifest.builder'
|
| 4 | import { profileTable } from '@app/services/export/table.descriptors'
|
| 5 | import { CsvTable } from '@app/services/export/types'
|
| 6 |
|
| 7 | describe('serializeCsv', () => {
|
| 8 | it('keeps the header row and a BOM when the table has no rows', () => {
|
| 9 | const file = serializeCsv(profileTable, [])
|
| 10 | const text = file.content.toString('utf8')
|
| 11 |
|
| 12 | expect(text.startsWith('\ufeff')).toBe(true)
|
| 13 | expect(file.rows).toBe(0)
|
| 14 |
|
| 15 | const lines = text.slice(1).split('\r\n').filter(Boolean)
|
| 16 | expect(lines).toHaveLength(1)
|
| 17 | expect(lines[0]).toContain('Email')
|
| 18 | })
|
| 19 |
|
| 20 | it('round-trips commas, double quotes, and newlines through escaped fields', () => {
|
| 21 | const table: CsvTable<{ text: string }> = {
|
| 22 | filename: 'notes.csv',
|
| 23 | columns: [{ header: 'Texto', value: (row) => row.text }],
|
| 24 | }
|
| 25 |
|
| 26 | const values = ['a,b', 'he said "hi"', 'line one\nline two']
|
| 27 | const file = serializeCsv(
|
| 28 | table,
|
| 29 | values.map((text) => ({ text })),
|
| 30 | )
|
| 31 | const body = file.content.toString('utf8').slice(1)
|
| 32 |
|
| 33 | // RFC 4180: every string field is quoted and any inner quote doubled, so
|
| 34 | // reading the quoted fields back and undoubling the quotes recovers the
|
| 35 | // original values byte-for-byte.
|
| 36 | const fields = [...body.matchAll(/"((?:[^"]|"")*)"/g)].map((match) =>
|
| 37 | match[1].replaceAll('""', '"'),
|
| 38 | )
|
| 39 |
|
| 40 | expect(fields).toEqual(['Texto', ...values])
|
| 41 | expect(file.rows).toBe(3)
|
| 42 | })
|
| 43 | })
|
| 44 |
|
| 45 | describe('buildManifest', () => {
|
| 46 | it('states the generation time and lists each file with its row count', () => {
|
| 47 | const manifest = buildManifest(
|
| 48 | [{ filename: 'profile.csv', content: Buffer.from(''), rows: 0 }],
|
| 49 | new Date('2026-10-04T12:00:00.000Z'),
|
| 50 | )
|
| 51 | const text = manifest.content.toString('utf8')
|
| 52 |
|
| 53 | expect(manifest.filename).toBe('leia-me.md')
|
| 54 | expect(text).toContain('2026-10-04T12:00:00.000Z')
|
| 55 | expect(text).toContain('`profile.csv` — 0 registros')
|
| 56 | expect(text).toContain('_id')
|
| 57 | })
|
| 58 |
|
| 59 | it('lists every file and singularizes a count of one', () => {
|
| 60 | const manifest = buildManifest(
|
| 61 | [
|
| 62 | { filename: 'moods.csv', content: Buffer.from(''), rows: 1 },
|
| 63 | { filename: 'triggers.csv', content: Buffer.from(''), rows: 3 },
|
| 64 | ],
|
| 65 | new Date('2026-10-04T12:00:00.000Z'),
|
| 66 | )
|
| 67 | const text = manifest.content.toString('utf8')
|
| 68 |
|
| 69 | expect(text).toContain('`moods.csv` — 1 registro')
|
| 70 | expect(text).toContain('`triggers.csv` — 3 registros')
|
| 71 | })
|
| 72 | })
|