forked from cardosofelipe/fast-next-template
Add organization members management components and tests
- 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.
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* 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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user