// src/hooks/useAppInitialization.test.tsx import { renderHook, waitFor } from '@testing-library/react'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { MemoryRouter, useNavigate } from 'react-router-dom'; import { useAppInitialization } from './useAppInitialization'; import { useAuth } from './useAuth'; import { useModal } from './useModal'; import { createMockUserProfile } from '../tests/utils/mockFactories'; // Mock dependencies vi.mock('./useAuth'); vi.mock('./useModal'); vi.mock('react-router-dom', async (importOriginal) => { const actual = await importOriginal(); return { ...actual, useNavigate: vi.fn(), }; }); vi.mock('../services/logger.client'); vi.mock('../config', () => ({ default: { app: { version: '1.0.1' }, }, })); const mockedUseAuth = vi.mocked(useAuth); const mockedUseModal = vi.mocked(useModal); const mockedUseNavigate = vi.mocked(useNavigate); const mockLogin = vi.fn().mockResolvedValue(undefined); const mockNavigate = vi.fn(); const mockOpenModal = vi.fn(); // Wrapper with MemoryRouter is needed because the hook uses useLocation and useNavigate const wrapper = ({ children, initialEntries = ['/'], }: { children: React.ReactNode; initialEntries?: string[]; }) => {children}; describe('useAppInitialization Hook', () => { beforeEach(() => { vi.clearAllMocks(); mockedUseNavigate.mockReturnValue(mockNavigate); mockedUseAuth.mockReturnValue({ userProfile: null, login: mockLogin, authStatus: 'SIGNED_OUT', isLoading: false, logout: vi.fn(), updateProfile: vi.fn(), }); mockedUseModal.mockReturnValue({ openModal: mockOpenModal, closeModal: vi.fn(), isModalOpen: vi.fn(), }); // Mock localStorage Object.defineProperty(window, 'localStorage', { value: { getItem: vi.fn().mockReturnValue(null), setItem: vi.fn(), removeItem: vi.fn(), clear: vi.fn(), }, writable: true, }); // Mock matchMedia Object.defineProperty(window, 'matchMedia', { value: vi.fn().mockImplementation((_query) => ({ matches: false, // default to light mode })), writable: true, configurable: true, }); }); it('should call login when googleAuthToken is in URL', async () => { renderHook(() => useAppInitialization(), { wrapper: (props) => wrapper({ ...props, initialEntries: ['/?googleAuthToken=test-token'] }), }); await waitFor(() => { expect(mockLogin).toHaveBeenCalledWith('test-token'); }); }); it('should call login when githubAuthToken is in URL', async () => { renderHook(() => useAppInitialization(), { wrapper: (props) => wrapper({ ...props, initialEntries: ['/?githubAuthToken=test-token'] }), }); await waitFor(() => { expect(mockLogin).toHaveBeenCalledWith('test-token'); }); }); it('should call navigate to clean the URL after processing a token', async () => { renderHook(() => useAppInitialization(), { wrapper: (props) => wrapper({ ...props, initialEntries: ['/some/path?googleAuthToken=test-token'] }), }); await waitFor(() => { expect(mockLogin).toHaveBeenCalledWith('test-token'); }); expect(mockNavigate).toHaveBeenCalledWith('/some/path', { replace: true }); }); it('should open "What\'s New" modal if version is new', () => { vi.spyOn(window.localStorage, 'getItem').mockReturnValue('1.0.0'); renderHook(() => useAppInitialization(), { wrapper }); expect(mockOpenModal).toHaveBeenCalledWith('whatsNew'); expect(window.localStorage.setItem).toHaveBeenCalledWith('lastSeenVersion', '1.0.1'); }); it('should not open "What\'s New" modal if version is the same', () => { vi.spyOn(window.localStorage, 'getItem').mockReturnValue('1.0.1'); renderHook(() => useAppInitialization(), { wrapper }); expect(mockOpenModal).not.toHaveBeenCalled(); }); it('should set dark mode from user profile', async () => { mockedUseAuth.mockReturnValue({ ...mockedUseAuth(), userProfile: createMockUserProfile({ preferences: { darkMode: true } }), }); const { result } = renderHook(() => useAppInitialization(), { wrapper }); await waitFor(() => { expect(result.current.isDarkMode).toBe(true); expect(document.documentElement.classList.contains('dark')).toBe(true); }); }); it('should set dark mode from localStorage', async () => { vi.spyOn(window.localStorage, 'getItem').mockImplementation((key) => key === 'darkMode' ? 'true' : null, ); const { result } = renderHook(() => useAppInitialization(), { wrapper }); await waitFor(() => { expect(result.current.isDarkMode).toBe(true); expect(document.documentElement.classList.contains('dark')).toBe(true); }); }); it('should set dark mode from system preference', async () => { vi.spyOn(window, 'matchMedia').mockReturnValue({ matches: true } as any); const { result } = renderHook(() => useAppInitialization(), { wrapper }); await waitFor(() => { expect(result.current.isDarkMode).toBe(true); expect(document.documentElement.classList.contains('dark')).toBe(true); }); }); it('should set unit system from user profile', async () => { mockedUseAuth.mockReturnValue({ ...mockedUseAuth(), userProfile: createMockUserProfile({ preferences: { unitSystem: 'metric' } }), }); const { result } = renderHook(() => useAppInitialization(), { wrapper }); await waitFor(() => { expect(result.current.unitSystem).toBe('metric'); }); }); it('should set unit system from localStorage', async () => { vi.spyOn(window.localStorage, 'getItem').mockImplementation((key) => key === 'unitSystem' ? 'metric' : null, ); const { result } = renderHook(() => useAppInitialization(), { wrapper }); await waitFor(() => { expect(result.current.unitSystem).toBe('metric'); }); }); });