/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { describe, it } from 'node:test'; import assert from 'node:assert'; import { formatCsv, formatJson, __internal } from './result-export.js'; const sample = { columns: ['express_id', 'name', 'is_external', 'note'], rows: [ [10, 'Wall A', true, 'plain'], [20, 'Wall, "B"', false, 'has comma + quote'], [30, 'Wall\nMulti', null, 'has newline'], ], }; describe('formatCsv', () => { it('writes a header row plus a body that contains every row', () => { const csv = formatCsv(sample); // Header line is the first physical line — easy to assert on. assert.ok(csv.startsWith('express_id,name,is_external,note\n')); // Each row's first cell value should appear in the CSV body. for (const row of sample.rows) { assert.ok(csv.includes(String(row[0])), `CSV missing express_id ${row[0]}`); } }); it('quotes cells containing comma, quote, or newline', () => { const csv = formatCsv(sample); // Wall, "B" → wrapped in quotes with embedded "" escapes. assert.ok(csv.includes('"Wall, ""B"""')); // Newline cell wrapped — embedded \n stays literal inside the quotes. assert.ok(csv.includes('"Wall\nMulti"')); }); it('renders booleans as true / false and null as empty', () => { const csv = formatCsv(sample); // Row 1 uses no special chars → its full unquoted line is locatable. assert.ok(csv.includes('10,Wall A,true,plain')); // Null cell collapses to empty between two commas. assert.ok(csv.includes(',,has newline')); }); it('terminates with a newline (POSIX-friendly)', () => { const csv = formatCsv({ columns: ['a'], rows: [['1']] }); assert.ok(csv.endsWith('\n')); }); it('handles empty result sets (header only)', () => { const csv = formatCsv({ columns: ['a', 'b'], rows: [] }); assert.strictEqual(csv, 'a,b\n'); }); }); describe('formatJson', () => { it('produces an array of column→value objects', () => { const json = formatJson(sample); const parsed = JSON.parse(json); assert.strictEqual(parsed.length, 3); assert.strictEqual(parsed[0].express_id, '10'); assert.strictEqual(parsed[0].is_external, 'true'); assert.strictEqual(parsed[2].is_external, ''); // null → '' }); it('is pretty-printed (multi-line, with indented keys)', () => { const json = formatJson({ columns: ['a'], rows: [['1']] }); // Indent=2: the inner object lives at 4 spaces of indent inside the array. assert.ok(json.includes('\n "a"')); }); it('handles empty result sets ("[]")', () => { assert.strictEqual(formatJson({ columns: ['a'], rows: [] }), '[]'); }); }); describe('__internal helpers', () => { it('cellToString covers booleans, bigint, numbers, and objects', () => { assert.strictEqual(__internal.cellToString(null), ''); assert.strictEqual(__internal.cellToString(undefined), ''); assert.strictEqual(__internal.cellToString(true), 'true'); assert.strictEqual(__internal.cellToString(42n), '42'); assert.strictEqual(__internal.cellToString(3.14), '3.14'); assert.strictEqual(__internal.cellToString(NaN), ''); assert.strictEqual(__internal.cellToString({ a: 1 }), '{"a":1}'); }); it('escapeCsvCell wraps + escapes only when needed', () => { assert.strictEqual(__internal.escapeCsvCell(''), ''); assert.strictEqual(__internal.escapeCsvCell('plain'), 'plain'); assert.strictEqual(__internal.escapeCsvCell('a,b'), '"a,b"'); assert.strictEqual(__internal.escapeCsvCell('a"b'), '"a""b"'); assert.strictEqual(__internal.escapeCsvCell('a\nb'), '"a\nb"'); }); it('sanitiseFilenameStem strips path-unsafe characters', () => { assert.strictEqual(__internal.sanitiseFilenameStem('My Query.csv'), 'My_Query_csv'); assert.strictEqual(__internal.sanitiseFilenameStem(' '), 'query'); assert.strictEqual(__internal.sanitiseFilenameStem('weird/../path'), 'weird_path'); }); });