All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 33m19s
653 lines
23 KiB
TypeScript
653 lines
23 KiB
TypeScript
// src/App.test.tsx
|
|
import React from 'react';
|
|
import { render, screen, waitFor, fireEvent, within, act } from '@testing-library/react';
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { MemoryRouter } from 'react-router-dom';
|
|
import App from './App';
|
|
import * as aiApiClient from './services/aiApiClient'; // Import aiApiClient
|
|
import * as apiClient from './services/apiClient';
|
|
import { AppProviders } from './providers/AppProviders';
|
|
import type { Flyer, UserProfile } from './types';
|
|
import {
|
|
createMockFlyer,
|
|
createMockUserProfile,
|
|
createMockUser,
|
|
} from './tests/utils/mockFactories';
|
|
import {
|
|
mockUseAuth,
|
|
mockUseFlyers,
|
|
mockUseMasterItems,
|
|
mockUseUserData,
|
|
mockUseFlyerItems,
|
|
} from './tests/setup/mockHooks';
|
|
import './tests/setup/mockUI';
|
|
import { useAppInitialization } from './hooks/useAppInitialization';
|
|
|
|
// Mock top-level components rendered by App's routes
|
|
|
|
vi.mock('./components/Header', () => ({
|
|
Header: ({ onOpenProfile, onOpenVoiceAssistant }: any) => (
|
|
<div data-testid="header-mock">
|
|
<button onClick={onOpenProfile}>Open Profile</button>
|
|
<button onClick={onOpenVoiceAssistant}>Open Voice Assistant</button>
|
|
</div>
|
|
),
|
|
}));
|
|
|
|
vi.mock('./components/Footer', () => ({
|
|
Footer: () => <div data-testid="footer-mock">Mock Footer</div>,
|
|
}));
|
|
|
|
vi.mock('./layouts/MainLayout', async () => {
|
|
const { Outlet } = await vi.importActual<typeof import('react-router-dom')>('react-router-dom');
|
|
return {
|
|
MainLayout: () => (
|
|
<div data-testid="main-layout-mock">
|
|
<Outlet />
|
|
</div>
|
|
),
|
|
};
|
|
});
|
|
|
|
vi.mock('./pages/HomePage', () => ({
|
|
HomePage: ({ selectedFlyer, onOpenCorrectionTool }: any) => (
|
|
<div data-testid="home-page-mock" data-selected-flyer-id={selectedFlyer?.flyer_id}>
|
|
<button onClick={onOpenCorrectionTool}>Open Correction Tool</button>
|
|
</div>
|
|
),
|
|
}));
|
|
|
|
vi.mock('./pages/admin/AdminPage', () => ({
|
|
AdminPage: () => <div data-testid="admin-page-mock">AdminPage</div>,
|
|
}));
|
|
|
|
vi.mock('./pages/admin/CorrectionsPage', () => ({
|
|
CorrectionsPage: () => <div data-testid="corrections-page-mock">CorrectionsPage</div>,
|
|
}));
|
|
|
|
vi.mock('./pages/admin/AdminStatsPage', () => ({
|
|
AdminStatsPage: () => <div data-testid="admin-stats-page-mock">AdminStatsPage</div>,
|
|
}));
|
|
|
|
vi.mock('./pages/admin/FlyerReviewPage', () => ({
|
|
FlyerReviewPage: () => <div data-testid="flyer-review-page-mock">FlyerReviewPage</div>,
|
|
}));
|
|
|
|
vi.mock('./pages/VoiceLabPage', () => ({
|
|
VoiceLabPage: () => <div data-testid="voice-lab-page-mock">VoiceLabPage</div>,
|
|
}));
|
|
|
|
vi.mock('./pages/ResetPasswordPage', () => ({
|
|
ResetPasswordPage: () => <div data-testid="reset-password-page-mock">ResetPasswordPage</div>,
|
|
}));
|
|
|
|
vi.mock('./pages/admin/components/ProfileManager', () => ({
|
|
ProfileManager: ({ isOpen, onClose, onProfileUpdate, onLoginSuccess }: any) =>
|
|
isOpen ? (
|
|
<div data-testid="profile-manager-mock">
|
|
<button onClick={onClose}>Close Profile</button>
|
|
<button onClick={() => onProfileUpdate({ full_name: 'Updated' })}>Update Profile</button>
|
|
<button onClick={() => onLoginSuccess({}, 'token', false)}>Login</button>
|
|
</div>
|
|
) : null,
|
|
}));
|
|
|
|
vi.mock('./features/voice-assistant/VoiceAssistant', () => ({
|
|
VoiceAssistant: ({ isOpen, onClose }: any) =>
|
|
isOpen ? (
|
|
<div data-testid="voice-assistant-mock">
|
|
<button onClick={onClose}>Close Voice Assistant</button>
|
|
</div>
|
|
) : null,
|
|
}));
|
|
|
|
vi.mock('./components/FlyerCorrectionTool', () => ({
|
|
FlyerCorrectionTool: ({ isOpen, onClose, onDataExtracted }: any) =>
|
|
isOpen ? (
|
|
<div data-testid="flyer-correction-tool-mock">
|
|
<button onClick={onClose}>Close Correction</button>
|
|
<button onClick={() => onDataExtracted('store_name', 'New Store')}>Extract Store</button>
|
|
<button onClick={() => onDataExtracted('dates', 'New Dates')}>Extract Dates</button>
|
|
</div>
|
|
) : null,
|
|
}));
|
|
|
|
// Mock pdfjs-dist to prevent the "DOMMatrix is not defined" error in JSDOM.
|
|
// This must be done in any test file that imports App.tsx.
|
|
vi.mock('pdfjs-dist', () => ({
|
|
// pdfjsLib: { GlobalWorkerOptions: { workerSrc: '' } },
|
|
GlobalWorkerOptions: { workerSrc: '' },
|
|
getDocument: vi.fn(() => ({
|
|
promise: Promise.resolve({ getPage: vi.fn() }),
|
|
})),
|
|
}));
|
|
|
|
// Mock the new config module
|
|
vi.mock('./config', () => ({
|
|
default: {
|
|
app: { version: '20250101-1200:abc1234:1.0.0', commitMessage: 'Initial commit', commitUrl: '#' },
|
|
google: { mapsEmbedApiKey: 'mock-key' },
|
|
},
|
|
}));
|
|
|
|
// Explicitly mock the hooks to ensure the component uses our spies
|
|
vi.mock('./hooks/useFlyers', async () => {
|
|
const hooks = await import('./tests/setup/mockHooks');
|
|
return { useFlyers: hooks.mockUseFlyers };
|
|
});
|
|
|
|
vi.mock('./hooks/useFlyerItems', async () => {
|
|
const hooks = await import('./tests/setup/mockHooks');
|
|
return { useFlyerItems: hooks.mockUseFlyerItems };
|
|
});
|
|
|
|
vi.mock('./hooks/useAppInitialization');
|
|
const mockedUseAppInitialization = vi.mocked(useAppInitialization);
|
|
|
|
vi.mock('./hooks/useAuth', async () => {
|
|
const hooks = await import('./tests/setup/mockHooks');
|
|
return { useAuth: hooks.mockUseAuth };
|
|
});
|
|
|
|
vi.mock('./components/AppGuard', async () => {
|
|
// We need to use the real useModal hook inside our mock AppGuard
|
|
const { useModal } = await vi.importActual<typeof import('./hooks/useModal')>('./hooks/useModal');
|
|
return {
|
|
AppGuard: ({ children }: { children: React.ReactNode }) => {
|
|
const { isModalOpen } = useModal();
|
|
return (
|
|
<div data-testid="app-guard-mock">
|
|
{children}
|
|
{isModalOpen('whatsNew') && <div data-testid="whats-new-modal-mock" />}
|
|
</div>
|
|
);
|
|
},
|
|
};
|
|
});
|
|
|
|
const mockedAiApiClient = vi.mocked(aiApiClient);
|
|
const mockedApiClient = vi.mocked(apiClient);
|
|
|
|
const mockFlyers: Flyer[] = [
|
|
createMockFlyer({ flyer_id: 1, store: { store_id: 1, name: 'Old Store' } }),
|
|
createMockFlyer({ flyer_id: 2, store: { store_id: 2, name: 'Another Store' } }),
|
|
];
|
|
|
|
describe('App Component', () => {
|
|
beforeEach(() => {
|
|
console.log('[TEST DEBUG] beforeEach: Clearing mocks and setting up defaults');
|
|
vi.clearAllMocks();
|
|
// Default auth state: loading or guest
|
|
// Mock the login function to simulate a successful login. Signature: (token, profile)
|
|
const mockLogin = vi.fn(async (_token: string, _profile?: UserProfile) => {
|
|
await act(async () => {
|
|
// Simulate fetching profile after login
|
|
const profileResponse = await mockedApiClient.getAuthenticatedUserProfile();
|
|
const userProfileData: UserProfile = await profileResponse.json();
|
|
mockUseAuth.mockReturnValue({
|
|
userProfile: userProfileData,
|
|
authStatus: 'AUTHENTICATED',
|
|
isLoading: false,
|
|
login: mockLogin, // Self-reference the mock
|
|
logout: vi.fn(),
|
|
updateProfile: vi.fn(),
|
|
});
|
|
});
|
|
});
|
|
mockUseAuth.mockReturnValue({
|
|
userProfile: null,
|
|
authStatus: 'SIGNED_OUT',
|
|
isLoading: false, // Start with isLoading: false for most tests
|
|
login: mockLogin,
|
|
logout: vi.fn(),
|
|
updateProfile: vi.fn(),
|
|
});
|
|
|
|
// Default data states for the new hooks
|
|
mockUseFlyers.mockReturnValue({
|
|
flyers: mockFlyers,
|
|
isLoadingFlyers: false,
|
|
});
|
|
mockUseMasterItems.mockReturnValue({
|
|
masterItems: [],
|
|
isLoading: false,
|
|
});
|
|
mockUseUserData.mockReturnValue({
|
|
watchedItems: [],
|
|
shoppingLists: [],
|
|
isLoadingShoppingLists: false,
|
|
setWatchedItems: vi.fn(),
|
|
setShoppingLists: vi.fn(),
|
|
});
|
|
mockUseFlyerItems.mockReturnValue({
|
|
flyerItems: [],
|
|
isLoading: false,
|
|
error: null,
|
|
});
|
|
mockedUseAppInitialization.mockReturnValue({ isDarkMode: false, unitSystem: 'imperial' });
|
|
|
|
// Default mocks for API calls
|
|
// Use mockImplementation to create a new Response object for each call,
|
|
// preventing "Body has already been read" errors.
|
|
// Use mockImplementation to create a new Response object for each call,
|
|
// preventing "Body has already been read" errors.
|
|
mockedApiClient.fetchFlyers.mockImplementation(() =>
|
|
Promise.resolve(new Response(JSON.stringify([]))),
|
|
);
|
|
// Mock getAuthenticatedUserProfile as it's called by useAuth's checkAuthToken and login
|
|
mockedApiClient.getAuthenticatedUserProfile.mockImplementation(() =>
|
|
Promise.resolve(
|
|
new Response(
|
|
JSON.stringify(
|
|
createMockUserProfile({
|
|
user: { user_id: 'test-user-id', email: 'test@example.com' },
|
|
full_name: 'Test User',
|
|
role: 'user',
|
|
points: 0,
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
mockedApiClient.fetchMasterItems.mockImplementation(() =>
|
|
Promise.resolve(new Response(JSON.stringify([]))),
|
|
);
|
|
mockedApiClient.fetchWatchedItems.mockImplementation(() =>
|
|
Promise.resolve(new Response(JSON.stringify([]))),
|
|
);
|
|
mockedApiClient.fetchShoppingLists.mockImplementation(() =>
|
|
Promise.resolve(new Response(JSON.stringify([]))),
|
|
);
|
|
mockedAiApiClient.rescanImageArea.mockResolvedValue(
|
|
new Response(JSON.stringify({ text: 'mocked text' })),
|
|
); // Mock for FlyerCorrectionTool
|
|
console.log('[TEST DEBUG] beforeEach: Setup complete');
|
|
});
|
|
|
|
const renderApp = (initialEntries = ['/']) => {
|
|
return render(
|
|
<MemoryRouter initialEntries={initialEntries}>
|
|
<AppProviders>
|
|
<App />
|
|
</AppProviders>
|
|
</MemoryRouter>,
|
|
);
|
|
};
|
|
|
|
it('should render the main layout and header', async () => {
|
|
// Simulate the auth hook finishing its initial check
|
|
mockedUseAppInitialization.mockReturnValue({ isDarkMode: false, unitSystem: 'imperial' });
|
|
mockUseAuth.mockReturnValue({
|
|
userProfile: null,
|
|
authStatus: 'SIGNED_OUT',
|
|
isLoading: false,
|
|
login: vi.fn(),
|
|
logout: vi.fn(),
|
|
updateProfile: vi.fn(),
|
|
});
|
|
|
|
renderApp();
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId('app-guard-mock')).toBeInTheDocument();
|
|
expect(screen.getByTestId('header-mock')).toBeInTheDocument();
|
|
// Check that the main layout and home page are rendered for the root path
|
|
expect(screen.getByTestId('main-layout-mock')).toBeInTheDocument();
|
|
expect(screen.getByTestId('home-page-mock')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should render the footer', async () => {
|
|
renderApp();
|
|
await waitFor(() => {
|
|
// This test will pass because we added the mock for the Footer component
|
|
expect(screen.getByTestId('footer-mock')).toBeInTheDocument();
|
|
expect(screen.getByText('Mock Footer')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should render the BulkImporter for an admin user', async () => {
|
|
console.log('[TEST DEBUG] Test Start: should render the BulkImporter for an admin user');
|
|
const mockAdminProfile: UserProfile = createMockUserProfile({
|
|
user: { user_id: 'admin-id', email: 'admin@example.com' },
|
|
role: 'admin',
|
|
});
|
|
|
|
// Force the auth hook to return an authenticated admin user
|
|
mockUseAuth.mockReturnValue({
|
|
userProfile: mockAdminProfile,
|
|
authStatus: 'AUTHENTICATED',
|
|
isLoading: false,
|
|
login: vi.fn(),
|
|
logout: vi.fn(),
|
|
updateProfile: vi.fn(),
|
|
});
|
|
|
|
console.log('[TEST DEBUG] Rendering App with /admin route');
|
|
renderApp(['/admin']);
|
|
|
|
await waitFor(() => {
|
|
console.log('[TEST DEBUG] Waiting for admin-page-mock');
|
|
expect(screen.getByTestId('header-mock')).toBeInTheDocument();
|
|
expect(screen.getByTestId('admin-page-mock')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should show a welcome message when no flyer is selected', async () => {
|
|
// Simulate the auth hook finishing its initial check
|
|
mockUseAuth.mockReturnValue({
|
|
userProfile: null,
|
|
authStatus: 'SIGNED_OUT',
|
|
isLoading: false,
|
|
login: vi.fn(),
|
|
logout: vi.fn(),
|
|
updateProfile: vi.fn(),
|
|
});
|
|
|
|
renderApp();
|
|
await waitFor(() => {
|
|
// The welcome message is inside HomePage, which is now mocked. We check for the mock instead.
|
|
expect(screen.getByTestId('home-page-mock')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should render the admin page on the /admin route', async () => {
|
|
const mockAdminProfile: UserProfile = createMockUserProfile({
|
|
user: createMockUser({ user_id: 'admin-id', email: 'admin@example.com' }),
|
|
role: 'admin',
|
|
});
|
|
|
|
// Directly set the auth state via the mocked hook
|
|
mockUseAuth.mockReturnValue({
|
|
userProfile: mockAdminProfile,
|
|
authStatus: 'AUTHENTICATED',
|
|
isLoading: false,
|
|
login: vi.fn(),
|
|
logout: vi.fn(),
|
|
updateProfile: vi.fn(),
|
|
});
|
|
|
|
console.log('[TEST DEBUG] Rendering App with /admin route');
|
|
renderApp(['/admin']);
|
|
|
|
console.log('[TEST DEBUG] Waiting for admin-page-mock');
|
|
expect(await screen.findByTestId('admin-page-mock')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should render the reset password page on the correct route', async () => {
|
|
renderApp(['/reset-password/some-token']);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId('reset-password-page-mock')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('Flyer Selection from URL', () => {
|
|
it('should select a flyer when flyerId is present in the URL', async () => {
|
|
renderApp(['/flyers/2']);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId('home-page-mock')).toHaveAttribute('data-selected-flyer-id', '2');
|
|
});
|
|
});
|
|
|
|
it('should not select a flyer if the flyerId from the URL does not exist', async () => {
|
|
// This test covers the `if (flyerToSelect)` branch in the useEffect.
|
|
renderApp(['/flyers/999']); // 999 does not exist in mockFlyers
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId('home-page-mock')).toBeInTheDocument();
|
|
});
|
|
// The main assertion is that no error is thrown.
|
|
});
|
|
|
|
it('should select the first flyer if no flyer is selected and flyers are available', async () => {
|
|
renderApp(['/']);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId('home-page-mock')).toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Modal Interactions', () => {
|
|
it('should open and close the ProfileManager modal', async () => {
|
|
console.log('[TEST DEBUG] Test Start: should open and close the ProfileManager modal');
|
|
renderApp();
|
|
expect(screen.queryByTestId('profile-manager-mock')).not.toBeInTheDocument();
|
|
|
|
// Open modal
|
|
fireEvent.click(screen.getByText('Open Profile'));
|
|
expect(await screen.findByTestId('profile-manager-mock')).toBeInTheDocument();
|
|
|
|
console.log('[TEST DEBUG] ProfileManager modal opened. Now closing...');
|
|
// Close modal
|
|
fireEvent.click(screen.getByText('Close Profile'));
|
|
await waitFor(() => {
|
|
expect(screen.queryByTestId('profile-manager-mock')).not.toBeInTheDocument();
|
|
});
|
|
console.log('[TEST DEBUG] ProfileManager modal closed.');
|
|
});
|
|
|
|
it('should open and close the VoiceAssistant modal for authenticated users', async () => {
|
|
console.log('[TEST DEBUG] Test Start: should open and close the VoiceAssistant modal');
|
|
mockUseAuth.mockReturnValue({
|
|
userProfile: createMockUserProfile({
|
|
role: 'user',
|
|
user: { user_id: '1', email: 'test@test.com' },
|
|
}),
|
|
authStatus: 'AUTHENTICATED',
|
|
isLoading: false,
|
|
login: vi.fn(),
|
|
logout: vi.fn(),
|
|
updateProfile: vi.fn(),
|
|
});
|
|
console.log('[TEST DEBUG] Rendering App');
|
|
renderApp();
|
|
expect(screen.queryByTestId('voice-assistant-mock')).not.toBeInTheDocument();
|
|
|
|
// Open modal
|
|
console.log('[TEST DEBUG] Clicking Open Voice Assistant');
|
|
fireEvent.click(screen.getByText('Open Voice Assistant'));
|
|
|
|
console.log('[TEST DEBUG] Waiting for voice-assistant-mock');
|
|
expect(await screen.findByTestId('voice-assistant-mock', {}, { timeout: 3000 })).toBeInTheDocument();
|
|
|
|
// Close modal
|
|
fireEvent.click(screen.getByText('Close Voice Assistant'));
|
|
await waitFor(() => {
|
|
expect(screen.queryByTestId('voice-assistant-mock')).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should not render the FlyerCorrectionTool if no flyer is selected', () => {
|
|
mockUseFlyers.mockReturnValue({ flyers: [], isLoadingFlyers: false });
|
|
renderApp();
|
|
// Try to open the modal via the mocked HomePage button
|
|
fireEvent.click(screen.getByText('Open Correction Tool'));
|
|
expect(screen.queryByTestId('flyer-correction-tool-mock')).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('should open and close the FlyerCorrectionTool modal', async () => {
|
|
renderApp();
|
|
expect(screen.queryByTestId('flyer-correction-tool-mock')).not.toBeInTheDocument();
|
|
|
|
// Open modal
|
|
fireEvent.click(screen.getByText('Open Correction Tool'));
|
|
expect(await screen.findByTestId('flyer-correction-tool-mock')).toBeInTheDocument();
|
|
|
|
// Close modal
|
|
fireEvent.click(screen.getByText('Close Correction'));
|
|
await waitFor(() => {
|
|
expect(screen.queryByTestId('flyer-correction-tool-mock')).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should render admin sub-routes correctly', async () => {
|
|
console.log('[TEST DEBUG] Test Start: should render admin sub-routes correctly');
|
|
const mockAdminProfile: UserProfile = createMockUserProfile({
|
|
user: { user_id: 'admin-id', email: 'admin@example.com' },
|
|
role: 'admin',
|
|
});
|
|
mockUseAuth.mockReturnValue({
|
|
userProfile: mockAdminProfile,
|
|
authStatus: 'AUTHENTICATED',
|
|
isLoading: false,
|
|
login: vi.fn(),
|
|
logout: vi.fn(),
|
|
updateProfile: vi.fn(),
|
|
});
|
|
|
|
console.log(
|
|
'Testing admin sub-routes with renderApp wrapper to ensure ModalProvider context',
|
|
);
|
|
console.log('[TEST DEBUG] Rendering App with /admin/corrections');
|
|
renderApp(['/admin/corrections']);
|
|
|
|
await waitFor(() => {
|
|
console.log('[TEST DEBUG] Waiting for corrections-page-mock');
|
|
expect(screen.getByTestId('corrections-page-mock')).toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Flyer Correction Tool Data Handling', () => {
|
|
it('should handle store name extraction from the correction tool', async () => {
|
|
// Ensure flyers are present so a flyer is auto-selected
|
|
mockUseFlyers.mockReturnValue({
|
|
flyers: mockFlyers,
|
|
isLoadingFlyers: false,
|
|
});
|
|
|
|
renderApp();
|
|
fireEvent.click(screen.getByText('Open Correction Tool'));
|
|
const correctionTool = await screen.findByTestId(
|
|
'flyer-correction-tool-mock',
|
|
{},
|
|
{ timeout: 2000 },
|
|
);
|
|
// We trigger the callback from the mock and ensure it doesn't crash.
|
|
fireEvent.click(within(correctionTool).getByText('Extract Store'));
|
|
// The test passes if no errors are thrown here.
|
|
});
|
|
|
|
it('should handle date extraction from the correction tool', async () => {
|
|
// Ensure flyers are present so a flyer is auto-selected
|
|
mockUseFlyers.mockReturnValue({
|
|
flyers: mockFlyers,
|
|
isLoadingFlyers: false,
|
|
});
|
|
|
|
renderApp();
|
|
fireEvent.click(screen.getByText('Open Correction Tool'));
|
|
const correctionTool = await screen.findByTestId(
|
|
'flyer-correction-tool-mock',
|
|
{},
|
|
{ timeout: 2000 },
|
|
);
|
|
fireEvent.click(within(correctionTool).getByText('Extract Dates'));
|
|
// The test passes if no errors are thrown here, covering the 'dates' branch.
|
|
});
|
|
});
|
|
|
|
describe('Profile and Login Handlers', () => {
|
|
it('should call updateProfile when handleProfileUpdate is triggered', async () => {
|
|
console.log(
|
|
'[TEST DEBUG] Test Start: should call updateProfile when handleProfileUpdate is triggered',
|
|
);
|
|
const mockUpdateProfile = vi.fn();
|
|
// To test profile updates, the user must be authenticated to see the "Update Profile" button.
|
|
mockUseAuth.mockReturnValue({
|
|
userProfile: createMockUserProfile({
|
|
user: { user_id: 'test-user', email: 'test@example.com' },
|
|
role: 'user',
|
|
}),
|
|
authStatus: 'AUTHENTICATED',
|
|
isLoading: false,
|
|
login: vi.fn(),
|
|
logout: vi.fn(),
|
|
updateProfile: mockUpdateProfile,
|
|
});
|
|
|
|
console.log('[TEST DEBUG] Rendering App');
|
|
renderApp();
|
|
console.log('[TEST DEBUG] Opening Profile');
|
|
fireEvent.click(screen.getByText('Open Profile'));
|
|
const profileManager = await screen.findByTestId('profile-manager-mock');
|
|
console.log('[TEST DEBUG] Clicking Update Profile');
|
|
fireEvent.click(within(profileManager).getByText('Update Profile'));
|
|
|
|
await waitFor(() => {
|
|
console.log('[TEST DEBUG] Checking mockUpdateProfile calls:', mockUpdateProfile.mock.calls);
|
|
expect(mockUpdateProfile).toHaveBeenCalledWith(
|
|
expect.objectContaining({ full_name: 'Updated' }),
|
|
);
|
|
});
|
|
});
|
|
|
|
it('should set an error state if login fails inside handleLoginSuccess', async () => {
|
|
console.log(
|
|
'[TEST DEBUG] Test Start: should set an error state if login fails inside handleLoginSuccess',
|
|
);
|
|
const mockLogin = vi.fn().mockRejectedValue(new Error('Login failed'));
|
|
mockUseAuth.mockReturnValue({
|
|
userProfile: null,
|
|
authStatus: 'SIGNED_OUT',
|
|
isLoading: false,
|
|
login: mockLogin,
|
|
logout: vi.fn(),
|
|
updateProfile: vi.fn(),
|
|
});
|
|
// Mock the login function to simulate a successful login. Signature: (token, profile)
|
|
const mockLoginSuccess = vi.fn(async (_token: string, _profile?: UserProfile) => {
|
|
// Simulate fetching profile after login
|
|
const profileResponse = await mockedApiClient.getAuthenticatedUserProfile();
|
|
const userProfileData: UserProfile = await profileResponse.json();
|
|
mockUseAuth.mockReturnValue({ ...mockUseAuth(), userProfile: userProfileData, authStatus: 'AUTHENTICATED' });
|
|
});
|
|
|
|
console.log('[TEST DEBUG] Rendering App');
|
|
renderApp();
|
|
console.log('[TEST DEBUG] Opening Profile');
|
|
fireEvent.click(screen.getByText('Open Profile'));
|
|
const loginButton = await screen.findByRole('button', { name: 'Login' });
|
|
console.log('[TEST DEBUG] Clicking Login');
|
|
fireEvent.click(loginButton);
|
|
|
|
// We need to wait for the async login function to be called and reject.
|
|
await waitFor(() => {
|
|
console.log('[TEST DEBUG] Checking mockLogin calls:', mockLogin.mock.calls);
|
|
expect(mockLogin).toHaveBeenCalled();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("Version Display and What's New", () => {
|
|
beforeEach(() => {
|
|
vi.mock('./config', () => ({
|
|
default: {
|
|
app: {
|
|
version: '2.0.0',
|
|
commitMessage: 'A new version!',
|
|
commitUrl: 'https://example.com/commit/2.0.0',
|
|
},
|
|
},
|
|
}));
|
|
});
|
|
|
|
it('should display the version number and commit link', () => {
|
|
renderApp();
|
|
const versionLink = screen.getByText(`Version: 2.0.0`);
|
|
expect(versionLink).toBeInTheDocument();
|
|
expect(versionLink).toHaveAttribute('href', 'https://example.com/commit/2.0.0');
|
|
});
|
|
|
|
it('should open the "What\'s New" modal when the question mark icon is clicked', async () => {
|
|
renderApp();
|
|
const openButton = await screen.findByTitle("Show what's new in this version");
|
|
fireEvent.click(openButton);
|
|
// The mock AppGuard now renders the modal when it's open
|
|
expect(await screen.findByTestId('whats-new-modal-mock')).toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|