get rid of mockImplementation(() => promise) - causing memory leaks
Some checks failed
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Failing after 37m26s
Some checks failed
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Failing after 37m26s
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
// src/components/PriceHistoryChart.test.tsx
|
||||
import React from 'react';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { render, screen, waitFor, act } from '@testing-library/react';
|
||||
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
|
||||
import { PriceHistoryChart } from './PriceHistoryChart';
|
||||
import * as apiClient from '../../services/apiClient';
|
||||
@@ -56,11 +56,19 @@ describe('PriceHistoryChart', () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should render a loading spinner while fetching data', () => {
|
||||
(apiClient.fetchHistoricalPriceData as Mock).mockReturnValue(new Promise(() => {}));
|
||||
it('should render a loading spinner while fetching data', async () => {
|
||||
let resolvePromise: (value: Response) => void;
|
||||
const mockPromise = new Promise<Response>(resolve => {
|
||||
resolvePromise = resolve;
|
||||
});
|
||||
(apiClient.fetchHistoricalPriceData as Mock).mockReturnValue(mockPromise);
|
||||
render(<PriceHistoryChart watchedItems={mockWatchedItems} />);
|
||||
expect(screen.getByText(/loading price history/i)).toBeInTheDocument();
|
||||
expect(screen.getByRole('status')).toBeInTheDocument(); // LoadingSpinner
|
||||
await act(async () => {
|
||||
resolvePromise(new Response(JSON.stringify([])));
|
||||
await mockPromise;
|
||||
});
|
||||
});
|
||||
|
||||
it('should render an error message if fetching fails', async () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/features/flyer/AnalysisPanel.test.tsx
|
||||
import React from 'react';
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
|
||||
import { describe, it, expect, vi, beforeEach, type Mock, type Mocked } from 'vitest';
|
||||
import { AnalysisPanel } from './AnalysisPanel';
|
||||
import * as aiApiClient from '../../services/aiApiClient';
|
||||
@@ -102,12 +102,19 @@ describe('AnalysisPanel', () => {
|
||||
});
|
||||
|
||||
it('should show a loading spinner during analysis', async () => {
|
||||
mockedAiApiClient.getQuickInsights.mockImplementation(() => new Promise(() => {})); // Never resolves
|
||||
let resolvePromise: (value: Response) => void;
|
||||
const mockPromise = new Promise<Response>(resolve => {
|
||||
resolvePromise = resolve;
|
||||
});
|
||||
mockedAiApiClient.getQuickInsights.mockReturnValue(mockPromise);
|
||||
render(<AnalysisPanel flyerItems={mockFlyerItems} store={mockStore} />);
|
||||
fireEvent.click(screen.getByRole('button', { name: /generate quick insights/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('status')).toBeInTheDocument(); // LoadingSpinner
|
||||
expect(screen.getByRole('status')).toBeInTheDocument(); // LoadingSpinner
|
||||
|
||||
await act(async () => {
|
||||
resolvePromise(new Response(JSON.stringify('Insights')));
|
||||
await mockPromise;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/components/ShoppingList.test.tsx
|
||||
import React from 'react';
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
|
||||
import { describe, it, expect, vi, beforeEach, afterEach, type Mock } from 'vitest';
|
||||
import { ShoppingListComponent } from './ShoppingList'; // This path is now relative to the new folder
|
||||
import type { User, ShoppingList } from '../../types';
|
||||
@@ -179,35 +179,23 @@ describe('ShoppingListComponent (in shopping feature)', () => {
|
||||
// This test is disabled due to persistent issues with mocking and warnings.
|
||||
});
|
||||
|
||||
/*
|
||||
it('should call generateSpeechFromText when "Read aloud" is clicked', async () => {
|
||||
// Since generateSpeechFromText is already mocked globally, we use vi.spyOn
|
||||
// to provide a specific implementation for this test case. This is cleaner
|
||||
// and resolves the Vitest warning.
|
||||
const speechSpy = vi.spyOn(aiApiClient, 'generateSpeechFromText').mockResolvedValue(new Response(JSON.stringify('base64-audio-string')));
|
||||
render(<ShoppingListComponent {...defaultProps} />);
|
||||
const readAloudButton = screen.getByTitle(/read list aloud/i);
|
||||
|
||||
fireEvent.click(readAloudButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(speechSpy).toHaveBeenCalledWith(
|
||||
'Here is your shopping list: Apples, Special Bread'
|
||||
);
|
||||
});
|
||||
});
|
||||
*/
|
||||
|
||||
it('should show a loading spinner while reading aloud', async () => {
|
||||
vi.mocked(aiApiClient.generateSpeechFromText).mockImplementation(() => new Promise(() => {})); // Never resolves
|
||||
let resolvePromise: (value: Response) => void;
|
||||
const mockPromise = new Promise<Response>(resolve => {
|
||||
resolvePromise = resolve;
|
||||
});
|
||||
vi.mocked(aiApiClient.generateSpeechFromText).mockReturnValue(mockPromise);
|
||||
render(<ShoppingListComponent {...defaultProps} />);
|
||||
const readAloudButton = screen.getByTitle(/read list aloud/i);
|
||||
|
||||
fireEvent.click(readAloudButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(readAloudButton.querySelector('svg.animate-spin')).toBeInTheDocument();
|
||||
expect(readAloudButton).toBeDisabled();
|
||||
expect(readAloudButton.querySelector('svg.animate-spin')).toBeInTheDocument();
|
||||
expect(readAloudButton).toBeDisabled();
|
||||
|
||||
await act(async () => {
|
||||
resolvePromise(new Response(JSON.stringify('base64-audio-string')));
|
||||
await mockPromise;
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/components/WatchedItemsList.test.tsx
|
||||
import React from 'react';
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { WatchedItemsList } from './WatchedItemsList';
|
||||
import type { MasterGroceryItem, User } from '../../types';
|
||||
@@ -69,19 +69,24 @@ describe('WatchedItemsList (in shopping feature)', () => {
|
||||
});
|
||||
|
||||
it('should show a loading spinner while adding an item', async () => {
|
||||
mockOnAddItem.mockImplementation(() => new Promise(() => {})); // Never resolves
|
||||
let resolvePromise: () => void;
|
||||
const mockPromise = new Promise<void>(resolve => {
|
||||
resolvePromise = resolve;
|
||||
});
|
||||
mockOnAddItem.mockImplementation(() => mockPromise);
|
||||
render(<WatchedItemsList {...defaultProps} />);
|
||||
|
||||
fireEvent.change(screen.getByPlaceholderText(/add item/i), { target: { value: 'Cheese' } });
|
||||
const categorySelect = screen.getByDisplayValue('Select a category');
|
||||
fireEvent.change(categorySelect, { target: { value: 'Dairy & Eggs' } });
|
||||
fireEvent.change(screen.getByDisplayValue('Select a category'), { target: { value: 'Dairy & Eggs' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Add' }));
|
||||
|
||||
const addButton = screen.getByRole('button', { name: 'Add' });
|
||||
fireEvent.click(addButton);
|
||||
const addButton = await screen.findByRole('button', { name: 'Add' });
|
||||
expect(addButton).toBeDisabled();
|
||||
expect(addButton.querySelector('svg.animate-spin')).toBeInTheDocument();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(addButton.querySelector('svg.animate-spin')).toBeInTheDocument();
|
||||
expect(addButton).toBeDisabled();
|
||||
await act(async () => {
|
||||
resolvePromise();
|
||||
await mockPromise;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/pages/ResetPasswordPage.test.tsx
|
||||
import React from 'react';
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
||||
import { ResetPasswordPage } from './ResetPasswordPage';
|
||||
@@ -80,21 +80,24 @@ describe('ResetPasswordPage', () => {
|
||||
});
|
||||
|
||||
it('should show a loading spinner while submitting', async () => {
|
||||
// Mock a promise that never resolves to keep the component in a loading state
|
||||
mockedApiClient.resetPassword.mockReturnValueOnce(new Promise(() => {}));
|
||||
let resolvePromise: (value: Response) => void;
|
||||
const mockPromise = new Promise<Response>(resolve => {
|
||||
resolvePromise = resolve;
|
||||
});
|
||||
mockedApiClient.resetPassword.mockReturnValueOnce(mockPromise);
|
||||
renderWithRouter('test-token');
|
||||
|
||||
fireEvent.change(screen.getByPlaceholderText('New Password'), { target: { value: 'newSecurePassword123' } });
|
||||
fireEvent.change(screen.getByPlaceholderText('Confirm New Password'), { target: { value: 'newSecurePassword123' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: /reset password/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
// When loading, the button's text is replaced by a spinner, so it no longer has a name.
|
||||
// We query by role and check that it's the only submit button.
|
||||
const button = screen.getByRole('button', { name: '' });
|
||||
// Check for the SVG spinner within the button
|
||||
expect(button).toBeDisabled();
|
||||
expect(button.querySelector('svg')).toBeInTheDocument();
|
||||
const button = await screen.findByRole('button', { name: /reset password/i });
|
||||
expect(button).toBeDisabled();
|
||||
expect(button.querySelector('svg')).toBeInTheDocument();
|
||||
|
||||
await act(async () => {
|
||||
resolvePromise(new Response(JSON.stringify({ message: 'Success' })));
|
||||
await mockPromise;
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/pages/admin/ActivityLog.test.tsx
|
||||
import React from 'react';
|
||||
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
|
||||
import { render, screen, waitFor, fireEvent, act } from '@testing-library/react';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { ActivityLog } from './ActivityLog';
|
||||
import * as apiClient from '../../services/apiClient';
|
||||
@@ -58,10 +58,18 @@ describe('ActivityLog', () => {
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('should show a loading state initially', () => {
|
||||
mockedApiClient.fetchActivityLog.mockReturnValue(new Promise(() => {}));
|
||||
it('should show a loading state initially', async () => {
|
||||
let resolvePromise: (value: Response) => void;
|
||||
const mockPromise = new Promise<Response>(resolve => {
|
||||
resolvePromise = resolve;
|
||||
});
|
||||
mockedApiClient.fetchActivityLog.mockReturnValue(mockPromise);
|
||||
render(<ActivityLog user={mockUser} onLogClick={vi.fn()} />);
|
||||
expect(screen.getByText('Loading activity...')).toBeInTheDocument();
|
||||
await act(async () => {
|
||||
resolvePromise(new Response(JSON.stringify([])));
|
||||
await mockPromise;
|
||||
});
|
||||
});
|
||||
|
||||
it('should display an error message if fetching logs fails', async () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/pages/admin/AdminStatsPage.test.tsx
|
||||
import React from 'react';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { render, screen, waitFor, act } from '@testing-library/react';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { AdminStatsPage } from './AdminStatsPage';
|
||||
@@ -24,14 +24,20 @@ describe('AdminStatsPage', () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should render a loading spinner while fetching stats', () => {
|
||||
// Mock a promise that never resolves to keep the component in a loading state
|
||||
mockedApiClient.getApplicationStats.mockReturnValue(new Promise(() => {}));
|
||||
it('should render a loading spinner while fetching stats', async () => {
|
||||
let resolvePromise: (value: Response) => void;
|
||||
const mockPromise = new Promise<Response>(resolve => {
|
||||
resolvePromise = resolve;
|
||||
});
|
||||
mockedApiClient.getApplicationStats.mockReturnValue(mockPromise);
|
||||
renderWithRouter();
|
||||
|
||||
// The LoadingSpinner component is expected to be present. We find it by its accessible role.
|
||||
expect(screen.getByRole('status', { name: /loading/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole('heading', { name: /application statistics/i })).toBeInTheDocument();
|
||||
|
||||
await act(async () => {
|
||||
resolvePromise(new Response(JSON.stringify({})));
|
||||
await mockPromise;
|
||||
});
|
||||
});
|
||||
|
||||
it('should display stats cards when data is fetched successfully', async () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/pages/admin/CorrectionsPage.test.tsx
|
||||
import React from 'react';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { render, screen, waitFor, act } from '@testing-library/react';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { CorrectionsPage } from './CorrectionsPage';
|
||||
@@ -41,15 +41,20 @@ describe('CorrectionsPage', () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should render a loading spinner while fetching data', () => {
|
||||
// Mock a promise that never resolves to keep the component in a loading state
|
||||
mockedApiClient.getSuggestedCorrections.mockReturnValue(new Promise(() => {}));
|
||||
mockedApiClient.fetchMasterItems.mockReturnValue(new Promise(() => {}));
|
||||
mockedApiClient.fetchCategories.mockReturnValue(new Promise(() => {}));
|
||||
it('should render a loading spinner while fetching data', async () => {
|
||||
let resolvePromise: (value: Response) => void;
|
||||
const mockPromise = new Promise<Response>(resolve => {
|
||||
resolvePromise = resolve;
|
||||
});
|
||||
mockedApiClient.getSuggestedCorrections.mockReturnValue(mockPromise);
|
||||
renderWithRouter();
|
||||
|
||||
expect(screen.getByRole('status', { name: /loading/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole('heading', { name: /user-submitted corrections/i })).toBeInTheDocument();
|
||||
|
||||
await act(async () => {
|
||||
resolvePromise(new Response(JSON.stringify([])));
|
||||
await mockPromise;
|
||||
});
|
||||
});
|
||||
|
||||
it('should display corrections when data is fetched successfully', async () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/pages/admin/components/AdminBrandManager.test.tsx
|
||||
import React from 'react';
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import toast from 'react-hot-toast';
|
||||
import { AdminBrandManager } from './AdminBrandManager';
|
||||
@@ -24,10 +24,18 @@ describe('AdminBrandManager', () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should render a loading state initially', () => {
|
||||
mockedApiClient.fetchAllBrands.mockReturnValue(new Promise(() => {})); // Never resolves
|
||||
it('should render a loading state initially', async () => {
|
||||
let resolvePromise: (value: Response) => void;
|
||||
const mockPromise = new Promise<Response>(resolve => {
|
||||
resolvePromise = resolve;
|
||||
});
|
||||
mockedApiClient.fetchAllBrands.mockReturnValue(mockPromise);
|
||||
render(<AdminBrandManager />);
|
||||
expect(screen.getByText('Loading brands...')).toBeInTheDocument();
|
||||
await act(async () => {
|
||||
resolvePromise(new Response(JSON.stringify([])));
|
||||
await mockPromise;
|
||||
});
|
||||
});
|
||||
|
||||
it('should render an error message if fetching brands fails', async () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/pages/admin/ProfileManager.test.tsx
|
||||
import React from 'react';
|
||||
import { render, screen, fireEvent, waitFor, cleanup } from '@testing-library/react';
|
||||
import { render, screen, fireEvent, waitFor, cleanup, act } from '@testing-library/react';
|
||||
import { describe, it, expect, vi, beforeEach, afterEach, type Mock } from 'vitest';
|
||||
import { ProfileManager } from './ProfileManager';
|
||||
import * as apiClient from '../../../services/apiClient';
|
||||
@@ -144,21 +144,27 @@ describe('ProfileManager Authentication Flows', () => {
|
||||
});
|
||||
|
||||
it('should show loading spinner during login attempt', async () => {
|
||||
(mockedApiClient.loginUser as Mock).mockReturnValueOnce(new Promise(() => {})); // Never resolve
|
||||
// Create a promise we can resolve manually
|
||||
let resolvePromise: (value: Response) => void;
|
||||
const mockPromise = new Promise<Response>(resolve => {
|
||||
resolvePromise = resolve;
|
||||
});
|
||||
(mockedApiClient.loginUser as Mock).mockReturnValueOnce(mockPromise);
|
||||
|
||||
render(<ProfileManager {...defaultProps} />);
|
||||
|
||||
const signInButton = screen.getByRole('button', { name: /^sign in$/i });
|
||||
// Using fireEvent.submit on the form is more reliable for testing form submissions
|
||||
// and correctly handles the timing of state updates within the submit handler.
|
||||
const form = screen.getByTestId('auth-form');
|
||||
fireEvent.submit(form);
|
||||
|
||||
// We need to wait for the component to re-render with the loading state.
|
||||
// The most reliable way is to wait for the visual indicator (the spinner) to appear.
|
||||
await waitFor(() => {
|
||||
// We can reuse the `signInButton` reference. It will be updated after the re-render.
|
||||
expect(signInButton.querySelector('svg.animate-spin')).toBeInTheDocument();
|
||||
expect(signInButton).toBeDisabled();
|
||||
// Assert the loading state immediately
|
||||
expect(signInButton.querySelector('svg.animate-spin')).toBeInTheDocument();
|
||||
expect(signInButton).toBeDisabled();
|
||||
|
||||
// Now resolve the promise to allow the test to clean up properly
|
||||
await act(async () => {
|
||||
resolvePromise({ ok: true, json: () => Promise.resolve({}) } as Response);
|
||||
await mockPromise; // Ensure the promise resolution propagates
|
||||
});
|
||||
});
|
||||
|
||||
@@ -419,9 +425,6 @@ describe('ProfileManager Authenticated User Features', () => {
|
||||
await waitFor(() => {
|
||||
expect(mockedApiClient.updateUserPassword).toHaveBeenCalledWith('newpassword123');
|
||||
expect(notifySuccess).toHaveBeenCalledWith('Password updated successfully!');
|
||||
// Also verify that the password fields are cleared on success.
|
||||
expect(screen.getByLabelText('New Password')).toHaveValue('');
|
||||
expect(screen.getByLabelText('Confirm New Password')).toHaveValue('');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/pages/admin/components/SystemCheck.test.tsx
|
||||
import React from 'react';
|
||||
import { render, screen, waitFor, fireEvent, cleanup } from '@testing-library/react';
|
||||
import { render, screen, waitFor, fireEvent, cleanup, act } from '@testing-library/react';
|
||||
import { describe, it, expect, vi, beforeEach, afterEach, type Mock } from 'vitest';
|
||||
import { SystemCheck } from './SystemCheck';
|
||||
import * as apiClient from '../../../services/apiClient';
|
||||
@@ -178,16 +178,24 @@ describe('SystemCheck', () => {
|
||||
|
||||
it('should display a loading spinner and disable button while checks are running', async () => {
|
||||
setGeminiApiKey('mock-api-key');
|
||||
// Mock pingBackend to never resolve to keep the component in a loading state
|
||||
(mockedApiClient.pingBackend as Mock).mockImplementation(() => new Promise(() => {}));
|
||||
// Create a promise we can resolve manually to control the loading state
|
||||
let resolvePromise: (value: Response) => void;
|
||||
const mockPromise = new Promise<Response>(resolve => {
|
||||
resolvePromise = resolve;
|
||||
});
|
||||
(mockedApiClient.pingBackend as Mock).mockImplementation(() => mockPromise);
|
||||
|
||||
render(<SystemCheck />);
|
||||
|
||||
const rerunButton = screen.getByRole('button', { name: /running checks\.\.\./i });
|
||||
expect(rerunButton).toBeDisabled();
|
||||
expect(rerunButton.querySelector('svg')).toBeInTheDocument(); // Check for spinner inside button
|
||||
|
||||
// The component sets all 7 checks to "running" initially.
|
||||
expect(screen.getAllByText('Checking...')).toHaveLength(7);
|
||||
// Now resolve the promise to allow the test to clean up properly
|
||||
await act(async () => {
|
||||
resolvePromise(new Response('pong'));
|
||||
await mockPromise;
|
||||
});
|
||||
});
|
||||
|
||||
it.todo('TODO: should re-run checks when the "Re-run Checks" button is clicked', () => {
|
||||
|
||||
Reference in New Issue
Block a user