import React, { useContext } from 'react';
import { vi, describe, it, expect, test } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { FilterProvider, FilterContext } from './filter-context';
import { type TreeNode } from './filter-types';
// Test component to access context values
const TestConsumer = () => {
const {
activeTests,
someChecked,
totalResultsCount,
filteredResultsCount,
timelineData,
tableData,
toggleVal,
updateParent,
resetTree,
} = useContext(FilterContext);
return (
{activeTests.join(',')}
{someChecked.toString()}
{totalResultsCount}
{filteredResultsCount}
{timelineData?.loaded.toString()}
{timelineData?.data?.rowData?.length || 0}
{tableData?.length || 0}
);
};
const mockRoots: Array = [
{
display: 'Complete Blood Count',
flatName: 'CBC',
hasData: true,
subSets: [
{
display: 'Hemoglobin',
flatName: 'CBC: Hemoglobin',
hasData: true,
obs: [
{
obsDatetime: '2024-01-15T10:00:00.000Z',
value: '12.5',
interpretation: 'NORMAL',
},
{
obsDatetime: '2024-01-10T10:00:00.000Z',
value: '12.0',
interpretation: 'NORMAL',
},
],
subSets: [],
},
{
display: 'White Blood Cell Count',
flatName: 'CBC: WBC',
hasData: true,
obs: [
{
obsDatetime: '2024-01-15T10:00:00.000Z',
value: '7.5',
interpretation: 'NORMAL',
},
],
subSets: [],
},
],
},
{
display: 'Lipid Panel',
flatName: 'Lipid',
hasData: true,
subSets: [
{
display: 'Total Cholesterol',
flatName: 'Lipid: Cholesterol',
hasData: true,
obs: [
{
obsDatetime: '2024-01-20T10:00:00.000Z',
value: '180',
interpretation: 'NORMAL',
},
],
subSets: [],
},
],
},
];
describe('FilterContext', () => {
describe('Initialization', () => {
it('should initialize with roots data', async () => {
render(
,
);
await waitFor(() => {
expect(screen.getByTestId('timeline-loaded')).toHaveTextContent('true');
});
});
it('should compute totalResultsCount from all observations', async () => {
render(
,
);
await waitFor(() => {
// 2 hemoglobin + 1 WBC + 1 cholesterol = 4 total
expect(screen.getByTestId('total-count')).toHaveTextContent('4');
});
});
it('should show filteredResultsCount equal to totalResultsCount when no filters applied', async () => {
render(
,
);
await waitFor(() => {
expect(screen.getByTestId('filtered-count')).toHaveTextContent('4');
});
expect(screen.getByTestId('some-checked')).toHaveTextContent('false');
});
});
describe('Active tests tracking', () => {
it('should track activeTests when checkboxes are toggled', async () => {
const user = userEvent.setup();
render(
,
);
await waitFor(() => {
expect(screen.getByTestId('timeline-loaded')).toHaveTextContent('true');
});
const toggleButton = screen.getByRole('button', { name: /toggle test1/i });
await user.click(toggleButton);
expect(screen.getByTestId('active-tests')).toHaveTextContent('Test1');
expect(screen.getByTestId('some-checked')).toHaveTextContent('true');
});
it('should update someChecked when tests are selected', async () => {
const user = userEvent.setup();
render(
,
);
await waitFor(() => {
expect(screen.getByTestId('timeline-loaded')).toHaveTextContent('true');
});
expect(screen.getByTestId('some-checked')).toHaveTextContent('false');
await user.click(screen.getByRole('button', { name: /toggle test1/i }));
expect(screen.getByTestId('some-checked')).toHaveTextContent('true');
});
});
describe('Filtered results count', () => {
it('should update filteredResultsCount when filters are applied', async () => {
const user = userEvent.setup();
const FilterTestComponent = () => {
const { toggleVal, filteredResultsCount, totalResultsCount, someChecked } = useContext(FilterContext);
return (
{totalResultsCount}
{filteredResultsCount}
{someChecked.toString()}
);
};
render(
,
);
await waitFor(() => {
expect(screen.getByTestId('total-count')).toHaveTextContent('4');
});
expect(screen.getByTestId('filtered-count')).toHaveTextContent('4');
expect(screen.getByTestId('some-checked')).toHaveTextContent('false');
await user.click(screen.getByRole('button', { name: /toggle hemoglobin/i }));
await waitFor(() => {
expect(screen.getByTestId('filtered-count')).toHaveTextContent('2');
});
expect(screen.getByTestId('some-checked')).toHaveTextContent('true');
});
});
describe('Timeline data', () => {
it('should generate timeline data with all tests when no filters applied', async () => {
render(
,
);
await waitFor(() => {
expect(screen.getByTestId('timeline-loaded')).toHaveTextContent('true');
});
expect(screen.getByTestId('timeline-rows')).toHaveTextContent('3');
});
it('should filter timeline data when tests are selected', async () => {
const user = userEvent.setup();
const TimelineTestComponent = () => {
const { toggleVal, timelineData } = useContext(FilterContext);
return (
{timelineData?.data?.rowData?.length || 0}
);
};
render(
,
);
await waitFor(() => {
expect(screen.getByTestId('timeline-rows')).toHaveTextContent('3');
});
await user.click(screen.getByRole('button', { name: /toggle hemoglobin/i }));
await waitFor(() => {
expect(screen.getByTestId('timeline-rows')).toHaveTextContent('1');
});
});
it('should collapse duplicate branches in timeline data and keep alias filters working', async () => {
const user = userEvent.setup();
const sharedConceptUuid = '729AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
const primaryFlatName = 'Hematology-Complete blood count-Platelets';
const selectedFlatName = 'Chemistry-Complete blood count-Platelets';
const sharedObs = [{ obsDatetime: '2024-11-04T05:48:00.000Z', value: '56.0', interpretation: 'LOW' as const }];
const roots: Array = [
{
display: 'Hematology',
flatName: 'Hematology',
hasData: true,
subSets: [
{
display: 'Complete blood count',
flatName: 'Hematology-Complete blood count',
hasData: true,
subSets: [
{
display: 'Platelets',
flatName: primaryFlatName,
conceptUuid: sharedConceptUuid,
hasData: true,
obs: sharedObs,
subSets: [],
},
],
},
],
},
{
display: 'Chemistry',
flatName: 'Chemistry',
hasData: true,
subSets: [
{
display: 'Complete blood count',
flatName: 'Chemistry-Complete blood count',
hasData: true,
subSets: [
{
display: 'Platelets',
flatName: selectedFlatName,
conceptUuid: sharedConceptUuid,
hasData: true,
obs: sharedObs,
subSets: [],
},
],
},
],
},
];
const TimelineAliasConsumer = () => {
const { toggleVal, timelineData } = useContext(FilterContext);
const rows = timelineData?.data?.rowData ?? [];
return (
{rows.length}
{rows.flatMap((row) => row.flatNames ?? []).join(',')}
);
};
render(
,
);
await waitFor(() => {
expect(screen.getByTestId('timeline-rows')).toHaveTextContent('1');
});
expect(screen.getByTestId('timeline-flat-names')).toHaveTextContent(selectedFlatName);
await user.click(screen.getByRole('button', { name: /toggle chemistry platelets/i }));
await waitFor(() => {
expect(screen.getByTestId('timeline-rows')).toHaveTextContent('1');
});
});
it('should sort timeline observations by date descending', async () => {
const TimelineDetailsComponent = () => {
const { timelineData } = useContext(FilterContext);
return (
{timelineData?.loaded.toString()}
{timelineData?.data?.parsedTime?.sortedTimes && (
{timelineData.data.parsedTime.sortedTimes[0]}
)}
);
};
render(
,
);
await waitFor(() => {
expect(screen.getByTestId('timeline-loaded')).toHaveTextContent('true');
});
const firstTime = screen.getByTestId('first-time').textContent;
expect(firstTime).toContain('2024-01-20');
});
});
describe('Table data', () => {
it('should generate grouped table data', async () => {
render(
,
);
await waitFor(() => {
const tableGroups = screen.getByTestId('table-groups');
expect(parseInt(tableGroups.textContent || '0')).toBeGreaterThan(0);
});
});
it('should group observations by panel and date', async () => {
const TableTestComponent = () => {
const { tableData } = useContext(FilterContext);
return (
{tableData?.length || 0}
{tableData?.map((group, index) => (
{group.key} - {group.date} - {group.entries.length}
))}
);
};
render(
,
);
await waitFor(() => {
expect(screen.getByTestId('table-groups')).not.toHaveTextContent('0');
});
const groups = screen.getAllByTestId(/^group-/);
expect(groups.length).toBeGreaterThan(0);
});
});
describe('Table data de-duplication by conceptUuid', () => {
const sharedConceptUuid = '729AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
const sharedObs = [{ obsDatetime: '2024-11-04T05:48:00.000Z', value: '56.0', interpretation: 'LOW' as const }];
const PlateletEntryConsumer = () => {
const { tableData } = useContext(FilterContext);
const plateletEntries = (tableData ?? [])
.flatMap((group) => group.entries)
.filter((entry) => entry.conceptUuid === sharedConceptUuid);
return (
{tableData?.length ?? 0}
{plateletEntries.length}
);
};
it('collapses the same observation appearing twice within one rendered panel', async () => {
// Two leaves resolve to the same panel group key ("Complete blood count") but
// carry distinct flatNames, mirroring obstree returning one branch per order.
// They share a conceptUuid and identical obs, so they must collapse to one row.
const roots: Array = [
{
display: 'Hematology',
flatName: 'Hematology',
hasData: true,
subSets: [
{
display: 'Complete blood count',
flatName: 'Hematology-Complete blood count',
hasData: true,
subSets: [
{
display: 'Platelets',
flatName: 'Hematology-Complete blood count-Platelets',
conceptUuid: sharedConceptUuid,
hasData: true,
obs: sharedObs,
subSets: [],
},
],
},
],
},
{
display: 'Chemistry',
flatName: 'Chemistry',
hasData: true,
subSets: [
{
display: 'Complete blood count',
flatName: 'Chemistry-Complete blood count',
hasData: true,
subSets: [
{
display: 'Platelets',
flatName: 'Chemistry-Complete blood count-Platelets',
conceptUuid: sharedConceptUuid,
hasData: true,
obs: sharedObs,
subSets: [],
},
],
},
],
},
];
render(
,
);
await waitFor(() => {
expect(screen.getByTestId('platelet-entry-count')).toHaveTextContent('1');
});
});
it('keeps the same observation that legitimately renders under two different panels', async () => {
// Same conceptUuid and obs, but the leaves resolve to different panel group
// keys ("Complete blood count" vs "Hematology"), mirroring a concept mounted
// beneath two roots. Both rows must survive.
const roots: Array = [
{
display: 'Hematology',
flatName: 'Hematology',
hasData: true,
subSets: [
{
display: 'Complete blood count',
flatName: 'Hematology-Complete blood count',
hasData: true,
subSets: [
{
display: 'Platelets',
flatName: 'Hematology-Complete blood count-Platelets',
conceptUuid: sharedConceptUuid,
hasData: true,
obs: sharedObs,
subSets: [],
},
],
},
],
},
{
display: 'Bloodwork',
flatName: 'Bloodwork',
hasData: true,
subSets: [
{
display: 'Hematology',
flatName: 'Bloodwork-Hematology',
hasData: true,
subSets: [
{
display: 'Complete blood count',
flatName: 'Bloodwork-Hematology-Complete blood count',
hasData: true,
subSets: [
{
display: 'Platelets',
flatName: 'Bloodwork-Hematology-Complete blood count-Platelets',
conceptUuid: sharedConceptUuid,
hasData: true,
obs: sharedObs,
subSets: [],
},
],
},
],
},
],
},
];
render(
,
);
await waitFor(() => {
expect(screen.getByTestId('platelet-entry-count')).toHaveTextContent('2');
});
});
});
describe('Normalized branch counts and true duplicate results', () => {
const sharedConceptUuid = '729AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
const obsAt = (value: string) => ({
obsDatetime: '2024-11-04T05:48:00.000Z',
value,
interpretation: 'LOW' as const,
});
const CountsConsumer = () => {
const { tableData, totalResultsCount } = useContext(FilterContext);
const plateletEntries = (tableData ?? [])
.flatMap((group) => group.entries)
.filter((entry) => entry.conceptUuid === sharedConceptUuid);
return (
{plateletEntries.length}
{totalResultsCount}
);
};
const plateletLeaf = (flatName: string, obs: Array>) => ({
display: 'Platelets',
flatName,
conceptUuid: sharedConceptUuid,
hasData: true,
obs,
subSets: [],
});
it('keeps the header count aligned with rendered rows when duplicate branches differ in flatName', async () => {
// Two branches with different flatNames resolve to the same rendered
// panel ("Complete blood count") and carry copies of the same obs list.
// They collapse to one row, and the count must follow the rows.
const roots: Array = [
{
display: 'Hematology',
flatName: 'Hematology',
hasData: true,
subSets: [
{
display: 'Complete blood count',
flatName: 'Hematology-Complete blood count',
hasData: true,
subSets: [plateletLeaf('Hematology-Complete blood count-Platelets', [obsAt('56.0')])],
},
],
},
{
display: 'Chemistry',
flatName: 'Chemistry',
hasData: true,
subSets: [
{
display: 'Complete blood count',
flatName: 'Chemistry-Complete blood count',
hasData: true,
subSets: [plateletLeaf('Chemistry-Complete blood count-Platelets', [obsAt('56.0')])],
},
],
},
];
render(
,
);
await waitFor(() => {
expect(screen.getByTestId('platelet-entry-count')).toHaveTextContent('1');
});
expect(screen.getByTestId('total-results-count')).toHaveTextContent('1');
});
it('keeps the header count aligned with rendered rows when duplicate branches share a flatName', async () => {
// Two roots produce the identical leaf flatName, mirroring the default
// Hematology + Bloodwork config after augmentation drops the Bloodwork
// prefix. One result must render as one row and count as one result,
// regardless of which layer collapses the duplicate.
const roots: Array = [
{
display: 'Hematology',
flatName: 'Hematology',
hasData: true,
subSets: [
{
display: 'Complete blood count',
flatName: 'Hematology-Complete blood count',
hasData: true,
subSets: [plateletLeaf('Hematology-Complete blood count-Platelets', [obsAt('56.0')])],
},
],
},
{
display: 'Bloodwork',
flatName: 'Bloodwork',
hasData: true,
subSets: [
{
display: 'Complete blood count',
flatName: 'Hematology-Complete blood count',
hasData: true,
subSets: [plateletLeaf('Hematology-Complete blood count-Platelets', [obsAt('56.0')])],
},
],
},
];
render(
,
);
await waitFor(() => {
expect(screen.getByTestId('platelet-entry-count')).toHaveTextContent('1');
});
expect(screen.getByTestId('total-results-count')).toHaveTextContent('1');
});
it('keeps two genuinely distinct results with equal value and datetime as two rows', async () => {
// Equal obs within a single branch's list are distinct real results
// (e.g. one result per order, entered against the same encounter), not
// copies. Both must render and both must count.
const roots: Array = [
{
display: 'Hematology',
flatName: 'Hematology',
hasData: true,
subSets: [
{
display: 'Complete blood count',
flatName: 'Hematology-Complete blood count',
hasData: true,
subSets: [plateletLeaf('Hematology-Complete blood count-Platelets', [obsAt('56.0'), obsAt('56.0')])],
},
],
},
];
render(
,
);
await waitFor(() => {
expect(screen.getByTestId('platelet-entry-count')).toHaveTextContent('2');
});
expect(screen.getByTestId('total-results-count')).toHaveTextContent('2');
});
});
describe('Actions', () => {
it('should toggle individual test via toggleVal', async () => {
const user = userEvent.setup();
const ActionTestComponent = () => {
const { toggleVal, checkboxes } = useContext(FilterContext);
return (
{checkboxes['CBC: Hemoglobin']?.toString() || 'false'}
);
};
render(
,
);
await waitFor(() => {
expect(screen.getByTestId('checkbox-state')).toBeInTheDocument();
});
expect(screen.getByTestId('checkbox-state')).toHaveTextContent('false');
await user.click(screen.getByRole('button', { name: /toggle/i }));
await waitFor(() => {
expect(screen.getByTestId('checkbox-state')).toHaveTextContent('true');
});
});
it('should reset all filters via resetTree', async () => {
const user = userEvent.setup();
const ResetTestComponent = () => {
const { toggleVal, resetTree, checkboxes, someChecked } = useContext(FilterContext);
return (
{someChecked.toString()}
{checkboxes['CBC: Hemoglobin']?.toString() || 'false'}
);
};
render(
,
);
await waitFor(() => {
expect(screen.getByTestId('some-checked')).toBeInTheDocument();
});
await user.click(screen.getByRole('button', { name: /toggle hemoglobin/i }));
await waitFor(() => {
expect(screen.getByTestId('some-checked')).toHaveTextContent('true');
});
expect(screen.getByTestId('hemoglobin-state')).toHaveTextContent('true');
await user.click(screen.getByRole('button', { name: /reset/i }));
await waitFor(() => {
expect(screen.getByTestId('some-checked')).toHaveTextContent('false');
});
expect(screen.getByTestId('hemoglobin-state')).toHaveTextContent('false');
});
});
describe('Edge cases', () => {
it('should handle empty roots gracefully', () => {
render(
,
);
expect(screen.getByTestId('total-count')).toHaveTextContent('0');
expect(screen.getByTestId('filtered-count')).toHaveTextContent('0');
});
it('should handle tests with no observations', async () => {
const emptyRoots: Array = [
{
display: 'Empty Panel',
flatName: 'Empty',
hasData: false,
subSets: [
{
display: 'Empty Test',
flatName: 'Empty: Test',
hasData: false,
obs: [],
subSets: [],
},
],
},
];
render(
,
);
await waitFor(() => {
expect(screen.getByTestId('total-count')).toHaveTextContent('0');
});
});
it('should reflect isLoading prop', () => {
const LoadingTestComponent = () => {
const { isLoading } = useContext(FilterContext);
return {isLoading.toString()}
;
};
const { rerender } = render(
,
);
expect(screen.getByTestId('is-loading')).toHaveTextContent('true');
rerender(
,
);
expect(screen.getByTestId('is-loading')).toHaveTextContent('false');
});
});
describe('Computed values reactivity', () => {
it('should recompute timelineData when checkboxes change', async () => {
const user = userEvent.setup();
const ReactivityTestComponent = () => {
const { toggleVal, timelineData } = useContext(FilterContext);
return (
{timelineData?.data?.rowData?.length || 0}
);
};
render(
,
);
await waitFor(() => {
expect(screen.getByTestId('row-count')).toHaveTextContent('3');
});
await user.click(screen.getByRole('button', { name: /toggle hemoglobin/i }));
await waitFor(() => {
expect(screen.getByTestId('row-count')).toHaveTextContent('1');
});
await user.click(screen.getByRole('button', { name: /toggle wbc/i }));
await waitFor(() => {
expect(screen.getByTestId('row-count')).toHaveTextContent('2');
});
});
it('should recompute filteredResultsCount when selections change', async () => {
const user = userEvent.setup();
const CountTestComponent = () => {
const { toggleVal, filteredResultsCount } = useContext(FilterContext);
return (
{filteredResultsCount}
);
};
render(
,
);
await waitFor(() => {
expect(screen.getByTestId('filtered-count')).toHaveTextContent('4');
});
await user.click(screen.getByRole('button', { name: /toggle hemoglobin/i }));
await waitFor(() => {
expect(screen.getByTestId('filtered-count')).toHaveTextContent('2');
});
await user.click(screen.getByRole('button', { name: /toggle cholesterol/i }));
await waitFor(() => {
expect(screen.getByTestId('filtered-count')).toHaveTextContent('3');
});
});
});
});