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'); }); }); }); });