- Implemented `OrganizationMembersContent`, `OrganizationMembersTable`, and `AddMemberDialog` components for organization members management. - Added unit tests for `OrganizationMembersContent` and `OrganizationMembersTable`, covering rendering, state handling, and edge cases. - Enhanced `useOrganizationMembers` and `useGetOrganization` hooks to support members list and pagination data integration. - Updated E2E tests to include organization members page interactions and improved reliability.
135 lines
3.7 KiB
TypeScript
135 lines
3.7 KiB
TypeScript
/**
|
|
* Tests for MemberActionMenu Component
|
|
*/
|
|
|
|
import React from 'react';
|
|
import { render, screen, waitFor } from '@testing-library/react';
|
|
import userEvent from '@testing-library/user-event';
|
|
import { MemberActionMenu } from '@/components/admin/organizations/MemberActionMenu';
|
|
import type { OrganizationMember } from '@/lib/api/hooks/useAdmin';
|
|
|
|
// Mock hooks
|
|
const mockRemoveMember = jest.fn();
|
|
jest.mock('@/lib/api/hooks/useAdmin', () => ({
|
|
useRemoveOrganizationMember: () => ({
|
|
mutateAsync: mockRemoveMember,
|
|
}),
|
|
}));
|
|
|
|
// Mock toast
|
|
jest.mock('sonner', () => ({
|
|
toast: {
|
|
success: jest.fn(),
|
|
error: jest.fn(),
|
|
},
|
|
}));
|
|
|
|
describe('MemberActionMenu', () => {
|
|
const mockMember: OrganizationMember = {
|
|
user_id: 'user-1',
|
|
email: 'john@test.com',
|
|
first_name: 'John',
|
|
last_name: 'Doe',
|
|
role: 'member',
|
|
joined_at: '2025-01-01T00:00:00Z',
|
|
};
|
|
|
|
const props = {
|
|
member: mockMember,
|
|
organizationId: 'org-1',
|
|
};
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
mockRemoveMember.mockResolvedValue({});
|
|
});
|
|
|
|
it('renders action menu button', () => {
|
|
render(<MemberActionMenu {...props} />);
|
|
|
|
const button = screen.getByRole('button', { name: /Actions for John Doe/i });
|
|
expect(button).toBeInTheDocument();
|
|
});
|
|
|
|
it('opens menu when button clicked', async () => {
|
|
const user = userEvent.setup();
|
|
render(<MemberActionMenu {...props} />);
|
|
|
|
const menuButton = screen.getByRole('button', { name: /Actions for/i });
|
|
await user.click(menuButton);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Remove Member')).toBeVisible();
|
|
});
|
|
});
|
|
|
|
it('shows remove member option in menu', async () => {
|
|
const user = userEvent.setup();
|
|
render(<MemberActionMenu {...props} />);
|
|
|
|
const menuButton = screen.getByRole('button', { name: /Actions for/i });
|
|
await user.click(menuButton);
|
|
|
|
await waitFor(() => {
|
|
const removeOption = screen.getByText('Remove Member');
|
|
expect(removeOption).toBeVisible();
|
|
});
|
|
});
|
|
|
|
it('opens confirmation dialog when remove clicked', async () => {
|
|
const user = userEvent.setup();
|
|
render(<MemberActionMenu {...props} />);
|
|
|
|
const menuButton = screen.getByRole('button', { name: /Actions for/i });
|
|
await user.click(menuButton);
|
|
|
|
const removeOption = await screen.findByText('Remove Member');
|
|
await user.click(removeOption);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText(/Are you sure you want to remove.*John Doe.*from this organization/)).toBeVisible();
|
|
});
|
|
});
|
|
|
|
it('closes dialog when cancel clicked', async () => {
|
|
const user = userEvent.setup();
|
|
render(<MemberActionMenu {...props} />);
|
|
|
|
// Open menu
|
|
const menuButton = screen.getByRole('button', { name: /Actions for/i });
|
|
await user.click(menuButton);
|
|
|
|
// Click remove
|
|
const removeOption = await screen.findByText('Remove Member');
|
|
await user.click(removeOption);
|
|
|
|
// Wait for dialog
|
|
await waitFor(() => {
|
|
expect(screen.getByRole('button', { name: 'Cancel' })).toBeVisible();
|
|
});
|
|
|
|
// Click cancel
|
|
const cancelButton = screen.getByRole('button', { name: 'Cancel' });
|
|
await user.click(cancelButton);
|
|
|
|
// Dialog should close
|
|
await waitFor(() => {
|
|
const confirmText = screen.queryByText(/Are you sure you want to remove/);
|
|
expect(confirmText).toBeNull();
|
|
});
|
|
});
|
|
|
|
it('uses email as fallback when name is missing', () => {
|
|
const memberWithoutName = {
|
|
...mockMember,
|
|
first_name: '',
|
|
last_name: null,
|
|
};
|
|
|
|
render(<MemberActionMenu member={memberWithoutName} organizationId="org-1" />);
|
|
|
|
const button = screen.getByRole('button', { name: /Actions for john@test.com/i });
|
|
expect(button).toBeInTheDocument();
|
|
});
|
|
});
|