feat: Add comprehensive test suite and misc improvements

- Add frontend unit tests with Vitest for components, hooks, pages, and utilities
- Add backend tests for webhooks, notifications, middleware, and edge cases
- Add ForgotPassword, NotFound, and ResetPassword pages
- Add migration for orphaned staff resources conversion
- Add coverage directory to gitignore (generated reports)
- Various bug fixes and improvements from previous work

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-08 02:36:46 -05:00
parent c220612214
commit 8dc2248f1f
145 changed files with 77947 additions and 1048 deletions

View File

@@ -0,0 +1,869 @@
/**
* Unit tests for BookingPage component
*
* Tests all booking functionality including:
* - Service selection and rendering
* - Date/time picker interaction
* - Multi-step booking flow
* - Booking confirmation
* - Loading states
* - Error states
* - Complete user flows
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { render, screen, fireEvent, waitFor, within } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { MemoryRouter, Route, Routes } from 'react-router-dom';
import React, { type ReactNode } from 'react';
import BookingPage from '../BookingPage';
import { useServices } from '../../../hooks/useServices';
import { User, Business, Service } from '../../../types';
// Mock the useServices hook
vi.mock('../../../hooks/useServices', () => ({
useServices: vi.fn(),
}));
// Mock lucide-react icons to avoid rendering issues in tests
vi.mock('lucide-react', () => ({
Check: () => <div data-testid="check-icon">Check</div>,
ChevronLeft: () => <div data-testid="chevron-left-icon">ChevronLeft</div>,
Calendar: () => <div data-testid="calendar-icon">Calendar</div>,
Clock: () => <div data-testid="clock-icon">Clock</div>,
AlertTriangle: () => <div data-testid="alert-icon">AlertTriangle</div>,
CreditCard: () => <div data-testid="credit-card-icon">CreditCard</div>,
Loader2: () => <div data-testid="loader-icon">Loader2</div>,
}));
// Test data factories
const createMockUser = (overrides?: Partial<User>): User => ({
id: '1',
name: 'John Doe',
email: 'john@example.com',
role: 'customer',
...overrides,
});
const createMockBusiness = (overrides?: Partial<Business>): Business => ({
id: '1',
name: 'Test Business',
subdomain: 'testbiz',
primaryColor: '#3B82F6',
secondaryColor: '#10B981',
whitelabelEnabled: false,
paymentsEnabled: true,
requirePaymentMethodToBook: false,
cancellationWindowHours: 24,
lateCancellationFeePercent: 50,
...overrides,
});
const createMockService = (overrides?: Partial<Service>): Service => ({
id: '1',
name: 'Haircut',
durationMinutes: 60,
price: 50.0,
description: 'Professional haircut service',
displayOrder: 0,
photos: [],
...overrides,
});
// Test wrapper with all necessary providers
const createWrapper = (queryClient: QueryClient, user: User, business: Business) => {
return ({ children }: { children: ReactNode }) => (
<QueryClientProvider client={queryClient}>
<MemoryRouter initialEntries={['/book']}>
<Routes>
<Route
path="/book"
element={
<div>
{React.cloneElement(children as React.ReactElement, {
// Simulate useOutletContext
})}
</div>
}
/>
</Routes>
</MemoryRouter>
</QueryClientProvider>
);
};
// Custom render function with context
const renderBookingPage = (user: User, business: Business, queryClient: QueryClient) => {
// Mock useOutletContext by wrapping the component
const BookingPageWithContext = () => {
// Simulate the outlet context
const context = { user, business };
// Pass context through a wrapper component
return React.createElement(BookingPage, { ...context } as any);
};
return render(
<QueryClientProvider client={queryClient}>
<MemoryRouter>
<BookingPageWithContext />
</MemoryRouter>
</QueryClientProvider>
);
};
describe('BookingPage', () => {
let queryClient: QueryClient;
let mockUser: User;
let mockBusiness: Business;
beforeEach(() => {
queryClient = new QueryClient({
defaultOptions: {
queries: { retry: false, gcTime: 0 },
mutations: { retry: false },
},
});
mockUser = createMockUser();
mockBusiness = createMockBusiness();
vi.clearAllMocks();
});
afterEach(() => {
queryClient.clear();
});
describe('Service Selection (Step 1)', () => {
it('should render loading state while fetching services', () => {
vi.mocked(useServices).mockReturnValue({
data: undefined,
isLoading: true,
isSuccess: false,
isError: false,
error: null,
} as any);
renderBookingPage(mockUser, mockBusiness, queryClient);
expect(screen.getByTestId('loader-icon')).toBeInTheDocument();
expect(screen.getByText('Step 1: Select a Service')).toBeInTheDocument();
});
it('should render empty state when no services available', () => {
vi.mocked(useServices).mockReturnValue({
data: [],
isLoading: false,
isSuccess: true,
isError: false,
error: null,
} as any);
renderBookingPage(mockUser, mockBusiness, queryClient);
expect(screen.getByText('No services available for booking at this time.')).toBeInTheDocument();
expect(screen.getByText('Step 1: Select a Service')).toBeInTheDocument();
});
it('should render list of available services', () => {
const mockServices = [
createMockService({ id: '1', name: 'Haircut', price: 50, durationMinutes: 60 }),
createMockService({ id: '2', name: 'Hair Color', price: 120, durationMinutes: 120 }),
createMockService({ id: '3', name: 'Styling', price: 40, durationMinutes: 45 }),
];
vi.mocked(useServices).mockReturnValue({
data: mockServices,
isLoading: false,
isSuccess: true,
isError: false,
error: null,
} as any);
renderBookingPage(mockUser, mockBusiness, queryClient);
expect(screen.getByText('Haircut')).toBeInTheDocument();
expect(screen.getByText('Hair Color')).toBeInTheDocument();
expect(screen.getByText('Styling')).toBeInTheDocument();
expect(screen.getByText('$50.00')).toBeInTheDocument();
expect(screen.getByText('$120.00')).toBeInTheDocument();
expect(screen.getByText('$40.00')).toBeInTheDocument();
});
it('should display service details including duration and description', () => {
const mockServices = [
createMockService({
id: '1',
name: 'Deep Tissue Massage',
price: 90,
durationMinutes: 90,
description: 'Relaxing full-body massage',
}),
];
vi.mocked(useServices).mockReturnValue({
data: mockServices,
isLoading: false,
isSuccess: true,
isError: false,
error: null,
} as any);
renderBookingPage(mockUser, mockBusiness, queryClient);
expect(screen.getByText('Deep Tissue Massage')).toBeInTheDocument();
expect(screen.getByText(/90 min.*Relaxing full-body massage/)).toBeInTheDocument();
expect(screen.getByText('$90.00')).toBeInTheDocument();
});
it('should advance to step 2 when a service is selected', async () => {
const mockServices = [
createMockService({ id: '1', name: 'Haircut', price: 50, durationMinutes: 60 }),
];
vi.mocked(useServices).mockReturnValue({
data: mockServices,
isLoading: false,
isSuccess: true,
isError: false,
error: null,
} as any);
renderBookingPage(mockUser, mockBusiness, queryClient);
const serviceButton = screen.getByRole('button', { name: /Haircut/i });
fireEvent.click(serviceButton);
await waitFor(() => {
expect(screen.getByText('Step 2: Choose a Time')).toBeInTheDocument();
});
});
it('should show correct subtitle for step 1', () => {
vi.mocked(useServices).mockReturnValue({
data: [],
isLoading: false,
isSuccess: true,
isError: false,
error: null,
} as any);
renderBookingPage(mockUser, mockBusiness, queryClient);
expect(screen.getByText('Pick from our list of available services.')).toBeInTheDocument();
});
});
describe('Time Selection (Step 2)', () => {
beforeEach(() => {
const mockServices = [
createMockService({ id: '1', name: 'Haircut', price: 50, durationMinutes: 60 }),
];
vi.mocked(useServices).mockReturnValue({
data: mockServices,
isLoading: false,
isSuccess: true,
isError: false,
error: null,
} as any);
});
it('should display available time slots', async () => {
renderBookingPage(mockUser, mockBusiness, queryClient);
// Select a service first
const serviceButton = screen.getByRole('button', { name: /Haircut/i });
fireEvent.click(serviceButton);
await waitFor(() => {
expect(screen.getByText('Step 2: Choose a Time')).toBeInTheDocument();
});
// Check that time slots are displayed
const timeButtons = screen.getAllByRole('button');
// Should have multiple time slot buttons
expect(timeButtons.length).toBeGreaterThan(1);
});
it('should show subtitle with current date', async () => {
renderBookingPage(mockUser, mockBusiness, queryClient);
// Select a service first
fireEvent.click(screen.getByRole('button', { name: /Haircut/i }));
await waitFor(() => {
const todayDate = new Date().toLocaleDateString();
expect(screen.getByText(new RegExp(`Available times for ${todayDate}`, 'i'))).toBeInTheDocument();
});
});
it('should advance to step 3 when a time is selected', async () => {
renderBookingPage(mockUser, mockBusiness, queryClient);
// Select a service
fireEvent.click(screen.getByRole('button', { name: /Haircut/i }));
await waitFor(() => {
expect(screen.getByText('Step 2: Choose a Time')).toBeInTheDocument();
});
// Select first available time slot
const timeButtons = screen.getAllByRole('button').filter(btn =>
btn.textContent && /\d{1,2}:\d{2}/.test(btn.textContent)
);
if (timeButtons.length > 0) {
fireEvent.click(timeButtons[0]);
await waitFor(() => {
expect(screen.getByText('Step 3: Confirm Details')).toBeInTheDocument();
});
}
});
it('should show back button on step 2', async () => {
renderBookingPage(mockUser, mockBusiness, queryClient);
// Select a service
fireEvent.click(screen.getByRole('button', { name: /Haircut/i }));
await waitFor(() => {
expect(screen.getByTestId('chevron-left-icon')).toBeInTheDocument();
});
});
it('should go back to step 1 when back button is clicked', async () => {
renderBookingPage(mockUser, mockBusiness, queryClient);
// Select a service
fireEvent.click(screen.getByRole('button', { name: /Haircut/i }));
await waitFor(() => {
expect(screen.getByText('Step 2: Choose a Time')).toBeInTheDocument();
});
// Click back button
const backButton = screen.getAllByRole('button').find(btn =>
btn.querySelector('[data-testid="chevron-left-icon"]')
);
if (backButton) {
fireEvent.click(backButton);
await waitFor(() => {
expect(screen.getByText('Step 1: Select a Service')).toBeInTheDocument();
});
}
});
});
describe('Booking Confirmation (Step 3)', () => {
beforeEach(() => {
const mockServices = [
createMockService({ id: '1', name: 'Haircut', price: 50, durationMinutes: 60 }),
];
vi.mocked(useServices).mockReturnValue({
data: mockServices,
isLoading: false,
isSuccess: true,
isError: false,
error: null,
} as any);
});
const navigateToStep3 = async () => {
renderBookingPage(mockUser, mockBusiness, queryClient);
// Step 1: Select service
fireEvent.click(screen.getByRole('button', { name: /Haircut/i }));
await waitFor(() => {
expect(screen.getByText('Step 2: Choose a Time')).toBeInTheDocument();
});
// Step 2: Select time
const timeButtons = screen.getAllByRole('button').filter(btn =>
btn.textContent && /\d{1,2}:\d{2}/.test(btn.textContent)
);
if (timeButtons.length > 0) {
fireEvent.click(timeButtons[0]);
}
await waitFor(() => {
expect(screen.getByText('Step 3: Confirm Details')).toBeInTheDocument();
});
};
it('should display booking confirmation details', async () => {
await navigateToStep3();
expect(screen.getByText('Confirm Your Booking')).toBeInTheDocument();
expect(screen.getByText(/You are booking/i)).toBeInTheDocument();
expect(screen.getByText(/Haircut/i)).toBeInTheDocument();
expect(screen.getByTestId('calendar-icon')).toBeInTheDocument();
});
it('should show confirm appointment button', async () => {
await navigateToStep3();
const confirmButton = screen.getByRole('button', { name: /Confirm Appointment/i });
expect(confirmButton).toBeInTheDocument();
});
it('should show subtitle with review instructions', async () => {
await navigateToStep3();
expect(screen.getByText('Please review your appointment details below.')).toBeInTheDocument();
});
it('should show back button on step 3', async () => {
await navigateToStep3();
expect(screen.getByTestId('chevron-left-icon')).toBeInTheDocument();
});
it('should go back to step 2 when back button is clicked', async () => {
await navigateToStep3();
// Click back button
const backButton = screen.getAllByRole('button').find(btn =>
btn.querySelector('[data-testid="chevron-left-icon"]')
);
if (backButton) {
fireEvent.click(backButton);
await waitFor(() => {
expect(screen.getByText('Step 2: Choose a Time')).toBeInTheDocument();
});
}
});
it('should advance to step 4 when confirm button is clicked', async () => {
await navigateToStep3();
const confirmButton = screen.getByRole('button', { name: /Confirm Appointment/i });
fireEvent.click(confirmButton);
await waitFor(() => {
expect(screen.getByText('Booking Confirmed')).toBeInTheDocument();
expect(screen.getByText('Appointment Booked!')).toBeInTheDocument();
});
});
});
describe('Booking Success (Step 4)', () => {
beforeEach(() => {
const mockServices = [
createMockService({ id: '1', name: 'Haircut', price: 50, durationMinutes: 60 }),
];
vi.mocked(useServices).mockReturnValue({
data: mockServices,
isLoading: false,
isSuccess: true,
isError: false,
error: null,
} as any);
});
const navigateToStep4 = async () => {
renderBookingPage(mockUser, mockBusiness, queryClient);
// Step 1: Select service
fireEvent.click(screen.getByRole('button', { name: /Haircut/i }));
await waitFor(() => {
expect(screen.getByText('Step 2: Choose a Time')).toBeInTheDocument();
});
// Step 2: Select time
const timeButtons = screen.getAllByRole('button').filter(btn =>
btn.textContent && /\d{1,2}:\d{2}/.test(btn.textContent)
);
if (timeButtons.length > 0) {
fireEvent.click(timeButtons[0]);
}
await waitFor(() => {
expect(screen.getByText('Step 3: Confirm Details')).toBeInTheDocument();
});
// Step 3: Confirm
fireEvent.click(screen.getByRole('button', { name: /Confirm Appointment/i }));
await waitFor(() => {
expect(screen.getByText('Booking Confirmed')).toBeInTheDocument();
});
};
it('should display success message with check icon', async () => {
await navigateToStep4();
expect(screen.getByText('Appointment Booked!')).toBeInTheDocument();
expect(screen.getByTestId('check-icon')).toBeInTheDocument();
});
it('should show booking confirmation details', async () => {
await navigateToStep4();
expect(screen.getByText(/Your appointment for/i)).toBeInTheDocument();
expect(screen.getByText(/Haircut/i)).toBeInTheDocument();
expect(screen.getByText(/is confirmed/i)).toBeInTheDocument();
});
it('should show confirmation email message', async () => {
await navigateToStep4();
expect(screen.getByText("We've sent a confirmation to your email.")).toBeInTheDocument();
});
it('should show "Go to Dashboard" link', async () => {
await navigateToStep4();
const dashboardLink = screen.getByRole('link', { name: /Go to Dashboard/i });
expect(dashboardLink).toBeInTheDocument();
expect(dashboardLink).toHaveAttribute('href', '/');
});
it('should show "Book Another" button', async () => {
await navigateToStep4();
const bookAnotherButton = screen.getByRole('button', { name: /Book Another/i });
expect(bookAnotherButton).toBeInTheDocument();
});
it('should reset flow when "Book Another" is clicked', async () => {
await navigateToStep4();
const bookAnotherButton = screen.getByRole('button', { name: /Book Another/i });
fireEvent.click(bookAnotherButton);
await waitFor(() => {
expect(screen.getByText('Step 1: Select a Service')).toBeInTheDocument();
});
});
it('should not show back button on step 4', async () => {
await navigateToStep4();
const backButtons = screen.queryAllByTestId('chevron-left-icon');
expect(backButtons.length).toBe(0);
});
});
describe('Complete User Flow', () => {
it('should complete entire booking flow from service selection to confirmation', async () => {
const mockServices = [
createMockService({ id: '1', name: 'Massage Therapy', price: 80, durationMinutes: 90 }),
];
vi.mocked(useServices).mockReturnValue({
data: mockServices,
isLoading: false,
isSuccess: true,
isError: false,
error: null,
} as any);
renderBookingPage(mockUser, mockBusiness, queryClient);
// Step 1: User sees and selects service
expect(screen.getByText('Step 1: Select a Service')).toBeInTheDocument();
expect(screen.getByText('Massage Therapy')).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: /Massage Therapy/i }));
// Step 2: User sees and selects time
await waitFor(() => {
expect(screen.getByText('Step 2: Choose a Time')).toBeInTheDocument();
});
const timeButtons = screen.getAllByRole('button').filter(btn =>
btn.textContent && /\d{1,2}:\d{2}/.test(btn.textContent)
);
fireEvent.click(timeButtons[0]);
// Step 3: User confirms booking
await waitFor(() => {
expect(screen.getByText('Step 3: Confirm Details')).toBeInTheDocument();
expect(screen.getByText(/Massage Therapy/i)).toBeInTheDocument();
});
fireEvent.click(screen.getByRole('button', { name: /Confirm Appointment/i }));
// Step 4: User sees success message
await waitFor(() => {
expect(screen.getByText('Booking Confirmed')).toBeInTheDocument();
expect(screen.getByText('Appointment Booked!')).toBeInTheDocument();
expect(screen.getByText(/Massage Therapy/i)).toBeInTheDocument();
});
});
it('should allow user to navigate backward through steps', async () => {
const mockServices = [
createMockService({ id: '1', name: 'Haircut', price: 50, durationMinutes: 60 }),
];
vi.mocked(useServices).mockReturnValue({
data: mockServices,
isLoading: false,
isSuccess: true,
isError: false,
error: null,
} as any);
renderBookingPage(mockUser, mockBusiness, queryClient);
// Go to step 2
fireEvent.click(screen.getByRole('button', { name: /Haircut/i }));
await waitFor(() => {
expect(screen.getByText('Step 2: Choose a Time')).toBeInTheDocument();
});
// Go back to step 1
const backButton1 = screen.getAllByRole('button').find(btn =>
btn.querySelector('[data-testid="chevron-left-icon"]')
);
if (backButton1) fireEvent.click(backButton1);
await waitFor(() => {
expect(screen.getByText('Step 1: Select a Service')).toBeInTheDocument();
});
// Go to step 2 again
fireEvent.click(screen.getByRole('button', { name: /Haircut/i }));
await waitFor(() => {
expect(screen.getByText('Step 2: Choose a Time')).toBeInTheDocument();
});
// Go to step 3
const timeButtons = screen.getAllByRole('button').filter(btn =>
btn.textContent && /\d{1,2}:\d{2}/.test(btn.textContent)
);
if (timeButtons.length > 0) fireEvent.click(timeButtons[0]);
await waitFor(() => {
expect(screen.getByText('Step 3: Confirm Details')).toBeInTheDocument();
});
// Go back to step 2
const backButton2 = screen.getAllByRole('button').find(btn =>
btn.querySelector('[data-testid="chevron-left-icon"]')
);
if (backButton2) fireEvent.click(backButton2);
await waitFor(() => {
expect(screen.getByText('Step 2: Choose a Time')).toBeInTheDocument();
});
});
});
describe('Edge Cases', () => {
it('should handle service with zero price', () => {
const mockServices = [
createMockService({ id: '1', name: 'Free Consultation', price: 0, durationMinutes: 30 }),
];
vi.mocked(useServices).mockReturnValue({
data: mockServices,
isLoading: false,
isSuccess: true,
isError: false,
error: null,
} as any);
renderBookingPage(mockUser, mockBusiness, queryClient);
expect(screen.getByText('$0.00')).toBeInTheDocument();
});
it('should handle service with long name', () => {
const mockServices = [
createMockService({
id: '1',
name: 'Very Long Service Name That Could Potentially Break The Layout',
price: 100,
durationMinutes: 120,
}),
];
vi.mocked(useServices).mockReturnValue({
data: mockServices,
isLoading: false,
isSuccess: true,
isError: false,
error: null,
} as any);
renderBookingPage(mockUser, mockBusiness, queryClient);
expect(screen.getByText('Very Long Service Name That Could Potentially Break The Layout')).toBeInTheDocument();
});
it('should handle service with long description', () => {
const longDescription = 'A'.repeat(200);
const mockServices = [
createMockService({
id: '1',
name: 'Service',
price: 50,
durationMinutes: 60,
description: longDescription,
}),
];
vi.mocked(useServices).mockReturnValue({
data: mockServices,
isLoading: false,
isSuccess: true,
isError: false,
error: null,
} as any);
renderBookingPage(mockUser, mockBusiness, queryClient);
expect(screen.getByText(new RegExp(longDescription))).toBeInTheDocument();
});
it('should handle multiple services with same price', () => {
const mockServices = [
createMockService({ id: '1', name: 'Service A', price: 50, durationMinutes: 60 }),
createMockService({ id: '2', name: 'Service B', price: 50, durationMinutes: 45 }),
createMockService({ id: '3', name: 'Service C', price: 50, durationMinutes: 30 }),
];
vi.mocked(useServices).mockReturnValue({
data: mockServices,
isLoading: false,
isSuccess: true,
isError: false,
error: null,
} as any);
renderBookingPage(mockUser, mockBusiness, queryClient);
const priceElements = screen.getAllByText('$50.00');
expect(priceElements.length).toBe(3);
});
it('should handle rapid step navigation', async () => {
const mockServices = [
createMockService({ id: '1', name: 'Haircut', price: 50, durationMinutes: 60 }),
];
vi.mocked(useServices).mockReturnValue({
data: mockServices,
isLoading: false,
isSuccess: true,
isError: false,
error: null,
} as any);
renderBookingPage(mockUser, mockBusiness, queryClient);
// Rapidly click through steps
fireEvent.click(screen.getByRole('button', { name: /Haircut/i }));
await waitFor(() => {
const timeButtons = screen.getAllByRole('button').filter(btn =>
btn.textContent && /\d{1,2}:\d{2}/.test(btn.textContent)
);
if (timeButtons.length > 0) {
fireEvent.click(timeButtons[0]);
}
});
await waitFor(() => {
const confirmButton = screen.queryByRole('button', { name: /Confirm Appointment/i });
if (confirmButton) {
fireEvent.click(confirmButton);
}
});
// Should end up at success page
await waitFor(() => {
expect(screen.getByText('Appointment Booked!')).toBeInTheDocument();
}, { timeout: 3000 });
});
});
describe('Accessibility', () => {
it('should have proper heading hierarchy', () => {
const mockServices = [
createMockService({ id: '1', name: 'Haircut', price: 50, durationMinutes: 60 }),
];
vi.mocked(useServices).mockReturnValue({
data: mockServices,
isLoading: false,
isSuccess: true,
isError: false,
error: null,
} as any);
renderBookingPage(mockUser, mockBusiness, queryClient);
const heading = screen.getByText('Step 1: Select a Service');
expect(heading).toBeInTheDocument();
});
it('should have clickable service buttons', () => {
const mockServices = [
createMockService({ id: '1', name: 'Haircut', price: 50, durationMinutes: 60 }),
];
vi.mocked(useServices).mockReturnValue({
data: mockServices,
isLoading: false,
isSuccess: true,
isError: false,
error: null,
} as any);
renderBookingPage(mockUser, mockBusiness, queryClient);
const serviceButton = screen.getByRole('button', { name: /Haircut/i });
expect(serviceButton).toBeInTheDocument();
expect(serviceButton).toBeEnabled();
});
it('should have navigable link in success step', async () => {
const mockServices = [
createMockService({ id: '1', name: 'Haircut', price: 50, durationMinutes: 60 }),
];
vi.mocked(useServices).mockReturnValue({
data: mockServices,
isLoading: false,
isSuccess: true,
isError: false,
error: null,
} as any);
renderBookingPage(mockUser, mockBusiness, queryClient);
// Navigate to success page
fireEvent.click(screen.getByRole('button', { name: /Haircut/i }));
await waitFor(() => {
const timeButtons = screen.getAllByRole('button').filter(btn =>
btn.textContent && /\d{1,2}:\d{2}/.test(btn.textContent)
);
if (timeButtons.length > 0) fireEvent.click(timeButtons[0]);
});
await waitFor(() => {
const confirmButton = screen.queryByRole('button', { name: /Confirm Appointment/i });
if (confirmButton) fireEvent.click(confirmButton);
});
await waitFor(() => {
const dashboardLink = screen.queryByRole('link', { name: /Go to Dashboard/i });
expect(dashboardLink).toBeInTheDocument();
});
});
});
});