Add staff permission controls for editing staff and customers
- 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>
This commit is contained in:
679
frontend/src/pages/customer/__tests__/CustomerSupport.test.tsx
Normal file
679
frontend/src/pages/customer/__tests__/CustomerSupport.test.tsx
Normal file
@@ -0,0 +1,679 @@
|
||||
/**
|
||||
* 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user