Files
syndarix/frontend/tests/components/admin/organizations/OrganizationActionMenu.test.tsx
Felipe Cardoso 2696f44198 Add unit tests for Organization Management components
- Added tests for `OrganizationListTable`, covering rendering, pagination, loading, and empty states.
- Introduced `OrganizationManagementContent` tests to evaluate orchestration, state management, and URL synchronization.
- Implemented tests for `OrganizationActionMenu`, focusing on dropdown actions, edit, delete, and view member flows.
- Improved test coverage and reliability for organization management features.
2025-11-06 20:48:10 +01:00

433 lines
12 KiB
TypeScript

/**
* Tests for OrganizationActionMenu Component
* Verifies dropdown menu actions and delete confirmation dialog
*/
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 { toast } from 'sonner';
// Mock dependencies
jest.mock('@/lib/api/hooks/useAdmin', () => ({
useDeleteOrganization: jest.fn(),
}));
jest.mock('sonner', () => ({
toast: {
success: jest.fn(),
error: jest.fn(),
},
}));
const mockUseDeleteOrganization = useDeleteOrganization as jest.MockedFunction<
typeof useDeleteOrganization
>;
describe('OrganizationActionMenu', () => {
const mockOrganization: Organization = {
id: '1',
name: 'Acme Corporation',
slug: 'acme-corporation',
description: 'Leading provider',
is_active: true,
created_at: '2025-01-01T00:00:00Z',
updated_at: '2025-01-01T00:00:00Z',
member_count: 10,
};
const mockDeleteMutate = jest.fn();
beforeEach(() => {
jest.clearAllMocks();
mockUseDeleteOrganization.mockReturnValue({
mutateAsync: mockDeleteMutate,
isPending: false,
} as any);
mockDeleteMutate.mockResolvedValue({});
});
describe('Menu Rendering', () => {
it('renders menu trigger button', () => {
render(
<OrganizationActionMenu
organization={mockOrganization}
onEdit={jest.fn()}
onViewMembers={jest.fn()}
/>
);
const menuButton = screen.getByRole('button', {
name: 'Actions for Acme Corporation',
});
expect(menuButton).toBeInTheDocument();
});
it('shows menu items when opened', async () => {
const user = userEvent.setup();
render(
<OrganizationActionMenu
organization={mockOrganization}
onEdit={jest.fn()}
onViewMembers={jest.fn()}
/>
);
const menuButton = screen.getByRole('button', {
name: 'Actions for Acme Corporation',
});
await user.click(menuButton);
expect(screen.getByText('Edit Organization')).toBeInTheDocument();
expect(screen.getByText('View Members')).toBeInTheDocument();
expect(screen.getByText('Delete Organization')).toBeInTheDocument();
});
});
describe('Edit Action', () => {
it('calls onEdit when edit is clicked', async () => {
const user = userEvent.setup();
const mockOnEdit = jest.fn();
render(
<OrganizationActionMenu
organization={mockOrganization}
onEdit={mockOnEdit}
onViewMembers={jest.fn()}
/>
);
const menuButton = screen.getByRole('button', {
name: 'Actions for Acme Corporation',
});
await user.click(menuButton);
const editButton = screen.getByText('Edit Organization');
await user.click(editButton);
expect(mockOnEdit).toHaveBeenCalledWith(mockOrganization);
});
it('does not call onEdit when handler is undefined', async () => {
const user = userEvent.setup();
render(
<OrganizationActionMenu
organization={mockOrganization}
onEdit={undefined}
onViewMembers={jest.fn()}
/>
);
const menuButton = screen.getByRole('button', {
name: 'Actions for Acme Corporation',
});
await user.click(menuButton);
const editButton = screen.getByText('Edit Organization');
// Should not throw error when clicked
await user.click(editButton);
});
it('closes menu after edit is clicked', async () => {
const user = userEvent.setup();
const mockOnEdit = jest.fn();
render(
<OrganizationActionMenu
organization={mockOrganization}
onEdit={mockOnEdit}
onViewMembers={jest.fn()}
/>
);
const menuButton = screen.getByRole('button', {
name: 'Actions for Acme Corporation',
});
await user.click(menuButton);
const editButton = screen.getByText('Edit Organization');
await user.click(editButton);
// Menu should close after clicking
await waitFor(() => {
const editButton = screen.queryByText('Edit Organization');
expect(editButton).toBeNull();
});
});
});
describe('View Members Action', () => {
it('calls onViewMembers when clicked', async () => {
const user = userEvent.setup();
const mockOnViewMembers = jest.fn();
render(
<OrganizationActionMenu
organization={mockOrganization}
onEdit={jest.fn()}
onViewMembers={mockOnViewMembers}
/>
);
const menuButton = screen.getByRole('button', {
name: 'Actions for Acme Corporation',
});
await user.click(menuButton);
const viewMembersButton = screen.getByText('View Members');
await user.click(viewMembersButton);
expect(mockOnViewMembers).toHaveBeenCalledWith(mockOrganization.id);
});
it('does not call onViewMembers when handler is undefined', async () => {
const user = userEvent.setup();
render(
<OrganizationActionMenu
organization={mockOrganization}
onEdit={jest.fn()}
onViewMembers={undefined}
/>
);
const menuButton = screen.getByRole('button', {
name: 'Actions for Acme Corporation',
});
await user.click(menuButton);
const viewMembersButton = screen.getByText('View Members');
// Should not throw error when clicked
await user.click(viewMembersButton);
});
it('closes menu after view members is clicked', async () => {
const user = userEvent.setup();
const mockOnViewMembers = jest.fn();
render(
<OrganizationActionMenu
organization={mockOrganization}
onEdit={jest.fn()}
onViewMembers={mockOnViewMembers}
/>
);
const menuButton = screen.getByRole('button', {
name: 'Actions for Acme Corporation',
});
await user.click(menuButton);
const viewMembersButton = screen.getByText('View Members');
await user.click(viewMembersButton);
// Menu should close after clicking
await waitFor(() => {
const viewButton = screen.queryByText('View Members');
expect(viewButton).toBeNull();
});
});
});
describe('Delete Action', () => {
it('shows confirmation dialog when delete is clicked', async () => {
const user = userEvent.setup();
render(
<OrganizationActionMenu
organization={mockOrganization}
onEdit={jest.fn()}
onViewMembers={jest.fn()}
/>
);
const menuButton = screen.getByRole('button', {
name: 'Actions for Acme Corporation',
});
await user.click(menuButton);
const deleteButton = screen.getByText('Delete Organization');
await user.click(deleteButton);
expect(screen.getByText('Delete Organization')).toBeInTheDocument();
expect(
screen.getByText(/Are you sure you want to delete Acme Corporation/)
).toBeInTheDocument();
});
it('shows warning about data loss in dialog', async () => {
const user = userEvent.setup();
render(
<OrganizationActionMenu
organization={mockOrganization}
onEdit={jest.fn()}
onViewMembers={jest.fn()}
/>
);
const menuButton = screen.getByRole('button', {
name: 'Actions for Acme Corporation',
});
await user.click(menuButton);
const deleteButton = screen.getByText('Delete Organization');
await user.click(deleteButton);
expect(
screen.getByText(/This action cannot be undone and will remove all associated data/)
).toBeInTheDocument();
});
it('closes dialog when cancel is clicked', async () => {
const user = userEvent.setup();
render(
<OrganizationActionMenu
organization={mockOrganization}
onEdit={jest.fn()}
onViewMembers={jest.fn()}
/>
);
const menuButton = screen.getByRole('button', {
name: 'Actions for Acme Corporation',
});
await user.click(menuButton);
const deleteButton = screen.getByText('Delete Organization');
await user.click(deleteButton);
const cancelButton = screen.getByRole('button', { name: 'Cancel' });
await user.click(cancelButton);
await waitFor(() => {
expect(
screen.queryByText(/Are you sure you want to delete/)
).not.toBeInTheDocument();
});
});
it('calls delete mutation when confirmed', async () => {
const user = userEvent.setup();
render(
<OrganizationActionMenu
organization={mockOrganization}
onEdit={jest.fn()}
onViewMembers={jest.fn()}
/>
);
const menuButton = screen.getByRole('button', {
name: 'Actions for Acme Corporation',
});
await user.click(menuButton);
const deleteButton = screen.getByText('Delete Organization');
await user.click(deleteButton);
const confirmButton = screen.getByRole('button', { name: 'Delete' });
await user.click(confirmButton);
await waitFor(() => {
expect(mockDeleteMutate).toHaveBeenCalledWith(mockOrganization.id);
});
});
it('shows success toast after deletion', async () => {
const user = userEvent.setup();
render(
<OrganizationActionMenu
organization={mockOrganization}
onEdit={jest.fn()}
onViewMembers={jest.fn()}
/>
);
const menuButton = screen.getByRole('button', {
name: 'Actions for Acme Corporation',
});
await user.click(menuButton);
const deleteButton = screen.getByText('Delete Organization');
await user.click(deleteButton);
const confirmButton = screen.getByRole('button', { name: 'Delete' });
await user.click(confirmButton);
await waitFor(() => {
expect(toast.success).toHaveBeenCalledWith(
'Acme Corporation has been deleted successfully.'
);
});
});
it('shows error toast on deletion failure', async () => {
const user = userEvent.setup();
const errorMessage = 'Failed to delete organization';
mockDeleteMutate.mockRejectedValueOnce(new Error(errorMessage));
render(
<OrganizationActionMenu
organization={mockOrganization}
onEdit={jest.fn()}
onViewMembers={jest.fn()}
/>
);
const menuButton = screen.getByRole('button', {
name: 'Actions for Acme Corporation',
});
await user.click(menuButton);
const deleteButton = screen.getByText('Delete Organization');
await user.click(deleteButton);
const confirmButton = screen.getByRole('button', { name: 'Delete' });
await user.click(confirmButton);
await waitFor(() => {
expect(toast.error).toHaveBeenCalledWith(errorMessage);
});
});
it('closes dialog after successful deletion', async () => {
const user = userEvent.setup();
render(
<OrganizationActionMenu
organization={mockOrganization}
onEdit={jest.fn()}
onViewMembers={jest.fn()}
/>
);
const menuButton = screen.getByRole('button', {
name: 'Actions for Acme Corporation',
});
await user.click(menuButton);
const deleteButton = screen.getByText('Delete Organization');
await user.click(deleteButton);
const confirmButton = screen.getByRole('button', { name: 'Delete' });
await user.click(confirmButton);
await waitFor(() => {
expect(
screen.queryByText(/Are you sure you want to delete/)
).not.toBeInTheDocument();
});
});
});
});