diff --git a/hooks/useRecentSearches.accessibility.test.ts b/hooks/useRecentSearches.accessibility.test.ts new file mode 100644 index 000000000..0d00af84e --- /dev/null +++ b/hooks/useRecentSearches.accessibility.test.ts @@ -0,0 +1,84 @@ +import { renderHook, act } from '@testing-library/react'; +import { describe, expect, it, beforeEach, vi } from 'vitest'; +import { useRecentSearches, STORAGE_KEY, MAX_SEARCHES } from './useRecentSearches'; + +describe('useRecentSearches - State Persistence & Array Ordering Operations', () => { + beforeEach(() => { + window.localStorage.clear(); + vi.clearAllMocks(); + }); + + // Test Case 1: Verifies loadFromStorage() integration on mount + it('hydrates state correctly from localStorage during the component initialization phase', () => { + const preExistingSearches = ['remix', 'astro']; + window.localStorage.setItem(STORAGE_KEY, JSON.stringify(preExistingSearches)); + + const { result } = renderHook(() => useRecentSearches()); + + // Confirms the mounting useEffect successfully reads and populates historical storage data + expect(result.current.searches).toEqual(preExistingSearches); + }); + + // Test Case 2: Verifies writeStorage() lifecycle updates + it('persists newly appended searches to localStorage on state changes', () => { + const { result } = renderHook(() => useRecentSearches()); + + act(() => { + result.current.addSearch('vue'); + }); + + expect(result.current.searches).toEqual(['vue']); + expect(JSON.parse(window.localStorage.getItem(STORAGE_KEY) || '[]')).toEqual(['vue']); + }); + + // Test Case 3: Verifies exact deduplication array shuffling behavior + it('moves duplicate search entries to the absolute front of the stack rather than stacking them', () => { + const { result } = renderHook(() => useRecentSearches()); + + act(() => { + result.current.addSearch('typescript'); + result.current.addSearch('javascript'); + }); + expect(result.current.searches).toEqual(['javascript', 'typescript']); + + act(() => { + // Re-adding 'typescript' should slide it to index 0, not append it + result.current.addSearch('typescript'); + }); + expect(result.current.searches).toEqual(['typescript', 'javascript']); + }); + + // Test Case 4: Verifies empty string rejection constraints + it('completely ignores empty strings or whitespace-only queries without modifying active state', () => { + const { result } = renderHook(() => useRecentSearches()); + + act(() => { + result.current.addSearch(''); + result.current.addSearch(' '); + }); + + expect(result.current.searches).toEqual([]); + }); + + // Test Case 5: Verifies targeted item eviction and state clearing boundaries + it('evicts only the specified single item when calling removeSearch and purges storage when cleared', () => { + const { result } = renderHook(() => useRecentSearches()); + + act(() => { + result.current.addSearch('tailwindcss'); + result.current.addSearch('bootstrap'); + result.current.addSearch('shadcn'); + }); + + act(() => { + result.current.removeSearch('bootstrap'); + }); + expect(result.current.searches).toEqual(['shadcn', 'tailwindcss']); + + act(() => { + result.current.clearSearches(); + }); + expect(result.current.searches).toEqual([]); + expect(window.localStorage.getItem(STORAGE_KEY)).toBeNull(); + }); +});