diff --git a/src/App.test.tsx b/src/App.test.tsx index 7a568eb5..42ed58ff 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -33,6 +33,14 @@ vi.mock('./pages/ResetPasswordPage', () => ({ ResetPasswordPage: () =>
({ AnonymousUserBanner: () =>
Anonymous User Banner
})); vi.mock('./pages/VoiceLabPage', () => ({ VoiceLabPage: () =>
Voice Lab Page
})); vi.mock('./components/WhatsNewModal', () => ({ WhatsNewModal: () =>
What's New Modal
})); +// 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', () => ({ + GlobalWorkerOptions: { workerSrc: '' }, + getDocument: vi.fn(() => ({ + promise: Promise.resolve({ getPage: vi.fn() }), + })), +})); // By casting the apiClient to `Mocked`, we get type-safe access // to Vitest's mock functions like `mockResolvedValue`. diff --git a/src/components/FlyerCorrectionTool.test.tsx b/src/components/FlyerCorrectionTool.test.tsx index d6184757..7bc1f251 100644 --- a/src/components/FlyerCorrectionTool.test.tsx +++ b/src/components/FlyerCorrectionTool.test.tsx @@ -63,7 +63,7 @@ describe('FlyerCorrectionTool', () => { it('should call onClose when the close button is clicked', () => { render(); - fireEvent.click(screen.getByRole('button', { name: /close/i })); + fireEvent.click(screen.getByLabelText('Close correction tool')); expect(defaultProps.onClose).toHaveBeenCalledTimes(1); }); diff --git a/src/routes/auth.test.ts b/src/routes/auth.test.ts index eec19e6f..d71a3bd2 100644 --- a/src/routes/auth.test.ts +++ b/src/routes/auth.test.ts @@ -56,7 +56,7 @@ vi.mock('./passport', () => ({ const app = express(); app.use(express.json({ strict: false })); app.use(cookieParser()); // Add cookie-parser middleware to populate req.cookies -app.use('/api/auth', authRouter); +app.use('/api/auth', authRouter); // This was missing describe('Auth Routes (/api/auth)', () => { beforeEach(() => { diff --git a/src/routes/system.test.ts b/src/routes/system.test.ts index 7c74eaa9..b1279247 100644 --- a/src/routes/system.test.ts +++ b/src/routes/system.test.ts @@ -1,17 +1,17 @@ // src/routes/system.test.ts -import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest'; import supertest from 'supertest'; import express from 'express'; -import type { ExecException } from 'child_process'; +import type { ExecException, ChildProcess } from 'child_process'; import systemRouter from './system'; -// Define a mock function at the module level. This will be our controlled version of `exec`. -const mockedExec = vi.fn(); - // Mock the child_process module. We explicitly define its exports. -vi.mock('child_process', () => ({ - exec: mockedExec, -})); +// The mock factory MUST NOT reference variables from outside its scope due to hoisting. +// Instead, we import the mocked function after the mock is declared. +vi.mock('child_process', () => ({ exec: vi.fn() })); + +// Now we can safely import the mocked function and control it. +import { exec } from 'child_process'; vi.mock('../services/logger.server', () => ({ logger: { @@ -29,6 +29,7 @@ app.use('/api/system', systemRouter); describe('System Routes (/api/system)', () => { beforeEach(() => { + // We cast here to get type-safe access to mock functions like .mockImplementation vi.clearAllMocks(); }); @@ -42,12 +43,11 @@ describe('System Routes (/api/system)', () => { └───────────┴───────────┘ `; // The `exec` callback receives (error, stdout, stderr). For success, error is null. - vi.mocked(mockedExec).mockImplementation(( - command: string, - callback: (error: ExecException | null, stdout: string, stderr: string) => void - ) => { - callback(null, pm2OnlineOutput, ''); - return {}; // Return a dummy child process object + // We must match the overloaded signature of `exec`. The second argument can be options or the callback. + vi.mocked(exec).mockImplementation((command, options, callback) => { + const cb = typeof options === 'function' ? options : callback; + cb?.(null, pm2OnlineOutput, ''); + return {} as ChildProcess; // Return a dummy child process object }); // Act @@ -63,12 +63,10 @@ describe('System Routes (/api/system)', () => { const pm2StoppedOutput = ` │ status │ stopped │ `; - vi.mocked(mockedExec).mockImplementation(( - command: string, - callback: (error: ExecException | null, stdout: string, stderr: string) => void - ) => { - callback(null, pm2StoppedOutput, ''); - return {}; + vi.mocked(exec).mockImplementation((command, options, callback) => { + const cb = typeof options === 'function' ? options : callback; + cb?.(null, pm2StoppedOutput, ''); + return {} as ChildProcess; }); // Act @@ -82,12 +80,10 @@ describe('System Routes (/api/system)', () => { it('should return success: false when pm2 process does not exist', async () => { // Arrange: Simulate the error and stdout when a process is not found. const processNotFoundOutput = "[PM2][ERROR] Process or Namespace flyer-crawler-api doesn't exist"; - vi.mocked(mockedExec).mockImplementation(( - command: string, - callback: (error: ExecException | null, stdout: string, stderr: string) => void - ) => { - callback(new Error('Command failed') as ExecException, processNotFoundOutput, ''); - return {}; + vi.mocked(exec).mockImplementation((command, options, callback) => { + const cb = typeof options === 'function' ? options : callback; + cb?.(new Error('Command failed') as ExecException, processNotFoundOutput, ''); + return {} as ChildProcess; }); // Act @@ -100,12 +96,10 @@ describe('System Routes (/api/system)', () => { it('should return 500 on a generic exec error', async () => { // Arrange: Simulate a generic failure of the `exec` command. - vi.mocked(mockedExec).mockImplementation(( - command: string, - callback: (error: ExecException | null, stdout: string, stderr: string) => void - ) => { - callback(new Error('Generic exec error') as ExecException, '', 'Some stderr output'); - return {}; + vi.mocked(exec).mockImplementation((command, options, callback) => { + const cb = typeof options === 'function' ? options : callback; + cb?.(new Error('Generic exec error') as ExecException, '', 'Some stderr output'); + return {} as ChildProcess; }); // Act @@ -120,12 +114,10 @@ describe('System Routes (/api/system)', () => { // Arrange: Simulate a scenario where the command writes to stderr but doesn't // produce a formal error object for the callback's first argument. const stderrMessage = 'A non-fatal warning or configuration issue.'; - vi.mocked(mockedExec).mockImplementation(( - command: string, - callback: (error: ExecException | null, stdout: string, stderr: string) => void - ) => { - callback(null, '', stderrMessage); - return {}; + vi.mocked(exec).mockImplementation((command, options, callback) => { + const cb = typeof options === 'function' ? options : callback; + cb?.(null, '', stderrMessage); + return {} as ChildProcess; }); // Act diff --git a/src/routes/user.test.ts b/src/routes/user.test.ts index d37badac..7e58df08 100644 --- a/src/routes/user.test.ts +++ b/src/routes/user.test.ts @@ -47,7 +47,7 @@ app.use(express.json()); app.use('/api/users', express.json({ strict: false }), userRouter); -describe.only('User Routes (/api/users)', () => { +describe('User Routes (/api/users)', () => { beforeEach(() => { vi.clearAllMocks(); // Default to unauthorized for all tests unless overridden in a `describe` block. diff --git a/src/tests/setup/unit-setup.ts b/src/tests/setup/unit-setup.ts index 5f4bf8ad..d0bb6e68 100644 --- a/src/tests/setup/unit-setup.ts +++ b/src/tests/setup/unit-setup.ts @@ -56,29 +56,11 @@ afterEach(cleanup); * This is the central point for mocking the database connection pool for unit tests. */ vi.mock('pg', () => { - // --- DIAGNOSTIC LOGGING #1: "Is the Mock Even Loading?" --- - // This log will appear once at the start of the test run to prove that this global mock is being used. - console.log('[vitest-mock-pg] Global `pg` mock from unit-setup.ts is being initialized.'); - - // These are the mock functions that will replace the real 'pg' methods. const mockQuery = vi.fn(); const mockRelease = vi.fn(); - const mockConnect = vi.fn().mockResolvedValue({ - query: mockQuery, - release: mockRelease, - }); + const mockConnect = vi.fn().mockResolvedValue({ query: mockQuery, release: mockRelease }); + const mockPool = vi.fn(() => ({ query: mockQuery, connect: mockConnect, totalCount: 10, idleCount: 5, waitingCount: 0, end: vi.fn() })); - // Mock the Pool class constructor. - const mockPool = vi.fn(() => ({ - query: mockQuery, - connect: mockConnect, - totalCount: 10, - idleCount: 5, - waitingCount: 0, - end: vi.fn(), - })); - - // The object returned here becomes the exports of the 'pg' module for all unit tests. return { __esModule: true, // This is important for ES module interoperability. Pool: mockPool, @@ -88,6 +70,27 @@ vi.mock('pg', () => { }; }); +/** + * Mocks the Google Generative AI package. This prevents real API calls during tests. + */ +vi.mock('@google/generative-ai', () => { + const mockGenerativeModel = { + generateContent: vi.fn().mockResolvedValue({ + response: { + text: () => 'Mocked AI response', + candidates: [{ content: { parts: [{ text: 'Mocked AI response' }] } }], + }, + }), + }; + return { + GoogleGenerativeAI: vi.fn(() => ({ + getGenerativeModel: () => mockGenerativeModel, + })), + HarmCategory: {}, + HarmBlockThreshold: {}, + }; +}); + /** * Mocks the entire apiClient module. * This ensures that all test files that import from apiClient will get this mocked version. @@ -160,4 +163,18 @@ vi.mock('../../services/logger', () => ({ vi.mock('../../services/notificationService', () => ({ notifySuccess: vi.fn(), notifyError: vi.fn(), +})); + +/** + * Mocks the `react-hot-toast` library to prevent actual toasts from rendering + * during tests. This also provides mock controls for functions like `toast.loading`. + */ +vi.mock('react-hot-toast', () => ({ + default: { + success: vi.fn(), + error: vi.fn(), + loading: vi.fn(() => 'toast-id-123'), // Return a dummy ID + dismiss: vi.fn(), + }, + Toaster: () => null, // The component renders nothing in tests })); \ No newline at end of file