diff --git a/app/compare/CompareClient.a11y-keyboard.test.tsx b/app/compare/CompareClient.a11y-keyboard.test.tsx new file mode 100644 index 000000000..0e925d0b6 --- /dev/null +++ b/app/compare/CompareClient.a11y-keyboard.test.tsx @@ -0,0 +1,63 @@ +import { render, screen } from '@testing-library/react'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import CompareClient from './CompareClient'; + +vi.mock('next/navigation', () => ({ + useSearchParams: () => new URLSearchParams(), + useRouter: () => ({ push: vi.fn(), replace: vi.fn() }), +})); + +vi.mock('@/hooks/useRecentSearches', () => ({ + useRecentSearches: () => ({ + searches: ['octocat vs dinesh9997'], + addSearch: vi.fn(), + removeSearch: vi.fn(), + clearSearches: vi.fn(), + }), +})); + +describe('CompareClient Keyboard Navigation & ARIA Accessibility', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('renders input fields with accessible ARIA labels', () => { + render(); + + const user1Input = screen.getByLabelText(/enter first github username to compare/i); + const user2Input = screen.getByLabelText(/enter second github username to compare/i); + + expect(user1Input).toBeInTheDocument(); + expect(user2Input).toBeInTheDocument(); + }); + + it('renders submit compare button with proper aria-label', () => { + render(); + + const compareBtn = screen.getByRole('button', { name: /compare two github profiles/i }); + + expect(compareBtn).toBeInTheDocument(); + }); + + it('includes focus-visible class names on interactive controls', () => { + render(); + + const user1Input = screen.getByLabelText(/enter first github username to compare/i); + const compareBtn = screen.getByRole('button', { name: /compare two github profiles/i }); + + expect(user1Input.className).toContain('focus-visible:ring-2'); + expect(compareBtn.className).toContain('focus-visible:ring-2'); + }); + + it('renders recent comparison history tags with accessible remove buttons', () => { + render(); + + const historyBtn = screen.getByRole('button', { name: 'octocat vs dinesh9997' }); + const removeBtn = screen.getByRole('button', { + name: /remove octocat vs dinesh9997 from recent comparisions/i, + }); + + expect(historyBtn).toBeInTheDocument(); + expect(removeBtn).toBeInTheDocument(); + }); +}); diff --git a/app/compare/CompareClient.tsx b/app/compare/CompareClient.tsx index 55ca8bed4..09dd6c4c5 100644 --- a/app/compare/CompareClient.tsx +++ b/app/compare/CompareClient.tsx @@ -1,7 +1,6 @@ 'use client'; import { copyToClipboard } from '@/utils/clipboard'; -import Image from 'next/image'; import { useState, useEffect, useCallback, useRef } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import TopRivalriesTicker from '@/components/TopRivalriesTicker'; @@ -1308,7 +1307,7 @@ export default function CompareClient() { setUser1Error(''); }} onKeyDown={(e) => e.key === 'Enter' && handleCompare(user1, user2)} - className="w-full pl-10 pr-4 py-3 rounded-xl border border-black/10 dark:border-[rgba(255,255,255,0.1)] bg-white dark:bg-[#0a0a0a] text-gray-900 dark:text-white text-sm placeholder:text-[#A1A1AA] focus:outline-none focus:border-emerald-500/50 transition-colors" + className="w-full pl-10 pr-4 py-3 rounded-xl border border-black/10 dark:border-[rgba(255,255,255,0.1)] bg-white dark:bg-[#0a0a0a] text-gray-900 dark:text-white text-sm placeholder:text-[#A1A1AA] focus:outline-none focus:border-emerald-500/50 focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:outline-none transition-colors" /> {user1Error && ( @@ -1337,7 +1336,7 @@ export default function CompareClient() { setUser2Error(''); }} onKeyDown={(e) => e.key === 'Enter' && handleCompare(user1, user2)} - className="w-full pl-10 pr-4 py-3 rounded-xl border border-black/10 dark:border-[rgba(255,255,255,0.1)] bg-white dark:bg-[#0a0a0a] text-gray-900 dark:text-white text-sm placeholder:text-[#A1A1AA] focus:outline-none focus:border-emerald-500/50 transition-colors" + className="w-full pl-10 pr-4 py-3 rounded-xl border border-black/10 dark:border-[rgba(255,255,255,0.1)] bg-white dark:bg-[#0a0a0a] text-gray-900 dark:text-white text-sm placeholder:text-[#A1A1AA] focus:outline-none focus:border-emerald-500/50 focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:outline-none transition-colors" /> {user2Error && ( @@ -1352,7 +1351,7 @@ export default function CompareClient() { onClick={() => handleCompare(user1, user2)} disabled={loading} aria-label="Compare two GitHub profiles" - className="flex items-center justify-center gap-2 px-6 py-3 rounded-xl bg-black dark:bg-white text-white dark:text-black text-sm font-semibold hover:bg-zinc-800 dark:hover:bg-zinc-100 transition-colors disabled:opacity-50 disabled:cursor-not-allowed" + className="flex items-center justify-center gap-2 px-6 py-3 rounded-xl bg-black dark:bg-white text-white dark:text-black text-sm font-semibold hover:bg-zinc-800 dark:hover:bg-zinc-100 focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:outline-none transition-colors disabled:opacity-50 disabled:cursor-not-allowed" > {loading ? : } {loading ? 'Comparing...' : 'Compare'} @@ -1370,7 +1369,7 @@ export default function CompareClient() { @@ -1394,14 +1393,14 @@ export default function CompareClient() { handleCompare(left, right); } }} - className="text-xs text-gray-700 dark:text-gray-300 hover:text-emerald-500 transition-colors" + className="text-xs text-gray-700 dark:text-gray-300 hover:text-emerald-500 focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:outline-none rounded px-1 transition-colors" > {search}