Files
fast-next-template/frontend/tests/components/admin/organizations/OrganizationListTable.test.tsx
Felipe Cardoso 652fb6b180 Remove obsolete tests and unused imports in organization members and management components
- Deleted `OrganizationMembersPage` tests as they are no longer relevant.
- Removed unused `updated_at` property from test data in multiple components.
- Cleaned up redundant `useAuth` imports from `OrganizationMembersContent` and `OrganizationManagementContent`.
2025-11-07 07:55:05 +01:00

384 lines
11 KiB
TypeScript

/**
* Tests for OrganizationListTable Component
* Verifies rendering, pagination, and organization interactions
*/
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { OrganizationListTable } from '@/components/admin/organizations/OrganizationListTable';
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>
),
}));
describe('OrganizationListTable', () => {
const mockOrganizations: Organization[] = [
{
id: '1',
name: 'Acme Corporation',
description: 'Leading provider of innovative solutions',
is_active: true,
created_at: '2025-01-01T00:00:00Z',
member_count: 15,
},
{
id: '2',
name: 'Tech Startup Inc',
description: null,
is_active: false,
created_at: '2025-01-15T00:00:00Z',
member_count: 3,
},
];
const mockPagination: PaginationMeta = {
total: 2,
page: 1,
page_size: 20,
total_pages: 1,
has_next: false,
has_prev: false,
};
const defaultProps = {
organizations: mockOrganizations,
pagination: mockPagination,
isLoading: false,
onPageChange: jest.fn(),
onEditOrganization: jest.fn(),
onViewMembers: jest.fn(),
};
beforeEach(() => {
jest.clearAllMocks();
});
describe('Rendering', () => {
it('renders table with column headers', () => {
render(<OrganizationListTable {...defaultProps} />);
expect(screen.getByText('Name')).toBeInTheDocument();
expect(screen.getByText('Description')).toBeInTheDocument();
expect(screen.getByText('Members')).toBeInTheDocument();
expect(screen.getByText('Status')).toBeInTheDocument();
expect(screen.getByText('Created')).toBeInTheDocument();
const actionsHeaders = screen.getAllByText('Actions');
expect(actionsHeaders.length).toBeGreaterThan(0);
});
it('renders organization data in table rows', () => {
render(<OrganizationListTable {...defaultProps} />);
expect(screen.getByText('Acme Corporation')).toBeInTheDocument();
expect(screen.getByText('Leading provider of innovative solutions')).toBeInTheDocument();
expect(screen.getByText('Tech Startup Inc')).toBeInTheDocument();
});
it('renders status badges correctly', () => {
render(<OrganizationListTable {...defaultProps} />);
expect(screen.getByText('Active')).toBeInTheDocument();
expect(screen.getByText('Inactive')).toBeInTheDocument();
});
it('formats dates correctly', () => {
render(<OrganizationListTable {...defaultProps} />);
expect(screen.getByText('Jan 1, 2025')).toBeInTheDocument();
expect(screen.getByText('Jan 15, 2025')).toBeInTheDocument();
});
it('renders member counts correctly', () => {
render(<OrganizationListTable {...defaultProps} />);
expect(screen.getByText('15')).toBeInTheDocument();
expect(screen.getByText('3')).toBeInTheDocument();
});
it('shows placeholder text for missing description', () => {
render(<OrganizationListTable {...defaultProps} />);
expect(screen.getByText('No description')).toBeInTheDocument();
});
it('renders action menu for each organization', () => {
render(<OrganizationListTable {...defaultProps} />);
expect(screen.getByTestId('action-menu-1')).toBeInTheDocument();
expect(screen.getByTestId('action-menu-2')).toBeInTheDocument();
});
});
describe('Loading State', () => {
it('renders skeleton loaders when loading', () => {
render(<OrganizationListTable {...defaultProps} isLoading={true} organizations={[]} />);
const skeletons = screen.getAllByRole('row').slice(1); // Exclude header row
expect(skeletons).toHaveLength(5); // 5 skeleton rows
});
it('does not render organization data when loading', () => {
render(<OrganizationListTable {...defaultProps} isLoading={true} />);
expect(screen.queryByText('Acme Corporation')).not.toBeInTheDocument();
});
});
describe('Empty State', () => {
it('shows empty message when no organizations', () => {
render(
<OrganizationListTable
{...defaultProps}
organizations={[]}
pagination={{ ...mockPagination, total: 0 }}
/>
);
expect(
screen.getByText('No organizations found.')
).toBeInTheDocument();
});
it('does not render pagination when empty', () => {
render(
<OrganizationListTable
{...defaultProps}
organizations={[]}
pagination={{ ...mockPagination, total: 0 }}
/>
);
expect(screen.queryByText('Previous')).not.toBeInTheDocument();
expect(screen.queryByText('Next')).not.toBeInTheDocument();
});
});
describe('View Members Interaction', () => {
it('calls onViewMembers when member count is clicked', async () => {
const user = userEvent.setup();
render(<OrganizationListTable {...defaultProps} />);
// Click on the member count for first organization
const memberButton = screen.getByText('15').closest('button');
expect(memberButton).not.toBeNull();
if (memberButton) {
await user.click(memberButton);
expect(defaultProps.onViewMembers).toHaveBeenCalledWith('1');
}
});
it('does not call onViewMembers when handler is undefined', async () => {
const user = userEvent.setup();
render(
<OrganizationListTable
{...defaultProps}
onViewMembers={undefined}
/>
);
const memberButton = screen.getByText('15').closest('button');
expect(memberButton).not.toBeNull();
// Should not throw error when clicked
if (memberButton) {
await user.click(memberButton);
}
});
});
describe('Pagination', () => {
it('renders pagination info correctly', () => {
render(<OrganizationListTable {...defaultProps} />);
expect(
screen.getByText('Showing 1 to 2 of 2 organizations')
).toBeInTheDocument();
});
it('calculates pagination range correctly for page 2', () => {
render(
<OrganizationListTable
{...defaultProps}
pagination={{
total: 50,
page: 2,
page_size: 20,
total_pages: 3,
has_next: true,
has_prev: true,
}}
/>
);
expect(
screen.getByText('Showing 21 to 40 of 50 organizations')
).toBeInTheDocument();
});
it('renders pagination buttons', () => {
render(<OrganizationListTable {...defaultProps} />);
expect(screen.getByText('Previous')).toBeInTheDocument();
expect(screen.getByText('Next')).toBeInTheDocument();
expect(screen.getByText('1')).toBeInTheDocument();
});
it('disables previous button on first page', () => {
render(<OrganizationListTable {...defaultProps} />);
const prevButton = screen.getByText('Previous').closest('button');
expect(prevButton).toBeDisabled();
});
it('disables next button on last page', () => {
render(<OrganizationListTable {...defaultProps} />);
const nextButton = screen.getByText('Next').closest('button');
expect(nextButton).toBeDisabled();
});
it('enables previous button when not on first page', () => {
render(
<OrganizationListTable
{...defaultProps}
pagination={{
...mockPagination,
page: 2,
has_prev: true,
}}
/>
);
const prevButton = screen.getByText('Previous').closest('button');
expect(prevButton).not.toBeDisabled();
});
it('enables next button when not on last page', () => {
render(
<OrganizationListTable
{...defaultProps}
pagination={{
...mockPagination,
has_next: true,
total_pages: 2,
}}
/>
);
const nextButton = screen.getByText('Next').closest('button');
expect(nextButton).not.toBeDisabled();
});
it('calls onPageChange when previous button is clicked', async () => {
const user = userEvent.setup();
render(
<OrganizationListTable
{...defaultProps}
pagination={{
...mockPagination,
page: 2,
has_prev: true,
}}
/>
);
const prevButton = screen.getByText('Previous').closest('button');
if (prevButton) {
await user.click(prevButton);
expect(defaultProps.onPageChange).toHaveBeenCalledWith(1);
}
});
it('calls onPageChange when next button is clicked', async () => {
const user = userEvent.setup();
render(
<OrganizationListTable
{...defaultProps}
pagination={{
...mockPagination,
has_next: true,
total_pages: 2,
}}
/>
);
const nextButton = screen.getByText('Next').closest('button');
if (nextButton) {
await user.click(nextButton);
expect(defaultProps.onPageChange).toHaveBeenCalledWith(2);
}
});
it('calls onPageChange when page number is clicked', async () => {
const user = userEvent.setup();
render(
<OrganizationListTable
{...defaultProps}
pagination={{
...mockPagination,
total_pages: 3,
}}
/>
);
const pageButton = screen.getByText('1').closest('button');
if (pageButton) {
await user.click(pageButton);
expect(defaultProps.onPageChange).toHaveBeenCalledWith(1);
}
});
it('highlights current page button', () => {
render(
<OrganizationListTable
{...defaultProps}
pagination={{
...mockPagination,
page: 2,
total_pages: 3,
}}
/>
);
const currentPageButton = screen.getByText('2').closest('button');
const otherPageButton = screen.getByText('1').closest('button');
// Current page should not have outline variant
expect(currentPageButton).not.toHaveClass('border-input');
// Other pages should have outline variant
expect(otherPageButton).toHaveClass('border-input');
});
it('renders ellipsis for large page counts', () => {
render(
<OrganizationListTable
{...defaultProps}
pagination={{
...mockPagination,
page: 5,
total_pages: 10,
}}
/>
);
const ellipses = screen.getAllByText('...');
expect(ellipses.length).toBeGreaterThan(0);
});
it('does not render pagination when loading', () => {
render(<OrganizationListTable {...defaultProps} isLoading={true} organizations={[]} />);
expect(screen.queryByText('Previous')).not.toBeInTheDocument();
expect(screen.queryByText('Next')).not.toBeInTheDocument();
});
});
});