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