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:
@@ -0,0 +1,630 @@
|
||||
/**
|
||||
* Unit tests for HelpApiDocs component
|
||||
*
|
||||
* Tests cover:
|
||||
* - Component rendering
|
||||
* - Navigation sections display
|
||||
* - Code examples in multiple languages
|
||||
* - Token selector functionality
|
||||
* - Section navigation and scroll behavior
|
||||
* - No test tokens warning banner
|
||||
* - Back button functionality
|
||||
* - Language switcher in code blocks
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import HelpApiDocs from '../HelpApiDocs';
|
||||
|
||||
// Mock the useTestTokensForDocs hook
|
||||
const mockTestTokensData = [
|
||||
{
|
||||
id: 'token-1',
|
||||
name: 'Test Token 1',
|
||||
key_prefix: 'ss_test_abc123',
|
||||
created_at: '2025-01-01T00:00:00Z',
|
||||
},
|
||||
{
|
||||
id: 'token-2',
|
||||
name: 'Test Token 2',
|
||||
key_prefix: 'ss_test_def456',
|
||||
created_at: '2025-01-02T00:00:00Z',
|
||||
},
|
||||
];
|
||||
|
||||
const mockUseTestTokensForDocs = vi.fn(() => ({
|
||||
data: mockTestTokensData,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
}));
|
||||
|
||||
vi.mock('../../hooks/useApiTokens', () => ({
|
||||
useTestTokensForDocs: mockUseTestTokensForDocs,
|
||||
}));
|
||||
|
||||
// Mock react-i18next
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string) => {
|
||||
const translations: Record<string, string> = {
|
||||
'common.back': 'Back',
|
||||
'help.api.title': 'API Documentation',
|
||||
'help.api.noTestTokensFound': 'No test tokens found',
|
||||
'help.api.noTestTokensDescription': 'Create a test API token to see interactive examples with your credentials.',
|
||||
'help.api.createTestToken': 'Create Test Token',
|
||||
'help.api.introduction': 'Introduction',
|
||||
'help.api.authentication': 'Authentication',
|
||||
'help.api.errors': 'Errors',
|
||||
'help.api.rateLimits': 'Rate Limits',
|
||||
'help.api.services': 'Services',
|
||||
'help.api.resources': 'Resources',
|
||||
'help.api.availability': 'Availability',
|
||||
'help.api.appointments': 'Appointments',
|
||||
'help.api.customers': 'Customers',
|
||||
'help.api.webhooks': 'Webhooks',
|
||||
'help.api.filtering': 'Filtering',
|
||||
'help.api.listServices': 'List all services',
|
||||
'help.api.retrieveService': 'Retrieve a service',
|
||||
'help.api.checkAvailability': 'Check availability',
|
||||
'help.api.createAppointment': 'Create an appointment',
|
||||
'help.api.retrieveAppointment': 'Retrieve an appointment',
|
||||
'help.api.updateAppointment': 'Update an appointment',
|
||||
'help.api.cancelAppointment': 'Cancel an appointment',
|
||||
'help.api.listAppointments': 'List all appointments',
|
||||
'help.api.businessObject': 'The business object',
|
||||
'help.api.serviceObject': 'The service object',
|
||||
'help.api.resourceObject': 'The resource object',
|
||||
'help.api.appointmentObject': 'The appointment object',
|
||||
'help.api.customerObject': 'The customer object',
|
||||
'help.api.createCustomer': 'Create a customer',
|
||||
'help.api.retrieveCustomer': 'Retrieve a customer',
|
||||
'help.api.updateCustomer': 'Update a customer',
|
||||
'help.api.listCustomers': 'List all customers',
|
||||
};
|
||||
return translations[key] || key;
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
// Mock useNavigate
|
||||
const mockNavigate = vi.fn();
|
||||
vi.mock('react-router-dom', async () => {
|
||||
const actual = await vi.importActual('react-router-dom');
|
||||
return {
|
||||
...actual,
|
||||
useNavigate: () => mockNavigate,
|
||||
};
|
||||
});
|
||||
|
||||
// Test wrapper with Router
|
||||
const createWrapper = () => {
|
||||
return ({ children }: { children: React.ReactNode }) => (
|
||||
<BrowserRouter>{children}</BrowserRouter>
|
||||
);
|
||||
};
|
||||
|
||||
describe('HelpApiDocs', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
// Reset scroll position
|
||||
window.scrollTo = vi.fn();
|
||||
|
||||
// Reset the mock to default behavior
|
||||
mockUseTestTokensForDocs.mockReturnValue({
|
||||
data: mockTestTokensData,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
});
|
||||
});
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('should render the API documentation page', () => {
|
||||
render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const heading = screen.getByText('API Documentation');
|
||||
expect(heading).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the back button', () => {
|
||||
render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const backButton = screen.getByRole('button', { name: /back/i });
|
||||
expect(backButton).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the page header', () => {
|
||||
render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const title = screen.getByText('API Documentation');
|
||||
expect(title).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Navigation', () => {
|
||||
it('should navigate back when back button is clicked', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const backButton = screen.getByRole('button', { name: /back/i });
|
||||
await user.click(backButton);
|
||||
|
||||
expect(mockNavigate).toHaveBeenCalledWith(-1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Main Sections', () => {
|
||||
it('should render introduction section', () => {
|
||||
render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const section = document.getElementById('introduction');
|
||||
expect(section).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render authentication section', () => {
|
||||
render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const section = document.getElementById('authentication');
|
||||
expect(section).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render errors section', () => {
|
||||
render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const section = document.getElementById('errors');
|
||||
expect(section).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render rate limits section', () => {
|
||||
render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const section = document.getElementById('rate-limits');
|
||||
expect(section).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render services section', () => {
|
||||
render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const section = document.getElementById('list-services');
|
||||
expect(section).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render appointments section', () => {
|
||||
render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const section = document.getElementById('create-appointment');
|
||||
expect(section).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render customers section', () => {
|
||||
render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const section = document.getElementById('create-customer');
|
||||
expect(section).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render webhooks section', () => {
|
||||
render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const section = document.getElementById('webhook-events');
|
||||
expect(section).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Token Selector', () => {
|
||||
it('should render token selector when tokens are available', () => {
|
||||
render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const tokenSelector = screen.getByRole('combobox');
|
||||
expect(tokenSelector).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display all available test tokens', () => {
|
||||
render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const tokenSelector = screen.getByRole('combobox');
|
||||
const options = Array.from(tokenSelector.querySelectorAll('option'));
|
||||
|
||||
expect(options).toHaveLength(2);
|
||||
expect(options[0]).toHaveTextContent('Test Token 1');
|
||||
expect(options[1]).toHaveTextContent('Test Token 2');
|
||||
});
|
||||
|
||||
it('should show token key prefix in selector', () => {
|
||||
render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const tokenSelector = screen.getByRole('combobox');
|
||||
expect(tokenSelector).toHaveTextContent('ss_test_abc123');
|
||||
});
|
||||
|
||||
it('should allow selecting a different token', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const tokenSelector = screen.getByRole('combobox') as HTMLSelectElement;
|
||||
|
||||
await user.selectOptions(tokenSelector, 'token-2');
|
||||
|
||||
expect(tokenSelector.value).toBe('token-2');
|
||||
});
|
||||
|
||||
it('should display key icon next to token selector', () => {
|
||||
const { container } = render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
// Look for the Key icon (lucide-react renders as svg)
|
||||
const keyIcon = container.querySelector('svg');
|
||||
expect(keyIcon).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('No Test Tokens Warning', () => {
|
||||
it('should show warning banner when no test tokens exist', async () => {
|
||||
mockUseTestTokensForDocs.mockReturnValue({
|
||||
data: [],
|
||||
isLoading: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
await waitFor(() => {
|
||||
const warning = screen.getByText('No test tokens found');
|
||||
expect(warning).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not show warning banner when tokens are loading', async () => {
|
||||
mockUseTestTokensForDocs.mockReturnValue({
|
||||
data: undefined,
|
||||
isLoading: true,
|
||||
error: null,
|
||||
});
|
||||
|
||||
render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const warning = screen.queryByText('No test tokens found');
|
||||
expect(warning).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not show warning banner when tokens exist', () => {
|
||||
render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const warning = screen.queryByText('No test tokens found');
|
||||
expect(warning).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Code Examples', () => {
|
||||
it('should render code blocks for API examples', () => {
|
||||
const { container } = render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
// Code blocks typically have <pre> or <code> tags
|
||||
const codeBlocks = container.querySelectorAll('pre');
|
||||
expect(codeBlocks.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should display cURL examples by default', () => {
|
||||
const { container } = render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
// Look for curl command in code blocks
|
||||
const pageText = container.textContent || '';
|
||||
expect(pageText).toContain('curl');
|
||||
});
|
||||
|
||||
it('should include API token in code examples', async () => {
|
||||
const { container } = render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
// Wait for the component to load and use the token
|
||||
await waitFor(() => {
|
||||
const pageText = container.textContent || '';
|
||||
expect(pageText).toContain('ss_test_abc123'); // The first token's prefix
|
||||
});
|
||||
});
|
||||
|
||||
it('should include sandbox URL in examples', () => {
|
||||
const { container } = render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const pageText = container.textContent || '';
|
||||
expect(pageText).toContain('sandbox.smoothschedule.com');
|
||||
});
|
||||
|
||||
it('should render language selector tabs', () => {
|
||||
const { container } = render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
// Look for language labels (cURL, Python, etc.)
|
||||
const pageText = container.textContent || '';
|
||||
expect(pageText).toContain('cURL');
|
||||
});
|
||||
|
||||
it('should allow switching between code languages', async () => {
|
||||
const user = userEvent.setup();
|
||||
const { container } = render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
// Find Python language button (if visible)
|
||||
// This is a simplified test - actual implementation may vary
|
||||
const buttons = container.querySelectorAll('button');
|
||||
const pythonButton = Array.from(buttons).find(btn =>
|
||||
btn.textContent?.includes('Python')
|
||||
);
|
||||
|
||||
if (pythonButton) {
|
||||
await user.click(pythonButton);
|
||||
|
||||
// After clicking, should show Python code
|
||||
await waitFor(() => {
|
||||
const pageText = container.textContent || '';
|
||||
expect(pageText).toContain('import requests');
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('HTTP Method Badges', () => {
|
||||
it('should render GET method badges', () => {
|
||||
const { container } = render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const pageText = container.textContent || '';
|
||||
expect(pageText).toContain('GET');
|
||||
});
|
||||
|
||||
it('should render POST method badges', () => {
|
||||
const { container } = render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const pageText = container.textContent || '';
|
||||
expect(pageText).toContain('POST');
|
||||
});
|
||||
|
||||
it('should render PATCH method badges', () => {
|
||||
const { container } = render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const pageText = container.textContent || '';
|
||||
expect(pageText).toContain('PATCH');
|
||||
});
|
||||
|
||||
it('should render DELETE method badges', () => {
|
||||
const { container } = render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const pageText = container.textContent || '';
|
||||
expect(pageText).toContain('DELETE');
|
||||
});
|
||||
});
|
||||
|
||||
describe('API Endpoints', () => {
|
||||
it('should document the list services endpoint', () => {
|
||||
const { container } = render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const section = document.getElementById('list-services');
|
||||
expect(section).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should document the create appointment endpoint', () => {
|
||||
const { container } = render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const section = document.getElementById('create-appointment');
|
||||
expect(section).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should document the check availability endpoint', () => {
|
||||
const { container } = render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const section = document.getElementById('check-availability');
|
||||
expect(section).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should document customer endpoints', () => {
|
||||
render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
expect(document.getElementById('create-customer')).toBeInTheDocument();
|
||||
expect(document.getElementById('retrieve-customer')).toBeInTheDocument();
|
||||
expect(document.getElementById('update-customer')).toBeInTheDocument();
|
||||
expect(document.getElementById('list-customers')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should document webhook endpoints', () => {
|
||||
render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
expect(document.getElementById('webhook-events')).toBeInTheDocument();
|
||||
expect(document.getElementById('create-webhook')).toBeInTheDocument();
|
||||
expect(document.getElementById('list-webhooks')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Page Structure', () => {
|
||||
it('should have a sticky header', () => {
|
||||
const { container } = render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const header = container.querySelector('header');
|
||||
expect(header).toHaveClass('sticky');
|
||||
});
|
||||
|
||||
it('should render main content area', () => {
|
||||
const { container } = render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const mainContent = container.querySelector('.min-h-screen');
|
||||
expect(mainContent).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should apply dark mode classes', () => {
|
||||
const { container } = render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const mainDiv = container.querySelector('.dark\\:bg-gray-900');
|
||||
expect(mainDiv).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Accessibility', () => {
|
||||
it('should have semantic header element', () => {
|
||||
const { container } = render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const header = container.querySelector('header');
|
||||
expect(header).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should have accessible back button', () => {
|
||||
render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const backButton = screen.getByRole('button', { name: /back/i });
|
||||
expect(backButton).toHaveAccessibleName();
|
||||
});
|
||||
|
||||
it('should have accessible token selector', async () => {
|
||||
render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
// Wait for the selector to be rendered
|
||||
await waitFor(() => {
|
||||
const selector = screen.getByRole('combobox');
|
||||
expect(selector).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should use section elements for content sections', () => {
|
||||
const { container } = render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const sections = container.querySelectorAll('section');
|
||||
expect(sections.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Internationalization', () => {
|
||||
it('should translate page title', () => {
|
||||
render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const title = screen.getByText('API Documentation');
|
||||
expect(title).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should translate back button text', () => {
|
||||
render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
const backButton = screen.getByRole('button', { name: /back/i });
|
||||
expect(backButton).toHaveTextContent('Back');
|
||||
});
|
||||
|
||||
it('should translate no tokens warning', async () => {
|
||||
const { useTestTokensForDocs } = await import('../../hooks/useApiTokens');
|
||||
vi.mocked(useTestTokensForDocs).mockReturnValue({
|
||||
data: [],
|
||||
isLoading: false,
|
||||
error: null,
|
||||
} as any);
|
||||
|
||||
render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('No test tokens found')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Integration', () => {
|
||||
it('should render complete API documentation page', async () => {
|
||||
render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
// Check for key elements
|
||||
expect(screen.getByText('API Documentation')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /back/i })).toBeInTheDocument();
|
||||
|
||||
// Wait for token selector to render
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('combobox')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(document.getElementById('introduction')).toBeInTheDocument();
|
||||
expect(document.getElementById('authentication')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle token selection and update examples', async () => {
|
||||
const user = userEvent.setup();
|
||||
const { container } = render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
// Wait for token selector to render
|
||||
const tokenSelector = await waitFor(() => {
|
||||
return screen.getByRole('combobox') as HTMLSelectElement;
|
||||
});
|
||||
|
||||
// Wait for initial token to appear
|
||||
await waitFor(() => {
|
||||
expect(container.textContent).toContain('ss_test_abc123');
|
||||
});
|
||||
|
||||
// Select second token
|
||||
await user.selectOptions(tokenSelector, 'token-2');
|
||||
|
||||
// Should now show second token in examples
|
||||
await waitFor(() => {
|
||||
expect(container.textContent).toContain('ss_test_def456');
|
||||
});
|
||||
});
|
||||
|
||||
it('should maintain structure with all sections present', () => {
|
||||
render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
// Verify all main sections exist
|
||||
const sections = [
|
||||
'introduction',
|
||||
'authentication',
|
||||
'errors',
|
||||
'rate-limits',
|
||||
'list-services',
|
||||
'check-availability',
|
||||
'create-appointment',
|
||||
'create-customer',
|
||||
'webhook-events',
|
||||
];
|
||||
|
||||
sections.forEach(sectionId => {
|
||||
const section = document.getElementById(sectionId);
|
||||
expect(section).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error Handling', () => {
|
||||
it('should handle missing token data gracefully', async () => {
|
||||
const { useTestTokensForDocs } = await import('../../hooks/useApiTokens');
|
||||
vi.mocked(useTestTokensForDocs).mockReturnValue({
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
} as any);
|
||||
|
||||
render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
// Should still render the page
|
||||
expect(screen.getByText('API Documentation')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle token loading state', async () => {
|
||||
const { useTestTokensForDocs } = await import('../../hooks/useApiTokens');
|
||||
vi.mocked(useTestTokensForDocs).mockReturnValue({
|
||||
data: undefined,
|
||||
isLoading: true,
|
||||
error: null,
|
||||
} as any);
|
||||
|
||||
render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
// Should render without token selector
|
||||
expect(screen.queryByRole('combobox')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should use default API key when no tokens available', async () => {
|
||||
const { useTestTokensForDocs } = await import('../../hooks/useApiTokens');
|
||||
vi.mocked(useTestTokensForDocs).mockReturnValue({
|
||||
data: [],
|
||||
isLoading: false,
|
||||
error: null,
|
||||
} as any);
|
||||
|
||||
const { container } = render(<HelpApiDocs />, { wrapper: createWrapper() });
|
||||
|
||||
// Should show default placeholder token
|
||||
await waitFor(() => {
|
||||
const pageText = container.textContent || '';
|
||||
expect(pageText).toContain('ss_test_');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user