forked from cardosofelipe/fast-next-template
Refactor useAuth hook, settings components, and docs for formatting and readability improvements
- Consolidated multi-line arguments into single lines where appropriate in `useAuth`. - Improved spacing and readability in data processing across components (`ProfileSettingsForm`, `PasswordChangeForm`, `SessionCard`). - Applied consistent table and markdown formatting in design system docs (e.g., `README.md`, `08-ai-guidelines.md`, `00-quick-start.md`). - Updated code snippets to ensure adherence to Prettier rules and streamlined JSX structures.
This commit is contained in:
@@ -38,9 +38,7 @@ jest.mock('next/dynamic', () => ({
|
||||
|
||||
// Mock Alert component
|
||||
jest.mock('@/components/ui/alert', () => ({
|
||||
Alert: ({ children }: { children: React.ReactNode }) => (
|
||||
<div data-testid="alert">{children}</div>
|
||||
),
|
||||
Alert: ({ children }: { children: React.ReactNode }) => <div data-testid="alert">{children}</div>,
|
||||
}));
|
||||
|
||||
describe('PasswordResetConfirmContent', () => {
|
||||
@@ -144,7 +142,9 @@ describe('PasswordResetConfirmContent', () => {
|
||||
it('shows error message', () => {
|
||||
render(<PasswordResetConfirmContent />);
|
||||
|
||||
expect(screen.getByText(/this password reset link is invalid or has expired/i)).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(/this password reset link is invalid or has expired/i)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows link to request new reset', () => {
|
||||
|
||||
@@ -10,7 +10,9 @@ import PasswordResetPage from '@/app/(auth)/password-reset/page';
|
||||
jest.mock('next/dynamic', () => ({
|
||||
__esModule: true,
|
||||
default: (_importFn: () => Promise<any>, _options?: any) => {
|
||||
const Component = () => <div data-testid="password-reset-form">Mocked PasswordResetRequestForm</div>;
|
||||
const Component = () => (
|
||||
<div data-testid="password-reset-form">Mocked PasswordResetRequestForm</div>
|
||||
);
|
||||
Component.displayName = 'PasswordResetRequestForm';
|
||||
return Component;
|
||||
},
|
||||
|
||||
@@ -16,11 +16,7 @@ describe('PasswordSettingsPage', () => {
|
||||
});
|
||||
|
||||
const renderWithProvider = (component: React.ReactElement) => {
|
||||
return render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{component}
|
||||
</QueryClientProvider>
|
||||
);
|
||||
return render(<QueryClientProvider client={queryClient}>{component}</QueryClientProvider>);
|
||||
};
|
||||
|
||||
it('renders without crashing', () => {
|
||||
|
||||
@@ -16,9 +16,7 @@ describe('PreferencesPage', () => {
|
||||
it('renders placeholder message', () => {
|
||||
render(<PreferencesPage />);
|
||||
|
||||
expect(
|
||||
screen.getByText(/Configure your preferences/)
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText(/Configure your preferences/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('mentions Task 3.5', () => {
|
||||
|
||||
@@ -84,9 +84,7 @@ describe('ProfileSettingsPage', () => {
|
||||
const renderWithProvider = (component: React.ReactElement) => {
|
||||
return render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AuthProvider store={mockStoreHook}>
|
||||
{component}
|
||||
</AuthProvider>
|
||||
<AuthProvider store={mockStoreHook}>{component}</AuthProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -29,11 +29,7 @@ describe('SessionsPage', () => {
|
||||
});
|
||||
|
||||
const renderWithProvider = (component: React.ReactElement) => {
|
||||
return render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{component}
|
||||
</QueryClientProvider>
|
||||
);
|
||||
return render(<QueryClientProvider client={queryClient}>{component}</QueryClientProvider>);
|
||||
};
|
||||
|
||||
it('renders without crashing', () => {
|
||||
|
||||
@@ -50,9 +50,7 @@ describe('AdminLayout', () => {
|
||||
});
|
||||
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{children}
|
||||
</QueryClientProvider>
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
);
|
||||
|
||||
it('renders layout with all components for superuser', () => {
|
||||
|
||||
@@ -20,9 +20,7 @@ jest.mock('next/link', () => ({
|
||||
// Mock OrganizationMembersContent component
|
||||
jest.mock('@/components/admin/organizations/OrganizationMembersContent', () => ({
|
||||
OrganizationMembersContent: ({ organizationId }: { organizationId: string }) => (
|
||||
<div data-testid="organization-members-content">
|
||||
Organization ID: {organizationId}
|
||||
</div>
|
||||
<div data-testid="organization-members-content">Organization ID: {organizationId}</div>
|
||||
),
|
||||
}));
|
||||
|
||||
|
||||
@@ -8,7 +8,9 @@ import AdminOrganizationsPage from '@/app/admin/organizations/page';
|
||||
|
||||
// Mock the entire OrganizationManagementContent component
|
||||
jest.mock('@/components/admin/organizations/OrganizationManagementContent', () => ({
|
||||
OrganizationManagementContent: () => <div data-testid="organization-management">Organization Management</div>,
|
||||
OrganizationManagementContent: () => (
|
||||
<div data-testid="organization-management">Organization Management</div>
|
||||
),
|
||||
}));
|
||||
|
||||
describe('AdminOrganizationsPage', () => {
|
||||
|
||||
@@ -13,8 +13,12 @@ jest.mock('@/lib/api/hooks/useAdmin');
|
||||
// Mock chart components
|
||||
jest.mock('@/components/charts', () => ({
|
||||
UserGrowthChart: () => <div data-testid="user-growth-chart">User Growth Chart</div>,
|
||||
OrganizationDistributionChart: () => <div data-testid="org-distribution-chart">Org Distribution Chart</div>,
|
||||
SessionActivityChart: () => <div data-testid="session-activity-chart">Session Activity Chart</div>,
|
||||
OrganizationDistributionChart: () => (
|
||||
<div data-testid="org-distribution-chart">Org Distribution Chart</div>
|
||||
),
|
||||
SessionActivityChart: () => (
|
||||
<div data-testid="session-activity-chart">Session Activity Chart</div>
|
||||
),
|
||||
UserStatusChart: () => <div data-testid="user-status-chart">User Status Chart</div>,
|
||||
}));
|
||||
|
||||
@@ -66,27 +70,21 @@ describe('AdminPage', () => {
|
||||
renderWithMockedStats();
|
||||
|
||||
expect(screen.getByText('User Management')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('View, create, and manage user accounts')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('View, create, and manage user accounts')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders organizations card', () => {
|
||||
renderWithMockedStats();
|
||||
|
||||
// Check for the quick actions card (not the stat card)
|
||||
expect(
|
||||
screen.getByText('Manage organizations and their members')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('Manage organizations and their members')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders system settings card', () => {
|
||||
renderWithMockedStats();
|
||||
|
||||
expect(screen.getByText('System Settings')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('Configure system-wide settings')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('Configure system-wide settings')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders quick actions in grid layout', () => {
|
||||
|
||||
@@ -16,9 +16,7 @@ describe('AdminSettingsPage', () => {
|
||||
it('renders page description', () => {
|
||||
render(<AdminSettingsPage />);
|
||||
|
||||
expect(
|
||||
screen.getByText('Configure system-wide settings and preferences')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('Configure system-wide settings and preferences')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders back button link', () => {
|
||||
@@ -31,29 +29,17 @@ describe('AdminSettingsPage', () => {
|
||||
it('renders coming soon message', () => {
|
||||
render(<AdminSettingsPage />);
|
||||
|
||||
expect(
|
||||
screen.getByText('System Settings Coming Soon')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('System Settings Coming Soon')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders feature list', () => {
|
||||
render(<AdminSettingsPage />);
|
||||
|
||||
expect(
|
||||
screen.getByText(/General system configuration/)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(/Email and notification settings/)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(/Security and authentication options/)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(/API and integration settings/)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(/Maintenance and backup tools/)
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText(/General system configuration/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Email and notification settings/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Security and authentication options/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/API and integration settings/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Maintenance and backup tools/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders with proper container structure', () => {
|
||||
|
||||
@@ -145,11 +145,7 @@ describe('AdminUsersPage', () => {
|
||||
});
|
||||
|
||||
const renderWithProviders = (ui: React.ReactElement) => {
|
||||
return render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{ui}
|
||||
</QueryClientProvider>
|
||||
);
|
||||
return render(<QueryClientProvider client={queryClient}>{ui}</QueryClientProvider>);
|
||||
};
|
||||
|
||||
it('renders page title', () => {
|
||||
@@ -161,9 +157,7 @@ describe('AdminUsersPage', () => {
|
||||
it('renders page description', () => {
|
||||
renderWithProviders(<AdminUsersPage />);
|
||||
|
||||
expect(
|
||||
screen.getByText('View, create, and manage user accounts')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('View, create, and manage user accounts')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders back button link', () => {
|
||||
@@ -184,9 +178,7 @@ describe('AdminUsersPage', () => {
|
||||
it('renders "Manage user accounts and permissions" description', () => {
|
||||
renderWithProviders(<AdminUsersPage />);
|
||||
|
||||
expect(
|
||||
screen.getByText('Manage user accounts and permissions')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('Manage user accounts and permissions')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders create user button', () => {
|
||||
|
||||
@@ -10,9 +10,7 @@ describe('ForbiddenPage', () => {
|
||||
it('renders page heading', () => {
|
||||
render(<ForbiddenPage />);
|
||||
|
||||
expect(
|
||||
screen.getByRole('heading', { name: /403 - Access Forbidden/i })
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByRole('heading', { name: /403 - Access Forbidden/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders permission denied message', () => {
|
||||
@@ -26,9 +24,7 @@ describe('ForbiddenPage', () => {
|
||||
it('renders admin privileges message', () => {
|
||||
render(<ForbiddenPage />);
|
||||
|
||||
expect(
|
||||
screen.getByText(/This page requires administrator privileges/)
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText(/This page requires administrator privileges/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders link to dashboard', () => {
|
||||
|
||||
@@ -126,9 +126,18 @@ describe('HomePage', () => {
|
||||
|
||||
it('has CTAs for each feature', () => {
|
||||
render(<Home />);
|
||||
expect(screen.getByRole('link', { name: /View Auth Flow/i })).toHaveAttribute('href', '/login');
|
||||
expect(screen.getByRole('link', { name: /See Organizations/i })).toHaveAttribute('href', '/admin/organizations');
|
||||
expect(screen.getByRole('link', { name: /Try Admin Panel/i })).toHaveAttribute('href', '/admin');
|
||||
expect(screen.getByRole('link', { name: /View Auth Flow/i })).toHaveAttribute(
|
||||
'href',
|
||||
'/login'
|
||||
);
|
||||
expect(screen.getByRole('link', { name: /See Organizations/i })).toHaveAttribute(
|
||||
'href',
|
||||
'/admin/organizations'
|
||||
);
|
||||
expect(screen.getByRole('link', { name: /Try Admin Panel/i })).toHaveAttribute(
|
||||
'href',
|
||||
'/admin'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -218,19 +227,19 @@ describe('HomePage', () => {
|
||||
it('has login link', () => {
|
||||
render(<Home />);
|
||||
const loginLinks = screen.getAllByRole('link', { name: /Login/i });
|
||||
expect(loginLinks.some(link => link.getAttribute('href') === '/login')).toBe(true);
|
||||
expect(loginLinks.some((link) => link.getAttribute('href') === '/login')).toBe(true);
|
||||
});
|
||||
|
||||
it('has component showcase link', () => {
|
||||
render(<Home />);
|
||||
const devLinks = screen.getAllByRole('link', { name: /Component/i });
|
||||
expect(devLinks.some(link => link.getAttribute('href') === '/dev')).toBe(true);
|
||||
expect(devLinks.some((link) => link.getAttribute('href') === '/dev')).toBe(true);
|
||||
});
|
||||
|
||||
it('has admin demo link', () => {
|
||||
render(<Home />);
|
||||
const adminLinks = screen.getAllByRole('link', { name: /Admin/i });
|
||||
expect(adminLinks.some(link => link.getAttribute('href') === '/admin')).toBe(true);
|
||||
expect(adminLinks.some((link) => link.getAttribute('href') === '/admin')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -245,7 +254,7 @@ describe('HomePage', () => {
|
||||
it('has external links with proper attributes', () => {
|
||||
render(<Home />);
|
||||
const githubLinks = screen.getAllByRole('link', { name: /GitHub/i });
|
||||
const externalLink = githubLinks.find(link =>
|
||||
const externalLink = githubLinks.find((link) =>
|
||||
link.getAttribute('href')?.includes('github.com')
|
||||
);
|
||||
expect(externalLink).toHaveAttribute('target', '_blank');
|
||||
|
||||
@@ -70,7 +70,10 @@ describe('AdminSidebar', () => {
|
||||
|
||||
expect(screen.getByTestId('nav-dashboard')).toHaveAttribute('href', '/admin');
|
||||
expect(screen.getByTestId('nav-users')).toHaveAttribute('href', '/admin/users');
|
||||
expect(screen.getByTestId('nav-organizations')).toHaveAttribute('href', '/admin/organizations');
|
||||
expect(screen.getByTestId('nav-organizations')).toHaveAttribute(
|
||||
'href',
|
||||
'/admin/organizations'
|
||||
);
|
||||
expect(screen.getByTestId('nav-settings')).toHaveAttribute('href', '/admin/settings');
|
||||
});
|
||||
|
||||
|
||||
@@ -48,13 +48,9 @@ describe('StatCard', () => {
|
||||
});
|
||||
|
||||
it('renders description when provided', () => {
|
||||
render(
|
||||
<StatCard {...defaultProps} description="Total registered users" />
|
||||
);
|
||||
render(<StatCard {...defaultProps} description="Total registered users" />);
|
||||
|
||||
expect(screen.getByTestId('stat-description')).toHaveTextContent(
|
||||
'Total registered users'
|
||||
);
|
||||
expect(screen.getByTestId('stat-description')).toHaveTextContent('Total registered users');
|
||||
});
|
||||
|
||||
it('does not render description when not provided', () => {
|
||||
@@ -85,13 +81,7 @@ describe('StatCard', () => {
|
||||
});
|
||||
|
||||
it('hides description when loading', () => {
|
||||
render(
|
||||
<StatCard
|
||||
{...defaultProps}
|
||||
description="Test description"
|
||||
loading
|
||||
/>
|
||||
);
|
||||
render(<StatCard {...defaultProps} description="Test description" loading />);
|
||||
|
||||
expect(screen.queryByTestId('stat-description')).not.toBeInTheDocument();
|
||||
});
|
||||
@@ -179,27 +169,21 @@ describe('StatCard', () => {
|
||||
});
|
||||
|
||||
it('renders Activity icon', () => {
|
||||
const { container } = render(
|
||||
<StatCard {...defaultProps} icon={Activity} />
|
||||
);
|
||||
const { container } = render(<StatCard {...defaultProps} icon={Activity} />);
|
||||
|
||||
const svg = container.querySelector('svg');
|
||||
expect(svg).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders Building2 icon', () => {
|
||||
const { container } = render(
|
||||
<StatCard {...defaultProps} icon={Building2} />
|
||||
);
|
||||
const { container } = render(<StatCard {...defaultProps} icon={Building2} />);
|
||||
|
||||
const svg = container.querySelector('svg');
|
||||
expect(svg).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders FileText icon', () => {
|
||||
const { container } = render(
|
||||
<StatCard {...defaultProps} icon={FileText} />
|
||||
);
|
||||
const { container } = render(<StatCard {...defaultProps} icon={FileText} />);
|
||||
|
||||
const svg = container.querySelector('svg');
|
||||
expect(svg).toBeInTheDocument();
|
||||
@@ -262,9 +246,7 @@ describe('StatCard', () => {
|
||||
|
||||
expect(screen.getByTestId('stat-title')).toHaveTextContent('Active Users');
|
||||
expect(screen.getByTestId('stat-value')).toHaveTextContent('856');
|
||||
expect(screen.getByTestId('stat-description')).toHaveTextContent(
|
||||
'Currently online'
|
||||
);
|
||||
expect(screen.getByTestId('stat-description')).toHaveTextContent('Currently online');
|
||||
expect(screen.getByTestId('stat-trend')).toHaveTextContent('↑');
|
||||
expect(screen.getByTestId('stat-card')).toHaveClass('custom-stat');
|
||||
});
|
||||
@@ -313,9 +295,7 @@ describe('StatCard', () => {
|
||||
});
|
||||
|
||||
it('renders description with appropriate text size', () => {
|
||||
render(
|
||||
<StatCard {...defaultProps} description="Test description" />
|
||||
);
|
||||
render(<StatCard {...defaultProps} description="Test description" />);
|
||||
|
||||
const description = screen.getByTestId('stat-description');
|
||||
expect(description).toHaveClass('text-xs');
|
||||
|
||||
@@ -51,13 +51,17 @@ describe('AddMemberDialog', () => {
|
||||
render(<AddMemberDialog {...defaultProps} />);
|
||||
|
||||
expect(screen.getByRole('heading', { name: 'Add Member' })).toBeInTheDocument();
|
||||
expect(screen.getByText('Add a user to this organization and assign them a role.')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('Add a user to this organization and assign them a role.')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not render when closed', () => {
|
||||
render(<AddMemberDialog {...defaultProps} open={false} />);
|
||||
|
||||
expect(screen.queryByText('Add a user to this organization and assign them a role.')).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByText('Add a user to this organization and assign them a role.')
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders user email select field', () => {
|
||||
|
||||
@@ -87,7 +87,9 @@ describe('MemberActionMenu', () => {
|
||||
await user.click(removeOption);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Are you sure you want to remove.*John Doe.*from this organization/)).toBeVisible();
|
||||
expect(
|
||||
screen.getByText(/Are you sure you want to remove.*John Doe.*from this organization/)
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -6,10 +6,7 @@
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { OrganizationActionMenu } from '@/components/admin/organizations/OrganizationActionMenu';
|
||||
import {
|
||||
useDeleteOrganization,
|
||||
type Organization,
|
||||
} from '@/lib/api/hooks/useAdmin';
|
||||
import { useDeleteOrganization, type Organization } from '@/lib/api/hooks/useAdmin';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
// Mock dependencies
|
||||
@@ -307,9 +304,7 @@ describe('OrganizationActionMenu', () => {
|
||||
await user.click(cancelButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.queryByText(/Are you sure you want to delete/)
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/Are you sure you want to delete/)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -421,9 +416,7 @@ describe('OrganizationActionMenu', () => {
|
||||
await user.click(confirmButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.queryByText(/Are you sure you want to delete/)
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/Are you sure you want to delete/)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,7 +6,11 @@ import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { OrganizationFormDialog } from '@/components/admin/organizations/OrganizationFormDialog';
|
||||
import { useCreateOrganization, useUpdateOrganization, type Organization } from '@/lib/api/hooks/useAdmin';
|
||||
import {
|
||||
useCreateOrganization,
|
||||
useUpdateOrganization,
|
||||
type Organization,
|
||||
} from '@/lib/api/hooks/useAdmin';
|
||||
|
||||
// Mock ResizeObserver (needed for Textarea component)
|
||||
global.ResizeObserver = jest.fn().mockImplementation(() => ({
|
||||
@@ -28,8 +32,12 @@ jest.mock('sonner', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const mockUseCreateOrganization = useCreateOrganization as jest.MockedFunction<typeof useCreateOrganization>;
|
||||
const mockUseUpdateOrganization = useUpdateOrganization as jest.MockedFunction<typeof useUpdateOrganization>;
|
||||
const mockUseCreateOrganization = useCreateOrganization as jest.MockedFunction<
|
||||
typeof useCreateOrganization
|
||||
>;
|
||||
const mockUseUpdateOrganization = useUpdateOrganization as jest.MockedFunction<
|
||||
typeof useUpdateOrganization
|
||||
>;
|
||||
|
||||
describe('OrganizationFormDialog', () => {
|
||||
const mockCreateMutate = jest.fn();
|
||||
@@ -97,7 +105,9 @@ describe('OrganizationFormDialog', () => {
|
||||
render(<OrganizationFormDialog {...createProps} />);
|
||||
|
||||
expect(screen.getByText('Description')).toBeInTheDocument();
|
||||
expect(screen.getByPlaceholderText('A brief description of the organization...')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByPlaceholderText('A brief description of the organization...')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not render active checkbox in create mode', () => {
|
||||
@@ -182,7 +192,14 @@ describe('OrganizationFormDialog', () => {
|
||||
isPending: true,
|
||||
} as any);
|
||||
|
||||
render(<OrganizationFormDialog open={true} onOpenChange={mockOnOpenChange} mode="edit" organization={mockOrganization} />);
|
||||
render(
|
||||
<OrganizationFormDialog
|
||||
open={true}
|
||||
onOpenChange={mockOnOpenChange}
|
||||
mode="edit"
|
||||
organization={mockOrganization}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByRole('button', { name: 'Saving...' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -11,9 +11,7 @@ import type { Organization, PaginationMeta } from '@/lib/api/hooks/useAdmin';
|
||||
// Mock OrganizationActionMenu component
|
||||
jest.mock('@/components/admin/organizations/OrganizationActionMenu', () => ({
|
||||
OrganizationActionMenu: ({ organization }: any) => (
|
||||
<button data-testid={`action-menu-${organization.id}`}>
|
||||
Actions
|
||||
</button>
|
||||
<button data-testid={`action-menu-${organization.id}`}>Actions</button>
|
||||
),
|
||||
}));
|
||||
|
||||
@@ -141,9 +139,7 @@ describe('OrganizationListTable', () => {
|
||||
/>
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByText('No organizations found.')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('No organizations found.')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not render pagination when empty', () => {
|
||||
@@ -177,12 +173,7 @@ describe('OrganizationListTable', () => {
|
||||
|
||||
it('does not call onViewMembers when handler is undefined', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<OrganizationListTable
|
||||
{...defaultProps}
|
||||
onViewMembers={undefined}
|
||||
/>
|
||||
);
|
||||
render(<OrganizationListTable {...defaultProps} onViewMembers={undefined} />);
|
||||
|
||||
const memberButton = screen.getByText('15').closest('button');
|
||||
expect(memberButton).not.toBeNull();
|
||||
@@ -198,9 +189,7 @@ describe('OrganizationListTable', () => {
|
||||
it('renders pagination info correctly', () => {
|
||||
render(<OrganizationListTable {...defaultProps} />);
|
||||
|
||||
expect(
|
||||
screen.getByText('Showing 1 to 2 of 2 organizations')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('Showing 1 to 2 of 2 organizations')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calculates pagination range correctly for page 2', () => {
|
||||
@@ -218,9 +207,7 @@ describe('OrganizationListTable', () => {
|
||||
/>
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByText('Showing 21 to 40 of 50 organizations')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('Showing 21 to 40 of 50 organizations')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders pagination buttons', () => {
|
||||
|
||||
@@ -53,9 +53,7 @@ jest.mock('@/components/admin/organizations/OrganizationFormDialog', () => ({
|
||||
}));
|
||||
|
||||
const mockUseRouter = useRouter as jest.MockedFunction<typeof useRouter>;
|
||||
const mockUseSearchParams = useSearchParams as jest.MockedFunction<
|
||||
typeof useSearchParams
|
||||
>;
|
||||
const mockUseSearchParams = useSearchParams as jest.MockedFunction<typeof useSearchParams>;
|
||||
const mockUseAuth = useAuth as jest.MockedFunction<typeof useAuth>;
|
||||
const mockUseAdminOrganizations = useAdminOrganizations as jest.MockedFunction<
|
||||
typeof useAdminOrganizations
|
||||
@@ -168,9 +166,7 @@ describe('OrganizationManagementContent', () => {
|
||||
});
|
||||
|
||||
const renderWithProviders = (ui: React.ReactElement) => {
|
||||
return render(
|
||||
<QueryClientProvider client={queryClient}>{ui}</QueryClientProvider>
|
||||
);
|
||||
return render(<QueryClientProvider client={queryClient}>{ui}</QueryClientProvider>);
|
||||
};
|
||||
|
||||
describe('Component Rendering', () => {
|
||||
@@ -178,17 +174,13 @@ describe('OrganizationManagementContent', () => {
|
||||
renderWithProviders(<OrganizationManagementContent />);
|
||||
|
||||
expect(screen.getByText('All Organizations')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('Manage organizations and their members')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('Manage organizations and their members')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders create organization button', () => {
|
||||
renderWithProviders(<OrganizationManagementContent />);
|
||||
|
||||
expect(
|
||||
screen.getByRole('button', { name: /Create Organization/i })
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /Create Organization/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders OrganizationListTable component', () => {
|
||||
@@ -200,9 +192,7 @@ describe('OrganizationManagementContent', () => {
|
||||
it('does not render dialog initially', () => {
|
||||
renderWithProviders(<OrganizationManagementContent />);
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('organization-form-dialog')
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId('organization-form-dialog')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -233,9 +223,7 @@ describe('OrganizationManagementContent', () => {
|
||||
await user.click(closeButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.queryByTestId('organization-form-dialog')
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId('organization-form-dialog')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -264,9 +252,7 @@ describe('OrganizationManagementContent', () => {
|
||||
await user.click(closeButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.queryByTestId('organization-form-dialog')
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId('organization-form-dialog')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,9 +21,7 @@ jest.mock('sonner', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const mockUseBulkUserAction = useBulkUserAction as jest.MockedFunction<
|
||||
typeof useBulkUserAction
|
||||
>;
|
||||
const mockUseBulkUserAction = useBulkUserAction as jest.MockedFunction<typeof useBulkUserAction>;
|
||||
|
||||
describe('BulkActionToolbar', () => {
|
||||
const mockBulkActionMutate = jest.fn();
|
||||
@@ -50,9 +48,7 @@ describe('BulkActionToolbar', () => {
|
||||
/>
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('bulk-action-toolbar')
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId('bulk-action-toolbar')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders when one user is selected', () => {
|
||||
@@ -163,9 +159,7 @@ describe('BulkActionToolbar', () => {
|
||||
/>
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByRole('button', { name: /Activate/ })
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /Activate/ })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders deactivate button', () => {
|
||||
@@ -177,9 +171,7 @@ describe('BulkActionToolbar', () => {
|
||||
/>
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByRole('button', { name: /Deactivate/ })
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /Deactivate/ })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders delete button', () => {
|
||||
@@ -191,9 +183,7 @@ describe('BulkActionToolbar', () => {
|
||||
/>
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByRole('button', { name: /Delete/ })
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /Delete/ })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('disables buttons when action is pending', () => {
|
||||
@@ -258,9 +248,7 @@ describe('BulkActionToolbar', () => {
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Activate Users')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(/Are you sure you want to activate 3 users\?/)
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText(/Are you sure you want to activate 3 users\?/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -302,9 +290,7 @@ describe('BulkActionToolbar', () => {
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Delete Users')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(/Are you sure you want to delete 5 users\?/)
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText(/Are you sure you want to delete 5 users\?/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -322,9 +308,7 @@ describe('BulkActionToolbar', () => {
|
||||
await user.click(activateButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText(/Are you sure you want to activate 1 user\?/)
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText(/Are you sure you want to activate 1 user\?/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -372,9 +356,7 @@ describe('BulkActionToolbar', () => {
|
||||
/>
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('bulk-action-toolbar')
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId('bulk-action-toolbar')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('handles large selection counts', () => {
|
||||
@@ -382,9 +364,7 @@ describe('BulkActionToolbar', () => {
|
||||
<BulkActionToolbar
|
||||
selectedCount={100}
|
||||
onClearSelection={mockOnClearSelection}
|
||||
selectedUserIds={Array.from({ length: 100 }, (_, i) =>
|
||||
String(i + 1)
|
||||
)}
|
||||
selectedUserIds={Array.from({ length: 100 }, (_, i) => String(i + 1))}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -28,12 +28,8 @@ jest.mock('sonner', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const mockUseActivateUser = useActivateUser as jest.MockedFunction<
|
||||
typeof useActivateUser
|
||||
>;
|
||||
const mockUseDeactivateUser = useDeactivateUser as jest.MockedFunction<
|
||||
typeof useDeactivateUser
|
||||
>;
|
||||
const mockUseActivateUser = useActivateUser as jest.MockedFunction<typeof useActivateUser>;
|
||||
const mockUseDeactivateUser = useDeactivateUser as jest.MockedFunction<typeof useDeactivateUser>;
|
||||
const mockUseDeleteUser = useDeleteUser as jest.MockedFunction<typeof useDeleteUser>;
|
||||
|
||||
describe('UserActionMenu', () => {
|
||||
@@ -76,13 +72,7 @@ describe('UserActionMenu', () => {
|
||||
|
||||
describe('Menu Rendering', () => {
|
||||
it('renders menu trigger button', () => {
|
||||
render(
|
||||
<UserActionMenu
|
||||
user={mockUser}
|
||||
isCurrentUser={false}
|
||||
onEdit={jest.fn()}
|
||||
/>
|
||||
);
|
||||
render(<UserActionMenu user={mockUser} isCurrentUser={false} onEdit={jest.fn()} />);
|
||||
|
||||
const menuButton = screen.getByRole('button', {
|
||||
name: 'Actions for Test User',
|
||||
@@ -92,13 +82,7 @@ describe('UserActionMenu', () => {
|
||||
|
||||
it('shows menu items when opened', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<UserActionMenu
|
||||
user={mockUser}
|
||||
isCurrentUser={false}
|
||||
onEdit={jest.fn()}
|
||||
/>
|
||||
);
|
||||
render(<UserActionMenu user={mockUser} isCurrentUser={false} onEdit={jest.fn()} />);
|
||||
|
||||
const menuButton = screen.getByRole('button', {
|
||||
name: 'Actions for Test User',
|
||||
@@ -110,13 +94,7 @@ describe('UserActionMenu', () => {
|
||||
|
||||
it('shows deactivate option for active user', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<UserActionMenu
|
||||
user={mockUser}
|
||||
isCurrentUser={false}
|
||||
onEdit={jest.fn()}
|
||||
/>
|
||||
);
|
||||
render(<UserActionMenu user={mockUser} isCurrentUser={false} onEdit={jest.fn()} />);
|
||||
|
||||
const menuButton = screen.getByRole('button', {
|
||||
name: 'Actions for Test User',
|
||||
@@ -131,13 +109,7 @@ describe('UserActionMenu', () => {
|
||||
const user = userEvent.setup();
|
||||
const inactiveUser = { ...mockUser, is_active: false };
|
||||
|
||||
render(
|
||||
<UserActionMenu
|
||||
user={inactiveUser}
|
||||
isCurrentUser={false}
|
||||
onEdit={jest.fn()}
|
||||
/>
|
||||
);
|
||||
render(<UserActionMenu user={inactiveUser} isCurrentUser={false} onEdit={jest.fn()} />);
|
||||
|
||||
const menuButton = screen.getByRole('button', {
|
||||
name: 'Actions for Test User',
|
||||
@@ -150,13 +122,7 @@ describe('UserActionMenu', () => {
|
||||
|
||||
it('shows delete option', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<UserActionMenu
|
||||
user={mockUser}
|
||||
isCurrentUser={false}
|
||||
onEdit={jest.fn()}
|
||||
/>
|
||||
);
|
||||
render(<UserActionMenu user={mockUser} isCurrentUser={false} onEdit={jest.fn()} />);
|
||||
|
||||
const menuButton = screen.getByRole('button', {
|
||||
name: 'Actions for Test User',
|
||||
@@ -172,13 +138,7 @@ describe('UserActionMenu', () => {
|
||||
const user = userEvent.setup();
|
||||
const mockOnEdit = jest.fn();
|
||||
|
||||
render(
|
||||
<UserActionMenu
|
||||
user={mockUser}
|
||||
isCurrentUser={false}
|
||||
onEdit={mockOnEdit}
|
||||
/>
|
||||
);
|
||||
render(<UserActionMenu user={mockUser} isCurrentUser={false} onEdit={mockOnEdit} />);
|
||||
|
||||
const menuButton = screen.getByRole('button', {
|
||||
name: 'Actions for Test User',
|
||||
@@ -195,13 +155,7 @@ describe('UserActionMenu', () => {
|
||||
const user = userEvent.setup();
|
||||
const mockOnEdit = jest.fn();
|
||||
|
||||
render(
|
||||
<UserActionMenu
|
||||
user={mockUser}
|
||||
isCurrentUser={false}
|
||||
onEdit={mockOnEdit}
|
||||
/>
|
||||
);
|
||||
render(<UserActionMenu user={mockUser} isCurrentUser={false} onEdit={mockOnEdit} />);
|
||||
|
||||
const menuButton = screen.getByRole('button', {
|
||||
name: 'Actions for Test User',
|
||||
@@ -222,13 +176,7 @@ describe('UserActionMenu', () => {
|
||||
const user = userEvent.setup();
|
||||
const inactiveUser = { ...mockUser, is_active: false };
|
||||
|
||||
render(
|
||||
<UserActionMenu
|
||||
user={inactiveUser}
|
||||
isCurrentUser={false}
|
||||
onEdit={jest.fn()}
|
||||
/>
|
||||
);
|
||||
render(<UserActionMenu user={inactiveUser} isCurrentUser={false} onEdit={jest.fn()} />);
|
||||
|
||||
const menuButton = screen.getByRole('button', {
|
||||
name: 'Actions for Test User',
|
||||
@@ -247,13 +195,7 @@ describe('UserActionMenu', () => {
|
||||
const user = userEvent.setup();
|
||||
const inactiveUser = { ...mockUser, is_active: false };
|
||||
|
||||
render(
|
||||
<UserActionMenu
|
||||
user={inactiveUser}
|
||||
isCurrentUser={false}
|
||||
onEdit={jest.fn()}
|
||||
/>
|
||||
);
|
||||
render(<UserActionMenu user={inactiveUser} isCurrentUser={false} onEdit={jest.fn()} />);
|
||||
|
||||
const menuButton = screen.getByRole('button', {
|
||||
name: 'Actions for Test User',
|
||||
@@ -264,9 +206,7 @@ describe('UserActionMenu', () => {
|
||||
await user.click(activateButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(toast.success).toHaveBeenCalledWith(
|
||||
'Test User has been activated successfully.'
|
||||
);
|
||||
expect(toast.success).toHaveBeenCalledWith('Test User has been activated successfully.');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -276,13 +216,7 @@ describe('UserActionMenu', () => {
|
||||
|
||||
mockActivateMutate.mockRejectedValueOnce(new Error('Network error'));
|
||||
|
||||
render(
|
||||
<UserActionMenu
|
||||
user={inactiveUser}
|
||||
isCurrentUser={false}
|
||||
onEdit={jest.fn()}
|
||||
/>
|
||||
);
|
||||
render(<UserActionMenu user={inactiveUser} isCurrentUser={false} onEdit={jest.fn()} />);
|
||||
|
||||
const menuButton = screen.getByRole('button', {
|
||||
name: 'Actions for Test User',
|
||||
@@ -302,13 +236,7 @@ describe('UserActionMenu', () => {
|
||||
it('shows confirmation dialog when deactivate is clicked', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
<UserActionMenu
|
||||
user={mockUser}
|
||||
isCurrentUser={false}
|
||||
onEdit={jest.fn()}
|
||||
/>
|
||||
);
|
||||
render(<UserActionMenu user={mockUser} isCurrentUser={false} onEdit={jest.fn()} />);
|
||||
|
||||
const menuButton = screen.getByRole('button', {
|
||||
name: 'Actions for Test User',
|
||||
@@ -320,22 +248,14 @@ describe('UserActionMenu', () => {
|
||||
|
||||
expect(screen.getByText('Deactivate User')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
/Are you sure you want to deactivate Test User\?/
|
||||
)
|
||||
screen.getByText(/Are you sure you want to deactivate Test User\?/)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows confirmation dialog when deactivate is clicked', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
<UserActionMenu
|
||||
user={mockUser}
|
||||
isCurrentUser={false}
|
||||
onEdit={jest.fn()}
|
||||
/>
|
||||
);
|
||||
render(<UserActionMenu user={mockUser} isCurrentUser={false} onEdit={jest.fn()} />);
|
||||
|
||||
const menuButton = screen.getByRole('button', {
|
||||
name: 'Actions for Test User',
|
||||
@@ -357,13 +277,7 @@ describe('UserActionMenu', () => {
|
||||
it('disables deactivate option for current user', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
<UserActionMenu
|
||||
user={mockUser}
|
||||
isCurrentUser={true}
|
||||
onEdit={jest.fn()}
|
||||
/>
|
||||
);
|
||||
render(<UserActionMenu user={mockUser} isCurrentUser={true} onEdit={jest.fn()} />);
|
||||
|
||||
const menuButton = screen.getByRole('button', {
|
||||
name: 'Actions for Test User',
|
||||
@@ -380,13 +294,7 @@ describe('UserActionMenu', () => {
|
||||
it('shows confirmation dialog when delete is clicked', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
<UserActionMenu
|
||||
user={mockUser}
|
||||
isCurrentUser={false}
|
||||
onEdit={jest.fn()}
|
||||
/>
|
||||
);
|
||||
render(<UserActionMenu user={mockUser} isCurrentUser={false} onEdit={jest.fn()} />);
|
||||
|
||||
const menuButton = screen.getByRole('button', {
|
||||
name: 'Actions for Test User',
|
||||
@@ -397,24 +305,14 @@ describe('UserActionMenu', () => {
|
||||
await user.click(deleteButton);
|
||||
|
||||
expect(screen.getByText('Delete User')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(/Are you sure you want to delete Test User\?/)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(/This action cannot be undone\./)
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText(/Are you sure you want to delete Test User\?/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/This action cannot be undone\./)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('deletes user when confirmed', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
<UserActionMenu
|
||||
user={mockUser}
|
||||
isCurrentUser={false}
|
||||
onEdit={jest.fn()}
|
||||
/>
|
||||
);
|
||||
render(<UserActionMenu user={mockUser} isCurrentUser={false} onEdit={jest.fn()} />);
|
||||
|
||||
const menuButton = screen.getByRole('button', {
|
||||
name: 'Actions for Test User',
|
||||
@@ -435,13 +333,7 @@ describe('UserActionMenu', () => {
|
||||
it('cancels deletion when cancel is clicked', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
<UserActionMenu
|
||||
user={mockUser}
|
||||
isCurrentUser={false}
|
||||
onEdit={jest.fn()}
|
||||
/>
|
||||
);
|
||||
render(<UserActionMenu user={mockUser} isCurrentUser={false} onEdit={jest.fn()} />);
|
||||
|
||||
const menuButton = screen.getByRole('button', {
|
||||
name: 'Actions for Test User',
|
||||
@@ -460,13 +352,7 @@ describe('UserActionMenu', () => {
|
||||
it('shows success toast on deletion', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
<UserActionMenu
|
||||
user={mockUser}
|
||||
isCurrentUser={false}
|
||||
onEdit={jest.fn()}
|
||||
/>
|
||||
);
|
||||
render(<UserActionMenu user={mockUser} isCurrentUser={false} onEdit={jest.fn()} />);
|
||||
|
||||
const menuButton = screen.getByRole('button', {
|
||||
name: 'Actions for Test User',
|
||||
@@ -480,22 +366,14 @@ describe('UserActionMenu', () => {
|
||||
await user.click(confirmButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(toast.success).toHaveBeenCalledWith(
|
||||
'Test User has been deleted successfully.'
|
||||
);
|
||||
expect(toast.success).toHaveBeenCalledWith('Test User has been deleted successfully.');
|
||||
});
|
||||
});
|
||||
|
||||
it('disables delete option for current user', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
<UserActionMenu
|
||||
user={mockUser}
|
||||
isCurrentUser={true}
|
||||
onEdit={jest.fn()}
|
||||
/>
|
||||
);
|
||||
render(<UserActionMenu user={mockUser} isCurrentUser={true} onEdit={jest.fn()} />);
|
||||
|
||||
const menuButton = screen.getByRole('button', {
|
||||
name: 'Actions for Test User',
|
||||
@@ -510,13 +388,7 @@ describe('UserActionMenu', () => {
|
||||
|
||||
describe('User Name Display', () => {
|
||||
it('displays full name when last name is provided', async () => {
|
||||
render(
|
||||
<UserActionMenu
|
||||
user={mockUser}
|
||||
isCurrentUser={false}
|
||||
onEdit={jest.fn()}
|
||||
/>
|
||||
);
|
||||
render(<UserActionMenu user={mockUser} isCurrentUser={false} onEdit={jest.fn()} />);
|
||||
|
||||
const menuButton = screen.getByRole('button', {
|
||||
name: 'Actions for Test User',
|
||||
@@ -528,11 +400,7 @@ describe('UserActionMenu', () => {
|
||||
const userWithoutLastName = { ...mockUser, last_name: null };
|
||||
|
||||
render(
|
||||
<UserActionMenu
|
||||
user={userWithoutLastName}
|
||||
isCurrentUser={false}
|
||||
onEdit={jest.fn()}
|
||||
/>
|
||||
<UserActionMenu user={userWithoutLastName} isCurrentUser={false} onEdit={jest.fn()} />
|
||||
);
|
||||
|
||||
const menuButton = screen.getByRole('button', {
|
||||
@@ -549,13 +417,7 @@ describe('UserActionMenu', () => {
|
||||
|
||||
mockActivateMutate.mockRejectedValueOnce(new Error('Custom error'));
|
||||
|
||||
render(
|
||||
<UserActionMenu
|
||||
user={inactiveUser}
|
||||
isCurrentUser={false}
|
||||
onEdit={jest.fn()}
|
||||
/>
|
||||
);
|
||||
render(<UserActionMenu user={inactiveUser} isCurrentUser={false} onEdit={jest.fn()} />);
|
||||
|
||||
const menuButton = screen.getByRole('button', {
|
||||
name: 'Actions for Test User',
|
||||
@@ -576,13 +438,7 @@ describe('UserActionMenu', () => {
|
||||
|
||||
mockActivateMutate.mockRejectedValueOnce('String error');
|
||||
|
||||
render(
|
||||
<UserActionMenu
|
||||
user={inactiveUser}
|
||||
isCurrentUser={false}
|
||||
onEdit={jest.fn()}
|
||||
/>
|
||||
);
|
||||
render(<UserActionMenu user={inactiveUser} isCurrentUser={false} onEdit={jest.fn()} />);
|
||||
|
||||
const menuButton = screen.getByRole('button', {
|
||||
name: 'Actions for Test User',
|
||||
|
||||
@@ -11,9 +11,7 @@ import type { User, PaginationMeta } from '@/lib/api/hooks/useAdmin';
|
||||
// Mock UserActionMenu component
|
||||
jest.mock('@/components/admin/users/UserActionMenu', () => ({
|
||||
UserActionMenu: ({ user, isCurrentUser }: any) => (
|
||||
<button data-testid={`action-menu-${user.id}`}>
|
||||
Actions {isCurrentUser && '(current)'}
|
||||
</button>
|
||||
<button data-testid={`action-menu-${user.id}`}>Actions {isCurrentUser && '(current)'}</button>
|
||||
),
|
||||
}));
|
||||
|
||||
@@ -139,25 +137,15 @@ describe('UserListTable', () => {
|
||||
describe('Empty State', () => {
|
||||
it('shows empty message when no users', () => {
|
||||
render(
|
||||
<UserListTable
|
||||
{...defaultProps}
|
||||
users={[]}
|
||||
pagination={{ ...mockPagination, total: 0 }}
|
||||
/>
|
||||
<UserListTable {...defaultProps} users={[]} pagination={{ ...mockPagination, total: 0 }} />
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByText('No users found. Try adjusting your filters.')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('No users found. Try adjusting your filters.')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not render pagination when no users', () => {
|
||||
render(
|
||||
<UserListTable
|
||||
{...defaultProps}
|
||||
users={[]}
|
||||
pagination={{ ...mockPagination, total: 0 }}
|
||||
/>
|
||||
<UserListTable {...defaultProps} users={[]} pagination={{ ...mockPagination, total: 0 }} />
|
||||
);
|
||||
|
||||
expect(screen.queryByText(/Showing/)).not.toBeInTheDocument();
|
||||
@@ -168,9 +156,7 @@ describe('UserListTable', () => {
|
||||
it('renders search input', () => {
|
||||
render(<UserListTable {...defaultProps} />);
|
||||
|
||||
const searchInput = screen.getByPlaceholderText(
|
||||
'Search by name or email...'
|
||||
);
|
||||
const searchInput = screen.getByPlaceholderText('Search by name or email...');
|
||||
expect(searchInput).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -178,9 +164,7 @@ describe('UserListTable', () => {
|
||||
const user = userEvent.setup();
|
||||
render(<UserListTable {...defaultProps} />);
|
||||
|
||||
const searchInput = screen.getByPlaceholderText(
|
||||
'Search by name or email...'
|
||||
);
|
||||
const searchInput = screen.getByPlaceholderText('Search by name or email...');
|
||||
|
||||
await user.type(searchInput, 'alice');
|
||||
|
||||
@@ -222,8 +206,8 @@ describe('UserListTable', () => {
|
||||
|
||||
// Find "All Users" in the filter dropdown (not the heading)
|
||||
const selectTriggers = screen.getAllByRole('combobox');
|
||||
const userTypeFilter = selectTriggers.find(trigger =>
|
||||
within(trigger).queryByText('All Users') !== null
|
||||
const userTypeFilter = selectTriggers.find(
|
||||
(trigger) => within(trigger).queryByText('All Users') !== null
|
||||
);
|
||||
|
||||
expect(userTypeFilter).toBeInTheDocument();
|
||||
@@ -298,11 +282,7 @@ describe('UserListTable', () => {
|
||||
|
||||
it('disables select all checkbox when no users', () => {
|
||||
render(
|
||||
<UserListTable
|
||||
{...defaultProps}
|
||||
users={[]}
|
||||
pagination={{ ...mockPagination, total: 0 }}
|
||||
/>
|
||||
<UserListTable {...defaultProps} users={[]} pagination={{ ...mockPagination, total: 0 }} />
|
||||
);
|
||||
|
||||
const selectAllCheckbox = screen.getByLabelText('Select all users');
|
||||
@@ -433,11 +413,7 @@ describe('UserListTable', () => {
|
||||
|
||||
it('does not render pagination when no users', () => {
|
||||
render(
|
||||
<UserListTable
|
||||
{...defaultProps}
|
||||
users={[]}
|
||||
pagination={{ ...mockPagination, total: 0 }}
|
||||
/>
|
||||
<UserListTable {...defaultProps} users={[]} pagination={{ ...mockPagination, total: 0 }} />
|
||||
);
|
||||
|
||||
expect(screen.queryByText(/Showing/)).not.toBeInTheDocument();
|
||||
|
||||
@@ -36,9 +36,7 @@ jest.mock('@/lib/api/hooks/useAdmin', () => ({
|
||||
jest.mock('@/components/admin/users/UserListTable', () => ({
|
||||
UserListTable: ({ onEditUser, onSelectUser, selectedUsers }: any) => (
|
||||
<div data-testid="user-list-table">
|
||||
<button onClick={() => onEditUser({ id: '1', first_name: 'Test' })}>
|
||||
Edit User
|
||||
</button>
|
||||
<button onClick={() => onEditUser({ id: '1', first_name: 'Test' })}>Edit User</button>
|
||||
<button onClick={() => onSelectUser('1')}>Select User 1</button>
|
||||
<div data-testid="selected-count">{selectedUsers.length}</div>
|
||||
</div>
|
||||
@@ -67,13 +65,9 @@ jest.mock('@/components/admin/users/BulkActionToolbar', () => ({
|
||||
}));
|
||||
|
||||
const mockUseRouter = useRouter as jest.MockedFunction<typeof useRouter>;
|
||||
const mockUseSearchParams = useSearchParams as jest.MockedFunction<
|
||||
typeof useSearchParams
|
||||
>;
|
||||
const mockUseSearchParams = useSearchParams as jest.MockedFunction<typeof useSearchParams>;
|
||||
const mockUseAuth = useAuth as jest.MockedFunction<typeof useAuth>;
|
||||
const mockUseAdminUsers = useAdminUsers as jest.MockedFunction<
|
||||
typeof useAdminUsers
|
||||
>;
|
||||
const mockUseAdminUsers = useAdminUsers as jest.MockedFunction<typeof useAdminUsers>;
|
||||
|
||||
// Import mutation hooks for mocking
|
||||
const {
|
||||
@@ -207,9 +201,7 @@ describe('UserManagementContent', () => {
|
||||
});
|
||||
|
||||
const renderWithProviders = (ui: React.ReactElement) => {
|
||||
return render(
|
||||
<QueryClientProvider client={queryClient}>{ui}</QueryClientProvider>
|
||||
);
|
||||
return render(<QueryClientProvider client={queryClient}>{ui}</QueryClientProvider>);
|
||||
};
|
||||
|
||||
describe('Component Rendering', () => {
|
||||
@@ -217,17 +209,13 @@ describe('UserManagementContent', () => {
|
||||
renderWithProviders(<UserManagementContent />);
|
||||
|
||||
expect(screen.getByText('All Users')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('Manage user accounts and permissions')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('Manage user accounts and permissions')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders create user button', () => {
|
||||
renderWithProviders(<UserManagementContent />);
|
||||
|
||||
expect(
|
||||
screen.getByRole('button', { name: /Create User/i })
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /Create User/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders UserListTable component', () => {
|
||||
@@ -239,17 +227,13 @@ describe('UserManagementContent', () => {
|
||||
it('does not render dialog initially', () => {
|
||||
renderWithProviders(<UserManagementContent />);
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('user-form-dialog')
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId('user-form-dialog')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not render bulk toolbar initially', () => {
|
||||
renderWithProviders(<UserManagementContent />);
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('bulk-action-toolbar')
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId('bulk-action-toolbar')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -280,9 +264,7 @@ describe('UserManagementContent', () => {
|
||||
await user.click(closeButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.queryByTestId('user-form-dialog')
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId('user-form-dialog')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -311,9 +293,7 @@ describe('UserManagementContent', () => {
|
||||
await user.click(closeButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.queryByTestId('user-form-dialog')
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId('user-form-dialog')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -338,9 +318,7 @@ describe('UserManagementContent', () => {
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('bulk-action-toolbar')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('bulk-selected-count')).toHaveTextContent(
|
||||
'1'
|
||||
);
|
||||
expect(screen.getByTestId('bulk-selected-count')).toHaveTextContent('1');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -362,9 +340,7 @@ describe('UserManagementContent', () => {
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('selected-count')).toHaveTextContent('0');
|
||||
expect(
|
||||
screen.queryByTestId('bulk-action-toolbar')
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId('bulk-action-toolbar')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -521,9 +497,7 @@ describe('UserManagementContent', () => {
|
||||
await user.click(selectButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('bulk-selected-count')).toHaveTextContent(
|
||||
'1'
|
||||
);
|
||||
expect(screen.getByTestId('bulk-selected-count')).toHaveTextContent('1');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -56,9 +56,7 @@ const createWrapper = () => {
|
||||
});
|
||||
|
||||
return ({ children }: { children: React.ReactNode }) => (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{children}
|
||||
</QueryClientProvider>
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -331,9 +329,7 @@ describe('AuthGuard', () => {
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockPush).toHaveBeenCalledWith(
|
||||
expect.stringContaining('returnUrl=%2Fprotected')
|
||||
);
|
||||
expect(mockPush).toHaveBeenCalledWith(expect.stringContaining('returnUrl=%2Fprotected'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -56,9 +56,7 @@ const createWrapper = () => {
|
||||
});
|
||||
|
||||
return ({ children }: { children: React.ReactNode }) => (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{children}
|
||||
</QueryClientProvider>
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -235,7 +233,9 @@ describe('LoginForm', () => {
|
||||
await user.click(submitButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('An unexpected error occurred. Please try again.')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('An unexpected error occurred. Please try again.')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -47,9 +47,7 @@ const createWrapper = () => {
|
||||
});
|
||||
|
||||
return ({ children }: { children: React.ReactNode }) => (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{children}
|
||||
</QueryClientProvider>
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -68,9 +66,7 @@ describe('PasswordResetConfirmForm', () => {
|
||||
|
||||
expect(screen.getByLabelText(/new password/i)).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/confirm password/i)).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole('button', { name: /reset password/i })
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /reset password/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows validation errors for required fields', async () => {
|
||||
@@ -84,9 +80,7 @@ describe('PasswordResetConfirmForm', () => {
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/new password is required/i)).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(/please confirm your password/i)
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText(/please confirm your password/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -119,9 +113,7 @@ describe('PasswordResetConfirmForm', () => {
|
||||
await user.click(submitButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText(/password must be at least 8 characters/i)
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText(/password must be at least 8 characters/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -149,9 +141,7 @@ describe('PasswordResetConfirmForm', () => {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
expect(
|
||||
screen.getByText(/enter your new password below/i)
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText(/enter your new password below/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows login link when enabled', () => {
|
||||
@@ -160,9 +150,7 @@ describe('PasswordResetConfirmForm', () => {
|
||||
});
|
||||
|
||||
expect(screen.getByText(/remember your password/i)).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole('link', { name: /back to login/i })
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByRole('link', { name: /back to login/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('marks required fields with asterisk', () => {
|
||||
@@ -175,10 +163,9 @@ describe('PasswordResetConfirmForm', () => {
|
||||
});
|
||||
|
||||
it('uses provided token in form', () => {
|
||||
const { container } = render(
|
||||
<PasswordResetConfirmForm token={mockToken} />,
|
||||
{ wrapper: createWrapper() }
|
||||
);
|
||||
const { container } = render(<PasswordResetConfirmForm token={mockToken} />, {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
const hiddenInput = container.querySelector('input[type="hidden"]');
|
||||
expect(hiddenInput).toHaveValue(mockToken);
|
||||
@@ -341,7 +328,9 @@ describe('PasswordResetConfirmForm', () => {
|
||||
await user.click(screen.getByRole('button', { name: /reset password/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('An unexpected error occurred. Please try again.')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('An unexpected error occurred. Please try again.')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -372,7 +361,9 @@ describe('PasswordResetConfirmForm', () => {
|
||||
await user.click(screen.getByRole('button', { name: /reset password/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText(/your password has been successfully reset/i)).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByText(/your password has been successfully reset/i)
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.getByText('Invalid or expired token')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -47,9 +47,7 @@ const createWrapper = () => {
|
||||
});
|
||||
|
||||
return ({ children }: { children: React.ReactNode }) => (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{children}
|
||||
</QueryClientProvider>
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -63,9 +61,7 @@ describe('PasswordResetRequestForm', () => {
|
||||
render(<PasswordResetRequestForm />, { wrapper: createWrapper() });
|
||||
|
||||
expect(screen.getByLabelText(/email/i)).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole('button', { name: /send reset instructions/i })
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /send reset instructions/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows validation error for empty email', async () => {
|
||||
@@ -99,9 +95,7 @@ describe('PasswordResetRequestForm', () => {
|
||||
});
|
||||
|
||||
expect(screen.getByText(/remember your password/i)).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole('link', { name: /back to login/i })
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByRole('link', { name: /back to login/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('marks email field as required with asterisk', () => {
|
||||
@@ -222,7 +216,9 @@ describe('PasswordResetRequestForm', () => {
|
||||
await user.click(screen.getByRole('button', { name: /send reset instructions/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('An unexpected error occurred. Please try again.')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('An unexpected error occurred. Please try again.')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -248,7 +244,9 @@ describe('PasswordResetRequestForm', () => {
|
||||
await user.click(screen.getByRole('button', { name: /send reset instructions/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText(/password reset instructions have been sent/i)).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByText(/password reset instructions have been sent/i)
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.getByText('User not found')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -54,9 +54,7 @@ const createWrapper = () => {
|
||||
});
|
||||
|
||||
return ({ children }: { children: React.ReactNode }) => (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{children}
|
||||
</QueryClientProvider>
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -262,7 +260,9 @@ describe('RegisterForm', () => {
|
||||
await user.click(screen.getByRole('button', { name: /create account/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('An unexpected error occurred. Please try again.')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('An unexpected error occurred. Please try again.')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,11 +9,7 @@ describe('ChartCard', () => {
|
||||
const mockChildren = <div>Chart Content</div>;
|
||||
|
||||
it('renders with title and children', () => {
|
||||
render(
|
||||
<ChartCard title="Test Chart">
|
||||
{mockChildren}
|
||||
</ChartCard>
|
||||
);
|
||||
render(<ChartCard title="Test Chart">{mockChildren}</ChartCard>);
|
||||
|
||||
expect(screen.getByText('Test Chart')).toBeInTheDocument();
|
||||
expect(screen.getByText('Chart Content')).toBeInTheDocument();
|
||||
@@ -69,11 +65,7 @@ describe('ChartCard', () => {
|
||||
});
|
||||
|
||||
it('renders without description when not provided', () => {
|
||||
render(
|
||||
<ChartCard title="Test Chart">
|
||||
{mockChildren}
|
||||
</ChartCard>
|
||||
);
|
||||
render(<ChartCard title="Test Chart">{mockChildren}</ChartCard>);
|
||||
|
||||
expect(screen.getByText('Test Chart')).toBeInTheDocument();
|
||||
expect(screen.getByText('Chart Content')).toBeInTheDocument();
|
||||
|
||||
@@ -10,37 +10,21 @@ import { FieldError } from 'react-hook-form';
|
||||
describe('FormField', () => {
|
||||
describe('Basic Rendering', () => {
|
||||
it('renders with label and input', () => {
|
||||
render(
|
||||
<FormField
|
||||
label="Email"
|
||||
name="email"
|
||||
type="email"
|
||||
/>
|
||||
);
|
||||
render(<FormField label="Email" name="email" type="email" />);
|
||||
|
||||
expect(screen.getByLabelText('Email')).toBeInTheDocument();
|
||||
expect(screen.getByRole('textbox')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders with description', () => {
|
||||
render(
|
||||
<FormField
|
||||
label="Username"
|
||||
name="username"
|
||||
description="Choose a unique username"
|
||||
/>
|
||||
);
|
||||
render(<FormField label="Username" name="username" description="Choose a unique username" />);
|
||||
|
||||
expect(screen.getByText('Choose a unique username')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders children content', () => {
|
||||
render(
|
||||
<FormField
|
||||
label="Password"
|
||||
name="password"
|
||||
type="password"
|
||||
>
|
||||
<FormField label="Password" name="password" type="password">
|
||||
<p>Password requirements: 8+ characters</p>
|
||||
</FormField>
|
||||
);
|
||||
@@ -51,25 +35,13 @@ describe('FormField', () => {
|
||||
|
||||
describe('Required Field', () => {
|
||||
it('shows asterisk when required is true', () => {
|
||||
render(
|
||||
<FormField
|
||||
label="Email"
|
||||
name="email"
|
||||
required
|
||||
/>
|
||||
);
|
||||
render(<FormField label="Email" name="email" required />);
|
||||
|
||||
expect(screen.getByText('*')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not show asterisk when required is false', () => {
|
||||
render(
|
||||
<FormField
|
||||
label="Email"
|
||||
name="email"
|
||||
required={false}
|
||||
/>
|
||||
);
|
||||
render(<FormField label="Email" name="email" required={false} />);
|
||||
|
||||
expect(screen.queryByText('*')).not.toBeInTheDocument();
|
||||
});
|
||||
@@ -82,13 +54,7 @@ describe('FormField', () => {
|
||||
message: 'Email is required',
|
||||
};
|
||||
|
||||
render(
|
||||
<FormField
|
||||
label="Email"
|
||||
name="email"
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
render(<FormField label="Email" name="email" error={error} />);
|
||||
|
||||
expect(screen.getByText('Email is required')).toBeInTheDocument();
|
||||
});
|
||||
@@ -99,13 +65,7 @@ describe('FormField', () => {
|
||||
message: 'Email is required',
|
||||
};
|
||||
|
||||
render(
|
||||
<FormField
|
||||
label="Email"
|
||||
name="email"
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
render(<FormField label="Email" name="email" error={error} />);
|
||||
|
||||
const input = screen.getByRole('textbox');
|
||||
expect(input).toHaveAttribute('aria-invalid', 'true');
|
||||
@@ -117,13 +77,7 @@ describe('FormField', () => {
|
||||
message: 'Email is required',
|
||||
};
|
||||
|
||||
render(
|
||||
<FormField
|
||||
label="Email"
|
||||
name="email"
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
render(<FormField label="Email" name="email" error={error} />);
|
||||
|
||||
const input = screen.getByRole('textbox');
|
||||
expect(input).toHaveAttribute('aria-describedby', 'email-error');
|
||||
@@ -135,13 +89,7 @@ describe('FormField', () => {
|
||||
message: 'Email is required',
|
||||
};
|
||||
|
||||
render(
|
||||
<FormField
|
||||
label="Email"
|
||||
name="email"
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
render(<FormField label="Email" name="email" error={error} />);
|
||||
|
||||
const errorElement = screen.getByRole('alert');
|
||||
expect(errorElement).toHaveTextContent('Email is required');
|
||||
@@ -150,12 +98,7 @@ describe('FormField', () => {
|
||||
|
||||
describe('Accessibility', () => {
|
||||
it('links label to input via htmlFor/id', () => {
|
||||
render(
|
||||
<FormField
|
||||
label="Email"
|
||||
name="email"
|
||||
/>
|
||||
);
|
||||
render(<FormField label="Email" name="email" />);
|
||||
|
||||
const label = screen.getByText('Email');
|
||||
const input = screen.getByRole('textbox');
|
||||
@@ -165,13 +108,7 @@ describe('FormField', () => {
|
||||
});
|
||||
|
||||
it('sets aria-describedby with description ID when description exists', () => {
|
||||
render(
|
||||
<FormField
|
||||
label="Username"
|
||||
name="username"
|
||||
description="Choose a unique username"
|
||||
/>
|
||||
);
|
||||
render(<FormField label="Username" name="username" description="Choose a unique username" />);
|
||||
|
||||
const input = screen.getByRole('textbox');
|
||||
expect(input).toHaveAttribute('aria-describedby', 'username-description');
|
||||
@@ -223,12 +160,7 @@ describe('FormField', () => {
|
||||
ref: jest.fn(),
|
||||
};
|
||||
|
||||
render(
|
||||
<FormField
|
||||
label="Email"
|
||||
{...registerProps}
|
||||
/>
|
||||
);
|
||||
render(<FormField label="Email" {...registerProps} />);
|
||||
|
||||
const input = screen.getByRole('textbox');
|
||||
expect(input).toBeInTheDocument();
|
||||
@@ -243,12 +175,7 @@ describe('FormField', () => {
|
||||
const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
expect(() => {
|
||||
render(
|
||||
<FormField
|
||||
label="Email"
|
||||
name={undefined}
|
||||
/>
|
||||
);
|
||||
render(<FormField label="Email" name={undefined} />);
|
||||
}).toThrow('FormField: name must be provided either explicitly or via register()');
|
||||
|
||||
consoleError.mockRestore();
|
||||
@@ -257,12 +184,7 @@ describe('FormField', () => {
|
||||
|
||||
describe('Layout and Styling', () => {
|
||||
it('applies correct spacing classes', () => {
|
||||
const { container } = render(
|
||||
<FormField
|
||||
label="Email"
|
||||
name="email"
|
||||
/>
|
||||
);
|
||||
const { container } = render(<FormField label="Email" name="email" />);
|
||||
|
||||
const wrapper = container.firstChild as HTMLElement;
|
||||
expect(wrapper).toHaveClass('space-y-2');
|
||||
@@ -274,13 +196,7 @@ describe('FormField', () => {
|
||||
message: 'Email is required',
|
||||
};
|
||||
|
||||
render(
|
||||
<FormField
|
||||
label="Email"
|
||||
name="email"
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
render(<FormField label="Email" name="email" error={error} />);
|
||||
|
||||
const errorElement = screen.getByRole('alert');
|
||||
expect(errorElement).toHaveClass('text-sm', 'text-destructive');
|
||||
@@ -288,11 +204,7 @@ describe('FormField', () => {
|
||||
|
||||
it('applies correct description styling', () => {
|
||||
const { container } = render(
|
||||
<FormField
|
||||
label="Email"
|
||||
name="email"
|
||||
description="We'll never share your email"
|
||||
/>
|
||||
<FormField label="Email" name="email" description="We'll never share your email" />
|
||||
);
|
||||
|
||||
const description = container.querySelector('#email-description');
|
||||
|
||||
@@ -70,9 +70,7 @@ describe('useFormError', () => {
|
||||
it('handles API error with general error message', () => {
|
||||
const { result } = renderHook(() => useTestForm());
|
||||
|
||||
const apiError = [
|
||||
{ code: 'AUTH_001', message: 'Invalid credentials' },
|
||||
];
|
||||
const apiError = [{ code: 'AUTH_001', message: 'Invalid credentials' }];
|
||||
|
||||
act(() => {
|
||||
result.current.formError.handleFormError(apiError);
|
||||
@@ -98,9 +96,7 @@ describe('useFormError', () => {
|
||||
});
|
||||
|
||||
it('handles API errors with field-specific errors without crashing', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTestForm({ email: '', password: '', username: '' })
|
||||
);
|
||||
const { result } = renderHook(() => useTestForm({ email: '', password: '', username: '' }));
|
||||
|
||||
const apiError = [
|
||||
{ code: 'VAL_004', message: 'Email is required', field: 'email' },
|
||||
@@ -129,7 +125,9 @@ describe('useFormError', () => {
|
||||
result.current.formError.handleFormError(unexpectedError);
|
||||
});
|
||||
|
||||
expect(result.current.formError.serverError).toBe('An unexpected error occurred. Please try again.');
|
||||
expect(result.current.formError.serverError).toBe(
|
||||
'An unexpected error occurred. Please try again.'
|
||||
);
|
||||
});
|
||||
|
||||
it('handles string errors', () => {
|
||||
@@ -139,7 +137,9 @@ describe('useFormError', () => {
|
||||
result.current.formError.handleFormError('Some error string');
|
||||
});
|
||||
|
||||
expect(result.current.formError.serverError).toBe('An unexpected error occurred. Please try again.');
|
||||
expect(result.current.formError.serverError).toBe(
|
||||
'An unexpected error occurred. Please try again.'
|
||||
);
|
||||
});
|
||||
|
||||
it('handles null errors', () => {
|
||||
@@ -149,7 +149,9 @@ describe('useFormError', () => {
|
||||
result.current.formError.handleFormError(null);
|
||||
});
|
||||
|
||||
expect(result.current.formError.serverError).toBe('An unexpected error occurred. Please try again.');
|
||||
expect(result.current.formError.serverError).toBe(
|
||||
'An unexpected error occurred. Please try again.'
|
||||
);
|
||||
});
|
||||
|
||||
it('handles undefined errors', () => {
|
||||
@@ -159,7 +161,9 @@ describe('useFormError', () => {
|
||||
result.current.formError.handleFormError(undefined);
|
||||
});
|
||||
|
||||
expect(result.current.formError.serverError).toBe('An unexpected error occurred. Please try again.');
|
||||
expect(result.current.formError.serverError).toBe(
|
||||
'An unexpected error occurred. Please try again.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -179,9 +183,7 @@ describe('useFormError', () => {
|
||||
});
|
||||
|
||||
it('clears form errors', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTestForm({ email: '', password: '', username: '' })
|
||||
);
|
||||
const { result } = renderHook(() => useTestForm({ email: '', password: '', username: '' }));
|
||||
|
||||
// Set field errors
|
||||
act(() => {
|
||||
@@ -199,9 +201,7 @@ describe('useFormError', () => {
|
||||
});
|
||||
|
||||
it('clears both server and form errors', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTestForm({ email: '', password: '', username: '' })
|
||||
);
|
||||
const { result } = renderHook(() => useTestForm({ email: '', password: '', username: '' }));
|
||||
|
||||
act(() => {
|
||||
result.current.formError.setServerError('Server error');
|
||||
@@ -219,14 +219,10 @@ describe('useFormError', () => {
|
||||
|
||||
describe('Integration Scenarios', () => {
|
||||
it('handles typical login flow with API error', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTestForm({ email: '', password: '', username: '' })
|
||||
);
|
||||
const { result } = renderHook(() => useTestForm({ email: '', password: '', username: '' }));
|
||||
|
||||
// Simulate API error response
|
||||
const apiError = [
|
||||
{ code: 'AUTH_001', message: 'Invalid email or password' },
|
||||
];
|
||||
const apiError = [{ code: 'AUTH_001', message: 'Invalid email or password' }];
|
||||
|
||||
act(() => {
|
||||
result.current.formError.handleFormError(apiError);
|
||||
|
||||
@@ -26,36 +26,49 @@ jest.mock('next/link', () => ({
|
||||
|
||||
// Mock DemoCredentialsModal
|
||||
jest.mock('@/components/home/DemoCredentialsModal', () => ({
|
||||
DemoCredentialsModal: ({ open, onClose }: any) => (
|
||||
open ? <div data-testid="demo-modal">
|
||||
<button onClick={onClose}>Close Modal</button>
|
||||
</div> : null
|
||||
),
|
||||
DemoCredentialsModal: ({ open, onClose }: any) =>
|
||||
open ? (
|
||||
<div data-testid="demo-modal">
|
||||
<button onClick={onClose}>Close Modal</button>
|
||||
</div>
|
||||
) : null,
|
||||
}));
|
||||
|
||||
describe('CTASection', () => {
|
||||
it('renders main headline', () => {
|
||||
render(<CTASection onOpenDemoModal={function(): void {
|
||||
throw new Error("Function not implemented.");
|
||||
} } />);
|
||||
render(
|
||||
<CTASection
|
||||
onOpenDemoModal={function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText(/Start Building,/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Not Boilerplating/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders subtext with key messaging', () => {
|
||||
render(<CTASection onOpenDemoModal={function(): void {
|
||||
throw new Error("Function not implemented.");
|
||||
} } />);
|
||||
render(
|
||||
<CTASection
|
||||
onOpenDemoModal={function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText(/Clone the repository, read the docs/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Free forever, MIT licensed/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders GitHub CTA button', () => {
|
||||
render(<CTASection onOpenDemoModal={function(): void {
|
||||
throw new Error("Function not implemented.");
|
||||
} } />);
|
||||
render(
|
||||
<CTASection
|
||||
onOpenDemoModal={function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const githubLink = screen.getByRole('link', { name: /get started on github/i });
|
||||
expect(githubLink).toHaveAttribute('href', 'https://github.com/your-org/fast-next-template');
|
||||
@@ -64,29 +77,44 @@ describe('CTASection', () => {
|
||||
});
|
||||
|
||||
it('renders Try Live Demo button', () => {
|
||||
render(<CTASection onOpenDemoModal={function(): void {
|
||||
throw new Error("Function not implemented.");
|
||||
} } />);
|
||||
render(
|
||||
<CTASection
|
||||
onOpenDemoModal={function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const demoButton = screen.getByRole('button', { name: /try live demo/i });
|
||||
expect(demoButton).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders Read Documentation link', () => {
|
||||
render(<CTASection onOpenDemoModal={function(): void {
|
||||
throw new Error("Function not implemented.");
|
||||
} } />);
|
||||
render(
|
||||
<CTASection
|
||||
onOpenDemoModal={function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const docsLink = screen.getByRole('link', { name: /read documentation/i });
|
||||
expect(docsLink).toHaveAttribute('href', 'https://github.com/your-org/fast-next-template#documentation');
|
||||
expect(docsLink).toHaveAttribute(
|
||||
'href',
|
||||
'https://github.com/your-org/fast-next-template#documentation'
|
||||
);
|
||||
expect(docsLink).toHaveAttribute('target', '_blank');
|
||||
expect(docsLink).toHaveAttribute('rel', 'noopener noreferrer');
|
||||
});
|
||||
|
||||
it('renders help text with internal links', () => {
|
||||
render(<CTASection onOpenDemoModal={function(): void {
|
||||
throw new Error("Function not implemented.");
|
||||
} } />);
|
||||
render(
|
||||
<CTASection
|
||||
onOpenDemoModal={function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText(/Need help getting started\?/i)).toBeInTheDocument();
|
||||
|
||||
@@ -109,25 +137,33 @@ describe('CTASection', () => {
|
||||
|
||||
describe('Accessibility', () => {
|
||||
it('has proper external link attributes', () => {
|
||||
render(<CTASection onOpenDemoModal={function(): void {
|
||||
throw new Error("Function not implemented.");
|
||||
} } />);
|
||||
render(
|
||||
<CTASection
|
||||
onOpenDemoModal={function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const externalLinks = [
|
||||
screen.getByRole('link', { name: /get started on github/i }),
|
||||
screen.getByRole('link', { name: /read documentation/i }),
|
||||
];
|
||||
|
||||
externalLinks.forEach(link => {
|
||||
externalLinks.forEach((link) => {
|
||||
expect(link).toHaveAttribute('target', '_blank');
|
||||
expect(link).toHaveAttribute('rel', 'noopener noreferrer');
|
||||
});
|
||||
});
|
||||
|
||||
it('has descriptive button text', () => {
|
||||
render(<CTASection onOpenDemoModal={function(): void {
|
||||
throw new Error("Function not implemented.");
|
||||
} } />);
|
||||
render(
|
||||
<CTASection
|
||||
onOpenDemoModal={function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByRole('button', { name: /try live demo/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -65,14 +65,14 @@ describe('DemoCredentialsModal', () => {
|
||||
render(<DemoCredentialsModal open={true} onClose={mockOnClose} />);
|
||||
|
||||
const copyButtons = screen.getAllByRole('button');
|
||||
const regularCopyButton = copyButtons.find(btn => btn.textContent?.includes('Copy'));
|
||||
const regularCopyButton = copyButtons.find((btn) => btn.textContent?.includes('Copy'));
|
||||
|
||||
fireEvent.click(regularCopyButton!);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(navigator.clipboard.writeText).toHaveBeenCalledWith('demo@example.com\nDemo123!');
|
||||
const copiedButtons = screen.getAllByRole('button');
|
||||
const copiedButton = copiedButtons.find(btn => btn.textContent?.includes('Copied!'));
|
||||
const copiedButton = copiedButtons.find((btn) => btn.textContent?.includes('Copied!'));
|
||||
expect(copiedButton).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -81,14 +81,14 @@ describe('DemoCredentialsModal', () => {
|
||||
render(<DemoCredentialsModal open={true} onClose={mockOnClose} />);
|
||||
|
||||
const copyButtons = screen.getAllByRole('button');
|
||||
const adminCopyButton = copyButtons.filter(btn => btn.textContent?.includes('Copy'))[1];
|
||||
const adminCopyButton = copyButtons.filter((btn) => btn.textContent?.includes('Copy'))[1];
|
||||
|
||||
fireEvent.click(adminCopyButton!);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(navigator.clipboard.writeText).toHaveBeenCalledWith('admin@example.com\nAdmin123!');
|
||||
const copiedButtons = screen.getAllByRole('button');
|
||||
const copiedButton = copiedButtons.find(btn => btn.textContent?.includes('Copied!'));
|
||||
const copiedButton = copiedButtons.find((btn) => btn.textContent?.includes('Copied!'));
|
||||
expect(copiedButton).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -98,12 +98,12 @@ describe('DemoCredentialsModal', () => {
|
||||
render(<DemoCredentialsModal open={true} onClose={mockOnClose} />);
|
||||
|
||||
const copyButtons = screen.getAllByRole('button');
|
||||
const copyButton = copyButtons.find(btn => btn.textContent?.includes('Copy'));
|
||||
const copyButton = copyButtons.find((btn) => btn.textContent?.includes('Copy'));
|
||||
fireEvent.click(copyButton!);
|
||||
|
||||
await waitFor(() => {
|
||||
const copiedButtons = screen.getAllByRole('button');
|
||||
const copiedButton = copiedButtons.find(btn => btn.textContent?.includes('Copied!'));
|
||||
const copiedButton = copiedButtons.find((btn) => btn.textContent?.includes('Copied!'));
|
||||
expect(copiedButton).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -111,7 +111,7 @@ describe('DemoCredentialsModal', () => {
|
||||
|
||||
await waitFor(() => {
|
||||
const buttons = screen.getAllByRole('button');
|
||||
const copiedButton = buttons.find(btn => btn.textContent?.includes('Copied!'));
|
||||
const copiedButton = buttons.find((btn) => btn.textContent?.includes('Copied!'));
|
||||
expect(copiedButton).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -129,7 +129,7 @@ describe('DemoCredentialsModal', () => {
|
||||
render(<DemoCredentialsModal open={true} onClose={mockOnClose} />);
|
||||
|
||||
const copyButtons = screen.getAllByRole('button');
|
||||
const copyButton = copyButtons.find(btn => btn.textContent?.includes('Copy'));
|
||||
const copyButton = copyButtons.find((btn) => btn.textContent?.includes('Copy'));
|
||||
fireEvent.click(copyButton!);
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -144,8 +144,8 @@ describe('DemoCredentialsModal', () => {
|
||||
|
||||
// Find the "Close" button (filter to get the one that's visible and is the footer button)
|
||||
const closeButtons = screen.getAllByRole('button', { name: 'Close' });
|
||||
const footerCloseButton = closeButtons.find(btn =>
|
||||
btn.textContent === 'Close' && !btn.querySelector('.sr-only')
|
||||
const footerCloseButton = closeButtons.find(
|
||||
(btn) => btn.textContent === 'Close' && !btn.querySelector('.sr-only')
|
||||
);
|
||||
fireEvent.click(footerCloseButton!);
|
||||
|
||||
|
||||
@@ -19,27 +19,36 @@ jest.mock('next/link', () => ({
|
||||
|
||||
// Mock DemoCredentialsModal
|
||||
jest.mock('@/components/home/DemoCredentialsModal', () => ({
|
||||
DemoCredentialsModal: ({ open, onClose }: any) => (
|
||||
open ? <div data-testid="demo-modal">
|
||||
<button onClick={onClose}>Close Modal</button>
|
||||
</div> : null
|
||||
),
|
||||
DemoCredentialsModal: ({ open, onClose }: any) =>
|
||||
open ? (
|
||||
<div data-testid="demo-modal">
|
||||
<button onClick={onClose}>Close Modal</button>
|
||||
</div>
|
||||
) : null,
|
||||
}));
|
||||
|
||||
describe('Header', () => {
|
||||
it('renders logo', () => {
|
||||
render(<Header onOpenDemoModal={function(): void {
|
||||
throw new Error("Function not implemented.");
|
||||
} } />);
|
||||
render(
|
||||
<Header
|
||||
onOpenDemoModal={function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText('FastNext')).toBeInTheDocument();
|
||||
expect(screen.getByText('Template')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('logo links to homepage', () => {
|
||||
render(<Header onOpenDemoModal={function(): void {
|
||||
throw new Error("Function not implemented.");
|
||||
} } />);
|
||||
render(
|
||||
<Header
|
||||
onOpenDemoModal={function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const logoLink = screen.getByRole('link', { name: /fastnext template/i });
|
||||
expect(logoLink).toHaveAttribute('href', '/');
|
||||
@@ -47,42 +56,61 @@ describe('Header', () => {
|
||||
|
||||
describe('Desktop Navigation', () => {
|
||||
it('renders navigation links', () => {
|
||||
render(<Header onOpenDemoModal={function(): void {
|
||||
throw new Error("Function not implemented.");
|
||||
} } />);
|
||||
render(
|
||||
<Header
|
||||
onOpenDemoModal={function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByRole('link', { name: 'Components' })).toHaveAttribute('href', '/dev');
|
||||
expect(screen.getByRole('link', { name: 'Admin Demo' })).toHaveAttribute('href', '/admin');
|
||||
});
|
||||
|
||||
it('renders GitHub link with star badge', () => {
|
||||
render(<Header onOpenDemoModal={function(): void {
|
||||
throw new Error("Function not implemented.");
|
||||
} } />);
|
||||
render(
|
||||
<Header
|
||||
onOpenDemoModal={function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const githubLinks = screen.getAllByRole('link', { name: /github/i });
|
||||
const desktopGithubLink = githubLinks.find(link =>
|
||||
const desktopGithubLink = githubLinks.find((link) =>
|
||||
link.getAttribute('href')?.includes('github.com')
|
||||
);
|
||||
|
||||
expect(desktopGithubLink).toHaveAttribute('href', 'https://github.com/your-org/fast-next-template');
|
||||
expect(desktopGithubLink).toHaveAttribute(
|
||||
'href',
|
||||
'https://github.com/your-org/fast-next-template'
|
||||
);
|
||||
expect(desktopGithubLink).toHaveAttribute('target', '_blank');
|
||||
expect(desktopGithubLink).toHaveAttribute('rel', 'noopener noreferrer');
|
||||
});
|
||||
|
||||
it('renders Try Demo button', () => {
|
||||
render(<Header onOpenDemoModal={function(): void {
|
||||
throw new Error("Function not implemented.");
|
||||
} } />);
|
||||
render(
|
||||
<Header
|
||||
onOpenDemoModal={function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const demoButton = screen.getByRole('button', { name: /try demo/i });
|
||||
expect(demoButton).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders Login button', () => {
|
||||
render(<Header onOpenDemoModal={function(): void {
|
||||
throw new Error("Function not implemented.");
|
||||
} } />);
|
||||
render(
|
||||
<Header
|
||||
onOpenDemoModal={function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const loginLinks = screen.getAllByRole('link', { name: /login/i });
|
||||
expect(loginLinks.length).toBeGreaterThan(0);
|
||||
@@ -102,9 +130,13 @@ describe('Header', () => {
|
||||
|
||||
describe('Mobile Menu', () => {
|
||||
it('renders mobile menu toggle button', () => {
|
||||
render(<Header onOpenDemoModal={function(): void {
|
||||
throw new Error("Function not implemented.");
|
||||
} } />);
|
||||
render(
|
||||
<Header
|
||||
onOpenDemoModal={function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
// SheetTrigger wraps the button, so we need to find it by aria-label
|
||||
const menuButton = screen.getByRole('button', { name: /toggle menu/i });
|
||||
@@ -112,9 +144,13 @@ describe('Header', () => {
|
||||
});
|
||||
|
||||
it('mobile menu contains navigation links', () => {
|
||||
render(<Header onOpenDemoModal={function(): void {
|
||||
throw new Error("Function not implemented.");
|
||||
} } />);
|
||||
render(
|
||||
<Header
|
||||
onOpenDemoModal={function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
// Note: SheetContent is hidden by default in tests, but we can verify the links exist
|
||||
// The actual mobile menu behavior is tested in E2E tests
|
||||
@@ -123,33 +159,44 @@ describe('Header', () => {
|
||||
});
|
||||
|
||||
it('mobile menu contains GitHub link', () => {
|
||||
render(<Header onOpenDemoModal={function(): void {
|
||||
throw new Error("Function not implemented.");
|
||||
} } />);
|
||||
render(
|
||||
<Header
|
||||
onOpenDemoModal={function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const githubLinks = screen.getAllByRole('link', { name: /github/i });
|
||||
expect(githubLinks.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('Accessibility', () => {
|
||||
it('has proper ARIA labels for icon buttons', () => {
|
||||
render(<Header onOpenDemoModal={function(): void {
|
||||
throw new Error("Function not implemented.");
|
||||
} } />);
|
||||
render(
|
||||
<Header
|
||||
onOpenDemoModal={function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const menuButton = screen.getByRole('button', { name: /toggle menu/i });
|
||||
expect(menuButton).toHaveAccessibleName();
|
||||
});
|
||||
|
||||
it('has proper external link attributes', () => {
|
||||
render(<Header onOpenDemoModal={function(): void {
|
||||
throw new Error("Function not implemented.");
|
||||
} } />);
|
||||
render(
|
||||
<Header
|
||||
onOpenDemoModal={function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const githubLinks = screen.getAllByRole('link', { name: /github/i });
|
||||
const externalLink = githubLinks.find(link =>
|
||||
const externalLink = githubLinks.find((link) =>
|
||||
link.getAttribute('href')?.includes('github.com')
|
||||
);
|
||||
|
||||
|
||||
@@ -28,18 +28,23 @@ jest.mock('next/link', () => ({
|
||||
|
||||
// Mock DemoCredentialsModal
|
||||
jest.mock('@/components/home/DemoCredentialsModal', () => ({
|
||||
DemoCredentialsModal: ({ open, onClose }: any) => (
|
||||
open ? <div data-testid="demo-modal">
|
||||
<button onClick={onClose}>Close Modal</button>
|
||||
</div> : null
|
||||
),
|
||||
DemoCredentialsModal: ({ open, onClose }: any) =>
|
||||
open ? (
|
||||
<div data-testid="demo-modal">
|
||||
<button onClick={onClose}>Close Modal</button>
|
||||
</div>
|
||||
) : null,
|
||||
}));
|
||||
|
||||
describe('HeroSection', () => {
|
||||
it('renders badge with key highlights', () => {
|
||||
render(<HeroSection onOpenDemoModal={function(): void {
|
||||
throw new Error("Function not implemented.");
|
||||
} } />);
|
||||
render(
|
||||
<HeroSection
|
||||
onOpenDemoModal={function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText('MIT Licensed')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('97% Test Coverage')[0]).toBeInTheDocument();
|
||||
@@ -47,36 +52,52 @@ describe('HeroSection', () => {
|
||||
});
|
||||
|
||||
it('renders main headline', () => {
|
||||
render(<HeroSection onOpenDemoModal={function(): void {
|
||||
throw new Error("Function not implemented.");
|
||||
} } />);
|
||||
render(
|
||||
<HeroSection
|
||||
onOpenDemoModal={function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getAllByText(/Everything You Need to Build/i)[0]).toBeInTheDocument();
|
||||
expect(screen.getAllByText(/Modern Web Applications/i)[0]).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders subheadline with key messaging', () => {
|
||||
render(<HeroSection onOpenDemoModal={function(): void {
|
||||
throw new Error("Function not implemented.");
|
||||
} } />);
|
||||
render(
|
||||
<HeroSection
|
||||
onOpenDemoModal={function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText(/Production-ready FastAPI \+ Next.js template/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Start building features on day one/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders Try Live Demo button', () => {
|
||||
render(<HeroSection onOpenDemoModal={function(): void {
|
||||
throw new Error("Function not implemented.");
|
||||
} } />);
|
||||
render(
|
||||
<HeroSection
|
||||
onOpenDemoModal={function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const demoButton = screen.getByRole('button', { name: /try live demo/i });
|
||||
expect(demoButton).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders View on GitHub link', () => {
|
||||
render(<HeroSection onOpenDemoModal={function(): void {
|
||||
throw new Error("Function not implemented.");
|
||||
} } />);
|
||||
render(
|
||||
<HeroSection
|
||||
onOpenDemoModal={function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const githubLink = screen.getByRole('link', { name: /view on github/i });
|
||||
expect(githubLink).toHaveAttribute('href', 'https://github.com/your-org/fast-next-template');
|
||||
@@ -85,18 +106,26 @@ describe('HeroSection', () => {
|
||||
});
|
||||
|
||||
it('renders Explore Components link', () => {
|
||||
render(<HeroSection onOpenDemoModal={function(): void {
|
||||
throw new Error("Function not implemented.");
|
||||
} } />);
|
||||
render(
|
||||
<HeroSection
|
||||
onOpenDemoModal={function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const componentsLink = screen.getByRole('link', { name: /explore components/i });
|
||||
expect(componentsLink).toHaveAttribute('href', '/dev');
|
||||
});
|
||||
|
||||
it('displays test coverage stats', () => {
|
||||
render(<HeroSection onOpenDemoModal={function(): void {
|
||||
throw new Error("Function not implemented.");
|
||||
} } />);
|
||||
render(
|
||||
<HeroSection
|
||||
onOpenDemoModal={function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const coverageTexts = screen.getAllByText('97%');
|
||||
expect(coverageTexts.length).toBeGreaterThan(0);
|
||||
@@ -121,18 +150,26 @@ describe('HeroSection', () => {
|
||||
|
||||
describe('Accessibility', () => {
|
||||
it('has proper heading hierarchy', () => {
|
||||
render(<HeroSection onOpenDemoModal={function(): void {
|
||||
throw new Error("Function not implemented.");
|
||||
} } />);
|
||||
render(
|
||||
<HeroSection
|
||||
onOpenDemoModal={function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const heading = screen.getAllByRole('heading', { level: 1 })[0];
|
||||
expect(heading).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('has proper external link attributes', () => {
|
||||
render(<HeroSection onOpenDemoModal={function(): void {
|
||||
throw new Error("Function not implemented.");
|
||||
} } />);
|
||||
render(
|
||||
<HeroSection
|
||||
onOpenDemoModal={function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const githubLink = screen.getByRole('link', { name: /view on github/i });
|
||||
expect(githubLink).toHaveAttribute('target', '_blank');
|
||||
|
||||
@@ -44,7 +44,9 @@ describe('StatsSection', () => {
|
||||
it('displays stat descriptions', () => {
|
||||
render(<StatsSection />);
|
||||
|
||||
expect(screen.getByText(/Comprehensive testing across backend and frontend/i)).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(/Comprehensive testing across backend and frontend/i)
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText(/Backend, frontend unit, and E2E tests/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Production-stable test suite/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Fully documented with OpenAPI/i)).toBeInTheDocument();
|
||||
@@ -100,14 +102,9 @@ describe('StatsSection', () => {
|
||||
it('has descriptive labels for stats', () => {
|
||||
render(<StatsSection />);
|
||||
|
||||
const statLabels = [
|
||||
'Test Coverage',
|
||||
'Passing Tests',
|
||||
'Flaky Tests',
|
||||
'API Endpoints',
|
||||
];
|
||||
const statLabels = ['Test Coverage', 'Passing Tests', 'Flaky Tests', 'API Endpoints'];
|
||||
|
||||
statLabels.forEach(label => {
|
||||
statLabels.forEach((label) => {
|
||||
expect(screen.getByText(label)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,7 +19,9 @@ describe('Footer', () => {
|
||||
render(<Footer />);
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
expect(screen.getByText(`© ${currentYear} FastNext Template. All rights reserved.`)).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(`© ${currentYear} FastNext Template. All rights reserved.`)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('applies correct styling classes', () => {
|
||||
|
||||
@@ -151,9 +151,7 @@ describe('Header', () => {
|
||||
|
||||
const adminLinks = screen.queryAllByRole('link', { name: /admin/i });
|
||||
// Filter out the one in the dropdown menu
|
||||
const navAdminLinks = adminLinks.filter(
|
||||
(link) => !link.closest('[role="menu"]')
|
||||
);
|
||||
const navAdminLinks = adminLinks.filter((link) => !link.closest('[role="menu"]'));
|
||||
expect(navAdminLinks).toHaveLength(0);
|
||||
});
|
||||
|
||||
|
||||
@@ -121,7 +121,9 @@ describe('PasswordChangeForm', () => {
|
||||
it('allows typing in confirm password field', async () => {
|
||||
renderWithProvider(<PasswordChangeForm />);
|
||||
|
||||
const confirmPasswordInput = screen.getByLabelText(/confirm new password/i) as HTMLInputElement;
|
||||
const confirmPasswordInput = screen.getByLabelText(
|
||||
/confirm new password/i
|
||||
) as HTMLInputElement;
|
||||
await user.type(confirmPasswordInput, 'NewPassword123!');
|
||||
|
||||
expect(confirmPasswordInput.value).toBe('NewPassword123!');
|
||||
|
||||
@@ -65,11 +65,7 @@ describe('ProfileSettingsForm', () => {
|
||||
});
|
||||
|
||||
const renderWithProvider = (component: React.ReactElement) => {
|
||||
return render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{component}
|
||||
</QueryClientProvider>
|
||||
);
|
||||
return render(<QueryClientProvider client={queryClient}>{component}</QueryClientProvider>);
|
||||
};
|
||||
|
||||
describe('Rendering', () => {
|
||||
|
||||
@@ -134,9 +134,7 @@ describe('SessionCard', () => {
|
||||
});
|
||||
|
||||
it('highlights current session with border', () => {
|
||||
const { container } = render(
|
||||
<SessionCard session={currentSession} onRevoke={mockOnRevoke} />
|
||||
);
|
||||
const { container } = render(<SessionCard session={currentSession} onRevoke={mockOnRevoke} />);
|
||||
|
||||
const card = container.querySelector('.border-primary');
|
||||
expect(card).toBeInTheDocument();
|
||||
|
||||
@@ -123,9 +123,7 @@ describe('SessionsManager', () => {
|
||||
|
||||
renderWithProvider(<SessionsManager />);
|
||||
|
||||
expect(
|
||||
screen.queryByRole('button', { name: /revoke all others/i })
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: /revoke all others/i })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('opens bulk revoke dialog', async () => {
|
||||
@@ -161,7 +159,7 @@ describe('SessionsManager', () => {
|
||||
|
||||
// Find the destructive button in the dialog (not the "Cancel" button)
|
||||
const buttons = screen.getAllByRole('button');
|
||||
const confirmButton = buttons.find(btn => btn.textContent === 'Revoke All Others');
|
||||
const confirmButton = buttons.find((btn) => btn.textContent === 'Revoke All Others');
|
||||
|
||||
if (confirmButton) {
|
||||
fireEvent.click(confirmButton);
|
||||
@@ -214,9 +212,7 @@ describe('SessionsManager', () => {
|
||||
|
||||
renderWithProvider(<SessionsManager />);
|
||||
|
||||
expect(
|
||||
screen.getByText(/you're viewing your only active session/i)
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText(/you're viewing your only active session/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows security tip', () => {
|
||||
@@ -229,9 +225,7 @@ describe('SessionsManager', () => {
|
||||
renderWithProvider(<SessionsManager />);
|
||||
|
||||
expect(screen.getByText(/security tip/i)).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(/if you see a session you don't recognize/i)
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText(/if you see a session you don't recognize/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('closes bulk revoke dialog on cancel', async () => {
|
||||
|
||||
@@ -182,5 +182,4 @@ describe('ThemeToggle', () => {
|
||||
expect(systemOption).toHaveTextContent('✓');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -22,14 +22,16 @@ describe('usePrefersReducedMotion', () => {
|
||||
mockListeners.push(listener);
|
||||
}
|
||||
}),
|
||||
removeEventListener: jest.fn((event: string, listener: (event: MediaQueryListEvent) => void) => {
|
||||
if (event === 'change') {
|
||||
const index = mockListeners.indexOf(listener);
|
||||
if (index > -1) {
|
||||
mockListeners.splice(index, 1);
|
||||
removeEventListener: jest.fn(
|
||||
(event: string, listener: (event: MediaQueryListEvent) => void) => {
|
||||
if (event === 'change') {
|
||||
const index = mockListeners.indexOf(listener);
|
||||
if (index > -1) {
|
||||
mockListeners.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
),
|
||||
dispatchEvent: jest.fn(),
|
||||
}));
|
||||
|
||||
@@ -96,7 +98,7 @@ describe('usePrefersReducedMotion', () => {
|
||||
|
||||
// Simulate media query change
|
||||
act(() => {
|
||||
mockListeners.forEach(listener => {
|
||||
mockListeners.forEach((listener) => {
|
||||
listener({ matches: true } as MediaQueryListEvent);
|
||||
});
|
||||
});
|
||||
@@ -126,7 +128,7 @@ describe('usePrefersReducedMotion', () => {
|
||||
|
||||
// Simulate media query change
|
||||
act(() => {
|
||||
mockListeners.forEach(listener => {
|
||||
mockListeners.forEach((listener) => {
|
||||
listener({ matches: false } as MediaQueryListEvent);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -56,9 +56,7 @@ describe('API Error Handling', () => {
|
||||
const errorsWithField: APIError[] = [
|
||||
{ code: 'VAL_001', message: 'Invalid input', field: 'email' },
|
||||
];
|
||||
const errorsWithoutField: APIError[] = [
|
||||
{ code: 'AUTH_001', message: 'Invalid credentials' },
|
||||
];
|
||||
const errorsWithoutField: APIError[] = [{ code: 'AUTH_001', message: 'Invalid credentials' }];
|
||||
|
||||
expect(isAPIErrorArray(errorsWithField)).toBe(true);
|
||||
expect(isAPIErrorArray(errorsWithoutField)).toBe(true);
|
||||
@@ -110,9 +108,7 @@ describe('API Error Handling', () => {
|
||||
|
||||
it('should handle strings', () => {
|
||||
const result = parseAPIError('some error string');
|
||||
expect(result).toEqual([
|
||||
{ code: 'UNKNOWN', message: ERROR_MESSAGES['UNKNOWN'] },
|
||||
]);
|
||||
expect(result).toEqual([{ code: 'UNKNOWN', message: ERROR_MESSAGES['UNKNOWN'] }]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -448,9 +444,7 @@ describe('API Error Handling', () => {
|
||||
response: {
|
||||
status: 401,
|
||||
data: {
|
||||
errors: [
|
||||
{ code: 'CUSTOM_ERROR', message: 'Custom structured error' },
|
||||
],
|
||||
errors: [{ code: 'CUSTOM_ERROR', message: 'Custom structured error' }],
|
||||
},
|
||||
statusText: 'Unauthorized',
|
||||
headers: {},
|
||||
@@ -461,9 +455,7 @@ describe('API Error Handling', () => {
|
||||
const result = parseAPIError(axiosError);
|
||||
|
||||
// Should return structured error, not the 401 default
|
||||
expect(result).toEqual([
|
||||
{ code: 'CUSTOM_ERROR', message: 'Custom structured error' },
|
||||
]);
|
||||
expect(result).toEqual([{ code: 'CUSTOM_ERROR', message: 'Custom structured error' }]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -554,9 +546,7 @@ describe('API Error Handling', () => {
|
||||
});
|
||||
|
||||
it('should use error code message if message is missing', () => {
|
||||
const errors: APIError[] = [
|
||||
{ code: 'VAL_002', message: '', field: 'email' },
|
||||
];
|
||||
const errors: APIError[] = [{ code: 'VAL_002', message: '', field: 'email' }];
|
||||
|
||||
const result = getFieldErrors(errors);
|
||||
|
||||
@@ -605,9 +595,7 @@ describe('API Error Handling', () => {
|
||||
});
|
||||
|
||||
it('should use error code message if message is missing', () => {
|
||||
const errors: APIError[] = [
|
||||
{ code: 'AUTH_001', message: '' },
|
||||
];
|
||||
const errors: APIError[] = [{ code: 'AUTH_001', message: '' }];
|
||||
|
||||
const result = getGeneralError(errors);
|
||||
|
||||
|
||||
@@ -34,7 +34,9 @@ jest.mock('@/lib/api/client');
|
||||
jest.mock('@/lib/auth/AuthContext');
|
||||
|
||||
const mockAdminListUsers = adminListUsers as jest.MockedFunction<typeof adminListUsers>;
|
||||
const mockAdminListOrganizations = adminListOrganizations as jest.MockedFunction<typeof adminListOrganizations>;
|
||||
const mockAdminListOrganizations = adminListOrganizations as jest.MockedFunction<
|
||||
typeof adminListOrganizations
|
||||
>;
|
||||
const mockAdminListSessions = adminListSessions as jest.MockedFunction<typeof adminListSessions>;
|
||||
const mockUseAuth = useAuth as jest.MockedFunction<typeof useAuth>;
|
||||
|
||||
@@ -53,19 +55,13 @@ describe('useAdmin hooks', () => {
|
||||
});
|
||||
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{children}
|
||||
</QueryClientProvider>
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
);
|
||||
|
||||
describe('useAdminStats', () => {
|
||||
const mockUsersData = {
|
||||
data: {
|
||||
data: [
|
||||
{ is_active: true },
|
||||
{ is_active: true },
|
||||
{ is_active: false },
|
||||
],
|
||||
data: [{ is_active: true }, { is_active: true }, { is_active: false }],
|
||||
pagination: { total: 3, page: 1, limit: 10000 },
|
||||
},
|
||||
};
|
||||
@@ -358,7 +354,7 @@ describe('useAdmin hooks', () => {
|
||||
limit: 20,
|
||||
search: 'admin',
|
||||
is_active: true,
|
||||
is_superuser: false
|
||||
is_superuser: false,
|
||||
},
|
||||
throwOnError: false,
|
||||
});
|
||||
@@ -476,7 +472,15 @@ describe('useAdmin hooks', () => {
|
||||
it('creates a user successfully', async () => {
|
||||
const mockCreateUser = adminCreateUser as jest.MockedFunction<typeof adminCreateUser>;
|
||||
mockCreateUser.mockResolvedValue({
|
||||
data: { id: '1', email: 'newuser@example.com', first_name: 'New', last_name: 'User', is_active: true, is_superuser: false, created_at: '2025-01-01T00:00:00Z' },
|
||||
data: {
|
||||
id: '1',
|
||||
email: 'newuser@example.com',
|
||||
first_name: 'New',
|
||||
last_name: 'User',
|
||||
is_active: true,
|
||||
is_superuser: false,
|
||||
created_at: '2025-01-01T00:00:00Z',
|
||||
},
|
||||
} as any);
|
||||
|
||||
const { result } = renderHook(() => useCreateUser(), { wrapper });
|
||||
@@ -524,7 +528,15 @@ describe('useAdmin hooks', () => {
|
||||
it('updates a user successfully', async () => {
|
||||
const mockUpdateUser = adminUpdateUser as jest.MockedFunction<typeof adminUpdateUser>;
|
||||
mockUpdateUser.mockResolvedValue({
|
||||
data: { id: '1', email: 'updated@example.com', first_name: 'Updated', last_name: 'User', is_active: true, is_superuser: false, created_at: '2025-01-01T00:00:00Z' },
|
||||
data: {
|
||||
id: '1',
|
||||
email: 'updated@example.com',
|
||||
first_name: 'Updated',
|
||||
last_name: 'User',
|
||||
is_active: true,
|
||||
is_superuser: false,
|
||||
created_at: '2025-01-01T00:00:00Z',
|
||||
},
|
||||
} as any);
|
||||
|
||||
const { result } = renderHook(() => useUpdateUser(), { wrapper });
|
||||
@@ -618,7 +630,9 @@ describe('useAdmin hooks', () => {
|
||||
|
||||
describe('useDeactivateUser', () => {
|
||||
it('deactivates a user successfully', async () => {
|
||||
const mockDeactivateUser = adminDeactivateUser as jest.MockedFunction<typeof adminDeactivateUser>;
|
||||
const mockDeactivateUser = adminDeactivateUser as jest.MockedFunction<
|
||||
typeof adminDeactivateUser
|
||||
>;
|
||||
mockDeactivateUser.mockResolvedValue({ data: { success: true } } as any);
|
||||
|
||||
const { result } = renderHook(() => useDeactivateUser(), { wrapper });
|
||||
@@ -634,7 +648,9 @@ describe('useAdmin hooks', () => {
|
||||
});
|
||||
|
||||
it('handles deactivate error', async () => {
|
||||
const mockDeactivateUser = adminDeactivateUser as jest.MockedFunction<typeof adminDeactivateUser>;
|
||||
const mockDeactivateUser = adminDeactivateUser as jest.MockedFunction<
|
||||
typeof adminDeactivateUser
|
||||
>;
|
||||
mockDeactivateUser.mockResolvedValue({ error: 'Deactivate failed' } as any);
|
||||
|
||||
const { result } = renderHook(() => useDeactivateUser(), { wrapper });
|
||||
|
||||
@@ -6,11 +6,7 @@
|
||||
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import {
|
||||
useIsAuthenticated,
|
||||
useCurrentUser,
|
||||
useIsAdmin,
|
||||
} from '@/lib/api/hooks/useAuth';
|
||||
import { useIsAuthenticated, useCurrentUser, useIsAdmin } from '@/lib/api/hooks/useAuth';
|
||||
import { AuthProvider } from '@/lib/auth/AuthContext';
|
||||
|
||||
// Mock auth state (Context-injected)
|
||||
@@ -52,9 +48,7 @@ const createWrapper = () => {
|
||||
|
||||
return ({ children }: { children: React.ReactNode }) => (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AuthProvider store={mockStoreHook}>
|
||||
{children}
|
||||
</AuthProvider>
|
||||
<AuthProvider store={mockStoreHook}>{children}</AuthProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -59,9 +59,7 @@ describe('useSession hooks', () => {
|
||||
});
|
||||
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{children}
|
||||
</QueryClientProvider>
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
);
|
||||
|
||||
describe('useListSessions', () => {
|
||||
|
||||
@@ -33,9 +33,7 @@ describe('useUser hooks', () => {
|
||||
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AuthProvider>
|
||||
{children}
|
||||
</AuthProvider>
|
||||
<AuthProvider>{children}</AuthProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
|
||||
@@ -130,7 +130,9 @@ describe('Storage Module', () => {
|
||||
};
|
||||
|
||||
// When setItem throws, isLocalStorageAvailable() returns false
|
||||
await expect(saveTokens(tokens)).rejects.toThrow('localStorage not available - cannot save tokens');
|
||||
await expect(saveTokens(tokens)).rejects.toThrow(
|
||||
'localStorage not available - cannot save tokens'
|
||||
);
|
||||
|
||||
Storage.prototype.setItem = originalSetItem;
|
||||
});
|
||||
@@ -171,7 +173,6 @@ describe('Storage Module', () => {
|
||||
|
||||
localStorage.removeItem = originalRemoveItem;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Storage method handling', () => {
|
||||
|
||||
@@ -51,11 +51,7 @@ describe('Auth Store', () => {
|
||||
const invalidUser = createMockUser({ id: '' });
|
||||
|
||||
await expect(
|
||||
useAuthStore.getState().setAuth(
|
||||
invalidUser,
|
||||
'valid.access.token',
|
||||
'valid.refresh.token'
|
||||
)
|
||||
useAuthStore.getState().setAuth(invalidUser, 'valid.access.token', 'valid.refresh.token')
|
||||
).rejects.toThrow('Invalid user object');
|
||||
});
|
||||
|
||||
@@ -63,11 +59,7 @@ describe('Auth Store', () => {
|
||||
const invalidUser = createMockUser({ id: ' ' });
|
||||
|
||||
await expect(
|
||||
useAuthStore.getState().setAuth(
|
||||
invalidUser,
|
||||
'valid.access.token',
|
||||
'valid.refresh.token'
|
||||
)
|
||||
useAuthStore.getState().setAuth(invalidUser, 'valid.access.token', 'valid.refresh.token')
|
||||
).rejects.toThrow('Invalid user object');
|
||||
});
|
||||
|
||||
@@ -75,11 +67,7 @@ describe('Auth Store', () => {
|
||||
const invalidUser = createMockUser({ email: '' });
|
||||
|
||||
await expect(
|
||||
useAuthStore.getState().setAuth(
|
||||
invalidUser,
|
||||
'valid.access.token',
|
||||
'valid.refresh.token'
|
||||
)
|
||||
useAuthStore.getState().setAuth(invalidUser, 'valid.access.token', 'valid.refresh.token')
|
||||
).rejects.toThrow('Invalid user object');
|
||||
});
|
||||
|
||||
@@ -101,11 +89,9 @@ describe('Auth Store', () => {
|
||||
(storage.saveTokens as jest.Mock).mockResolvedValue(undefined);
|
||||
|
||||
await expect(
|
||||
useAuthStore.getState().setAuth(
|
||||
validUser,
|
||||
'header.payload.signature',
|
||||
'header.payload.signature'
|
||||
)
|
||||
useAuthStore
|
||||
.getState()
|
||||
.setAuth(validUser, 'header.payload.signature', 'header.payload.signature')
|
||||
).resolves.not.toThrow();
|
||||
|
||||
const state = useAuthStore.getState();
|
||||
@@ -145,11 +131,9 @@ describe('Auth Store', () => {
|
||||
(storage.saveTokens as jest.Mock).mockResolvedValue(undefined);
|
||||
|
||||
await expect(
|
||||
useAuthStore.getState().setAuth(
|
||||
validUser,
|
||||
'header.payload.signature',
|
||||
'header.payload.signature'
|
||||
)
|
||||
useAuthStore
|
||||
.getState()
|
||||
.setAuth(validUser, 'header.payload.signature', 'header.payload.signature')
|
||||
).resolves.not.toThrow();
|
||||
});
|
||||
});
|
||||
@@ -264,11 +248,9 @@ describe('Auth Store', () => {
|
||||
// First set auth
|
||||
const validUser = createMockUser();
|
||||
|
||||
await useAuthStore.getState().setAuth(
|
||||
validUser,
|
||||
'header.payload.signature',
|
||||
'header.payload.signature'
|
||||
);
|
||||
await useAuthStore
|
||||
.getState()
|
||||
.setAuth(validUser, 'header.payload.signature', 'header.payload.signature');
|
||||
|
||||
expect(useAuthStore.getState().isAuthenticated).toBe(true);
|
||||
|
||||
@@ -298,20 +280,14 @@ describe('Auth Store', () => {
|
||||
describe('setTokens', () => {
|
||||
it('should update tokens while preserving user state', async () => {
|
||||
// First set initial auth with user
|
||||
await useAuthStore.getState().setAuth(
|
||||
createMockUser({ id: 'user-1' }),
|
||||
'old.access.token',
|
||||
'old.refresh.token'
|
||||
);
|
||||
await useAuthStore
|
||||
.getState()
|
||||
.setAuth(createMockUser({ id: 'user-1' }), 'old.access.token', 'old.refresh.token');
|
||||
|
||||
const oldUser = useAuthStore.getState().user;
|
||||
|
||||
// Now update just the tokens
|
||||
await useAuthStore.getState().setTokens(
|
||||
'new.access.token',
|
||||
'new.refresh.token',
|
||||
900
|
||||
);
|
||||
await useAuthStore.getState().setTokens('new.access.token', 'new.refresh.token', 900);
|
||||
|
||||
const state = useAuthStore.getState();
|
||||
expect(state.accessToken).toBe('new.access.token');
|
||||
@@ -344,16 +320,18 @@ describe('Auth Store', () => {
|
||||
describe('setUser', () => {
|
||||
it('should update user while preserving auth state', async () => {
|
||||
// First set initial auth
|
||||
await useAuthStore.getState().setAuth(
|
||||
createMockUser({ id: 'user-1' }),
|
||||
'valid.access.token',
|
||||
'valid.refresh.token'
|
||||
);
|
||||
await useAuthStore
|
||||
.getState()
|
||||
.setAuth(createMockUser({ id: 'user-1' }), 'valid.access.token', 'valid.refresh.token');
|
||||
|
||||
const oldToken = useAuthStore.getState().accessToken;
|
||||
|
||||
// Update just the user
|
||||
const newUser = createMockUser({ id: 'user-1', email: 'updated@example.com', is_superuser: true });
|
||||
const newUser = createMockUser({
|
||||
id: 'user-1',
|
||||
email: 'updated@example.com',
|
||||
is_superuser: true,
|
||||
});
|
||||
useAuthStore.getState().setUser(newUser);
|
||||
|
||||
const state = useAuthStore.getState();
|
||||
@@ -478,18 +456,11 @@ describe('Auth Store', () => {
|
||||
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
|
||||
|
||||
await expect(
|
||||
useAuthStore.getState().setAuth(
|
||||
mockUser,
|
||||
'valid.access.token',
|
||||
'valid.refresh.token'
|
||||
)
|
||||
useAuthStore.getState().setAuth(mockUser, 'valid.access.token', 'valid.refresh.token')
|
||||
).rejects.toThrow('Storage error');
|
||||
|
||||
// Verify error was logged before throwing
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||
'Failed to save auth state:',
|
||||
expect.any(Error)
|
||||
);
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith('Failed to save auth state:', expect.any(Error));
|
||||
|
||||
consoleErrorSpy.mockRestore();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user