fix ing the mock mock mock !
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 1h4m27s

This commit is contained in:
2025-12-20 11:50:21 -08:00
parent 74ed4d2d3d
commit bf4646fbe5
8 changed files with 124 additions and 16 deletions

View File

@@ -48,6 +48,79 @@ vi.mock('./config', () => ({
}, },
})); }));
// Mock pages and components to isolate App testing
vi.mock('./pages/HomePage', () => ({
HomePage: ({ onOpenCorrectionTool }: any) => (
<div data-testid="home-page-mock">
<h1>Home Page</h1>
<button onClick={onOpenCorrectionTool}>Open Correction Tool</button>
</div>
),
}));
vi.mock('./pages/admin/AdminPage', () => ({
AdminPage: () => <div data-testid="admin-page-mock">Admin Page</div>,
}));
vi.mock('./pages/admin/CorrectionsPage', () => ({
CorrectionsPage: () => <div data-testid="corrections-page-mock">Corrections Page</div>,
}));
vi.mock('./pages/admin/AdminStatsPage', () => ({
AdminStatsPage: () => <div data-testid="admin-stats-page-mock">Admin Stats Page</div>,
}));
vi.mock('./pages/VoiceLabPage', () => ({
VoiceLabPage: () => <div data-testid="voice-lab-page-mock">Voice Lab Page</div>,
}));
vi.mock('./pages/ResetPasswordPage', () => ({
ResetPasswordPage: () => <div data-testid="reset-password-page-mock">Reset Password</div>,
}));
vi.mock('./pages/admin/components/ProfileManager', () => ({
ProfileManager: ({ isOpen, onClose, onProfileUpdate }: any) => isOpen ? (
<div data-testid="profile-manager-mock">
<button onClick={onClose}>Close Profile</button>
<button onClick={() => onProfileUpdate({ full_name: 'Updated' })}>Update Profile</button>
<button>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', '2024-01-01')}>Extract Dates</button>
</div>
) : null,
}));
vi.mock('./components/WhatsNewModal', () => ({
WhatsNewModal: ({ isOpen }: any) => isOpen ? <div data-testid="whats-new-modal-mock">What's New</div> : null,
}));
vi.mock('./layouts/MainLayout', () => ({
MainLayout: () => {
const { Outlet } = require('react-router-dom');
return (
<div data-testid="main-layout-mock">
<Outlet />
</div>
);
},
}));
const mockedAiApiClient = vi.mocked(aiApiClient); // Mock aiApiClient const mockedAiApiClient = vi.mocked(aiApiClient); // Mock aiApiClient
const mockedApiClient = vi.mocked(apiClient); const mockedApiClient = vi.mocked(apiClient);

View File

