unit test auto-provider refactor
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 19m58s
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 19m58s
This commit is contained in:
@@ -4,13 +4,14 @@ import { screen, waitFor } from '@testing-library/react';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { AppGuard } from './AppGuard';
|
||||
import { useAppInitialization } from '../hooks/useAppInitialization';
|
||||
import * as apiClient from '../services/apiClient';
|
||||
import { useModal } from '../hooks/useModal';
|
||||
import { renderWithProviders } from '../tests/utils/renderWithProviders';
|
||||
|
||||
// Mock dependencies
|
||||
// The apiClient is mocked globally in `src/tests/setup/globalApiMock.ts`.
|
||||
vi.mock('../hooks/useAppInitialization');
|
||||
vi.mock('../hooks/useModal');
|
||||
vi.mock('../services/apiClient');
|
||||
vi.mock('./WhatsNewModal', () => ({
|
||||
WhatsNewModal: ({ isOpen }: { isOpen: boolean }) =>
|
||||
isOpen ? <div data-testid="whats-new-modal-mock" /> : null,
|
||||
@@ -21,6 +22,7 @@ vi.mock('../config', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const mockedApiClient = vi.mocked(apiClient);
|
||||
const mockedUseAppInitialization = vi.mocked(useAppInitialization);
|
||||
const mockedUseModal = vi.mocked(useModal);
|
||||
|
||||
|
||||
@@ -10,16 +10,9 @@ import { renderWithProviders } from '../tests/utils/renderWithProviders';
|
||||
// Unmock the component to test the real implementation
|
||||
vi.unmock('./FlyerCorrectionTool');
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock('../services/aiApiClient');
|
||||
vi.mock('../services/notificationService');
|
||||
vi.mock('../services/logger', () => ({
|
||||
logger: {
|
||||
error: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
const mockedAiApiClient = aiApiClient as Mocked<typeof aiApiClient>;
|
||||
// The aiApiClient, notificationService, and logger are mocked globally.
|
||||
// We can get a typed reference to the aiApiClient for individual test overrides.
|
||||
const mockedAiApiClient = vi.mocked(aiApiClient);
|
||||
const mockedNotifySuccess = notifySuccess as Mocked<typeof notifySuccess>;
|
||||
const mockedNotifyError = notifyError as Mocked<typeof notifyError>;
|
||||
|
||||
|
||||
@@ -9,14 +9,9 @@ import { createMockLeaderboardUser } from '../tests/utils/mockFactories';
|
||||
import { createMockLogger } from '../tests/utils/mockLogger';
|
||||
import { renderWithProviders } from '../tests/utils/renderWithProviders';
|
||||
|
||||
// Mock the apiClient
|
||||
vi.mock('../services/apiClient'); // This was correct
|
||||
const mockedApiClient = apiClient as Mocked<typeof apiClient>;
|
||||
|
||||
// Mock the logger
|
||||
vi.mock('../services/logger', () => ({
|
||||
logger: createMockLogger(),
|
||||
}));
|
||||
// The apiClient and logger are mocked globally.
|
||||
// We can get a typed reference to the apiClient for individual test overrides.
|
||||
const mockedApiClient = vi.mocked(apiClient);
|
||||
|
||||
// Mock lucide-react icons to prevent rendering errors in the test environment
|
||||
vi.mock('lucide-react', () => ({
|
||||
|
||||
@@ -2,16 +2,15 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { RecipeSuggester } from './RecipeSuggester';
|
||||
import { suggestRecipe } from '../services/apiClient';
|
||||
import { RecipeSuggester } from './RecipeSuggester'; // This should be after mocks
|
||||
import * as apiClient from '../services/apiClient';
|
||||
import { logger } from '../services/logger.client';
|
||||
import { renderWithProviders } from '../tests/utils/renderWithProviders';
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
// Mock the API client
|
||||
vi.mock('../services/apiClient', () => ({
|
||||
suggestRecipe: vi.fn(),
|
||||
}));
|
||||
// The apiClient is mocked globally in `src/tests/setup/globalApiMock.ts`.
|
||||
// We can get a typed reference to it for individual test overrides.
|
||||
const mockedApiClient = vi.mocked(apiClient);
|
||||
|
||||
// Mock the logger
|
||||
vi.mock('../services/logger.client', () => ({
|
||||
@@ -45,7 +44,7 @@ describe('RecipeSuggester Component', () => {
|
||||
await user.click(button);
|
||||
|
||||
expect(await screen.findByText('Please enter at least one ingredient.')).toBeInTheDocument();
|
||||
expect(suggestRecipe).not.toHaveBeenCalled();
|
||||
expect(mockedApiClient.suggestRecipe).not.toHaveBeenCalled();
|
||||
console.log('TEST: Validation error displayed correctly');
|
||||
});
|
||||
|
||||
@@ -60,7 +59,7 @@ describe('RecipeSuggester Component', () => {
|
||||
// Mock successful API response
|
||||
const mockSuggestion = 'Here is a nice Chicken and Rice recipe...';
|
||||
// Add a delay to ensure the loading state is visible during the test
|
||||
vi.mocked(suggestRecipe).mockImplementation(async () => {
|
||||
mockedApiClient.suggestRecipe.mockImplementation(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
return { ok: true, json: async () => ({ suggestion: mockSuggestion }) } as Response;
|
||||
});
|
||||
@@ -76,7 +75,7 @@ describe('RecipeSuggester Component', () => {
|
||||
expect(screen.getByText(mockSuggestion)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(suggestRecipe).toHaveBeenCalledWith(['chicken', 'rice']);
|
||||
expect(mockedApiClient.suggestRecipe).toHaveBeenCalledWith(['chicken', 'rice']);
|
||||
console.log('TEST: Suggestion displayed and API called with correct args');
|
||||
});
|
||||
|
||||
@@ -90,7 +89,7 @@ describe('RecipeSuggester Component', () => {
|
||||
|
||||
// Mock API failure response
|
||||
const errorMessage = 'Invalid ingredients provided.';
|
||||
vi.mocked(suggestRecipe).mockResolvedValue({
|
||||
mockedApiClient.suggestRecipe.mockResolvedValue({
|
||||
ok: false,
|
||||
json: async () => ({ message: errorMessage }),
|
||||
} as Response);
|
||||
@@ -117,7 +116,7 @@ describe('RecipeSuggester Component', () => {
|
||||
|
||||
// Mock network error
|
||||
const networkError = new Error('Network Error');
|
||||
vi.mocked(suggestRecipe).mockRejectedValue(networkError);
|
||||
mockedApiClient.suggestRecipe.mockRejectedValue(networkError);
|
||||
|
||||
const button = screen.getByRole('button', { name: /Suggest a Recipe/i });
|
||||
await user.click(button);
|
||||
@@ -148,7 +147,7 @@ describe('RecipeSuggester Component', () => {
|
||||
await user.type(input, 'tofu');
|
||||
|
||||
// Mock success for the second click
|
||||
vi.mocked(suggestRecipe).mockResolvedValue({
|
||||
mockedApiClient.suggestRecipe.mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({ suggestion: 'Tofu Stir Fry' }),
|
||||
} as Response);
|
||||
|
||||
@@ -4,11 +4,9 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { useFlyerItems } from './useFlyerItems';
|
||||
import { useApiOnMount } from './useApiOnMount';
|
||||
import { createMockFlyer, createMockFlyerItem } from '../tests/utils/mockFactories';
|
||||
import * as apiClient from '../services/apiClient';
|
||||
|
||||
// Mock the underlying useApiOnMount hook to isolate the useFlyerItems hook's logic.
|
||||
vi.mock('./useApiOnMount');
|
||||
vi.mock('../services/apiClient');
|
||||
|
||||
const mockedUseApiOnMount = vi.mocked(useApiOnMount);
|
||||
|
||||
@@ -61,7 +59,6 @@ describe('useFlyerItems Hook', () => {
|
||||
expect(result.current.flyerItems).toEqual([]);
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
expect(result.current.error).toBeNull();
|
||||
|
||||
// Assert: Check that useApiOnMount was called with `enabled: false`.
|
||||
expect(mockedUseApiOnMount).toHaveBeenCalledWith(
|
||||
expect.any(Function), // the wrapped fetcher function
|
||||
@@ -172,7 +169,7 @@ describe('useFlyerItems Hook', () => {
|
||||
const wrappedFetcher = mockedUseApiOnMount.mock.calls[0][0];
|
||||
const mockResponse = new Response();
|
||||
vi.mocked(apiClient.fetchFlyerItems).mockResolvedValue(mockResponse);
|
||||
|
||||
//FIX: Missing apiClient import here
|
||||
const response = await wrappedFetcher(123);
|
||||
|
||||
expect(apiClient.fetchFlyerItems).toHaveBeenCalledWith(123);
|
||||
@@ -180,3 +177,6 @@ describe('useFlyerItems Hook', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
import * as apiClient from '../services/apiClient';
|
||||
|
||||
const mockedApiClient = vi.mocked(apiClient);
|
||||
|
||||
@@ -29,7 +29,6 @@ type MockApiResult = {
|
||||
vi.mock('./useApi');
|
||||
vi.mock('../hooks/useAuth');
|
||||
vi.mock('../hooks/useUserData');
|
||||
vi.mock('../services/apiClient');
|
||||
|
||||
// The apiClient is globally mocked in our test setup, so we just need to cast it
|
||||
const mockedUseApi = vi.mocked(useApi);
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
vi.mock('./useApi');
|
||||
vi.mock('../hooks/useAuth');
|
||||
vi.mock('../hooks/useUserData');
|
||||
vi.mock('../services/apiClient');
|
||||
|
||||
// The apiClient is globally mocked in our test setup, so we just need to cast it
|
||||
const mockedUseApi = vi.mocked(useApi);
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
// src/components/MyDealsPage.test.tsx
|
||||
// src/pages/MyDealsPage.test.tsx
|
||||
import React from 'react';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { describe, it, expect, vi, beforeEach, type Mocked } from 'vitest';
|
||||
import MyDealsPage from './MyDealsPage';
|
||||
import * as apiClient from '../services/apiClient';
|
||||
import { WatchedItemDeal } from '../types';
|
||||
import type { WatchedItemDeal } from '../types';
|
||||
import { logger } from '../services/logger.client';
|
||||
import { createMockWatchedItemDeal } from '../tests/utils/mockFactories';
|
||||
|
||||
// Mock the apiClient. The component now directly uses `fetchBestSalePrices`.
|
||||
// By mocking the entire module, we can control the behavior of `fetchBestSalePrices`
|
||||
// for our tests.
|
||||
vi.mock('../services/apiClient');
|
||||
const mockedApiClient = apiClient as Mocked<typeof apiClient>;
|
||||
// The apiClient is mocked globally in `src/tests/setup/globalApiMock.ts`.
|
||||
const mockedApiClient = vi.mocked(apiClient);
|
||||
|
||||
// Mock the logger
|
||||
vi.mock('../services/logger.client', () => ({
|
||||
|
||||
@@ -10,13 +10,7 @@ import { logger } from '../services/logger.client';
|
||||
// The apiClient and logger are now mocked globally.
|
||||
const mockedApiClient = vi.mocked(apiClient);
|
||||
|
||||
vi.mock('../services/logger.client', () => ({
|
||||
logger: {
|
||||
info: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
// The logger is mocked globally.
|
||||
// Helper function to render the component within a router context
|
||||
const renderWithRouter = (token: string) => {
|
||||
return render(
|
||||
|
||||
@@ -11,16 +11,8 @@ import {
|
||||
createMockUser,
|
||||
} from '../tests/utils/mockFactories';
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock('../services/apiClient'); // This was correct
|
||||
vi.mock('../services/logger.client', () => ({
|
||||
logger: {
|
||||
info: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
}));
|
||||
vi.mock('../services/notificationService');
|
||||
vi.mock('../services/aiApiClient'); // Mock aiApiClient as it's used in the component
|
||||
// The apiClient, logger, notificationService, and aiApiClient are all mocked globally.
|
||||
// We can get a typed reference to the notificationService for individual test overrides.
|
||||
const mockedNotificationService = vi.mocked(await import('../services/notificationService'));
|
||||
vi.mock('../components/AchievementsList', () => ({
|
||||
AchievementsList: ({ achievements }: { achievements: (UserAchievement & Achievement)[] }) => (
|
||||
@@ -28,7 +20,7 @@ vi.mock('../components/AchievementsList', () => ({
|
||||
),
|
||||
}));
|
||||
|
||||
const mockedApiClient = apiClient as Mocked<typeof apiClient>;
|
||||
const mockedApiClient = vi.mocked(apiClient);
|
||||
|
||||
// --- Mock Data ---
|
||||
const mockProfile: UserProfile = createMockUserProfile({
|
||||
|
||||
@@ -10,21 +10,10 @@ import { logger } from '../services/logger.client';
|
||||
// Extensive logging for debugging
|
||||
const LOG_PREFIX = '[TEST DEBUG]';
|
||||
|
||||
vi.mock('../services/notificationService');
|
||||
|
||||
// 1. Mock the module to replace its exports with mock functions.
|
||||
vi.mock('../services/aiApiClient');
|
||||
// 2. Get a typed reference to the mocked module to control its functions in tests.
|
||||
// The aiApiClient, notificationService, and logger are mocked globally.
|
||||
// We can get a typed reference to the aiApiClient for individual test overrides.
|
||||
const mockedAiApiClient = vi.mocked(aiApiClient);
|
||||
|
||||
// Mock the logger
|
||||
vi.mock('../services/logger.client', () => ({
|
||||
logger: {
|
||||
info: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
// Define mock at module level so it can be referenced in the implementation
|
||||
const mockAudioPlay = vi.fn(() => {
|
||||
console.log(`${LOG_PREFIX} mockAudioPlay executed`);
|
||||
|
||||
@@ -6,16 +6,9 @@ import { MemoryRouter } from 'react-router-dom';
|
||||
import * as apiClient from '../../services/apiClient';
|
||||
import { logger } from '../../services/logger.client';
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock('../../services/apiClient', () => ({
|
||||
getFlyersForReview: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../services/logger.client', () => ({
|
||||
logger: {
|
||||
error: vi.fn(),
|
||||
},
|
||||
}));
|
||||
// The apiClient and logger are mocked globally.
|
||||
// We can get a typed reference to the apiClient for individual test overrides.
|
||||
const mockedApiClient = vi.mocked(apiClient);
|
||||
|
||||
// Mock LoadingSpinner to simplify DOM and avoid potential issues
|
||||
vi.mock('../../components/LoadingSpinner', () => ({
|
||||
@@ -29,7 +22,7 @@ describe('FlyerReviewPage', () => {
|
||||
|
||||
it('renders loading spinner initially', () => {
|
||||
// Mock a promise that doesn't resolve immediately to check loading state
|
||||
vi.mocked(apiClient.getFlyersForReview).mockReturnValue(new Promise(() => {}));
|
||||
mockedApiClient.getFlyersForReview.mockReturnValue(new Promise(() => {}));
|
||||
|
||||
render(
|
||||
<MemoryRouter>
|
||||
@@ -41,7 +34,7 @@ describe('FlyerReviewPage', () => {
|
||||
});
|
||||
|
||||
it('renders empty state when no flyers are returned', async () => {
|
||||
vi.mocked(apiClient.getFlyersForReview).mockResolvedValue({
|
||||
mockedApiClient.getFlyersForReview.mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => [],
|
||||
} as Response);
|
||||
@@ -84,7 +77,7 @@ describe('FlyerReviewPage', () => {
|
||||
},
|
||||
];
|
||||
|
||||
vi.mocked(apiClient.getFlyersForReview).mockResolvedValue({
|
||||
mockedApiClient.getFlyersForReview.mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => mockFlyers,
|
||||
} as Response);
|
||||
@@ -114,7 +107,7 @@ describe('FlyerReviewPage', () => {
|
||||
});
|
||||
|
||||
it('renders error message when API response is not ok', async () => {
|
||||
vi.mocked(apiClient.getFlyersForReview).mockResolvedValue({
|
||||
mockedApiClient.getFlyersForReview.mockResolvedValue({
|
||||
ok: false,
|
||||
json: async () => ({ message: 'Server error' }),
|
||||
} as Response);
|
||||
@@ -138,7 +131,7 @@ describe('FlyerReviewPage', () => {
|
||||
|
||||
it('renders error message when API throws an error', async () => {
|
||||
const networkError = new Error('Network error');
|
||||
vi.mocked(apiClient.getFlyersForReview).mockRejectedValue(networkError);
|
||||
mockedApiClient.getFlyersForReview.mockRejectedValue(networkError);
|
||||
|
||||
render(
|
||||
<MemoryRouter>
|
||||
@@ -159,7 +152,7 @@ describe('FlyerReviewPage', () => {
|
||||
|
||||
it('renders a generic error for non-Error rejections', async () => {
|
||||
const nonErrorRejection = { message: 'This is not an Error object' };
|
||||
vi.mocked(apiClient.getFlyersForReview).mockRejectedValue(nonErrorRejection);
|
||||
mockedApiClient.getFlyersForReview.mockRejectedValue(nonErrorRejection);
|
||||
|
||||
render(
|
||||
<MemoryRouter>
|
||||
|
||||
@@ -12,14 +12,9 @@ import {
|
||||
} from '../../../tests/utils/mockFactories';
|
||||
import { renderWithProviders } from '../../../tests/utils/renderWithProviders';
|
||||
|
||||
// Cast the mocked module to its mocked type to retain type safety and autocompletion.
|
||||
// The apiClient is now mocked globally via src/tests/setup/tests-setup-unit.ts.
|
||||
const mockedApiClient = apiClient as Mocked<typeof apiClient>;
|
||||
|
||||
// Mock the logger
|
||||
vi.mock('../../../services/logger', () => ({
|
||||
logger: { info: vi.fn(), error: vi.fn() },
|
||||
}));
|
||||
// The apiClient and logger are mocked globally.
|
||||
// We can get a typed reference to the apiClient for individual test overrides.
|
||||
const mockedApiClient = vi.mocked(apiClient);
|
||||
|
||||
// Mock the ConfirmationModal to test its props and interactions
|
||||
// The ConfirmationModal is now in a different directory.
|
||||
|
||||
@@ -21,25 +21,10 @@ vi.mock('../../../components/PasswordInput', () => ({
|
||||
PasswordInput: (props: any) => <input {...props} data-testid="password-input" />,
|
||||
}));
|
||||
|
||||
// The apiClient, notificationService, react-hot-toast, and logger are all mocked globally.
|
||||
// We can get a typed reference to the apiClient for individual test overrides.
|
||||
const mockedApiClient = vi.mocked(apiClient, true);
|
||||
|
||||
vi.mock('../../../services/notificationService');
|
||||
vi.mock('react-hot-toast', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
success: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
}));
|
||||
vi.mock('../../../services/logger.client', () => ({
|
||||
logger: {
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
const mockOnClose = vi.fn();
|
||||
const mockOnLoginSuccess = vi.fn();
|
||||
const mockOnSignOut = vi.fn();
|
||||
|
||||
@@ -8,46 +8,11 @@ import toast from 'react-hot-toast';
|
||||
import { createMockUser } from '../../../tests/utils/mockFactories';
|
||||
import { renderWithProviders } from '../../../tests/utils/renderWithProviders';
|
||||
|
||||
// Mock the entire apiClient module to ensure all exports are defined.
|
||||
// This is the primary fix for the error: [vitest] No "..." export is defined on the mock.
|
||||
vi.mock('../../../services/apiClient', () => ({
|
||||
// Mocks for providers used by renderWithProviders
|
||||
fetchFlyers: vi.fn(),
|
||||
fetchMasterItems: vi.fn(),
|
||||
fetchWatchedItems: vi.fn(),
|
||||
fetchShoppingLists: vi.fn(),
|
||||
getAuthenticatedUserProfile: vi.fn(),
|
||||
pingBackend: vi.fn(),
|
||||
checkStorage: vi.fn(),
|
||||
checkDbPoolHealth: vi.fn(),
|
||||
checkPm2Status: vi.fn(),
|
||||
checkRedisHealth: vi.fn(),
|
||||
checkDbSchema: vi.fn(),
|
||||
loginUser: vi.fn(),
|
||||
triggerFailingJob: vi.fn(),
|
||||
clearGeocodeCache: vi.fn(),
|
||||
}));
|
||||
// Get a type-safe mocked version of the apiClient module.
|
||||
// The apiClient is mocked globally in `src/tests/setup/globalApiMock.ts`.
|
||||
// We can get a type-safe mocked version of the module to override functions for specific tests.
|
||||
const mockedApiClient = vi.mocked(apiClient);
|
||||
|
||||
// Correct the relative path to the logger module.
|
||||
vi.mock('../../../services/logger', () => ({
|
||||
logger: {
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock toast to check for notifications
|
||||
vi.mock('react-hot-toast', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
success: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
}));
|
||||
// The logger and react-hot-toast are mocked globally.
|
||||
|
||||
describe('SystemCheck', () => {
|
||||
// Store original env variable
|
||||
|
||||
@@ -6,14 +6,8 @@ import { ApiProvider } from './ApiProvider';
|
||||
import { ApiContext } from '../contexts/ApiContext';
|
||||
import * as apiClient from '../services/apiClient';
|
||||
|
||||
// Mock the apiClient module.
|
||||
// Since ApiProvider and ApiContext import * as apiClient, mocking it ensures
|
||||
// we control the reference identity and can verify it's being passed correctly.
|
||||
vi.mock('../services/apiClient', () => ({
|
||||
fetchFlyers: vi.fn(),
|
||||
fetchMasterItems: vi.fn(),
|
||||
// Add other mocked methods as needed for the shape to be valid-ish
|
||||
}));
|
||||
// The apiClient is mocked globally in `src/tests/setup/globalApiMock.ts`.
|
||||
// This test verifies that the ApiProvider correctly provides this mocked module.
|
||||
|
||||
describe('ApiProvider & ApiContext', () => {
|
||||
const TestConsumer = () => {
|
||||
|
||||
@@ -4,12 +4,12 @@ import { render, screen, waitFor, fireEvent, act } from '@testing-library/react'
|
||||
import { describe, it, expect, vi, beforeEach, type Mocked } from 'vitest';
|
||||
import { AuthProvider } from './AuthProvider';
|
||||
import { AuthContext } from '../contexts/AuthContext';
|
||||
import * as apiClient from '../services/apiClient';
|
||||
import * as tokenStorage from '../services/tokenStorage';
|
||||
import { createMockUserProfile } from '../tests/utils/mockFactories';
|
||||
import * as apiClient from '../services/apiClient';
|
||||
|
||||
// Mocks
|
||||
vi.mock('../services/apiClient');
|
||||
// The apiClient is mocked globally in `src/tests/setup/globalApiMock.ts`.
|
||||
vi.mock('../services/tokenStorage');
|
||||
vi.mock('../services/logger.client', () => ({
|
||||
logger: {
|
||||
@@ -20,7 +20,7 @@ vi.mock('../services/logger.client', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const mockedApiClient = apiClient as Mocked<typeof apiClient>;
|
||||
const mockedApiClient = vi.mocked(apiClient);
|
||||
const mockedTokenStorage = tokenStorage as Mocked<typeof tokenStorage>;
|
||||
|
||||
const mockProfile = createMockUserProfile({
|
||||
|
||||
65
src/tests/setup/globalApiMock.ts
Normal file
65
src/tests/setup/globalApiMock.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
// src/tests/setup/globalApiMock.ts
|
||||
import { vi } from 'vitest';
|
||||
|
||||
/**
|
||||
* Mocks the entire apiClient module.
|
||||
* This global mock is loaded for all tests via the `setupFiles` config in vitest.config.ts.
|
||||
* It prevents test failures in components that use providers (like FlyersProvider, AuthProvider)
|
||||
* which make API calls on mount when using `renderWithProviders`.
|
||||
*
|
||||
* Individual tests can override specific functions as needed, for example:
|
||||
*
|
||||
* import { vi } from 'vitest';
|
||||
* import * as apiClient from '../services/apiClient';
|
||||
*
|
||||
* const mockedApiClient = vi.mocked(apiClient);
|
||||
*
|
||||
* it('should test something', () => {
|
||||
* mockedApiClient.someFunction.mockResolvedValue({ ... });
|
||||
* // ... rest of the test
|
||||
* });
|
||||
*/
|
||||
vi.mock('../../services/apiClient', () => ({
|
||||
// --- Provider Mocks (with default successful responses) ---
|
||||
// These are essential for any test using renderWithProviders, as AppProviders
|
||||
// will mount all these data providers.
|
||||
fetchFlyers: vi.fn(() => Promise.resolve(new Response(JSON.stringify({ flyers: [], hasMore: false })))),
|
||||
fetchMasterItems: vi.fn(() => Promise.resolve(new Response(JSON.stringify([])))),
|
||||
fetchWatchedItems: vi.fn(() => Promise.resolve(new Response(JSON.stringify([])))),
|
||||
fetchShoppingLists: vi.fn(() => Promise.resolve(new Response(JSON.stringify([])))),
|
||||
getAuthenticatedUserProfile: vi.fn(() => Promise.resolve(new Response(JSON.stringify(null)))),
|
||||
fetchCategories: vi.fn(() => Promise.resolve(new Response(JSON.stringify([])))), // For CorrectionsPage
|
||||
fetchAllBrands: vi.fn(() => Promise.resolve(new Response(JSON.stringify([])))), // For AdminBrandManager
|
||||
|
||||
// --- General Mocks (return empty vi.fn() by default) ---
|
||||
// These functions are commonly used and can be implemented in specific tests.
|
||||
suggestRecipe: vi.fn(),
|
||||
getApplicationStats: vi.fn(),
|
||||
getSuggestedCorrections: vi.fn(),
|
||||
approveCorrection: vi.fn(),
|
||||
rejectCorrection: vi.fn(),
|
||||
updateSuggestedCorrection: vi.fn(),
|
||||
pingBackend: vi.fn(),
|
||||
checkStorage: vi.fn(),
|
||||
checkDbPoolHealth: vi.fn(),
|
||||
checkPm2Status: vi.fn(),
|
||||
checkRedisHealth: vi.fn(),
|
||||
checkDbSchema: vi.fn(),
|
||||
loginUser: vi.fn(),
|
||||
registerUser: vi.fn(),
|
||||
requestPasswordReset: vi.fn(),
|
||||
triggerFailingJob: vi.fn(),
|
||||
clearGeocodeCache: vi.fn(),
|
||||
uploadBrandLogo: vi.fn(),
|
||||
fetchActivityLog: vi.fn(),
|
||||
updateUserProfile: vi.fn(),
|
||||
updateUserPassword: vi.fn(),
|
||||
updateUserPreferences: vi.fn(),
|
||||
exportUserData: vi.fn(),
|
||||
deleteUserAccount: vi.fn(),
|
||||
getUserAddress: vi.fn(),
|
||||
updateUserAddress: vi.fn(),
|
||||
geocodeAddress: vi.fn(),
|
||||
getFlyersForReview: vi.fn(),
|
||||
fetchLeaderboard: vi.fn(),
|
||||
}));
|
||||
@@ -257,67 +257,6 @@ vi.mock('@google/genai', () => {
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Mocks the entire apiClient module.
|
||||
* This ensures that all test files that import from apiClient will get this mocked version.
|
||||
*/
|
||||
vi.mock('../../services/apiClient', () => ({
|
||||
// --- Auth ---
|
||||
registerUser: vi.fn(),
|
||||
loginUser: vi.fn(),
|
||||
getAuthenticatedUserProfile: vi.fn(),
|
||||
requestPasswordReset: vi.fn(),
|
||||
resetPassword: vi.fn(),
|
||||
updateUserPassword: vi.fn(),
|
||||
deleteUserAccount: vi.fn(),
|
||||
updateUserPreferences: vi.fn(),
|
||||
updateUserProfile: vi.fn(),
|
||||
// --- Data Fetching & Manipulation ---
|
||||
fetchFlyers: vi.fn(),
|
||||
fetchFlyerItems: vi.fn(),
|
||||
// Provide a default implementation that returns a valid Response object to prevent timeouts.
|
||||
fetchFlyerItemsForFlyers: vi.fn(() => Promise.resolve(new Response(JSON.stringify([])))),
|
||||
countFlyerItemsForFlyers: vi.fn(() =>
|
||||
Promise.resolve(new Response(JSON.stringify({ count: 0 }))),
|
||||
),
|
||||
fetchMasterItems: vi.fn(),
|
||||
fetchWatchedItems: vi.fn(),
|
||||
addWatchedItem: vi.fn(),
|
||||
removeWatchedItem: vi.fn(),
|
||||
fetchShoppingLists: vi.fn(),
|
||||
createShoppingList: vi.fn(),
|
||||
deleteShoppingList: vi.fn(),
|
||||
addShoppingListItem: vi.fn(),
|
||||
updateShoppingListItem: vi.fn(),
|
||||
removeShoppingListItem: vi.fn(),
|
||||
fetchHistoricalPriceData: vi.fn(),
|
||||
processFlyerFile: vi.fn(),
|
||||
uploadLogoAndUpdateStore: vi.fn(),
|
||||
exportUserData: vi.fn(),
|
||||
// --- Address ---
|
||||
getUserAddress: vi.fn(),
|
||||
updateUserAddress: vi.fn(),
|
||||
geocodeAddress: vi.fn(() => Promise.resolve(new Response(JSON.stringify({ lat: 0, lng: 0 })))),
|
||||
// --- Admin ---
|
||||
getSuggestedCorrections: vi.fn(),
|
||||
fetchCategories: vi.fn(),
|
||||
approveCorrection: vi.fn(),
|
||||
rejectCorrection: vi.fn(),
|
||||
updateSuggestedCorrection: vi.fn(),
|
||||
getApplicationStats: vi.fn(),
|
||||
fetchActivityLog: vi.fn(),
|
||||
fetchAllBrands: vi.fn(),
|
||||
uploadBrandLogo: vi.fn(),
|
||||
// --- System ---
|
||||
pingBackend: vi.fn(),
|
||||
checkDbSchema: vi.fn(),
|
||||
checkStorage: vi.fn(),
|
||||
checkDbPoolHealth: vi.fn(),
|
||||
checkRedisHealth: vi.fn(),
|
||||
checkPm2Status: vi.fn(),
|
||||
fetchLeaderboard: vi.fn(),
|
||||
}));
|
||||
|
||||
// FIX: Mock the aiApiClient module as well, which is used by AnalysisPanel
|
||||
vi.mock('../../services/aiApiClient', () => ({
|
||||
// Provide a default implementation that returns a valid Response object to prevent timeouts.
|
||||
|
||||
102
src/types/exif-parser.d.ts
vendored
102
src/types/exif-parser.d.ts
vendored
@@ -5,4 +5,104 @@
|
||||
* which does not ship with its own TypeScript types. This allows TypeScript
|
||||
* to recognize it as a module and avoids "implicit any" errors.
|
||||
*/
|
||||
declare module 'exif-parser';
|
||||
declare module 'exif-parser' {
|
||||
/**
|
||||
* Represents the size of the image.
|
||||
*/
|
||||
export interface ImageSize {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents thumbnail data if available.
|
||||
*/
|
||||
export interface Thumbnail {
|
||||
format: string;
|
||||
width: number;
|
||||
height: number;
|
||||
offset: number;
|
||||
size: number;
|
||||
buffer: Buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents GPS information if available.
|
||||
*/
|
||||
export interface GPS {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
altitude: number;
|
||||
latitudeRef: string;
|
||||
longitudeRef: string;
|
||||
altitudeRef: number;
|
||||
GPSDateStamp: string;
|
||||
GPSTimeStamp: number[]; // [hour, minute, second]
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the parsed EXIF data structure.
|
||||
* This includes common tags and derived properties.
|
||||
*/
|
||||
export interface ExifData {
|
||||
/**
|
||||
* A dictionary of raw EXIF tags. Keys are tag names (e.g., 'Make', 'Model', 'DateTimeOriginal').
|
||||
* Values can be of various types (string, number, Date, etc.).
|
||||
*/
|
||||
tags: {
|
||||
Make?: string;
|
||||
Model?: string;
|
||||
Orientation?: number;
|
||||
XResolution?: number;
|
||||
YResolution?: number;
|
||||
ResolutionUnit?: number;
|
||||
DateTimeOriginal?: Date; // Parsed into a Date object
|
||||
DateTimeDigitized?: Date;
|
||||
ExposureTime?: number;
|
||||
FNumber?: number;
|
||||
ISOSpeedRatings?: number;
|
||||
ShutterSpeedValue?: number;
|
||||
ApertureValue?: number;
|
||||
BrightnessValue?: number;
|
||||
ExposureBiasValue?: number;
|
||||
MaxApertureValue?: number;
|
||||
MeteringMode?: number;
|
||||
LightSource?: number;
|
||||
Flash?: number;
|
||||
FocalLength?: number;
|
||||
ColorSpace?: number;
|
||||
ExifImageWidth?: number;
|
||||
ExifImageHeight?: number;
|
||||
ExposureMode?: number;
|
||||
WhiteBalance?: number;
|
||||
DigitalZoomRatio?: number;
|
||||
FocalLengthIn35mmFilm?: number;
|
||||
SceneCaptureType?: number;
|
||||
GainControl?: number;
|
||||
Contrast?: number;
|
||||
Saturation?: number;
|
||||
Sharpness?: number;
|
||||
SubjectDistanceRange?: number;
|
||||
GPSVersionID?: number[];
|
||||
GPSLatitudeRef?: string;
|
||||
GPSLatitude?: number[];
|
||||
GPSLongitudeRef?: string;
|
||||
GPSLongitude?: number[];
|
||||
GPSAltitudeRef?: number;
|
||||
GPSAltitude?: number;
|
||||
GPSTimeStamp?: number[];
|
||||
GPSDateStamp?: string;
|
||||
[key: string]: any; // Allow for other, less common tags
|
||||
};
|
||||
imageSize: ImageSize;
|
||||
thumbnail?: Thumbnail;
|
||||
gps?: GPS;
|
||||
}
|
||||
|
||||
export class ExifParser {
|
||||
static create(buffer: Buffer): ExifParser;
|
||||
parse(): ExifData;
|
||||
}
|
||||
|
||||
export default ExifParser;
|
||||
}
|
||||
80
src/types/pdf-poppler.d.ts
vendored
80
src/types/pdf-poppler.d.ts
vendored
@@ -7,37 +7,115 @@
|
||||
* structure, preventing import errors and enabling type checking.
|
||||
*/
|
||||
declare module 'pdf-poppler' {
|
||||
/**
|
||||
* Defines the options available for the main `convert` method.
|
||||
* This appears to be a simplified wrapper around pdftocairo.
|
||||
*/
|
||||
export interface ConvertOptions {
|
||||
/**
|
||||
* The output image format.
|
||||
*/
|
||||
format?: 'jpeg' | 'png' | 'tiff';
|
||||
/**
|
||||
* The directory where output images will be saved.
|
||||
*/
|
||||
out_dir?: string;
|
||||
/**
|
||||
* The prefix for the output image files.
|
||||
*/
|
||||
out_prefix?: string;
|
||||
/**
|
||||
* Specify a page number to convert a specific page, or null to convert all pages.
|
||||
*/
|
||||
page?: number | null;
|
||||
/**
|
||||
* Specifies the resolution, in DPI. The default is 72 DPI.
|
||||
*/
|
||||
resolution?: number;
|
||||
/**
|
||||
* Scales each page to fit in scale-to x scale-to pixel square.
|
||||
*/
|
||||
scale_to?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the options available for the pdfToCairo conversion method.
|
||||
* This interface can be expanded as more options are used.
|
||||
* These options correspond to the command-line arguments for the `pdftocairo` utility.
|
||||
*/
|
||||
export interface PopplerOptions {
|
||||
antialias?: 'default' | 'gray' | 'none' | 'subpixel';
|
||||
cropBox?: boolean;
|
||||
cropHeight?: number;
|
||||
cropWidth?: number;
|
||||
cropSize?: number;
|
||||
cropX?: number;
|
||||
cropY?: number;
|
||||
duplex?: boolean;
|
||||
epsFile?: boolean;
|
||||
expand?: boolean;
|
||||
firstPage?: number;
|
||||
grayFile?: boolean;
|
||||
lastPage?: number;
|
||||
jpegFile?: boolean;
|
||||
jpegOptions?: string;
|
||||
level2?: boolean;
|
||||
level3?: boolean;
|
||||
monoFile?: boolean;
|
||||
noCenter?: boolean;
|
||||
noCrop?: boolean;
|
||||
noRotate?: boolean;
|
||||
noShrink?: boolean;
|
||||
ownerPassword?: string;
|
||||
paperHeight?: number;
|
||||
paperWidth?: number;
|
||||
paperSize?: 'letter' | 'legal' | 'A4' | 'A3' | 'match';
|
||||
pngFile?: boolean;
|
||||
psFile?: boolean;
|
||||
pdfFile?: boolean;
|
||||
resolution?: number;
|
||||
scaleTo?: number;
|
||||
scaleToX?: number;
|
||||
scaleToY?: number;
|
||||
svgFile?: boolean;
|
||||
tiffFile?: boolean;
|
||||
userPassword?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the structure of the PDF information object returned by `pdfInfo`.
|
||||
*/
|
||||
export interface PdfInfo {
|
||||
// Based on common pdfinfo output
|
||||
title: string;
|
||||
author: string;
|
||||
creator: string;
|
||||
producer: string;
|
||||
creationDate: string;
|
||||
modDate: string;
|
||||
tagged: boolean;
|
||||
form: string;
|
||||
pages: number;
|
||||
encrypted: boolean;
|
||||
pageSize: string;
|
||||
fileSize: string;
|
||||
optimized: boolean;
|
||||
pdfVersion: string;
|
||||
}
|
||||
|
||||
export class Poppler {
|
||||
constructor(binPath?: string);
|
||||
pdfToCairo(file: string, outputFilePrefix?: string, options?: PopplerOptions): Promise<string>;
|
||||
pdfInfo(file: string, options?: { ownerPassword?: string; userPassword?: string }): Promise<PdfInfo>;
|
||||
pdfToPs(file: string, outputFile: string, options?: any): Promise<string>;
|
||||
pdfToText(file: string, outputFile: string, options?: any): Promise<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a PDF file to images. This seems to be a convenience function provided by the library.
|
||||
* @param pdfPath The path to the PDF file.
|
||||
* @param options The conversion options.
|
||||
*/
|
||||
export function convert(pdfPath: string, options?: ConvertOptions): Promise<string>;
|
||||
|
||||
export default Poppler;
|
||||
}
|
||||
|
||||
@@ -41,12 +41,14 @@ export default defineConfig({
|
||||
// By default, Vitest does not suppress console logs.
|
||||
// The onConsoleLog hook is only needed if you want to conditionally filter specific logs.
|
||||
// Keeping the default behavior is often safer to avoid missing important warnings.
|
||||
|
||||
environment: 'jsdom',
|
||||
// Explicitly point Vitest to the correct tsconfig and enable globals.
|
||||
globals: true, // tsconfig is auto-detected, so the explicit property is not needed and causes an error.
|
||||
globalSetup: './src/tests/setup/global-setup.ts',
|
||||
setupFiles: ['./src/tests/setup/tests-setup-unit.ts'],
|
||||
// The globalApiMock MUST come first to ensure it's applied before other mocks that might depend on it.
|
||||
setupFiles: [
|
||||
'./src/tests/setup/globalApiMock.ts',
|
||||
'./src/tests/setup/tests-setup-unit.ts',
|
||||
],
|
||||
// Explicitly include only test files.
|
||||
// We remove 'src/vite-env.d.ts' which was causing it to be run as a test.
|
||||
include: ['src/**/*.test.{ts,tsx}'],
|
||||
|
||||
Reference in New Issue
Block a user