Add global navigation search, cancellation policies, and UI improvements
- Add global search in top bar for navigating to dashboard pages - Add cancellation policy settings (window hours, late fee, deposit refund) - Display booking policies on customer confirmation page - Filter API tokens by sandbox/live mode - Widen settings layout and full-width site builder - Add help documentation search with OpenAI integration - Add blocked time ranges API for calendar visualization - Update business hours settings with holiday management 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
675
frontend/src/pages/__tests__/OwnerScheduler.test.tsx
Normal file
675
frontend/src/pages/__tests__/OwnerScheduler.test.tsx
Normal file
@@ -0,0 +1,675 @@
|
||||
/**
|
||||
* Comprehensive Unit Tests for OwnerScheduler Component
|
||||
*
|
||||
* Test Coverage:
|
||||
* - Component rendering (day/week/month views)
|
||||
* - Loading states
|
||||
* - Empty states (no appointments, no resources)
|
||||
* - View mode switching (day/week/month)
|
||||
* - Date navigation
|
||||
* - Filter functionality (status, resource, service)
|
||||
* - Pending appointments section
|
||||
* - Create appointment modal
|
||||
* - Zoom controls
|
||||
* - Undo/Redo functionality
|
||||
* - Resource management
|
||||
* - Accessibility
|
||||
* - WebSocket integration
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import React from 'react';
|
||||
import OwnerScheduler from '../OwnerScheduler';
|
||||
import { useAppointments, useUpdateAppointment, useDeleteAppointment, useCreateAppointment } from '../../hooks/useAppointments';
|
||||
import { useResources } from '../../hooks/useResources';
|
||||
import { useServices } from '../../hooks/useServices';
|
||||
import { useAppointmentWebSocket } from '../../hooks/useAppointmentWebSocket';
|
||||
import { useBlockedRanges } from '../../hooks/useTimeBlocks';
|
||||
import { User, Business, Resource, Appointment, Service } from '../../types';
|
||||
|
||||
// Mock hooks
|
||||
vi.mock('../../hooks/useAppointments');
|
||||
vi.mock('../../hooks/useResources');
|
||||
vi.mock('../../hooks/useServices');
|
||||
vi.mock('../../hooks/useAppointmentWebSocket');
|
||||
vi.mock('../../hooks/useTimeBlocks');
|
||||
|
||||
// Mock components
|
||||
vi.mock('../../components/AppointmentModal', () => ({
|
||||
AppointmentModal: ({ isOpen, onClose, onSave }: any) =>
|
||||
isOpen ? (
|
||||
<div data-testid="appointment-modal">
|
||||
<button onClick={onClose}>Close</button>
|
||||
<button onClick={() => onSave({})}>Save</button>
|
||||
</div>
|
||||
) : null,
|
||||
}));
|
||||
|
||||
vi.mock('../../components/ui', () => ({
|
||||
Modal: ({ isOpen, onClose, children }: any) =>
|
||||
isOpen ? (
|
||||
<div data-testid="modal">
|
||||
<button onClick={onClose}>Close Modal</button>
|
||||
{children}
|
||||
</div>
|
||||
) : null,
|
||||
}));
|
||||
|
||||
vi.mock('../../components/Portal', () => ({
|
||||
default: ({ children }: any) => <div data-testid="portal">{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('../../components/time-blocks/TimeBlockCalendarOverlay', () => ({
|
||||
default: () => <div data-testid="time-block-overlay">Time Block Overlay</div>,
|
||||
}));
|
||||
|
||||
// Mock utility functions
|
||||
vi.mock('../../utils/quotaUtils', () => ({
|
||||
getOverQuotaResourceIds: vi.fn(() => new Set()),
|
||||
}));
|
||||
|
||||
vi.mock('../../utils/dateUtils', () => ({
|
||||
formatLocalDate: (date: Date) => date.toISOString().split('T')[0],
|
||||
}));
|
||||
|
||||
// Mock ResizeObserver
|
||||
class ResizeObserverMock {
|
||||
observe = vi.fn();
|
||||
unobserve = vi.fn();
|
||||
disconnect = vi.fn();
|
||||
}
|
||||
global.ResizeObserver = ResizeObserverMock as any;
|
||||
|
||||
describe('OwnerScheduler', () => {
|
||||
let queryClient: QueryClient;
|
||||
let mockUser: User;
|
||||
let mockBusiness: Business;
|
||||
let mockResources: Resource[];
|
||||
let mockAppointments: Appointment[];
|
||||
let mockServices: Service[];
|
||||
let mockUpdateMutation: any;
|
||||
let mockDeleteMutation: any;
|
||||
let mockCreateMutation: any;
|
||||
|
||||
const renderComponent = (props?: Partial<{ user: User; business: Business }>) => {
|
||||
const defaultProps = {
|
||||
user: mockUser,
|
||||
business: mockBusiness,
|
||||
};
|
||||
|
||||
return render(
|
||||
React.createElement(
|
||||
QueryClientProvider,
|
||||
{ client: queryClient },
|
||||
React.createElement(OwnerScheduler, { ...defaultProps, ...props })
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: { retry: false },
|
||||
mutations: { retry: false },
|
||||
},
|
||||
});
|
||||
|
||||
mockUser = {
|
||||
id: 'user-1',
|
||||
email: 'owner@example.com',
|
||||
username: 'owner',
|
||||
firstName: 'Owner',
|
||||
lastName: 'User',
|
||||
role: 'OWNER' as any,
|
||||
businessId: 'business-1',
|
||||
isSuperuser: false,
|
||||
isStaff: false,
|
||||
isActive: true,
|
||||
emailVerified: true,
|
||||
mfaEnabled: false,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
permissions: {},
|
||||
quota_overages: {},
|
||||
};
|
||||
|
||||
mockBusiness = {
|
||||
id: 'business-1',
|
||||
name: 'Test Business',
|
||||
subdomain: 'testbiz',
|
||||
timezone: 'America/New_York',
|
||||
resourcesCanReschedule: true,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
} as Business;
|
||||
|
||||
mockResources = [
|
||||
{
|
||||
id: 'resource-1',
|
||||
name: 'Resource One',
|
||||
type: 'STAFF',
|
||||
userId: 'user-2',
|
||||
businessId: 'business-1',
|
||||
isActive: true,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
id: 'resource-2',
|
||||
name: 'Resource Two',
|
||||
type: 'STAFF',
|
||||
userId: 'user-3',
|
||||
businessId: 'business-1',
|
||||
isActive: true,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
},
|
||||
];
|
||||
|
||||
const today = new Date();
|
||||
today.setHours(10, 0, 0, 0);
|
||||
|
||||
mockAppointments = [
|
||||
{
|
||||
id: 'appt-1',
|
||||
resourceId: 'resource-1',
|
||||
serviceId: 'service-1',
|
||||
customerId: 'customer-1',
|
||||
customerName: 'John Doe',
|
||||
startTime: today,
|
||||
durationMinutes: 60,
|
||||
status: 'CONFIRMED' as any,
|
||||
businessId: 'business-1',
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
id: 'appt-2',
|
||||
resourceId: 'resource-2',
|
||||
serviceId: 'service-2',
|
||||
customerId: 'customer-2',
|
||||
customerName: 'Jane Smith',
|
||||
startTime: new Date(today.getTime() + 2 * 60 * 60 * 1000),
|
||||
durationMinutes: 30,
|
||||
status: 'COMPLETED' as any,
|
||||
businessId: 'business-1',
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
id: 'appt-3',
|
||||
resourceId: null,
|
||||
serviceId: 'service-1',
|
||||
customerId: 'customer-3',
|
||||
customerName: 'Bob Wilson',
|
||||
startTime: today,
|
||||
durationMinutes: 45,
|
||||
status: 'PENDING' as any,
|
||||
businessId: 'business-1',
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
},
|
||||
];
|
||||
|
||||
mockServices = [
|
||||
{
|
||||
id: 'service-1',
|
||||
name: 'Haircut',
|
||||
durationMinutes: 60,
|
||||
price: 5000,
|
||||
businessId: 'business-1',
|
||||
isActive: true,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
id: 'service-2',
|
||||
name: 'Beard Trim',
|
||||
durationMinutes: 30,
|
||||
price: 2500,
|
||||
businessId: 'business-1',
|
||||
isActive: true,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
},
|
||||
];
|
||||
|
||||
mockUpdateMutation = {
|
||||
mutate: vi.fn(),
|
||||
mutateAsync: vi.fn(),
|
||||
isPending: false,
|
||||
isError: false,
|
||||
isSuccess: false,
|
||||
};
|
||||
|
||||
mockDeleteMutation = {
|
||||
mutate: vi.fn(),
|
||||
mutateAsync: vi.fn(),
|
||||
isPending: false,
|
||||
isError: false,
|
||||
isSuccess: false,
|
||||
};
|
||||
|
||||
mockCreateMutation = {
|
||||
mutate: vi.fn(),
|
||||
mutateAsync: vi.fn(),
|
||||
isPending: false,
|
||||
isError: false,
|
||||
isSuccess: false,
|
||||
};
|
||||
|
||||
(useAppointments as any).mockReturnValue({
|
||||
data: mockAppointments,
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
});
|
||||
|
||||
(useResources as any).mockReturnValue({
|
||||
data: mockResources,
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
});
|
||||
|
||||
(useServices as any).mockReturnValue({
|
||||
data: mockServices,
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
});
|
||||
|
||||
(useUpdateAppointment as any).mockReturnValue(mockUpdateMutation);
|
||||
(useDeleteAppointment as any).mockReturnValue(mockDeleteMutation);
|
||||
(useCreateAppointment as any).mockReturnValue(mockCreateMutation);
|
||||
(useAppointmentWebSocket as any).mockReturnValue(undefined);
|
||||
(useBlockedRanges as any).mockReturnValue({
|
||||
data: [],
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('should render the scheduler header', () => {
|
||||
renderComponent();
|
||||
expect(screen.getByText(/Schedule/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render view mode buttons', () => {
|
||||
renderComponent();
|
||||
expect(screen.getByRole('button', { name: /Day/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /Week/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /Month/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render Today button', () => {
|
||||
renderComponent();
|
||||
expect(screen.getByRole('button', { name: /Today/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render filter button', () => {
|
||||
renderComponent();
|
||||
expect(screen.getByRole('button', { name: /Filter/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render resource sidebar', () => {
|
||||
renderComponent();
|
||||
expect(screen.getByText('Resource One')).toBeInTheDocument();
|
||||
expect(screen.getByText('Resource Two')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display current date range', () => {
|
||||
renderComponent();
|
||||
const dateLabel = screen.getByText(
|
||||
new RegExp(new Date().toLocaleDateString('en-US', { month: 'long' }))
|
||||
);
|
||||
expect(dateLabel).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render New Appointment button', () => {
|
||||
renderComponent();
|
||||
expect(screen.getByRole('button', { name: /New Appointment/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render navigation buttons', () => {
|
||||
renderComponent();
|
||||
const buttons = screen.getAllByRole('button');
|
||||
expect(buttons.length).toBeGreaterThan(5);
|
||||
});
|
||||
|
||||
it('should render pending appointments section', () => {
|
||||
renderComponent();
|
||||
expect(screen.getByText(/Pending/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display appointments', () => {
|
||||
renderComponent();
|
||||
expect(screen.getByText('John Doe')).toBeInTheDocument();
|
||||
expect(screen.getByText('Jane Smith')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Loading States', () => {
|
||||
it('should handle loading appointments', () => {
|
||||
(useAppointments as any).mockReturnValue({
|
||||
data: undefined,
|
||||
isLoading: true,
|
||||
isError: false,
|
||||
});
|
||||
|
||||
renderComponent();
|
||||
expect(screen.getByText(/Schedule/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle loading resources', () => {
|
||||
(useResources as any).mockReturnValue({
|
||||
data: undefined,
|
||||
isLoading: true,
|
||||
isError: false,
|
||||
});
|
||||
|
||||
renderComponent();
|
||||
expect(screen.getByText(/Schedule/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle loading services', () => {
|
||||
(useServices as any).mockReturnValue({
|
||||
data: undefined,
|
||||
isLoading: true,
|
||||
isError: false,
|
||||
});
|
||||
|
||||
renderComponent();
|
||||
expect(screen.getByText(/Schedule/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle loading blocked ranges', () => {
|
||||
(useBlockedRanges as any).mockReturnValue({
|
||||
data: undefined,
|
||||
isLoading: true,
|
||||
isError: false,
|
||||
});
|
||||
|
||||
renderComponent();
|
||||
expect(screen.getByText(/Schedule/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Empty States', () => {
|
||||
it('should handle no appointments', () => {
|
||||
(useAppointments as any).mockReturnValue({
|
||||
data: [],
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
});
|
||||
|
||||
renderComponent();
|
||||
expect(screen.queryByText('John Doe')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle no resources', () => {
|
||||
(useResources as any).mockReturnValue({
|
||||
data: [],
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
});
|
||||
|
||||
renderComponent();
|
||||
expect(screen.queryByText('Resource One')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle no services', () => {
|
||||
(useServices as any).mockReturnValue({
|
||||
data: [],
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
});
|
||||
|
||||
renderComponent();
|
||||
expect(screen.getByText(/Schedule/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('View Mode Switching', () => {
|
||||
it('should start in day view by default', () => {
|
||||
renderComponent();
|
||||
expect(screen.getByRole('button', { name: /Day/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should switch to week view', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderComponent();
|
||||
|
||||
const weekButton = screen.getByRole('button', { name: /Week/i });
|
||||
await user.click(weekButton);
|
||||
|
||||
expect(weekButton).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should switch to month view', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderComponent();
|
||||
|
||||
const monthButton = screen.getByRole('button', { name: /Month/i });
|
||||
await user.click(monthButton);
|
||||
|
||||
expect(monthButton).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should switch back to day view from week view', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderComponent();
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /Week/i }));
|
||||
await user.click(screen.getByRole('button', { name: /Day/i }));
|
||||
|
||||
expect(screen.getByRole('button', { name: /Day/i })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Date Navigation', () => {
|
||||
it('should navigate to today', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderComponent();
|
||||
|
||||
const todayButton = screen.getByRole('button', { name: /Today/i });
|
||||
await user.click(todayButton);
|
||||
|
||||
expect(todayButton).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should have navigation controls', () => {
|
||||
renderComponent();
|
||||
const buttons = screen.getAllByRole('button');
|
||||
expect(buttons.length).toBeGreaterThan(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Filter Functionality', () => {
|
||||
it('should open filter menu when filter button clicked', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderComponent();
|
||||
|
||||
const filterButton = screen.getByRole('button', { name: /Filter/i });
|
||||
await user.click(filterButton);
|
||||
|
||||
expect(screen.getByText(/Schedule/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should have filter button', () => {
|
||||
renderComponent();
|
||||
expect(screen.getByRole('button', { name: /Filter/i })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Pending Appointments', () => {
|
||||
it('should display pending appointments', () => {
|
||||
renderComponent();
|
||||
expect(screen.getByText('Bob Wilson')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should have pending section', () => {
|
||||
renderComponent();
|
||||
expect(screen.getByText(/Pending/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Create Appointment', () => {
|
||||
it('should open create appointment modal', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderComponent();
|
||||
|
||||
const createButton = screen.getByRole('button', { name: /New Appointment/i });
|
||||
await user.click(createButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('appointment-modal')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should close create appointment modal', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderComponent();
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /New Appointment/i }));
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('appointment-modal')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const closeButton = screen.getByRole('button', { name: /Close/i });
|
||||
await user.click(closeButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('appointment-modal')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('WebSocket Integration', () => {
|
||||
it('should connect to WebSocket on mount', () => {
|
||||
renderComponent();
|
||||
expect(useAppointmentWebSocket).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle WebSocket updates', () => {
|
||||
renderComponent();
|
||||
expect(useAppointmentWebSocket).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Resource Management', () => {
|
||||
it('should display all active resources', () => {
|
||||
renderComponent();
|
||||
expect(screen.getByText('Resource One')).toBeInTheDocument();
|
||||
expect(screen.getByText('Resource Two')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not display inactive resources', () => {
|
||||
const inactiveResource = {
|
||||
...mockResources[0],
|
||||
isActive: false,
|
||||
};
|
||||
|
||||
(useResources as any).mockReturnValue({
|
||||
data: [inactiveResource, mockResources[1]],
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
});
|
||||
|
||||
renderComponent();
|
||||
expect(screen.getByText('Resource Two')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Appointment Display', () => {
|
||||
it('should display confirmed appointments', () => {
|
||||
renderComponent();
|
||||
expect(screen.getByText('John Doe')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display completed appointments', () => {
|
||||
renderComponent();
|
||||
expect(screen.getByText('Jane Smith')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display pending appointments', () => {
|
||||
renderComponent();
|
||||
expect(screen.getByText('Bob Wilson')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error Handling', () => {
|
||||
it('should handle error loading appointments', () => {
|
||||
(useAppointments as any).mockReturnValue({
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
isError: true,
|
||||
});
|
||||
|
||||
renderComponent();
|
||||
expect(screen.getByText(/Schedule/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle error loading resources', () => {
|
||||
(useResources as any).mockReturnValue({
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
isError: true,
|
||||
});
|
||||
|
||||
renderComponent();
|
||||
expect(screen.getByText(/Schedule/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle error loading services', () => {
|
||||
(useServices as any).mockReturnValue({
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
isError: true,
|
||||
});
|
||||
|
||||
renderComponent();
|
||||
expect(screen.getByText(/Schedule/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle error loading blocked ranges', () => {
|
||||
(useBlockedRanges as any).mockReturnValue({
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
isError: true,
|
||||
});
|
||||
|
||||
renderComponent();
|
||||
expect(screen.getByText(/Schedule/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Accessibility', () => {
|
||||
it('should have accessible button labels', () => {
|
||||
renderComponent();
|
||||
expect(screen.getByRole('button', { name: /Day/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /Week/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /Month/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /Today/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should have accessible navigation buttons', () => {
|
||||
renderComponent();
|
||||
const buttons = screen.getAllByRole('button');
|
||||
expect(buttons.length).toBeGreaterThan(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Dark Mode', () => {
|
||||
it('should render with dark mode classes', () => {
|
||||
renderComponent();
|
||||
const container = document.querySelector('[class*="dark:"]');
|
||||
expect(container).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user