import React from 'react'; import { vi, describe, it, expect, test, beforeEach } from 'vitest'; import userEvent from '@testing-library/user-event'; import { render, screen } from '@testing-library/react'; import { useObstreeData } from './trendline-resource'; import Trendline from './trendline.component'; const mockLineChart = vi.fn((_props: unknown) => null); vi.mock('@carbon/charts-react', async () => { const actual = (await vi.importActual('@carbon/charts-react')) as object; return { ...actual, LineChart: (props: unknown) => { mockLineChart(props); return null; }, }; }); const mockUseObstreeData = vi.mocked(useObstreeData); vi.mock('./trendline-resource', async () => ({ ...((await vi.importActual('./trendline-resource')) as object), useObstreeData: vi.fn(), })); describe('Trendline', () => { const patientUuid = 'test-patient-uuid'; const conceptUuid = 'test-concept-uuid'; beforeEach(() => { mockLineChart.mockClear(); }); it('renders a loading state when fetching trendline data', () => { mockUseObstreeData.mockReturnValue({ trendlineData: { obs: [], display: '', hiNormal: 0, lowNormal: 0, units: '', flatName: '', }, isLoading: true, isValidating: false, }); render(); expect(screen.getByTestId('trendline-loading')).toBeInTheDocument(); expect(screen.getByTestId('trendline-loading-header')).toBeInTheDocument(); expect(screen.getByTestId('trendline-loading-tabs')).toBeInTheDocument(); expect(screen.getByTestId('trendline-loading-chart')).toBeInTheDocument(); }); it('renders an empty state when there are no observations', () => { mockUseObstreeData.mockReturnValue({ trendlineData: { obs: [], display: '', hiNormal: 0, lowNormal: 0, units: '', flatName: '', }, isLoading: false, isValidating: false, }); render(); expect(screen.getByTitle(/empty data illustration/i)).toBeInTheDocument(); expect(screen.getByText(/there are no observations to display for this patient/i)).toBeInTheDocument(); }); it('renders chart and table toggle buttons when data is available', async () => { const user = userEvent.setup(); mockUseObstreeData.mockReturnValue({ trendlineData: { obs: [ { obsDatetime: '05-Sept-2021, 04:07 AM', value: '5', interpretation: 'NORMAL' }, { obsDatetime: '11-May-2023, 11:02 AM', value: '6', interpretation: 'HIGH' }, ], display: 'Test Chart Title', hiNormal: 10, lowNormal: 1, units: 'mg/dL', flatName: '', }, isLoading: false, isValidating: false, }); render(); const showResultsTableButton = screen.getByRole('button', { name: /show results table/i }); expect(screen.getByRole('tablist', { name: /trendline range tabs/i })).toBeInTheDocument(); const expectedTabs = [/1 day/, /5 days/, /1 month/, /6 months/, /1 year/, /5 years/, /all/]; expectedTabs.forEach((tab) => { expect(screen.getByRole('tab', { name: new RegExp(tab, 'i') })).toBeInTheDocument(); }); expect(showResultsTableButton).toBeInTheDocument(); expect(screen.getByText(/Test Chart Title/)).toBeInTheDocument(); await user.click(showResultsTableButton); expect(screen.getByRole('button', { name: /hide results table/i })).toBeInTheDocument(); expect(screen.getByRole('columnheader', { name: /date and time/i })).toBeInTheDocument(); expect(screen.getByRole('columnheader', { name: /value/i })).toBeInTheDocument(); expect(screen.getByRole('columnheader', { name: /reference range/i })).toBeInTheDocument(); expect(screen.getByText('05-Sept-2021, 04:07 AM')).toBeInTheDocument(); expect(screen.getByText('5')).toBeInTheDocument(); expect(screen.getByText('11-May-2023, 11:02 AM')).toBeInTheDocument(); expect(screen.getByText('6')).toBeInTheDocument(); // Both rows show the same concept-level range (no observation-level ranges in test data) expect(screen.getAllByText('1 – 10 mg/dL')).toHaveLength(2); }); it('toggles the results table visibility when the show results table button is clicked', async () => { const user = userEvent.setup(); mockUseObstreeData.mockReturnValue({ trendlineData: { obs: [{ obsDatetime: '2023-01-01', value: '5', interpretation: 'NORMAL' }], flatName: '', display: 'Test Chart Title', hiNormal: 10, lowNormal: 1, units: 'mg/dL', }, isLoading: false, isValidating: false, }); render(); const toggleButton = screen.getByRole('button', { name: /show results table/i }); await user.click(toggleButton); expect(screen.getByRole('button', { name: /hide results table/i })).toBeInTheDocument(); }); it('shows reference range in the tooltip content', () => { mockUseObstreeData.mockReturnValue({ trendlineData: { obs: [ { obsDatetime: '2023-01-01T00:00:00.000Z', value: '5', interpretation: 'NORMAL', lowNormal: 10, hiNormal: 12, }, ], display: 'Test Chart Title', hiNormal: 20, lowNormal: 1, units: 'mg/dL', flatName: '', }, isLoading: false, isValidating: false, }); render(); expect(mockLineChart).toHaveBeenCalled(); const calls = mockLineChart.mock.calls as unknown as Array< [ { data: Array<{ date: Date; value: number; group: string; min?: number; max?: number; rangeLabel?: string }>; options: { tooltip: { customHTML: (data: Array) => string } }; }, ] >; const chartProps = calls[0][0]; const tooltipHtml = chartProps.options.tooltip.customHTML([chartProps.data[0]]); expect(tooltipHtml).toContain('Range'); expect(tooltipHtml).toContain('10 – 12 mg/dL'); }); it('falls back to concept-level range for the tooltip when observation range is missing', () => { mockUseObstreeData.mockReturnValue({ trendlineData: { obs: [ { obsDatetime: '2023-01-01T00:00:00.000Z', value: '5', interpretation: 'NORMAL', }, ], display: 'Test Chart Title', hiNormal: 10, lowNormal: 1, units: 'mg/dL', flatName: '', }, isLoading: false, isValidating: false, }); render(); const calls = mockLineChart.mock.calls as unknown as Array< [ { data: Array<{ date: Date; value: number; group: string; min?: number; max?: number; rangeLabel?: string }>; options: { tooltip: { customHTML: (data: Array) => string } }; }, ] >; const chartProps = calls[0][0]; const tooltipHtml = chartProps.options.tooltip.customHTML([chartProps.data[0]]); expect(tooltipHtml).toContain('1 – 10 mg/dL'); }); });