- Add can_edit_staff and can_edit_customers dangerous permissions - Move Site Builder, Services, Locations, Time Blocks, Payments to Settings permissions - Link Edit Others' Schedules and Edit Own Schedule permissions - Add permission checks to StaffViewSet (partial_update, toggle_active, verify_email) - Add permission checks to CustomerViewSet (update, partial_update, verify_email) - Fix CustomerViewSet permission key mismatch (can_access_customers) - Hide Edit/Verify buttons on Staff and Customers pages without permission - Make dangerous permissions section more visually distinct (darker red) - Fix StaffDashboard links to use correct paths (/dashboard/my-schedule) - Disable settings sub-permissions when Access Settings is unchecked 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
680 lines
23 KiB
TypeScript
680 lines
23 KiB
TypeScript
/**
|
|
* Unit tests for CustomerSupport component
|
|
*
|
|
* Tests support ticket functionality including:
|
|
* - Ticket list display
|
|
* - Ticket creation
|
|
* - Ticket detail view
|
|
* - Comments/conversation
|
|
* - Status badges
|
|
* - Loading and error states
|
|
*/
|
|
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
import React from 'react';
|
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
import { MemoryRouter, Outlet, Routes, Route } from 'react-router-dom';
|
|
import CustomerSupport from '../CustomerSupport';
|
|
|
|
// Mock react-i18next
|
|
vi.mock('react-i18next', () => ({
|
|
useTranslation: () => ({
|
|
t: (key: string, fallback?: string, options?: any) => {
|
|
if (options?.number !== undefined) {
|
|
return fallback?.replace('{{number}}', options.number) || key;
|
|
}
|
|
if (options?.date !== undefined) {
|
|
return fallback?.replace('{{date}}', options.date) || key;
|
|
}
|
|
return fallback || key;
|
|
},
|
|
}),
|
|
}));
|
|
|
|
// Mock the ticket hooks
|
|
const mockTickets = vi.fn();
|
|
const mockCreateTicket = vi.fn();
|
|
const mockTicketComments = vi.fn();
|
|
const mockCreateTicketComment = vi.fn();
|
|
|
|
vi.mock('../../../hooks/useTickets', () => ({
|
|
useTickets: () => mockTickets(),
|
|
useCreateTicket: () => ({
|
|
mutateAsync: mockCreateTicket,
|
|
isPending: false,
|
|
}),
|
|
useTicketComments: (ticketId: string) => mockTicketComments(ticketId),
|
|
useCreateTicketComment: () => ({
|
|
mutateAsync: mockCreateTicketComment,
|
|
isPending: false,
|
|
}),
|
|
}));
|
|
|
|
const mockUser = {
|
|
id: 'user-1',
|
|
email: 'customer@example.com',
|
|
name: 'John Doe',
|
|
role: 'customer' as const,
|
|
};
|
|
|
|
const mockBusiness = {
|
|
id: 'biz-1',
|
|
name: 'Test Business',
|
|
subdomain: 'test',
|
|
cancellationWindowHours: 24,
|
|
lateCancellationFeePercent: 50,
|
|
};
|
|
|
|
const defaultTickets = [
|
|
{
|
|
id: '1',
|
|
ticketNumber: 'TKT-001',
|
|
ticketType: 'CUSTOMER',
|
|
subject: 'Appointment rescheduling',
|
|
description: 'I need to reschedule my appointment',
|
|
status: 'OPEN',
|
|
priority: 'MEDIUM',
|
|
category: 'APPOINTMENT',
|
|
createdAt: '2024-12-01T10:00:00Z',
|
|
updatedAt: '2024-12-01T10:00:00Z',
|
|
creatorEmail: 'customer@example.com',
|
|
creatorFullName: 'John Doe',
|
|
},
|
|
{
|
|
id: '2',
|
|
ticketNumber: 'TKT-002',
|
|
ticketType: 'CUSTOMER',
|
|
subject: 'Refund request',
|
|
description: 'I would like a refund for my last appointment',
|
|
status: 'RESOLVED',
|
|
priority: 'HIGH',
|
|
category: 'REFUND',
|
|
createdAt: '2024-11-20T14:00:00Z',
|
|
updatedAt: '2024-11-22T16:00:00Z',
|
|
resolvedAt: '2024-11-22T16:00:00Z',
|
|
creatorEmail: 'customer@example.com',
|
|
creatorFullName: 'John Doe',
|
|
},
|
|
{
|
|
id: '3',
|
|
ticketNumber: 'TKT-003',
|
|
ticketType: 'PLATFORM',
|
|
subject: 'Platform issue',
|
|
description: 'This should be filtered out',
|
|
status: 'OPEN',
|
|
priority: 'LOW',
|
|
category: 'OTHER',
|
|
createdAt: '2024-11-15T09:00:00Z',
|
|
updatedAt: '2024-11-15T09:00:00Z',
|
|
creatorEmail: 'platform@example.com',
|
|
creatorFullName: 'Platform User',
|
|
},
|
|
];
|
|
|
|
const defaultComments = [
|
|
{
|
|
id: '1',
|
|
ticket: '1',
|
|
author: 'staff-1',
|
|
authorEmail: 'staff@example.com',
|
|
authorFullName: 'Support Staff',
|
|
commentText: 'We have received your request and are looking into it.',
|
|
isInternal: false,
|
|
createdAt: '2024-12-01T11:00:00Z',
|
|
},
|
|
{
|
|
id: '2',
|
|
ticket: '1',
|
|
author: 'staff-2',
|
|
authorEmail: 'manager@example.com',
|
|
authorFullName: 'Manager',
|
|
commentText: 'Internal note: escalate this',
|
|
isInternal: true,
|
|
createdAt: '2024-12-01T12:00:00Z',
|
|
},
|
|
{
|
|
id: '3',
|
|
ticket: '1',
|
|
author: 'user-1',
|
|
authorEmail: 'customer@example.com',
|
|
authorFullName: 'John Doe',
|
|
commentText: 'Thank you for your help!',
|
|
isInternal: false,
|
|
createdAt: '2024-12-01T13:00:00Z',
|
|
},
|
|
];
|
|
|
|
const createWrapper = () => {
|
|
const queryClient = new QueryClient({
|
|
defaultOptions: { queries: { retry: false } },
|
|
});
|
|
|
|
const OutletWrapper = () => {
|
|
return React.createElement(Outlet, {
|
|
context: { user: mockUser, business: mockBusiness },
|
|
});
|
|
};
|
|
|
|
return ({ children }: { children: React.ReactNode }) =>
|
|
React.createElement(
|
|
QueryClientProvider,
|
|
{ client: queryClient },
|
|
React.createElement(
|
|
MemoryRouter,
|
|
{ initialEntries: ['/customer/support'] },
|
|
React.createElement(
|
|
Routes,
|
|
null,
|
|
React.createElement(Route, {
|
|
element: React.createElement(OutletWrapper),
|
|
children: React.createElement(Route, {
|
|
path: 'customer/support',
|
|
element: children,
|
|
}),
|
|
})
|
|
)
|
|
)
|
|
);
|
|
};
|
|
|
|
describe('CustomerSupport', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mockTickets.mockReturnValue({
|
|
data: defaultTickets,
|
|
isLoading: false,
|
|
refetch: vi.fn(),
|
|
});
|
|
mockTicketComments.mockReturnValue({
|
|
data: [],
|
|
isLoading: false,
|
|
});
|
|
});
|
|
|
|
describe('Page Header', () => {
|
|
it('should render the page title', () => {
|
|
render(React.createElement(CustomerSupport), { wrapper: createWrapper() });
|
|
expect(screen.getByText('Support')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should render the page subtitle', () => {
|
|
render(React.createElement(CustomerSupport), { wrapper: createWrapper() });
|
|
expect(screen.getByText(/Get help with your appointments and account/)).toBeInTheDocument();
|
|
});
|
|
|
|
it('should render New Request button', () => {
|
|
render(React.createElement(CustomerSupport), { wrapper: createWrapper() });
|
|
expect(screen.getByText('New Request')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('Quick Help Section', () => {
|
|
it('should render Quick Help heading', () => {
|
|
render(React.createElement(CustomerSupport), { wrapper: createWrapper() });
|
|
expect(screen.getByText('Quick Help')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should render Contact Us option', () => {
|
|
render(React.createElement(CustomerSupport), { wrapper: createWrapper() });
|
|
expect(screen.getByText('Contact Us')).toBeInTheDocument();
|
|
expect(screen.getByText('Submit a support request')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should render Email Us option', () => {
|
|
render(React.createElement(CustomerSupport), { wrapper: createWrapper() });
|
|
expect(screen.getByText('Email Us')).toBeInTheDocument();
|
|
expect(screen.getByText('Get help via email')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should have email link with business subdomain', () => {
|
|
render(React.createElement(CustomerSupport), { wrapper: createWrapper() });
|
|
const emailLink = screen.getByText('Email Us').closest('a');
|
|
expect(emailLink).toHaveAttribute('href', 'mailto:support@test.smoothschedule.com');
|
|
});
|
|
|
|
it('should open new ticket form when Contact Us is clicked', () => {
|
|
render(React.createElement(CustomerSupport), { wrapper: createWrapper() });
|
|
const contactButton = screen.getAllByText('Contact Us')[0].closest('a');
|
|
fireEvent.click(contactButton!);
|
|
|
|
expect(screen.getByText('Submit a Support Request')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('My Support Requests Section', () => {
|
|
it('should render section heading', () => {
|
|
render(React.createElement(CustomerSupport), { wrapper: createWrapper() });
|
|
expect(screen.getByText('My Support Requests')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display customer tickets only', () => {
|
|
render(React.createElement(CustomerSupport), { wrapper: createWrapper() });
|
|
expect(screen.getByText('Appointment rescheduling')).toBeInTheDocument();
|
|
expect(screen.getByText('Refund request')).toBeInTheDocument();
|
|
expect(screen.queryByText('Platform issue')).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('should display ticket numbers', () => {
|
|
render(React.createElement(CustomerSupport), { wrapper: createWrapper() });
|
|
expect(screen.getByText(/TKT-001/)).toBeInTheDocument();
|
|
expect(screen.getByText(/TKT-002/)).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display ticket status badges', () => {
|
|
render(React.createElement(CustomerSupport), { wrapper: createWrapper() });
|
|
expect(screen.getByText(/open/i)).toBeInTheDocument();
|
|
expect(screen.getByText(/resolved/i)).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display ticket creation dates', () => {
|
|
render(React.createElement(CustomerSupport), { wrapper: createWrapper() });
|
|
const dateElements = screen.getAllByText(/12\/1\/2024|11\/20\/2024/);
|
|
expect(dateElements.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should render loading state', () => {
|
|
mockTickets.mockReturnValue({
|
|
data: [],
|
|
isLoading: true,
|
|
refetch: vi.fn(),
|
|
});
|
|
render(React.createElement(CustomerSupport), { wrapper: createWrapper() });
|
|
expect(screen.getByText('Loading...')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should render empty state when no tickets', () => {
|
|
mockTickets.mockReturnValue({
|
|
data: [],
|
|
isLoading: false,
|
|
refetch: vi.fn(),
|
|
});
|
|
render(React.createElement(CustomerSupport), { wrapper: createWrapper() });
|
|
expect(screen.getByText(/haven't submitted any support requests yet/)).toBeInTheDocument();
|
|
});
|
|
|
|
it('should show Submit first request button in empty state', () => {
|
|
mockTickets.mockReturnValue({
|
|
data: [],
|
|
isLoading: false,
|
|
refetch: vi.fn(),
|
|
});
|
|
render(React.createElement(CustomerSupport), { wrapper: createWrapper() });
|
|
expect(screen.getByText('Submit your first request')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should open ticket detail when ticket is clicked', () => {
|
|
render(React.createElement(CustomerSupport), { wrapper: createWrapper() });
|
|
const ticketButton = screen.getByText('Appointment rescheduling').closest('button');
|
|
fireEvent.click(ticketButton!);
|
|
|
|
expect(screen.getByText('Appointment Details')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('New Ticket Form Modal', () => {
|
|
beforeEach(() => {
|
|
render(React.createElement(CustomerSupport), { wrapper: createWrapper() });
|
|
const newRequestButton = screen.getAllByText('New Request')[0];
|
|
fireEvent.click(newRequestButton);
|
|
});
|
|
|
|
it('should render form modal when New Request is clicked', () => {
|
|
expect(screen.getByText('Submit a Support Request')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should render subject input field', () => {
|
|
expect(screen.getByLabelText(/subject/i)).toBeInTheDocument();
|
|
});
|
|
|
|
it('should render category dropdown', () => {
|
|
expect(screen.getByLabelText(/category/i)).toBeInTheDocument();
|
|
});
|
|
|
|
it('should render priority dropdown', () => {
|
|
expect(screen.getByLabelText(/priority/i)).toBeInTheDocument();
|
|
});
|
|
|
|
it('should render description textarea', () => {
|
|
expect(screen.getByLabelText(/description/i)).toBeInTheDocument();
|
|
});
|
|
|
|
it('should render Cancel button', () => {
|
|
expect(screen.getByText('Cancel')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should render Submit Request button', () => {
|
|
expect(screen.getByText('Submit Request')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should close modal when Cancel is clicked', () => {
|
|
const cancelButton = screen.getByText('Cancel');
|
|
fireEvent.click(cancelButton);
|
|
|
|
expect(screen.queryByText('Submit a Support Request')).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('should close modal when clicking outside', () => {
|
|
const backdrop = screen.getByText('Submit a Support Request').closest('.fixed');
|
|
fireEvent.click(backdrop!);
|
|
|
|
expect(screen.queryByText('Submit a Support Request')).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('should not close modal when clicking inside form', () => {
|
|
const formContent = screen.getByLabelText(/subject/i).closest('.bg-white');
|
|
fireEvent.click(formContent!);
|
|
|
|
expect(screen.getByText('Submit a Support Request')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should submit form with correct data', async () => {
|
|
const subjectInput = screen.getByLabelText(/subject/i);
|
|
const descriptionInput = screen.getByLabelText(/description/i);
|
|
const submitButton = screen.getByText('Submit Request');
|
|
|
|
fireEvent.change(subjectInput, { target: { value: 'Test ticket' } });
|
|
fireEvent.change(descriptionInput, { target: { value: 'Test description' } });
|
|
fireEvent.click(submitButton);
|
|
|
|
await waitFor(() => {
|
|
expect(mockCreateTicket).toHaveBeenCalledWith({
|
|
subject: 'Test ticket',
|
|
description: 'Test description',
|
|
category: 'GENERAL_INQUIRY',
|
|
priority: 'MEDIUM',
|
|
ticketType: 'CUSTOMER',
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should display category options', () => {
|
|
const categorySelect = screen.getByLabelText(/category/i);
|
|
expect(categorySelect).toBeInTheDocument();
|
|
// Options are rendered as part of select
|
|
expect(screen.getByText(/appointment/i)).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display priority options', () => {
|
|
const prioritySelect = screen.getByLabelText(/priority/i);
|
|
expect(prioritySelect).toBeInTheDocument();
|
|
expect(screen.getByText(/medium/i)).toBeInTheDocument();
|
|
});
|
|
|
|
it('should require subject field', () => {
|
|
const subjectInput = screen.getByLabelText(/subject/i) as HTMLInputElement;
|
|
expect(subjectInput.required).toBe(true);
|
|
});
|
|
|
|
it('should require description field', () => {
|
|
const descriptionInput = screen.getByLabelText(/description/i) as HTMLTextAreaElement;
|
|
expect(descriptionInput.required).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('Ticket Detail View', () => {
|
|
beforeEach(() => {
|
|
mockTicketComments.mockReturnValue({
|
|
data: defaultComments,
|
|
isLoading: false,
|
|
});
|
|
render(React.createElement(CustomerSupport), { wrapper: createWrapper() });
|
|
const ticketButton = screen.getByText('Appointment rescheduling').closest('button');
|
|
fireEvent.click(ticketButton!);
|
|
});
|
|
|
|
it('should display ticket subject', () => {
|
|
expect(screen.getByText('Appointment rescheduling')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display ticket number', () => {
|
|
expect(screen.getByText(/Ticket #TKT-001/)).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display creation date', () => {
|
|
expect(screen.getByText(/Created.*12\/1\/2024/)).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display status badge', () => {
|
|
expect(screen.getByText(/open/i)).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display priority badge', () => {
|
|
expect(screen.getByText(/medium/i)).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display ticket description', () => {
|
|
expect(screen.getByText('I need to reschedule my appointment')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display back button', () => {
|
|
expect(screen.getByText(/Back to tickets/)).toBeInTheDocument();
|
|
});
|
|
|
|
it('should return to ticket list when back button is clicked', () => {
|
|
const backButton = screen.getByText(/Back to tickets/);
|
|
fireEvent.click(backButton);
|
|
|
|
expect(screen.getByText('My Support Requests')).toBeInTheDocument();
|
|
expect(screen.queryByText(/Ticket #TKT-001/)).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('should display Conversation heading', () => {
|
|
expect(screen.getByText('Conversation')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should filter out internal comments', () => {
|
|
expect(screen.getByText('We have received your request and are looking into it.')).toBeInTheDocument();
|
|
expect(screen.getByText('Thank you for your help!')).toBeInTheDocument();
|
|
expect(screen.queryByText('Internal note: escalate this')).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('should display comment author names', () => {
|
|
expect(screen.getByText('Support Staff')).toBeInTheDocument();
|
|
expect(screen.getByText('John Doe')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display comment timestamps', () => {
|
|
const timestamps = screen.getAllByText(/12\/1\/2024/);
|
|
expect(timestamps.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should render reply form', () => {
|
|
expect(screen.getByLabelText('Your Reply')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should render Send Reply button', () => {
|
|
expect(screen.getByText('Send Reply')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should submit reply when Send Reply is clicked', async () => {
|
|
const replyInput = screen.getByLabelText('Your Reply');
|
|
const sendButton = screen.getByText('Send Reply');
|
|
|
|
fireEvent.change(replyInput, { target: { value: 'My reply message' } });
|
|
fireEvent.click(sendButton);
|
|
|
|
await waitFor(() => {
|
|
expect(mockCreateTicketComment).toHaveBeenCalledWith({
|
|
ticketId: '1',
|
|
commentData: {
|
|
commentText: 'My reply message',
|
|
isInternal: false,
|
|
},
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should clear reply input after submission', async () => {
|
|
const replyInput = screen.getByLabelText('Your Reply') as HTMLTextAreaElement;
|
|
const sendButton = screen.getByText('Send Reply');
|
|
|
|
fireEvent.change(replyInput, { target: { value: 'My reply' } });
|
|
fireEvent.click(sendButton);
|
|
|
|
await waitFor(() => {
|
|
expect(replyInput.value).toBe('');
|
|
});
|
|
});
|
|
|
|
it('should disable Send Reply button when input is empty', () => {
|
|
const sendButton = screen.getByText('Send Reply');
|
|
expect(sendButton).toBeDisabled();
|
|
});
|
|
|
|
it('should enable Send Reply button when input has text', () => {
|
|
const replyInput = screen.getByLabelText('Your Reply');
|
|
const sendButton = screen.getByText('Send Reply');
|
|
|
|
fireEvent.change(replyInput, { target: { value: 'Some text' } });
|
|
expect(sendButton).not.toBeDisabled();
|
|
});
|
|
|
|
it('should show empty state when no comments', () => {
|
|
mockTicketComments.mockReturnValue({
|
|
data: [],
|
|
isLoading: false,
|
|
});
|
|
render(React.createElement(CustomerSupport), { wrapper: createWrapper() });
|
|
const ticketButton = screen.getByText('Appointment rescheduling').closest('button');
|
|
fireEvent.click(ticketButton!);
|
|
|
|
expect(screen.getByText(/No replies yet/)).toBeInTheDocument();
|
|
});
|
|
|
|
it('should show loading state for comments', () => {
|
|
mockTicketComments.mockReturnValue({
|
|
data: [],
|
|
isLoading: true,
|
|
});
|
|
render(React.createElement(CustomerSupport), { wrapper: createWrapper() });
|
|
const ticketButton = screen.getByText('Appointment rescheduling').closest('button');
|
|
fireEvent.click(ticketButton!);
|
|
|
|
expect(screen.getByText('Loading...')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('Closed Ticket Behavior', () => {
|
|
beforeEach(() => {
|
|
const closedTicket = {
|
|
...defaultTickets[0],
|
|
status: 'CLOSED',
|
|
};
|
|
mockTickets.mockReturnValue({
|
|
data: [closedTicket],
|
|
isLoading: false,
|
|
refetch: vi.fn(),
|
|
});
|
|
mockTicketComments.mockReturnValue({
|
|
data: [],
|
|
isLoading: false,
|
|
});
|
|
render(React.createElement(CustomerSupport), { wrapper: createWrapper() });
|
|
const ticketButton = screen.getByText('Appointment rescheduling').closest('button');
|
|
fireEvent.click(ticketButton!);
|
|
});
|
|
|
|
it('should not show reply form for closed tickets', () => {
|
|
expect(screen.queryByLabelText('Your Reply')).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('should show closed ticket message', () => {
|
|
expect(screen.getByText(/This ticket is closed/)).toBeInTheDocument();
|
|
});
|
|
|
|
it('should suggest opening new request for closed tickets', () => {
|
|
expect(screen.getByText(/open a new support request/)).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('Status Messages', () => {
|
|
it('should show open status message', () => {
|
|
render(React.createElement(CustomerSupport), { wrapper: createWrapper() });
|
|
const ticketButton = screen.getByText('Appointment rescheduling').closest('button');
|
|
fireEvent.click(ticketButton!);
|
|
|
|
expect(screen.getByText(/request has been received/)).toBeInTheDocument();
|
|
});
|
|
|
|
it('should show in progress status message', () => {
|
|
const inProgressTicket = {
|
|
...defaultTickets[0],
|
|
status: 'IN_PROGRESS',
|
|
};
|
|
mockTickets.mockReturnValue({
|
|
data: [inProgressTicket],
|
|
isLoading: false,
|
|
refetch: vi.fn(),
|
|
});
|
|
render(React.createElement(CustomerSupport), { wrapper: createWrapper() });
|
|
const ticketButton = screen.getByText('Appointment rescheduling').closest('button');
|
|
fireEvent.click(ticketButton!);
|
|
|
|
expect(screen.getByText(/currently working on your request/)).toBeInTheDocument();
|
|
});
|
|
|
|
it('should show awaiting response status message', () => {
|
|
const awaitingTicket = {
|
|
...defaultTickets[0],
|
|
status: 'AWAITING_RESPONSE',
|
|
};
|
|
mockTickets.mockReturnValue({
|
|
data: [awaitingTicket],
|
|
isLoading: false,
|
|
refetch: vi.fn(),
|
|
});
|
|
render(React.createElement(CustomerSupport), { wrapper: createWrapper() });
|
|
const ticketButton = screen.getByText('Appointment rescheduling').closest('button');
|
|
fireEvent.click(ticketButton!);
|
|
|
|
expect(screen.getByText(/need additional information/)).toBeInTheDocument();
|
|
});
|
|
|
|
it('should show resolved status message', () => {
|
|
mockTickets.mockReturnValue({
|
|
data: [defaultTickets[1]], // Resolved ticket
|
|
isLoading: false,
|
|
refetch: vi.fn(),
|
|
});
|
|
render(React.createElement(CustomerSupport), { wrapper: createWrapper() });
|
|
const ticketButton = screen.getByText('Refund request').closest('button');
|
|
fireEvent.click(ticketButton!);
|
|
|
|
expect(screen.getByText(/request has been resolved/)).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('Status and Priority Badges', () => {
|
|
it('should render OPEN status badge correctly', () => {
|
|
render(React.createElement(CustomerSupport), { wrapper: createWrapper() });
|
|
const openBadge = screen.getByText(/open/i);
|
|
expect(openBadge).toBeInTheDocument();
|
|
});
|
|
|
|
it('should render RESOLVED status badge correctly', () => {
|
|
render(React.createElement(CustomerSupport), { wrapper: createWrapper() });
|
|
const resolvedBadge = screen.getByText(/resolved/i);
|
|
expect(resolvedBadge).toBeInTheDocument();
|
|
});
|
|
|
|
it('should render MEDIUM priority badge correctly', () => {
|
|
render(React.createElement(CustomerSupport), { wrapper: createWrapper() });
|
|
const ticketButton = screen.getByText('Appointment rescheduling').closest('button');
|
|
fireEvent.click(ticketButton!);
|
|
|
|
const mediumBadge = screen.getByText(/medium/i);
|
|
expect(mediumBadge).toBeInTheDocument();
|
|
});
|
|
|
|
it('should render HIGH priority badge correctly', () => {
|
|
render(React.createElement(CustomerSupport), { wrapper: createWrapper() });
|
|
const ticketButton = screen.getByText('Refund request').closest('button');
|
|
fireEvent.click(ticketButton!);
|
|
|
|
const highBadge = screen.getByText(/high/i);
|
|
expect(highBadge).toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|