Skip to content
61 changes: 61 additions & 0 deletions apps/dashboard/src/api/__tests__/graphqlService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import { graphqlClient } from '../graphqlClient';
import {
useApprovePARequest,
useDenyPARequest,
useProcessPARequest,
useConnectionStatus,
QUERY_KEYS,
} from '../graphqlService';

const mockRequest = vi.mocked(graphqlClient.request);
Expand Down Expand Up @@ -75,6 +77,65 @@ describe('useDenyPARequest', () => {
});
});

describe('useProcessPARequest', () => {
beforeEach(() => {
vi.clearAllMocks();
});

it('should call processPARequest mutation with correct id', async () => {
mockRequest.mockResolvedValueOnce({
processPARequest: { id: 'PA-001', status: 'ready' },
});

const { result } = renderHook(() => useProcessPARequest(), {
wrapper: createWrapper(),
});

result.current.mutate('PA-001');

await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(mockRequest).toHaveBeenCalledOnce();
const callArgs = mockRequest.mock.calls[0];
expect(callArgs[0]).toContain('processPARequest');
expect(callArgs[1]).toEqual({ id: 'PA-001' });
Comment on lines +98 to +100
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix TypeScript tuple access error causing pipeline failure.

The pipeline reports TS2493: Tuple type '[options: RequestOptions<object, unknown>]' of length '1' has no element at index '1'. The mock type inference doesn't recognize the second argument. Apply the same workaround used in useDenyPARequest test at line 74.

🐛 Proposed fix
-    const callArgs = mockRequest.mock.calls[0];
+    const callArgs = mockRequest.mock.calls[0] as unknown[];
     expect(callArgs[0]).toContain('processPARequest');
     expect(callArgs[1]).toEqual({ id: 'PA-001' });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const callArgs = mockRequest.mock.calls[0];
expect(callArgs[0]).toContain('processPARequest');
expect(callArgs[1]).toEqual({ id: 'PA-001' });
const callArgs = mockRequest.mock.calls[0] as unknown[];
expect(callArgs[0]).toContain('processPARequest');
expect(callArgs[1]).toEqual({ id: 'PA-001' });
🧰 Tools
🪛 GitHub Actions: CI

[error] 100-100: TS2493: Tuple type '[options: RequestOptions<object, unknown>]' of length '1' has no element at index '1'.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/api/__tests__/graphqlService.test.ts` around lines 98 -
100, The TypeScript tuple error comes from accessing
mockRequest.mock.calls[0][1]; fix it by asserting the call tuple as a flexible
array like the other test: change the assignment of callArgs to cast the mock
call to an any[] (e.g., const callArgs = mockRequest.mock.calls[0] as unknown as
any[]), then keep the checks expect(callArgs[0]).toContain('processPARequest')
and expect(callArgs[1]).toEqual({ id: 'PA-001' }) so TypeScript no longer
complains when accessing index 1.

});

it('useProcessPARequest_InvalidatesCorrectQueries_OnSuccess', async () => {
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
});

// Seed the cache with data for each query key we expect to be invalidated
queryClient.setQueryData(QUERY_KEYS.paRequests, []);
queryClient.setQueryData(QUERY_KEYS.paRequest('PA-002'), { id: 'PA-002' });
queryClient.setQueryData(QUERY_KEYS.paStats, { ready: 0 });
queryClient.setQueryData(QUERY_KEYS.activity, []);

const wrapper = ({ children }: { children: React.ReactNode }) =>
React.createElement(QueryClientProvider, { client: queryClient }, children);

mockRequest.mockResolvedValueOnce({
processPARequest: { id: 'PA-002', status: 'ready' },
});

const { result } = renderHook(() => useProcessPARequest(), { wrapper });

// Spy on invalidateQueries
const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries');

result.current.mutate('PA-002');

await waitFor(() => expect(result.current.isSuccess).toBe(true));

// Verify all four query keys are invalidated
const invalidatedKeys = invalidateSpy.mock.calls.map(call => call[0]?.queryKey);
expect(invalidatedKeys).toContainEqual(QUERY_KEYS.paRequests);
expect(invalidatedKeys).toContainEqual(QUERY_KEYS.paRequest('PA-002'));
expect(invalidatedKeys).toContainEqual(QUERY_KEYS.paStats);
expect(invalidatedKeys).toContainEqual(QUERY_KEYS.activity);
});
});

describe('useConnectionStatus', () => {
beforeEach(() => {
vi.clearAllMocks();
Expand Down
110 changes: 110 additions & 0 deletions apps/dashboard/src/components/__tests__/EvidencePanel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,114 @@ describe('EvidencePanel', () => {
expect(screen.getByTestId('evidence-skeleton')).toBeInTheDocument();
});
});

describe('Intelligence-shaped evidence', () => {
it('EvidencePanel_WithIntelligenceEvidence_DisplaysCriterionIdAsLabel', () => {
const intelligenceEvidence: EvidenceItem[] = [
{
criterionId: 'conservative_therapy',
status: 'MET',
evidence: 'Patient completed 8 weeks of physical therapy and NSAID treatment',
source: 'Clinical Notes - 2026-01-15',
confidence: 0.95,
},
{
criterionId: 'medical_necessity',
status: 'MET',
evidence: 'MRI indicated for evaluation of persistent symptoms',
source: 'Order Entry - 2026-01-20',
confidence: 0.88,
},
];
render(<EvidencePanel evidence={intelligenceEvidence} />);

expect(screen.getByText('Conservative Therapy')).toBeInTheDocument();
expect(screen.getByText('Medical Necessity')).toBeInTheDocument();
});

it('EvidencePanel_WithUnclearStatus_ShowsDistinctBadge', () => {
const evidence: EvidenceItem[] = [
{
criterionId: 'diagnosis_present',
status: 'UNCLEAR',
evidence: 'Diagnosis code needs verification',
source: 'System',
confidence: 0.5,
},
];
render(<EvidencePanel evidence={evidence} />);

const badge = screen.getByText('Unclear');
expect(badge).toBeInTheDocument();
// UNCLEAR uses warning styling
expect(badge.className).toMatch(/bg-warning/);
expect(badge.className).toMatch(/text-warning/);
// Should NOT use MET (success) or NOT_MET (destructive) styling
expect(badge.className).not.toMatch(/bg-success/);
expect(badge.className).not.toMatch(/text-success/);
expect(badge.className).not.toMatch(/bg-destructive/);
expect(badge.className).not.toMatch(/text-destructive/);
});

it('EvidencePanel_WithAllThreeStatuses_ShowsCorrectSummaryBreakdown', () => {
const mixedEvidence: EvidenceItem[] = [
{
criterionId: 'conservative_therapy',
status: 'MET',
evidence: 'PT completed',
source: 'Notes',
confidence: 0.95,
},
{
criterionId: 'diagnosis_present',
status: 'UNCLEAR',
evidence: 'Needs verification',
source: 'System',
confidence: 0.5,
},
{
criterionId: 'imaging_prior',
status: 'NOT_MET',
evidence: 'No prior imaging found',
source: 'Radiology',
confidence: 0.9,
},
];
render(<EvidencePanel evidence={mixedEvidence} />);

expect(screen.getByText('3 criteria analyzed')).toBeInTheDocument();
});

it('EvidencePanel_WithLowConfidence_ShowsWarningColor', () => {
const evidence: EvidenceItem[] = [
{
criterionId: 'medical_necessity',
status: 'MET',
evidence: 'Some evidence found',
source: 'Notes',
confidence: 0.35,
},
];
render(<EvidencePanel evidence={evidence} />);

// 35% confidence should show destructive color (below 0.5 threshold)
expect(screen.getByText(/35%/)).toBeInTheDocument();
});

it('EvidencePanel_WithBorderlineConfidence_ShowsCorrectColor', () => {
const evidence: EvidenceItem[] = [
{
criterionId: 'conservative_therapy',
status: 'MET',
evidence: 'Partial documentation',
source: 'Notes',
confidence: 0.5,
},
];
render(<EvidencePanel evidence={evidence} />);

// 50% is at the boundary — should show warning color
expect(screen.getByText(/50%/)).toBeInTheDocument();
});
});
});
148 changes: 148 additions & 0 deletions apps/dashboard/src/components/__tests__/PARequestCard.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { describe, it, expect, vi } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { PARequestCard, type PARequest } from '../PARequestCard';

// Mock @tanstack/react-router Link component
vi.mock('@tanstack/react-router', () => ({
Link: ({ children, to, params }: { children: React.ReactNode; to: string; params?: Record<string, string> }) => (
<a href={`${to}/${params?.transactionId ?? ''}`} data-testid="router-link">
{children}
</a>
),
}));

function createMockPARequest(overrides: Partial<PARequest> = {}): PARequest {
return {
id: 'PA-001',
patientName: 'Jane Smith',
patientId: 'MRN-12345',
procedureCode: '72148',
procedureName: 'MRI Lumbar Spine',
payer: 'Blue Cross Blue Shield',
currentStep: 'process',
createdAt: new Date().toISOString(),
encounterId: 'ENC-001',
...overrides,
};
}

describe('PARequestCard', () => {
describe('rendering', () => {
it('PARequestCard_WithValidRequest_DisplaysPatientInfo', () => {
const request = createMockPARequest();
render(<PARequestCard request={request} />);

expect(screen.getByText('Jane Smith')).toBeInTheDocument();
expect(screen.getByText(/MRN-12345/)).toBeInTheDocument();
});

it('PARequestCard_WithValidRequest_DisplaysProcedure', () => {
const request = createMockPARequest();
render(<PARequestCard request={request} />);

expect(screen.getByText('72148')).toBeInTheDocument();
expect(screen.getByText('MRI Lumbar Spine')).toBeInTheDocument();
});

it('PARequestCard_WithValidRequest_DisplaysPayer', () => {
const request = createMockPARequest();
render(<PARequestCard request={request} />);

expect(screen.getByText('Blue Cross Blue Shield')).toBeInTheDocument();
});
});

describe('confidence display', () => {
it('PARequestCard_WithHighConfidence_DisplaysPercentage', () => {
const request = createMockPARequest({ confidenceScore: 0.92 });
render(<PARequestCard request={request} />);

expect(screen.getByText(/92%/)).toBeInTheDocument();
});

it('PARequestCard_WithMediumConfidence_DisplaysPercentage', () => {
const request = createMockPARequest({ confidenceScore: 0.65 });
render(<PARequestCard request={request} />);

expect(screen.getByText(/65%/)).toBeInTheDocument();
});

it('PARequestCard_WithLowConfidence_DisplaysReviewBadge', () => {
const request = createMockPARequest({ confidenceScore: 0.42 });
render(<PARequestCard request={request} />);

// Low confidence (< 0.5) shows "Review" text in a badge (data-slot="badge")
const badges = screen.getAllByText('Review');
const confidenceBadge = badges.find(el => el.getAttribute('data-slot') === 'badge');
expect(confidenceBadge).toBeDefined();
});

it('PARequestCard_WithUndefinedConfidence_ShowsNoConfidenceBadge', () => {
const request = createMockPARequest({ confidenceScore: undefined });
render(<PARequestCard request={request} />);

// No confidence percentage badge should render
expect(screen.queryByText(/\d+%/)).not.toBeInTheDocument();
// The only "Review" text should be from the WorkflowProgress step, not a badge
const reviewElements = screen.queryAllByText('Review');
const confidenceBadge = reviewElements.find(el => el.getAttribute('data-slot') === 'badge');
expect(confidenceBadge).toBeUndefined();
});

it('PARequestCard_WithRealConfidenceRange_DisplaysCorrectly', () => {
// Test with various realistic confidence values from Intelligence
const request = createMockPARequest({ confidenceScore: 0.78 });
render(<PARequestCard request={request} />);

expect(screen.getByText(/78%/)).toBeInTheDocument();
});
});

describe('workflow states', () => {
it('PARequestCard_InProcessingState_ShowsProcessingIndicator', () => {
const request = createMockPARequest({ currentStep: 'process' });
render(<PARequestCard request={request} />);

expect(screen.getByText('Processing...')).toBeInTheDocument();
});

it('PARequestCard_InDeliverState_ShowsReviewButton', () => {
const request = createMockPARequest({ currentStep: 'deliver' });
render(<PARequestCard request={request} />);

expect(screen.getByText('Review & Confirm')).toBeInTheDocument();
});

it('PARequestCard_InReviewCompletedState_ShowsSubmittedMessage', () => {
const request = createMockPARequest({
currentStep: 'review',
stepStatuses: { review: 'completed' },
});
render(<PARequestCard request={request} />);

expect(screen.getByText('Submitted to athenahealth')).toBeInTheDocument();
});
});

describe('attention flag', () => {
it('PARequestCard_RequiresAttention_HasWarningRing', () => {
const request = createMockPARequest({ requiresAttention: true });
const { container } = render(<PARequestCard request={request} />);

const card = container.firstChild as HTMLElement;
expect(card.className).toMatch(/ring-warning/);
});
});

describe('callbacks', () => {
it('PARequestCard_OnReviewClick_CallsCallback', () => {
const onReview = vi.fn();
const request = createMockPARequest({ currentStep: 'deliver' });
render(<PARequestCard request={request} onReview={onReview} />);

fireEvent.click(screen.getByText('Review & Confirm'));

expect(onReview).toHaveBeenCalledWith('PA-001');
});
});
});
Loading
Loading