test(export): stale-group + same-date ordinal coverage; comment/guard polish

This commit is contained in:
Experiments DB Dev
2026-07-19 15:18:56 -04:00
parent 33ee191966
commit 4f3fe64cad
4 changed files with 25 additions and 2 deletions
+1 -1
View File
@@ -44,7 +44,7 @@ export default function ExportDataModal({
return () => { alive = false; }; return () => { alive = false; };
}, [experimentId]); }, [experimentId]);
const params = useMemo(() => listExportableParams(dailyTemplate, statuses), [dailyTemplate, statuses]); const params = useMemo(() => listExportableParams(dailyTemplate ?? [], statuses), [dailyTemplate, statuses]);
const effDataId = dataId ?? params[0]?.id ?? null; const effDataId = dataId ?? params[0]?.id ?? null;
const dataParam = params.find((p) => p.id === effDataId) ?? params[0] ?? null; const dataParam = params.find((p) => p.id === effDataId) ?? params[0] ?? null;
+3 -1
View File
@@ -207,7 +207,7 @@ function escapeCsvCell(value) {
} }
// Serialize a matrix to an RFC-4180-ish CSV string (LF line endings): // Serialize a matrix to an RFC-4180-ish CSV string (LF line endings):
// <pinned label>,<pinned value> // Data,<metric>
// (blank line) // (blank line)
// [Group,<group per subject column>] -- only when groupAxis === 'col' // [Group,<group per subject column>] -- only when groupAxis === 'col'
// <corner>[,Group?],<column headers...> // <corner>[,Group?],<column headers...>
@@ -222,6 +222,8 @@ export function toCSV(matrix) {
lines.push(groupRow.map(escapeCsvCell).join(',')); lines.push(groupRow.map(escapeCsvCell).join(','));
} }
// toCSV is a general serializer; the subject-series builder never emits groupAxis
// === 'row' (only 'col' or null), but the branch is retained for that generality.
const header = matrix.groupAxis === 'row' const header = matrix.groupAxis === 'row'
? ['Group', matrix.corner, ...matrix.columns.map((c) => c.label)] ? ['Group', matrix.corner, ...matrix.columns.map((c) => c.label)]
: [matrix.corner, ...matrix.columns.map((c) => c.label)]; : [matrix.corner, ...matrix.columns.map((c) => c.label)];
+11
View File
@@ -110,4 +110,15 @@ describe('ExportDataModal', () => {
expect(text).toMatch(/^Group,Control,Drug$/m); expect(text).toMatch(/^Group,Control,Drug$/m);
await dl.restore(); await dl.restore();
}); });
it('a stale/unset group field coerces to None (no Group row in the export)', async () => {
const dl = mockDownload();
renderModal({ groupField: 'ghost-field' });
await screen.findByLabelText('X axis (rows)');
await waitFor(() => expect(screen.getByText(/2 rows/i)).toBeInTheDocument());
fireEvent.click(screen.getByText('Export CSV'));
const text = await readBlobText(dl.getBlob());
expect(text).not.toMatch(/^Group,/m);
await dl.restore();
});
}); });
+10
View File
@@ -237,6 +237,16 @@ describe('buildSampleIndex', () => {
expect(idx.get('a1').get(250).date).toContain('2026-07-01'); expect(idx.get('a1').get(250).date).toContain('2026-07-01');
expect(idx.get('a1').get(260).date).toContain('2026-07-03'); expect(idx.get('a1').get(260).date).toContain('2026-07-03');
}); });
it('# days reach counts per-status, not per-distinct-date (same-day statuses get separate ordinals)', () => {
const sameDay = [
{ animal_id: 'z1', date: '2026-07-01T08:00:00Z', analysis_summary: { total: 1 } },
{ animal_id: 'z1', date: '2026-07-01T20:00:00Z', analysis_summary: { total: 2 } },
];
const idx = buildSampleIndex(sameDay, '__days__', []);
expect(idx.get('z1').has(1)).toBe(true);
expect(idx.get('z1').has(2)).toBe(true);
expect(idx.get('z1').has(3)).toBe(false);
});
}); });
import { buildSubjectSeriesMatrix } from '../src/lib/dataExport'; import { buildSubjectSeriesMatrix } from '../src/lib/dataExport';