feat(frontend): toCSV + csvFilename for data export
This commit is contained in:
@@ -112,3 +112,23 @@ export function buildMatrix(statuses, animals, param) {
|
||||
|
||||
return { columns, rows };
|
||||
}
|
||||
|
||||
function escapeCsvCell(value) {
|
||||
const s = value === null || value === undefined ? '' : String(value);
|
||||
return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
|
||||
}
|
||||
|
||||
// Serialize a matrix to an RFC-4180-ish CSV string (LF line endings).
|
||||
// First column is the date; remaining columns follow matrix.columns order.
|
||||
export function toCSV(matrix) {
|
||||
const header = ['Date', ...matrix.columns.map((c) => c.header)].map(escapeCsvCell).join(',');
|
||||
const lines = matrix.rows.map((r) => [r.date, ...r.values].map(escapeCsvCell).join(','));
|
||||
return [header, ...lines].join('\n');
|
||||
}
|
||||
|
||||
// Build a download filename from the experiment title and parameter label.
|
||||
export function csvFilename(title, label) {
|
||||
const slug = (s) => String(s ?? '').replace(/[^a-z0-9]+/gi, '-').replace(/^-+|-+$/g, '');
|
||||
const base = [slug(title), slug(label)].filter(Boolean).join('-');
|
||||
return `${base || 'export'}.csv`;
|
||||
}
|
||||
|
||||
@@ -125,3 +125,33 @@ describe('buildMatrix', () => {
|
||||
expect(m.columns.length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
import { toCSV, csvFilename } from '../src/lib/dataExport';
|
||||
|
||||
describe('toCSV', () => {
|
||||
it('writes a header row of Date + column headers, then data rows', () => {
|
||||
const matrix = {
|
||||
columns: [{ id: 'a1', header: 'Alpha' }, { id: 'a2', header: 'Beta' }],
|
||||
rows: [{ date: '2026-07-01', values: [5, ''] }, { date: '2026-07-02', values: ['', 9] }],
|
||||
};
|
||||
expect(toCSV(matrix)).toBe('Date,Alpha,Beta\n2026-07-01,5,\n2026-07-02,,9');
|
||||
});
|
||||
it('escapes commas, quotes, and newlines per RFC 4180', () => {
|
||||
const matrix = {
|
||||
columns: [{ id: 'a1', header: 'Note, field' }],
|
||||
rows: [{ date: '2026-07-01', values: ['he said "hi"'] }, { date: '2026-07-02', values: ['line1\nline2'] }],
|
||||
};
|
||||
expect(toCSV(matrix)).toBe(
|
||||
'Date,"Note, field"\n2026-07-01,"he said ""hi"""\n2026-07-02,"line1\nline2"',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('csvFilename', () => {
|
||||
it('slugifies the experiment title and parameter label', () => {
|
||||
expect(csvFilename('My Study #1', 'Success rate')).toBe('My-Study-1-Success-rate.csv');
|
||||
});
|
||||
it('falls back to "export" when both slugs are empty', () => {
|
||||
expect(csvFilename('', '')).toBe('export.csv');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user