import { renderHook, act } from '@testing-library/react'; import { describe, it, expect, beforeEach, vi } from 'vitest'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { LanguageProvider, useLanguage, DEFAULT_LANGUAGES, type LanguageDefinition, } from './LanguageContext'; import React from 'react'; function makeWrapper(props?: Partial>) { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } }); return ({ children }: { children: React.ReactNode }) => ( {children} ); } describe('LanguageContext', () => { beforeEach(() => { window.localStorage.clear(); }); describe('default behavior', () => { it('provides default language (pt-BR)', () => { const { result } = renderHook(() => useLanguage(), { wrapper: makeWrapper() }); expect(result.current.language).toBe('pt-BR'); }); it('exposes the default three languages', () => { const { result } = renderHook(() => useLanguage(), { wrapper: makeWrapper() }); expect(result.current.availableLanguages).toHaveLength(3); expect(result.current.availableLanguages.map(l => l.code)).toEqual(['pt-BR', 'en', 'es']); }); it('is not monolingual when using defaults', () => { const { result } = renderHook(() => useLanguage(), { wrapper: makeWrapper() }); expect(result.current.isMonolingual).toBe(false); }); it('updates language', () => { const { result } = renderHook(() => useLanguage(), { wrapper: makeWrapper() }); act(() => result.current.setLanguage('en')); expect(result.current.language).toBe('en'); }); }); describe('monolingual mode', () => { const englishOnly: LanguageDefinition[] = [{ code: 'en', label: 'English' }]; it('reports isMonolingual = true with a single language', () => { const { result } = renderHook(() => useLanguage(), { wrapper: makeWrapper({ availableLanguages: englishOnly }), }); expect(result.current.isMonolingual).toBe(true); expect(result.current.availableLanguages).toEqual(englishOnly); }); it('defaults to the only language available', () => { const { result } = renderHook(() => useLanguage(), { wrapper: makeWrapper({ availableLanguages: englishOnly }), }); expect(result.current.language).toBe('en'); }); }); describe('custom languages', () => { it('accepts extra languages beyond the defaults', () => { const extended: LanguageDefinition[] = [ ...DEFAULT_LANGUAGES, { code: 'fr', label: 'Français', shortLabel: 'FR' }, ]; const { result } = renderHook(() => useLanguage(), { wrapper: makeWrapper({ availableLanguages: extended }), }); expect(result.current.availableLanguages).toHaveLength(4); act(() => result.current.setLanguage('fr')); expect(result.current.language).toBe('fr'); }); it('ignores attempts to switch to a language not in the list', () => { const { result } = renderHook(() => useLanguage(), { wrapper: makeWrapper({ availableLanguages: [{ code: 'en', label: 'English' }] }), }); act(() => result.current.setLanguage('xx-XX' as any)); // language should remain 'en' (the only valid option) expect(result.current.language).toBe('en'); }); }); describe('defaultLanguage prop', () => { it('respects an explicit defaultLanguage', () => { const { result } = renderHook(() => useLanguage(), { wrapper: makeWrapper({ defaultLanguage: 'es' }), }); expect(result.current.language).toBe('es'); }); }); describe('error handling', () => { it('throws when availableLanguages is an empty array', () => { // Suppress React's error logging for this expected throw const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); expect(() => renderHook(() => useLanguage(), { wrapper: makeWrapper({ availableLanguages: [] }) }) ).toThrow(/availableLanguages.*cannot be empty/); consoleErrorSpy.mockRestore(); }); }); describe('reference stability', () => { it('keeps setLanguage stable across re-renders with the same languages', () => { const { result, rerender } = renderHook(() => useLanguage(), { wrapper: makeWrapper() }); const firstSetLanguage = result.current.setLanguage; rerender(); expect(result.current.setLanguage).toBe(firstSetLanguage); }); }); });