@@ -18,10 +18,11 @@ vi.mock('../../services/logger.client', () => ({
vi.mock('../../services/aiAnalysisService', () => { vi.mock('../../services/aiAnalysisService', () => {
console.log('DEBUG: Setting up AiAnalysisService mock'); console.log('DEBUG: Setting up AiAnalysisService mock');
return { return {
AiAnalysisService: vi.fn().mockImplementation(() => { AiAnalysisService: class {
console.log('DEBUG: AiAnalysisService constructor mocked call'); constructor() {
return {}; console.log('DEBUG: AiAnalysisService constructor mocked call');
}), }
},
}; };
}); });

View File

@@ -32,6 +32,7 @@ const mockFlyers: Flyer[] = [
store: { store_id: 102, name: 'Walmart' }, store: { store_id: 102, name: 'Walmart' },
valid_from: '2023-10-06', valid_from: '2023-10-06',
valid_to: '2023-10-06', // Same day valid_to: '2023-10-06', // Same day
icon_url: '', // Force empty to test default icon rendering
}), }),
createMockFlyer({ createMockFlyer({
flyer_id: 3, flyer_id: 3,
@@ -39,7 +40,7 @@ const mockFlyers: Flyer[] = [
item_count: 10, item_count: 10,
image_url: 'http://example.com/flyer3.jpg', image_url: 'http://example.com/flyer3.jpg',
icon_url: 'http://example.com/icon3.png', icon_url: 'http://example.com/icon3.png',
store: undefined, // No store data store: null as any, // Force null to ensure "Unknown Store" fallback triggers
valid_from: '2023-10-07', valid_from: '2023-10-07',
valid_to: '2023-10-08', valid_to: '2023-10-08',
store_address: '456 Side St, Ottawa', store_address: '456 Side St, Ottawa',

View File

@@ -13,6 +13,15 @@ vi.mock('../services/apiClient', () => ({
fetchFlyerItemsForFlyers: vi.fn(), fetchFlyerItemsForFlyers: vi.fn(),
})); }));
// Mock the hooks to avoid Missing Context errors
vi.mock('./useFlyers', () => ({
useFlyers: () => mockUseFlyers(),
}));
vi.mock('../hooks/useUserData', () => ({
useUserData: () => mockUseUserData(),
}));
// The apiClient is globally mocked in our test setup, so we just need to cast it // The apiClient is globally mocked in our test setup, so we just need to cast it
const mockedApiClient = vi.mocked(apiClient); const mockedApiClient = vi.mocked(apiClient);

View File

@@ -4,6 +4,7 @@ import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import { useProfileAddress } from './useProfileAddress'; import { useProfileAddress } from './useProfileAddress';
import { useApi } from './useApi'; import { useApi } from './useApi';
import { logger } from '../services/logger.client';
import { createMockAddress, createMockProfile } from '../tests/utils/mockFactories'; import { createMockAddress, createMockProfile } from '../tests/utils/mockFactories';
// Mock dependencies // Mock dependencies
@@ -58,11 +59,19 @@ describe('useProfileAddress Hook', () => {
mockGeocode = vi.fn(); mockGeocode = vi.fn();
mockFetchAddress = vi.fn(); mockFetchAddress = vi.fn();
// Setup the mock for useApi // Setup the mock for useApi to handle multiple renders and hook calls.
// It's called twice in the hook: geocode, then fetchAddress // The hook calls useApi twice per render in a stable order:
mockedUseApi // 1. geocodeWrapper (via geocode)
.mockReturnValueOnce({ execute: mockGeocode, loading: false, error: null, data: null, reset: vi.fn(), isRefetching: false }) // 2. fetchAddressWrapper (via fetchAddress)
.mockReturnValueOnce({ execute: mockFetchAddress, loading: false, error: null, data: null, reset: vi.fn(), isRefetching: false }); let callCount = 0;
mockedUseApi.mockImplementation(() => {
callCount++;
if (callCount % 2 !== 0) {
return { execute: mockGeocode, loading: false, error: null, data: null, reset: vi.fn(), isRefetching: false };
} else {
return { execute: mockFetchAddress, loading: false, error: null, data: null, reset: vi.fn(), isRefetching: false };
}
});
}); });
it('should initialize with empty address and initialAddress', () => { it('should initialize with empty address and initialAddress', () => {
@@ -119,7 +128,6 @@ describe('useProfileAddress Hook', () => {
}); });
it('should handle fetch failure gracefully', async () => { it('should handle fetch failure gracefully', async () => {
const loggerSpy = vi.spyOn(require('../services/logger.client').logger, 'warn');
mockFetchAddress.mockResolvedValue(null); // useApi returns null on failure mockFetchAddress.mockResolvedValue(null); // useApi returns null on failure
const { result } = renderHook(() => useProfileAddress(mockProfile, true)); const { result } = renderHook(() => useProfileAddress(mockProfile, true));
@@ -128,7 +136,7 @@ describe('useProfileAddress Hook', () => {
}); });
expect(result.current.address).toEqual({}); expect(result.current.address).toEqual({});
expect(loggerSpy).toHaveBeenCalledWith(expect.stringContaining('Fetch returned null or undefined')); expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('Fetch returned null or undefined'));
}); });
}); });

View File

