Files

125 lines
4.9 KiB
React
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React from 'react';
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
import ExportDataModal from '../src/components/ExportDataModal';
import * as client from '../src/api/client';
jest.mock('../src/api/client', () => ({
experimentsApi: { getDailyStatuses: jest.fn() },
}));
const animals = [
{ id: 'a1', animal_name: 'Alpha', animal_id_string: 'R-001', subject_info: { grp: 'Control' } },
{ id: 'a2', animal_name: 'Beta', animal_id_string: 'R-002', subject_info: { grp: 'Drug' } },
];
const statuses = [
{ animal_id: 'a1', date: '2026-07-01T00:00:00Z', analysis_summary: { total: 5, success_rate: 0.5 } },
{ animal_id: 'a1', date: '2026-07-02T00:00:00Z', analysis_summary: { total: 7, success_rate: 0.7 } },
{ animal_id: 'a2', date: '2026-07-01T00:00:00Z', analysis_summary: { total: 9, success_rate: 0.9 } },
];
const subjectTemplate = [{ fieldId: 'grp', label: 'Group', active: true }];
beforeEach(() => {
jest.clearAllMocks();
client.experimentsApi.getDailyStatuses.mockResolvedValue(statuses);
});
// Intercept the Blob handed to URL.createObjectURL so tests can read the real CSV.
// Also stub anchor.click() — jsdom treats a click on <a href="blob:..."> as an
// unimplemented navigation that throws async and can flake a later test.
function mockDownload() {
let blob = null;
const origCreate = global.URL.createObjectURL;
const origRevoke = global.URL.revokeObjectURL;
const origClick = window.HTMLAnchorElement.prototype.click;
global.URL.createObjectURL = jest.fn((b) => { blob = b; return 'blob:mock'; });
global.URL.revokeObjectURL = jest.fn();
window.HTMLAnchorElement.prototype.click = jest.fn();
return {
getBlob: () => blob,
restore: async () => {
await new Promise((r) => setTimeout(r, 0)); // flush handleExport's setTimeout revoke
global.URL.createObjectURL = origCreate;
global.URL.revokeObjectURL = origRevoke;
window.HTMLAnchorElement.prototype.click = origClick;
},
};
}
function readBlobText(blob) {
return new Promise((resolve, reject) => {
const fr = new FileReader();
fr.onload = () => resolve(fr.result);
fr.onerror = reject;
fr.readAsText(blob);
});
}
function renderModal(props = {}) {
return render(
<ExportDataModal
experimentId="exp-1"
experimentTitle="My Study"
dailyTemplate={[]}
animals={animals}
subjectTemplate={subjectTemplate}
groupField="__none__"
onClose={jest.fn()}
{...props}
/>,
);
}
describe('ExportDataModal', () => {
it('renders X / Data / Group by pickers; X defaults to Date', async () => {
renderModal();
expect(await screen.findByLabelText('X axis (rows)')).toHaveValue('__date__');
expect(screen.getByLabelText('Data (cells)')).toBeInTheDocument();
expect(screen.getByLabelText('Group by')).toHaveValue('__none__');
await waitFor(() => expect(screen.getByText(/2 rows × 2 subjects/i)).toBeInTheDocument());
});
it('default export (X=Date, Data=Total attempts) has subject columns, date rows, blank where missing', async () => {
const dl = mockDownload();
renderModal();
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).toBe('Data,Total attempts\n\nDate,Alpha,Beta\n2026-07-01,5,9\n2026-07-02,7,');
await dl.restore();
});
it('switching X to # Days Reach makes rows the day ordinals', async () => {
const dl = mockDownload();
renderModal();
fireEvent.change(await screen.findByLabelText('X axis (rows)'), { target: { value: '__days__' } });
await waitFor(() => expect(screen.getByText(/2 rows/i)).toBeInTheDocument());
fireEvent.click(screen.getByText('Export CSV'));
const text = await readBlobText(dl.getBlob());
expect(text).toBe('Data,Total attempts\n\n# Days Reach (ordinal),Alpha,Beta\n1,5,9\n2,7,');
await dl.restore();
});
it('a valid group field adds a Group row and clusters subjects', async () => {
const dl = mockDownload();
renderModal({ groupField: 'grp' });
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).toMatch(/^Group,Control,Drug$/m);
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();
});
});