diff --git a/components/PredictionCard.tsx b/components/PredictionCard.tsx index 8216213..fd8ec92 100644 --- a/components/PredictionCard.tsx +++ b/components/PredictionCard.tsx @@ -8,6 +8,7 @@ import { OutcomeIcon } from '@/components/icons/OutcomeIcons'; import type { OutcomeVariant } from '@/components/icons/OutcomeIcons'; import { getCategoryEmojiUrl } from '@/lib/categories/emojiMap'; import { Skeleton } from '@/components/ui/skeleton'; +import { Tooltip } from '@/app/components/Tooltip'; interface PredictionCardProps { /** The prediction data to display. When omitted, a themed skeleton is rendered. */ @@ -22,6 +23,14 @@ const statusMap: Record = { + won: 'This prediction was correct', + lost: 'This prediction was incorrect', + pending: 'This prediction is awaiting resolution', + active: 'This prediction is currently active', +}; + /** * Maps a PredictionStatus to a shape-based OutcomeVariant so that status * differentiation does not rely on color alone (WCAG 2.1 AA 1.4.1). @@ -129,16 +138,16 @@ const PredictionCard: React.FC = ({ prediction }) => { )} - {/* - * Shape icon (color-blind safe) — rendered BEFORE the status icon. - * aria-hidden because the Badge's aria-label already names the status. - * Distinguishable by shape under Deuteranopia & Tritanopia simulations. - */} - - @@ -156,14 +165,19 @@ const PredictionCard: React.FC = ({ prediction }) => { {/* touch-target: guarantees ≥44px tap area on the Odds trigger (WCAG 2.5.5). */} - + +

Implied probability: {(1 / odds * 100).toFixed(1)}%

@@ -196,4 +210,4 @@ const PredictionCard: React.FC = ({ prediction }) => { ); }; -export default PredictionCard; +export default PredictionCard; \ No newline at end of file diff --git a/components/__tests__/PredictionCard.test.tsx b/components/__tests__/PredictionCard.test.tsx index 10fbd22..5504fa5 100644 --- a/components/__tests__/PredictionCard.test.tsx +++ b/components/__tests__/PredictionCard.test.tsx @@ -161,6 +161,82 @@ describe('PredictionsList loading state', () => { }); }); +// --- Tooltip Tests --- + +describe('PredictionCard tooltips', () => { + it('renders tooltip content on the status icon when hovered', async () => { + const user = userEvent.setup(); + render(); + + // The Tooltip component wraps the icon. On hover, the tooltip content should appear. + // Instead of targeting the icon directly (which is aria-hidden), hover the status badge area. + const statusBadge = screen.getByLabelText('Status: Active'); + await user.hover(statusBadge); + + // The tooltip for 'active' status should appear + expect(await screen.findByText('This prediction is currently active')).toBeInTheDocument(); + }); + + it('renders correct tooltip content for each status variant', async () => { + const user = userEvent.setup(); + const statuses: Array<{ status: Prediction['status']; tooltip: string }> = [ + { status: 'won', tooltip: 'This prediction was correct' }, + { status: 'lost', tooltip: 'This prediction was incorrect' }, + { status: 'pending', tooltip: 'This prediction is awaiting resolution' }, + ]; + + for (const { status, tooltip } of statuses) { + const prediction: Prediction = { + ...mockPrediction, + id: `${status}-test`, + status, + ...(status === 'won' || status === 'lost' ? { resolvedDate: '01/06/2023' } : {}), + }; + const { unmount } = render(); + + const badge = screen.getByLabelText(`Status: ${status.charAt(0).toUpperCase() + status.slice(1)}`); + await user.hover(badge); + expect(await screen.findByText(tooltip)).toBeInTheDocument(); + + unmount(); + } + }); + + it('shows odds expand/collapse tooltip on the odds trigger', async () => { + const user = userEvent.setup(); + render(); + + // Hover over the odds trigger button + const oddsTrigger = document.querySelector('[aria-controls="odds-breakdown"]') as HTMLElement; + await user.hover(oddsTrigger); + + // Should show the expand tooltip initially + expect(await screen.findByText('Click to expand odds details')).toBeInTheDocument(); + }); + + it('updates odds tooltip after expanding', async () => { + const user = userEvent.setup(); + render(); + + const oddsTrigger = document.querySelector('[aria-controls="odds-breakdown"]') as HTMLElement; + + // Click to expand + await user.click(oddsTrigger); + expect(oddsTrigger).toHaveAttribute('aria-expanded', 'true'); + + // Now hover again - should show "collapse" tooltip + await user.hover(oddsTrigger); + expect(await screen.findByText('Click to collapse odds details')).toBeInTheDocument(); + }); + + it('does not show tooltip content by default (no hover)', () => { + render(); + + expect(screen.queryByText('This prediction is currently active')).toBeNull(); + expect(screen.queryByText('Click to expand odds details')).toBeNull(); + }); +}); + // --- Touch Target Tests (WCAG 2.5.5 / Apple HIG ≥44px) --- describe('PredictionCard touch targets', () => { @@ -194,4 +270,4 @@ describe('PredictionCard touch targets', () => { await user.click(oddsTrigger); expect(oddsTrigger).toHaveAttribute('aria-expanded', 'true'); }); -}); +}); \ No newline at end of file