@@ -29,6 +29,7 @@ const mockLogs: ActivityLogItem[] = [
display_text: 'Processed a new flyer for Walmart.', display_text: 'Processed a new flyer for Walmart.',
details: { flyer_id: 1, store_name: 'Walmart', user_avatar_url: 'http://example.com/avatar.png', user_full_name: 'Test User' }, details: { flyer_id: 1, store_name: 'Walmart', user_avatar_url: 'http://example.com/avatar.png', user_full_name: 'Test User' },
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
}, },
{ {
activity_log_id: 2, activity_log_id: 2,
@@ -37,6 +38,7 @@ const mockLogs: ActivityLogItem[] = [
display_text: 'Jane Doe added a new recipe: Pasta Carbonara', display_text: 'Jane Doe added a new recipe: Pasta Carbonara',
details: { recipe_id: 1, recipe_name: 'Pasta Carbonara', user_full_name: 'Jane Doe' }, details: { recipe_id: 1, recipe_name: 'Pasta Carbonara', user_full_name: 'Jane Doe' },
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
}, },
{ {
activity_log_id: 3, activity_log_id: 3,
@@ -45,6 +47,7 @@ const mockLogs: ActivityLogItem[] = [
display_text: 'John Smith shared a list.', display_text: 'John Smith shared a list.',
details: { list_name: 'Weekly Groceries', shopping_list_id: 10, user_full_name: 'John Smith', shared_with_name: 'Test User' }, details: { list_name: 'Weekly Groceries', shopping_list_id: 10, user_full_name: 'John Smith', shared_with_name: 'Test User' },
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
}, },
{ {
activity_log_id: 4, activity_log_id: 4,
@@ -53,6 +56,7 @@ const mockLogs: ActivityLogItem[] = [
display_text: 'New user joined', display_text: 'New user joined',
details: { full_name: 'Newbie User' }, // No avatar provided to test fallback details: { full_name: 'Newbie User' }, // No avatar provided to test fallback
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
}, },
{ {
activity_log_id: 5, activity_log_id: 5,
@@ -61,6 +65,7 @@ const mockLogs: ActivityLogItem[] = [
display_text: 'User favorited a recipe', display_text: 'User favorited a recipe',
details: { recipe_name: 'Best Pizza', user_full_name: 'Pizza Lover', user_avatar_url: 'http://example.com/pizza.png' }, details: { recipe_name: 'Best Pizza', user_full_name: 'Pizza Lover', user_avatar_url: 'http://example.com/pizza.png' },
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
}, },
{ {
activity_log_id: 6, activity_log_id: 6,
@@ -69,6 +74,7 @@ const mockLogs: ActivityLogItem[] = [
display_text: 'Something happened', display_text: 'Something happened',
details: {} as any, details: {} as any,
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
}, },
]; ];
@@ -196,6 +202,7 @@ describe('ActivityLog', () => {
display_text: '...', display_text: '...',
details: { flyer_id: 1 } as any, // Missing store_name details: { flyer_id: 1 } as any, // Missing store_name
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
}, },
{ {
activity_log_id: 102, activity_log_id: 102,
@@ -204,6 +211,7 @@ describe('ActivityLog', () => {
display_text: '...', display_text: '...',
details: { recipe_id: 1 } as any, // Missing recipe_name details: { recipe_id: 1 } as any, // Missing recipe_name
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
}, },
{ {
activity_log_id: 103, activity_log_id: 103,
@@ -212,6 +220,7 @@ describe('ActivityLog', () => {
display_text: '...', display_text: '...',
details: {} as any, // Missing full_name details: {} as any, // Missing full_name
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
}, },
{ {
activity_log_id: 104, activity_log_id: 104,
@@ -220,6 +229,7 @@ describe('ActivityLog', () => {
display_text: '...', display_text: '...',
details: { recipe_id: 2 } as any, // Missing recipe_name details: { recipe_id: 2 } as any, // Missing recipe_name
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
}, },
{ {
activity_log_id: 105, activity_log_id: 105,
@@ -228,6 +238,7 @@ describe('ActivityLog', () => {
display_text: '...', display_text: '...',
details: { shopping_list_id: 1 } as any, // Missing list_name and shared_with_name details: { shopping_list_id: 1 } as any, // Missing list_name and shared_with_name
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
}, },
{ {
activity_log_id: 106, activity_log_id: 106,
@@ -236,6 +247,7 @@ describe('ActivityLog', () => {
display_text: '...', display_text: '...',
details: { flyer_id: 2, user_avatar_url: 'http://img.com/a.png' } as any, // Missing user_full_name for alt text details: { flyer_id: 2, user_avatar_url: 'http://img.com/a.png' } as any, // Missing user_full_name for alt text
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
}, },
]; ];
@@ -243,7 +255,7 @@ describe('ActivityLog', () => {
render(<ActivityLog user={mockUser} />); render(<ActivityLog user={mockUser} />);
await waitFor(() => { await waitFor(() => {
expect(screen.getByText('a store')).toBeInTheDocument(); expect(screen.getAllByText('a store')[0]).toBeInTheDocument();
expect(screen.getByText('Untitled Recipe')).toBeInTheDocument(); expect(screen.getByText('Untitled Recipe')).toBeInTheDocument();
expect(screen.getByText('A new user')).toBeInTheDocument(); expect(screen.getByText('A new user')).toBeInTheDocument();
expect(screen.getByText('a recipe')).toBeInTheDocument(); expect(screen.getByText('a recipe')).toBeInTheDocument();

View File

@@ -8,7 +8,6 @@ import { notifySuccess, notifyError } from '../../../services/notificationServic
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import * as logger from '../../../services/logger.client'; import * as logger from '../../../services/logger.client';
import { createMockProfile, createMockAddress, createMockUser } from '../../../tests/utils/mockFactories'; import { createMockProfile, createMockAddress, createMockUser } from '../../../tests/utils/mockFactories';
import { createMockLogger } from '../../../tests/utils/mockLogger';
// Unmock the component to test the real implementation // Unmock the component to test the real implementation
vi.unmock('./ProfileManager'); vi.unmock('./ProfileManager');
@@ -24,7 +23,12 @@ vi.mock('react-hot-toast', () => ({
}, },
})); }));
vi.mock('../../../services/logger.client', () => ({ vi.mock('../../../services/logger.client', () => ({
logger: createMockLogger(), logger: {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
},
})); }));
const mockOnClose = vi.fn(); const mockOnClose = vi.fn();

View File

@@ -177,7 +177,7 @@ describe('AI Routes (/api/ai)', () => {
await supertest(authenticatedApp).post('/api/ai/upload-and-process').field('checksum', 'addr-checksum').attach('flyerFile', imagePath); await supertest(authenticatedApp).post('/api/ai/upload-and-process').field('checksum', 'addr-checksum').attach('flyerFile', imagePath);
// Assert // Assert
expect(vi.mocked(flyerQueue.add).mock.calls[0][1].userProfileAddress).toBe('123 Main St, Anytown, CA, 12345, USA'); expect(vi.mocked(flyerQueue.add).mock.calls[0][1].userProfileAddress).toBe('123 Pacific St, Anytown, BC, V8T 1A1, CA');
}); });
}); });