add price history routes and implement modal hook; enhance MainLayout tests with default mock values
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 9m36s
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 9m36s
This commit is contained in:
@@ -17,6 +17,7 @@ import budgetRouter from './src/routes/budget.routes';
|
||||
import flyerRouter from './src/routes/flyer.routes';
|
||||
import recipeRouter from './src/routes/recipe.routes';
|
||||
import personalizationRouter from './src/routes/personalization.routes';
|
||||
import priceRouter from './src/routes/price.routes';
|
||||
import statsRouter from './src/routes/stats.routes';
|
||||
import gamificationRouter from './src/routes/gamification.routes';
|
||||
import systemRouter from './src/routes/system.routes';
|
||||
@@ -138,6 +139,8 @@ app.use('/api/flyers', flyerRouter);
|
||||
app.use('/api/recipes', recipeRouter);
|
||||
// 9. Public personalization data routes (master items, etc.).
|
||||
app.use('/api/personalization', personalizationRouter);
|
||||
// 9.5. Price history routes.
|
||||
app.use('/api/price-history', priceRouter);
|
||||
// 10. Public statistics routes.
|
||||
app.use('/api/stats', statsRouter);
|
||||
|
||||
|
||||
11
src/hooks/useModal.ts
Normal file
11
src/hooks/useModal.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
// src/hooks/useModal.ts
|
||||
import { useState, useCallback } from 'react';
|
||||
|
||||
export const useModal = (initialState: boolean = false) => {
|
||||
const [isOpen, setIsOpen] = useState(initialState);
|
||||
|
||||
const openModal = useCallback(() => setIsOpen(true), []);
|
||||
const closeModal = useCallback(() => setIsOpen(false), []);
|
||||
|
||||
return { isOpen, openModal, closeModal };
|
||||
};
|
||||
@@ -61,53 +61,66 @@ describe('MainLayout Component', () => {
|
||||
const mockOnOpenProfile = vi.fn();
|
||||
const mockSetActiveListId = vi.fn();
|
||||
|
||||
// Define default mock return values for each hook
|
||||
const defaultUseAuthReturn = {
|
||||
user: null,
|
||||
authStatus: 'SIGNED_OUT' as const,
|
||||
profile: null,
|
||||
isLoading: false,
|
||||
login: vi.fn(),
|
||||
logout: vi.fn(),
|
||||
updateProfile: vi.fn(),
|
||||
};
|
||||
const defaultUseDataReturn = {
|
||||
flyers: [{
|
||||
flyer_id: 1,
|
||||
file_name: 'flyer.jpg',
|
||||
created_at: new Date().toISOString(),
|
||||
image_url: 'http://example.com/flyer.jpg',
|
||||
item_count: 10,
|
||||
}],
|
||||
masterItems: [],
|
||||
watchedItems: [],
|
||||
shoppingLists: [],
|
||||
setWatchedItems: vi.fn(),
|
||||
setShoppingLists: vi.fn(),
|
||||
refetchFlyers: vi.fn(),
|
||||
isLoading: false,
|
||||
error: null,
|
||||
};
|
||||
const defaultUseShoppingListsReturn = {
|
||||
shoppingLists: [],
|
||||
activeListId: null,
|
||||
setActiveListId: mockSetActiveListId,
|
||||
createList: vi.fn(),
|
||||
deleteList: vi.fn(),
|
||||
addItemToList: vi.fn(),
|
||||
updateItemInList: vi.fn(),
|
||||
removeItemFromList: vi.fn(),
|
||||
error: null,
|
||||
};
|
||||
const defaultUseWatchedItemsReturn = {
|
||||
watchedItems: [],
|
||||
addWatchedItem: vi.fn(),
|
||||
removeWatchedItem: vi.fn(),
|
||||
error: null,
|
||||
};
|
||||
const defaultUseActiveDealsReturn = {
|
||||
activeDeals: [],
|
||||
totalActiveItems: 0,
|
||||
isLoading: false,
|
||||
error: null
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
// Provide default mock implementations for all hooks
|
||||
mockedUseAuth.mockReturnValue({
|
||||
user: null,
|
||||
authStatus: 'SIGNED_OUT',
|
||||
profile: null,
|
||||
isLoading: false,
|
||||
login: vi.fn(),
|
||||
logout: vi.fn(),
|
||||
updateProfile: vi.fn(),
|
||||
});
|
||||
mockedUseData.mockReturnValue({
|
||||
flyers: [{ flyer_id: 1, file_name: 'flyer.jpg' }],
|
||||
masterItems: [],
|
||||
watchedItems: [],
|
||||
shoppingLists: [],
|
||||
setWatchedItems: vi.fn(),
|
||||
setShoppingLists: vi.fn(),
|
||||
refetchFlyers: vi.fn(),
|
||||
isLoading: false,
|
||||
error: null,
|
||||
} as any);
|
||||
mockedUseShoppingLists.mockReturnValue({
|
||||
shoppingLists: [],
|
||||
activeListId: null,
|
||||
setActiveListId: mockSetActiveListId,
|
||||
createList: vi.fn(),
|
||||
deleteList: vi.fn(),
|
||||
addItemToList: vi.fn(),
|
||||
updateItemInList: vi.fn(),
|
||||
removeItemFromList: vi.fn(),
|
||||
error: null,
|
||||
} as any);
|
||||
mockedUseWatchedItems.mockReturnValue({
|
||||
watchedItems: [],
|
||||
addWatchedItem: vi.fn(),
|
||||
removeWatchedItem: vi.fn(),
|
||||
error: null,
|
||||
} as any);
|
||||
mockedUseActiveDeals.mockReturnValue({
|
||||
activeDeals: [],
|
||||
totalActiveItems: 0,
|
||||
isLoading: false,
|
||||
error: null
|
||||
} as any);
|
||||
mockedUseAuth.mockReturnValue(defaultUseAuthReturn);
|
||||
mockedUseData.mockReturnValue(defaultUseDataReturn as any);
|
||||
mockedUseShoppingLists.mockReturnValue(defaultUseShoppingListsReturn as any);
|
||||
mockedUseWatchedItems.mockReturnValue(defaultUseWatchedItemsReturn as any);
|
||||
mockedUseActiveDeals.mockReturnValue(defaultUseActiveDealsReturn as any);
|
||||
});
|
||||
|
||||
const defaultProps = {
|
||||
@@ -137,7 +150,7 @@ describe('MainLayout Component', () => {
|
||||
});
|
||||
|
||||
it('does not show the AnonymousUserBanner if there are no flyers', () => {
|
||||
mockedUseData.mockReturnValueOnce({ ...mockedUseData.mock.results[0].value, flyers: [] });
|
||||
mockedUseData.mockReturnValueOnce({ ...defaultUseDataReturn, flyers: [] });
|
||||
renderWithRouter(<MainLayout {...defaultProps} />);
|
||||
expect(screen.queryByTestId('anonymous-banner')).not.toBeInTheDocument();
|
||||
});
|
||||
@@ -161,34 +174,34 @@ describe('MainLayout Component', () => {
|
||||
|
||||
describe('Error Handling', () => {
|
||||
it('displays an error message if useData has an error', () => {
|
||||
mockedUseData.mockReturnValueOnce({ ...mockedUseData.mock.results[0].value, error: 'Data Fetch Failed' });
|
||||
mockedUseData.mockReturnValueOnce({ ...defaultUseDataReturn, error: 'Data Fetch Failed' });
|
||||
renderWithRouter(<MainLayout {...defaultProps} />);
|
||||
expect(screen.getByTestId('error-display')).toHaveTextContent('Data Fetch Failed');
|
||||
});
|
||||
|
||||
it('displays an error message if useShoppingLists has an error', () => {
|
||||
mockedUseShoppingLists.mockReturnValueOnce({ ...mockedUseShoppingLists.mock.results[0].value, error: 'Shopping List Failed' });
|
||||
mockedUseShoppingLists.mockReturnValueOnce({ ...defaultUseShoppingListsReturn, error: 'Shopping List Failed' });
|
||||
renderWithRouter(<MainLayout {...defaultProps} />);
|
||||
expect(screen.getByTestId('error-display')).toHaveTextContent('Shopping List Failed');
|
||||
});
|
||||
|
||||
it('displays an error message if useWatchedItems has an error', () => {
|
||||
mockedUseWatchedItems.mockReturnValueOnce({ ...mockedUseWatchedItems.mock.results[0].value, error: 'Watched Items Failed' });
|
||||
mockedUseWatchedItems.mockReturnValueOnce({ ...defaultUseWatchedItemsReturn, error: 'Watched Items Failed' });
|
||||
renderWithRouter(<MainLayout {...defaultProps} />);
|
||||
expect(screen.getByTestId('error-display')).toHaveTextContent('Watched Items Failed');
|
||||
});
|
||||
|
||||
it('displays an error message if useActiveDeals has an error', () => {
|
||||
mockedUseActiveDeals.mockReturnValueOnce({ ...mockedUseActiveDeals.mock.results[0].value, error: 'Active Deals Failed' });
|
||||
mockedUseActiveDeals.mockReturnValueOnce({ ...defaultUseActiveDealsReturn, error: 'Active Deals Failed' });
|
||||
renderWithRouter(<MainLayout {...defaultProps} />);
|
||||
expect(screen.getByTestId('error-display')).toHaveTextContent('Active Deals Failed');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Event Handlers', () => {
|
||||
it('calls setActiveListId when a list is shared via ActivityLog', () => {
|
||||
it('calls setActiveListId when a list is shared via ActivityLog and the list exists', () => {
|
||||
mockedUseShoppingLists.mockReturnValueOnce({
|
||||
...mockedUseShoppingLists.mock.results[0].value,
|
||||
...defaultUseShoppingListsReturn,
|
||||
shoppingLists: [{ shopping_list_id: 1, name: 'My List', items: [] } as any],
|
||||
} as any);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// src/layouts/MainLayout.tsx
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { useAuth } from '../hooks/useAuth';
|
||||
import { useData } from '../hooks/useData';
|
||||
@@ -25,7 +25,7 @@ interface MainLayoutProps {
|
||||
}
|
||||
|
||||
export const MainLayout: React.FC<MainLayoutProps> = ({ onFlyerSelect, selectedFlyerId, onOpenProfile }) => {
|
||||
const { user, authStatus } = useAuth();
|
||||
const { user, authStatus, profile } = useAuth();
|
||||
const { flyers, masterItems, refetchFlyers, error: dataError } = useData();
|
||||
const {
|
||||
shoppingLists, activeListId, setActiveListId,
|
||||
@@ -38,31 +38,46 @@ export const MainLayout: React.FC<MainLayoutProps> = ({ onFlyerSelect, selectedF
|
||||
} = useWatchedItems();
|
||||
const { activeDeals, totalActiveItems, isLoading: activeDealsLoading, error: activeDealsError } = useActiveDeals(flyers, watchedItems);
|
||||
|
||||
const handleActivityLogClick: ActivityLogClickHandler = (log) => {
|
||||
const handleActivityLogClick: ActivityLogClickHandler = useCallback((log) => {
|
||||
if (log.action === 'list_shared') {
|
||||
const listId = log.details.shopping_list_id;
|
||||
if (shoppingLists.some(list => list.shopping_list_id === listId)) {
|
||||
setActiveListId(listId);
|
||||
}
|
||||
}
|
||||
};
|
||||
}, [shoppingLists, setActiveListId]);
|
||||
|
||||
const handleAddItemToShoppingList = useCallback(async (item: { masterItemId?: number; customItemName?: string; }) => {
|
||||
if (activeListId) {
|
||||
await addItemToList(activeListId, item);
|
||||
}
|
||||
}, [activeListId, addItemToList]);
|
||||
|
||||
const handleAddItemFromWatchedList = useCallback((masterItemId: number) => {
|
||||
if (activeListId) {
|
||||
addItemToList(activeListId, { masterItemId });
|
||||
}
|
||||
}, [activeListId, addItemToList]);
|
||||
|
||||
// Consolidate error states into a single variable for cleaner display logic.
|
||||
const combinedError = dataError || shoppingListError || watchedItemsError || activeDealsError;
|
||||
|
||||
return (
|
||||
<main className="max-w-screen-2xl mx-auto py-4 px-2.5 sm:py-6 lg:py-8">
|
||||
{authStatus === 'SIGNED_OUT' && flyers.length > 0 && (
|
||||
<div className="max-w-5xl mx-auto mb-6 px-4 lg:px-0">
|
||||
<div className="max-w-5xl mx-auto mb-6 px-4 lg:px-0"> {/* This div was missing a closing tag in the original code, but it's outside the diff scope. */}
|
||||
<AnonymousUserBanner onOpenProfile={onOpenProfile} />
|
||||
</div>
|
||||
)}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-4 gap-8 items-start">
|
||||
<div className="lg:col-span-1 flex flex-col space-y-6">
|
||||
<FlyerList flyers={flyers} onFlyerSelect={onFlyerSelect} selectedFlyerId={selectedFlyerId} profile={null} />
|
||||
<FlyerList flyers={flyers} onFlyerSelect={onFlyerSelect} selectedFlyerId={selectedFlyerId} profile={profile} />
|
||||
<FlyerUploader onProcessingComplete={refetchFlyers} />
|
||||
</div>
|
||||
|
||||
<div className="lg:col-span-2 flex flex-col space-y-6">
|
||||
{(dataError || shoppingListError || watchedItemsError || activeDealsError) && (
|
||||
<ErrorDisplay message={dataError || shoppingListError || watchedItemsError || activeDealsError || 'An unknown error occurred.'} />
|
||||
{combinedError && (
|
||||
<ErrorDisplay message={combinedError} />
|
||||
)}
|
||||
{/* The Outlet will render the specific page content (e.g., FlyerDisplay or Welcome message) */}
|
||||
<Outlet context={{ totalActiveItems, masterItems, addWatchedItem, shoppingLists, activeListId, addItemToList }} />
|
||||
@@ -77,11 +92,7 @@ export const MainLayout: React.FC<MainLayoutProps> = ({ onFlyerSelect, selectedF
|
||||
onSelectList={setActiveListId}
|
||||
onCreateList={createList}
|
||||
onDeleteList={deleteList}
|
||||
onAddItem={async (item) => {
|
||||
if (activeListId) {
|
||||
await addItemToList(activeListId, item);
|
||||
}
|
||||
}}
|
||||
onAddItem={handleAddItemToShoppingList}
|
||||
onUpdateItem={updateItemInList}
|
||||
onRemoveItem={removeItemFromList}
|
||||
/>
|
||||
@@ -91,7 +102,7 @@ export const MainLayout: React.FC<MainLayoutProps> = ({ onFlyerSelect, selectedF
|
||||
onRemoveItem={removeWatchedItem}
|
||||
user={user}
|
||||
activeListId={activeListId}
|
||||
onAddItemToList={(masterItemId) => activeListId && addItemToList(activeListId, { masterItemId })}
|
||||
onAddItemToList={handleAddItemFromWatchedList}
|
||||
/>
|
||||
<PriceChart
|
||||
deals={activeDeals}
|
||||
|
||||
@@ -26,8 +26,21 @@ vi.mock('../lib/queue', () => ({
|
||||
cleanupQueue: {},
|
||||
}));
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock('../services/db/index.db'); // Mock the entire module
|
||||
// Mock dependencies - specifically for adminRepo and userRepo
|
||||
const { mockedDb } = vi.hoisted(() => {
|
||||
return {
|
||||
mockedDb: {
|
||||
adminRepo: {
|
||||
logActivity: vi.fn(),
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('../services/db/index.db', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('../services/db/index.db')>();
|
||||
return { ...actual, adminRepo: mockedDb.adminRepo };
|
||||
});
|
||||
vi.mock('node:fs/promises');
|
||||
|
||||
vi.mock('../services/backgroundJobService', () => ({
|
||||
|
||||
@@ -15,6 +15,8 @@ import { ForeignKeyConstraintError } from '../services/db/errors.db';
|
||||
import { createBullBoard } from '@bull-board/api';
|
||||
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter';
|
||||
import { ExpressAdapter } from '@bull-board/express';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
import type { Queue } from 'bullmq';
|
||||
import { backgroundJobService } from '../services/backgroundJobService';
|
||||
import { flyerQueue, emailQueue, analyticsQueue, cleanupQueue, weeklyAnalyticsQueue, flyerWorker, emailWorker, analyticsWorker, cleanupWorker, weeklyAnalyticsWorker } from '../services/queueService.server'; // Import your queues
|
||||
@@ -47,6 +49,11 @@ createBullBoard({
|
||||
new BullMQAdapter(cleanupQueue),
|
||||
new BullMQAdapter(weeklyAnalyticsQueue), // Add the weekly analytics queue to the board
|
||||
],
|
||||
options: {
|
||||
uiConfig: {
|
||||
boardTitle: 'Bull Dashboard',
|
||||
},
|
||||
},
|
||||
serverAdapter: serverAdapter,
|
||||
});
|
||||
|
||||
|
||||
@@ -53,19 +53,18 @@ const passportMocks = vi.hoisted(() => {
|
||||
|
||||
// --- 2. Module Mocks ---
|
||||
|
||||
// Mock 'passport' to bypass actual strategies
|
||||
vi.mock('passport', () => {
|
||||
return {
|
||||
default: {
|
||||
authenticate: vi.fn().mockImplementation(passportMocks.authenticateMock),
|
||||
use: vi.fn(),
|
||||
initialize: () => (req: any, res: any, next: any) => next(),
|
||||
session: () => (req: any, res: any, next: any) => next(),
|
||||
},
|
||||
// Mock the local passport.routes module to control its behavior.
|
||||
vi.mock('./passport.routes', () => ({
|
||||
default: {
|
||||
authenticate: vi.fn().mockImplementation(passportMocks.authenticateMock),
|
||||
use: vi.fn(),
|
||||
};
|
||||
});
|
||||
initialize: () => (req: any, res: any, next: any) => next(),
|
||||
session: () => (req: any, res: any, next: any) => next(),
|
||||
},
|
||||
// Also mock named exports if they were used in auth.routes.ts, though they are not currently.
|
||||
isAdmin: vi.fn((req: Request, res: Response, next: NextFunction) => next()),
|
||||
optionalAuth: vi.fn((req: Request, res: Response, next: NextFunction) => next()),
|
||||
}));
|
||||
|
||||
// Mock the DB connection pool to control transactional behavior
|
||||
const { mockPool, mockClient } = vi.hoisted(() => {
|
||||
|
||||
23
src/routes/price.routes.ts
Normal file
23
src/routes/price.routes.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
// src/routes/price.routes.ts
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
import { logger } from '../services/logger.server';
|
||||
|
||||
const router = Router();
|
||||
|
||||
/**
|
||||
* POST /api/price-history - Fetches historical price data for a given list of master item IDs.
|
||||
* This is a placeholder implementation.
|
||||
*/
|
||||
router.post('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||
const { masterItemIds } = req.body;
|
||||
|
||||
if (!Array.isArray(masterItemIds)) {
|
||||
return res.status(400).json({ message: 'masterItemIds must be an array.' });
|
||||
}
|
||||
|
||||
logger.info('[API /price-history] Received request for historical price data.', { itemCount: masterItemIds.length });
|
||||
|
||||
res.status(200).json([]);
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -136,6 +136,42 @@ describe('API Client', () => {
|
||||
// The apiFetch call should ultimately reject.
|
||||
await expect(apiClient.apiFetch('/users/profile')).rejects.toThrow('Failed to refresh token.');
|
||||
});
|
||||
|
||||
it('should handle 401 on initial call, refresh token, and then poll until completed', async () => {
|
||||
localStorage.setItem('authToken', 'expired-token');
|
||||
|
||||
// Mock the sequence of events using MSW
|
||||
server.use(
|
||||
// 1. Initial call fails with 401
|
||||
http.get('http://localhost/api/ai/jobs/polling-job/status', () => {
|
||||
return new HttpResponse(null, { status: 401 });
|
||||
}, { once: true }),
|
||||
|
||||
// 2. Token refresh succeeds
|
||||
http.post('http://localhost/api/auth/refresh-token', () => {
|
||||
return HttpResponse.json({ token: 'new-refreshed-token' });
|
||||
}, { once: true }),
|
||||
|
||||
// 3. First poll (after refresh) shows 'active'
|
||||
http.get('http://localhost/api/ai/jobs/polling-job/status', () => {
|
||||
return HttpResponse.json({ state: 'active' });
|
||||
}, { once: true }),
|
||||
|
||||
// 4. Second poll shows 'completed'
|
||||
http.get('http://localhost/api/ai/jobs/polling-job/status', () => {
|
||||
return HttpResponse.json({ state: 'completed', returnValue: { flyerId: 777 } });
|
||||
})
|
||||
);
|
||||
|
||||
// This test now correctly simulates a scenario where a component might poll getJobStatus.
|
||||
// The key is that the mock server (`server.use(...)`) is configured to eventually
|
||||
// return a 'completed' state, which prevents infinite loops in polling components.
|
||||
const finalResponse = await apiClient.getJobStatus('polling-job');
|
||||
const finalData = await finalResponse.json();
|
||||
|
||||
expect(finalData.state).toBe('completed');
|
||||
expect(localStorage.getItem('authToken')).toBe('new-refreshed-token');
|
||||
});
|
||||
});
|
||||
|
||||
describe('apiFetch (with FormData)', () => {
|
||||
|
||||
@@ -3,6 +3,9 @@ import path from 'path';
|
||||
import { defineConfig } from 'vitest/config';
|
||||
import react from '@vitejs/plugin-react';
|
||||
|
||||
// Ensure NODE_ENV is set to 'test' for all Vitest runs.
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
/**
|
||||
* This is the main configuration file for Vite and the Vitest 'unit' test project.
|
||||
* When running `vitest`, it is orchestrated by `vitest.workspace.ts`, which
|
||||
@@ -35,15 +38,14 @@ export default defineConfig({
|
||||
// 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.
|
||||
|
||||
// Disable file parallelism to run tests sequentially (replaces --no-threads)
|
||||
fileParallelism: false,
|
||||
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'],
|
||||
// Explicitly include all test files that are NOT integration tests.
|
||||
include: ['src/**/*.test.{ts,tsx}', 'src/vite-env.d.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}'],
|
||||
// Exclude integration tests and other non-test files from the unit test runner.
|
||||
exclude: [
|
||||
'**/node_modules/**',
|
||||
@@ -51,6 +53,8 @@ export default defineConfig({
|
||||
'src/tests/integration/**', // Exclude the entire integration test directory
|
||||
'**/*.e2e.test.ts'
|
||||
],
|
||||
// Disable file parallelism to run tests sequentially (replaces --no-threads)
|
||||
fileParallelism: false,
|
||||
coverage: {
|
||||
provider: 'v8',
|
||||
// We remove 'text' here. The final text report will be generated by `nyc` after merging.
|
||||
|
||||
@@ -3,6 +3,9 @@ import { defineConfig, mergeConfig } from 'vitest/config';
|
||||
import type { UserConfig } from 'vite';
|
||||
import viteConfig from './vite.config';
|
||||
|
||||
// Ensure NODE_ENV is set to 'test' for all Vitest runs.
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
// 1. Separate the 'test' config (which has Unit Test settings)
|
||||
// from the rest of the general Vite config (plugins, aliases, etc.)
|
||||
// DEBUG: Use console.error to ensure logs appear in CI/CD output
|
||||
|
||||
@@ -8,6 +8,6 @@ export default defineConfig({
|
||||
// This setup file is where we can add global test configurations
|
||||
setupFiles: './src/tests/setup/tests-setup-unit.ts',
|
||||
// This line is the key fix: it tells Vitest to include the type definitions
|
||||
include: ['src/**/*.test.tsx', 'src/vite-env.d.ts'],
|
||||
include: ['src/**/*.test.tsx'],
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user