;
+
+describe('OrganizationFormDialog', () => {
+ const mockCreateMutate = jest.fn();
+ const mockUpdateMutate = jest.fn();
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+
+ mockUseCreateOrganization.mockReturnValue({
+ mutateAsync: mockCreateMutate,
+ isError: false,
+ error: null,
+ isPending: false,
+ } as any);
+
+ mockUseUpdateOrganization.mockReturnValue({
+ mutateAsync: mockUpdateMutate,
+ isError: false,
+ error: null,
+ isPending: false,
+ } as any);
+
+ mockCreateMutate.mockResolvedValue({});
+ mockUpdateMutate.mockResolvedValue({});
+ });
+
+ describe('Module Exports', () => {
+ it('exports OrganizationFormDialog component', () => {
+ const module = require('@/components/admin/organizations/OrganizationFormDialog');
+ expect(module.OrganizationFormDialog).toBeDefined();
+ expect(typeof module.OrganizationFormDialog).toBe('function');
+ });
+
+ it('component is a valid React component', () => {
+ const { OrganizationFormDialog } = require('@/components/admin/organizations/OrganizationFormDialog');
+ expect(OrganizationFormDialog.name).toBe('OrganizationFormDialog');
+ });
+ });
+
+ describe('Hook Integration', () => {
+ it('imports useCreateOrganization hook', () => {
+ // Verify hook mock is set up
+ expect(mockUseCreateOrganization).toBeDefined();
+ expect(typeof mockUseCreateOrganization).toBe('function');
+ });
+
+ it('imports useUpdateOrganization hook', () => {
+ // Verify hook mock is set up
+ expect(mockUseUpdateOrganization).toBeDefined();
+ expect(typeof mockUseUpdateOrganization).toBe('function');
+ });
+
+ it('hook mocks return expected structure', () => {
+ const createResult = mockUseCreateOrganization();
+ const updateResult = mockUseUpdateOrganization();
+
+ expect(createResult).toHaveProperty('mutateAsync');
+ expect(createResult).toHaveProperty('isError');
+ expect(createResult).toHaveProperty('error');
+ expect(createResult).toHaveProperty('isPending');
+
+ expect(updateResult).toHaveProperty('mutateAsync');
+ expect(updateResult).toHaveProperty('isError');
+ expect(updateResult).toHaveProperty('error');
+ expect(updateResult).toHaveProperty('isPending');
+ });
+ });
+
+ describe('Error State Handling', () => {
+ it('handles create error state', () => {
+ mockUseCreateOrganization.mockReturnValue({
+ mutateAsync: mockCreateMutate,
+ isError: true,
+ error: new Error('Create failed'),
+ isPending: false,
+ } as any);
+
+ const result = mockUseCreateOrganization();
+ expect(result.isError).toBe(true);
+ expect(result.error).toBeInstanceOf(Error);
+ });
+
+ it('handles update error state', () => {
+ mockUseUpdateOrganization.mockReturnValue({
+ mutateAsync: mockUpdateMutate,
+ isError: true,
+ error: new Error('Update failed'),
+ isPending: false,
+ } as any);
+
+ const result = mockUseUpdateOrganization();
+ expect(result.isError).toBe(true);
+ expect(result.error).toBeInstanceOf(Error);
+ });
+ });
+
+ describe('Loading State Handling', () => {
+ it('handles create loading state', () => {
+ mockUseCreateOrganization.mockReturnValue({
+ mutateAsync: mockCreateMutate,
+ isError: false,
+ error: null,
+ isPending: true,
+ } as any);
+
+ const result = mockUseCreateOrganization();
+ expect(result.isPending).toBe(true);
+ });
+
+ it('handles update loading state', () => {
+ mockUseUpdateOrganization.mockReturnValue({
+ mutateAsync: mockUpdateMutate,
+ isError: false,
+ error: null,
+ isPending: true,
+ } as any);
+
+ const result = mockUseUpdateOrganization();
+ expect(result.isPending).toBe(true);
+ });
+ });
+
+ describe('Mutation Functions', () => {
+ it('create mutation is callable', async () => {
+ const createResult = mockUseCreateOrganization();
+ await createResult.mutateAsync({} as any);
+ expect(mockCreateMutate).toHaveBeenCalledWith({});
+ });
+
+ it('update mutation is callable', async () => {
+ const updateResult = mockUseUpdateOrganization();
+ await updateResult.mutateAsync({} as any);
+ expect(mockUpdateMutate).toHaveBeenCalledWith({});
+ });
+
+ it('create mutation resolves successfully', async () => {
+ const createResult = mockUseCreateOrganization();
+ const result = await createResult.mutateAsync({} as any);
+ expect(result).toEqual({});
+ });
+
+ it('update mutation resolves successfully', async () => {
+ const updateResult = mockUseUpdateOrganization();
+ const result = await updateResult.mutateAsync({} as any);
+ expect(result).toEqual({});
+ });
+ });
+
+ describe('Component Implementation', () => {
+ it('component file contains expected functionality markers', () => {
+ const fs = require('fs');
+ const path = require('path');
+ const componentPath = path.join(
+ __dirname,
+ '../../../../src/components/admin/organizations/OrganizationFormDialog.tsx'
+ );
+ const source = fs.readFileSync(componentPath, 'utf8');
+
+ // Verify component has key features
+ expect(source).toContain('OrganizationFormDialog');
+ expect(source).toContain('useCreateOrganization');
+ expect(source).toContain('useUpdateOrganization');
+ expect(source).toContain('useForm');
+ expect(source).toContain('zodResolver');
+ expect(source).toContain('Dialog');
+ expect(source).toContain('name');
+ expect(source).toContain('description');
+ expect(source).toContain('is_active');
+ expect(source).toContain('slug');
+ });
+
+ it('component implements create mode', () => {
+ const fs = require('fs');
+ const path = require('path');
+ const componentPath = path.join(
+ __dirname,
+ '../../../../src/components/admin/organizations/OrganizationFormDialog.tsx'
+ );
+ const source = fs.readFileSync(componentPath, 'utf8');
+
+ expect(source).toContain('Create Organization');
+ expect(source).toContain('createOrganization');
+ });
+
+ it('component implements edit mode', () => {
+ const fs = require('fs');
+ const path = require('path');
+ const componentPath = path.join(
+ __dirname,
+ '../../../../src/components/admin/organizations/OrganizationFormDialog.tsx'
+ );
+ const source = fs.readFileSync(componentPath, 'utf8');
+
+ expect(source).toContain('Edit Organization');
+ expect(source).toContain('updateOrganization');
+ });
+
+ it('component has form validation schema', () => {
+ const fs = require('fs');
+ const path = require('path');
+ const componentPath = path.join(
+ __dirname,
+ '../../../../src/components/admin/organizations/OrganizationFormDialog.tsx'
+ );
+ const source = fs.readFileSync(componentPath, 'utf8');
+
+ expect(source).toContain('organizationFormSchema');
+ expect(source).toContain('.string()');
+ expect(source).toContain('.boolean()');
+ });
+
+ it('component has name validation requirements', () => {
+ const fs = require('fs');
+ const path = require('path');
+ const componentPath = path.join(
+ __dirname,
+ '../../../../src/components/admin/organizations/OrganizationFormDialog.tsx'
+ );
+ const source = fs.readFileSync(componentPath, 'utf8');
+
+ expect(source).toContain('Organization name is required');
+ expect(source).toMatch(/2|two/i); // Name length requirement
+ });
+
+ it('component handles slug generation', () => {
+ const fs = require('fs');
+ const path = require('path');
+ const componentPath = path.join(
+ __dirname,
+ '../../../../src/components/admin/organizations/OrganizationFormDialog.tsx'
+ );
+ const source = fs.readFileSync(componentPath, 'utf8');
+
+ expect(source).toContain('slug');
+ expect(source).toContain('toLowerCase');
+ expect(source).toContain('replace');
+ });
+
+ it('component handles toast notifications', () => {
+ const fs = require('fs');
+ const path = require('path');
+ const componentPath = path.join(
+ __dirname,
+ '../../../../src/components/admin/organizations/OrganizationFormDialog.tsx'
+ );
+ const source = fs.readFileSync(componentPath, 'utf8');
+
+ expect(source).toContain('toast');
+ expect(source).toContain('sonner');
+ });
+
+ it('component implements Dialog UI', () => {
+ const fs = require('fs');
+ const path = require('path');
+ const componentPath = path.join(
+ __dirname,
+ '../../../../src/components/admin/organizations/OrganizationFormDialog.tsx'
+ );
+ const source = fs.readFileSync(componentPath, 'utf8');
+
+ expect(source).toContain('DialogContent');
+ expect(source).toContain('DialogHeader');
+ expect(source).toContain('DialogTitle');
+ expect(source).toContain('DialogDescription');
+ expect(source).toContain('DialogFooter');
+ });
+
+ it('component has form inputs', () => {
+ const fs = require('fs');
+ const path = require('path');
+ const componentPath = path.join(
+ __dirname,
+ '../../../../src/components/admin/organizations/OrganizationFormDialog.tsx'
+ );
+ const source = fs.readFileSync(componentPath, 'utf8');
+
+ expect(source).toContain('Input');
+ expect(source).toContain('Textarea');
+ expect(source).toContain('Checkbox');
+ expect(source).toContain('Label');
+ expect(source).toContain('Button');
+ });
+
+ it('component has cancel and submit buttons', () => {
+ const fs = require('fs');
+ const path = require('path');
+ const componentPath = path.join(
+ __dirname,
+ '../../../../src/components/admin/organizations/OrganizationFormDialog.tsx'
+ );
+ const source = fs.readFileSync(componentPath, 'utf8');
+
+ expect(source).toContain('Cancel');
+ expect(source).toMatch(/Create Organization|Save Changes/);
+ });
+
+ it('component has active status checkbox for edit mode', () => {
+ const fs = require('fs');
+ const path = require('path');
+ const componentPath = path.join(
+ __dirname,
+ '../../../../src/components/admin/organizations/OrganizationFormDialog.tsx'
+ );
+ const source = fs.readFileSync(componentPath, 'utf8');
+
+ expect(source).toContain('Organization is active');
+ expect(source).toContain('isEdit');
+ });
+ });
+});
diff --git a/frontend/tests/components/admin/organizations/OrganizationListTable.test.tsx b/frontend/tests/components/admin/organizations/OrganizationListTable.test.tsx
new file mode 100644
index 0000000..e5227de
--- /dev/null
+++ b/frontend/tests/components/admin/organizations/OrganizationListTable.test.tsx
@@ -0,0 +1,387 @@
+/**
+ * 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) => (
+
+ ),
+}));
+
+describe('OrganizationListTable', () => {
+ const mockOrganizations: Organization[] = [
+ {
+ id: '1',
+ name: 'Acme Corporation',
+ slug: 'acme-corporation',
+ description: 'Leading provider of innovative solutions',
+ is_active: true,
+ created_at: '2025-01-01T00:00:00Z',
+ updated_at: '2025-01-01T00:00:00Z',
+ member_count: 15,
+ },
+ {
+ id: '2',
+ name: 'Tech Startup Inc',
+ slug: 'tech-startup-inc',
+ description: null,
+ is_active: false,
+ created_at: '2025-01-15T00:00:00Z',
+ updated_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();
+
+ 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();
+
+ 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();
+
+ expect(screen.getByText('Active')).toBeInTheDocument();
+ expect(screen.getByText('Inactive')).toBeInTheDocument();
+ });
+
+ it('formats dates correctly', () => {
+ render();
+
+ expect(screen.getByText('Jan 1, 2025')).toBeInTheDocument();
+ expect(screen.getByText('Jan 15, 2025')).toBeInTheDocument();
+ });
+
+ it('renders member counts correctly', () => {
+ render();
+
+ expect(screen.getByText('15')).toBeInTheDocument();
+ expect(screen.getByText('3')).toBeInTheDocument();
+ });
+
+ it('shows placeholder text for missing description', () => {
+ render();
+
+ expect(screen.getByText('No description')).toBeInTheDocument();
+ });
+
+ it('renders action menu for each organization', () => {
+ render();
+
+ expect(screen.getByTestId('action-menu-1')).toBeInTheDocument();
+ expect(screen.getByTestId('action-menu-2')).toBeInTheDocument();
+ });
+ });
+
+ describe('Loading State', () => {
+ it('renders skeleton loaders when loading', () => {
+ render();
+
+ 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();
+
+ expect(screen.queryByText('Acme Corporation')).not.toBeInTheDocument();
+ });
+ });
+
+ describe('Empty State', () => {
+ it('shows empty message when no organizations', () => {
+ render(
+
+ );
+
+ expect(
+ screen.getByText('No organizations found.')
+ ).toBeInTheDocument();
+ });
+
+ it('does not render pagination when empty', () => {
+ render(
+
+ );
+
+ 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();
+
+ // 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(
+
+ );
+
+ 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();
+
+ expect(
+ screen.getByText('Showing 1 to 2 of 2 organizations')
+ ).toBeInTheDocument();
+ });
+
+ it('calculates pagination range correctly for page 2', () => {
+ render(
+
+ );
+
+ expect(
+ screen.getByText('Showing 21 to 40 of 50 organizations')
+ ).toBeInTheDocument();
+ });
+
+ it('renders pagination buttons', () => {
+ render();
+
+ expect(screen.getByText('Previous')).toBeInTheDocument();
+ expect(screen.getByText('Next')).toBeInTheDocument();
+ expect(screen.getByText('1')).toBeInTheDocument();
+ });
+
+ it('disables previous button on first page', () => {
+ render();
+
+ const prevButton = screen.getByText('Previous').closest('button');
+ expect(prevButton).toBeDisabled();
+ });
+
+ it('disables next button on last page', () => {
+ render();
+
+ const nextButton = screen.getByText('Next').closest('button');
+ expect(nextButton).toBeDisabled();
+ });
+
+ it('enables previous button when not on first page', () => {
+ render(
+
+ );
+
+ const prevButton = screen.getByText('Previous').closest('button');
+ expect(prevButton).not.toBeDisabled();
+ });
+
+ it('enables next button when not on last page', () => {
+ render(
+
+ );
+
+ 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(
+
+ );
+
+ 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(
+
+ );
+
+ 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(
+
+ );
+
+ const pageButton = screen.getByText('1').closest('button');
+ if (pageButton) {
+ await user.click(pageButton);
+ expect(defaultProps.onPageChange).toHaveBeenCalledWith(1);
+ }
+ });
+
+ it('highlights current page button', () => {
+ render(
+
+ );
+
+ 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(
+
+ );
+
+ const ellipses = screen.getAllByText('...');
+ expect(ellipses.length).toBeGreaterThan(0);
+ });
+
+ it('does not render pagination when loading', () => {
+ render();
+
+ expect(screen.queryByText('Previous')).not.toBeInTheDocument();
+ expect(screen.queryByText('Next')).not.toBeInTheDocument();
+ });
+ });
+});
diff --git a/frontend/tests/components/admin/organizations/OrganizationManagementContent.test.tsx b/frontend/tests/components/admin/organizations/OrganizationManagementContent.test.tsx
new file mode 100644
index 0000000..925aa5b
--- /dev/null
+++ b/frontend/tests/components/admin/organizations/OrganizationManagementContent.test.tsx
@@ -0,0 +1,426 @@
+/**
+ * Tests for OrganizationManagementContent Component
+ * Verifies component orchestration, state management, and URL synchronization
+ */
+
+import { render, screen, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { useRouter, useSearchParams } from 'next/navigation';
+import { OrganizationManagementContent } from '@/components/admin/organizations/OrganizationManagementContent';
+import { useAuth } from '@/lib/auth/AuthContext';
+import { useAdminOrganizations } from '@/lib/api/hooks/useAdmin';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+
+// Mock Next.js navigation
+const mockPush = jest.fn();
+const mockSearchParams = new URLSearchParams();
+
+jest.mock('next/navigation', () => ({
+ useRouter: jest.fn(),
+ useSearchParams: jest.fn(),
+}));
+
+// Mock hooks
+jest.mock('@/lib/auth/AuthContext');
+jest.mock('@/lib/api/hooks/useAdmin', () => ({
+ useAdminOrganizations: jest.fn(),
+ useCreateOrganization: jest.fn(),
+ useUpdateOrganization: jest.fn(),
+ useDeleteOrganization: jest.fn(),
+}));
+
+// Mock child components
+jest.mock('@/components/admin/organizations/OrganizationListTable', () => ({
+ OrganizationListTable: ({ onEditOrganization, onViewMembers }: any) => (
+
+
+
+
+ ),
+}));
+
+jest.mock('@/components/admin/organizations/OrganizationFormDialog', () => ({
+ OrganizationFormDialog: ({ open, mode, organization, onOpenChange }: any) =>
+ open ? (
+
+
{mode}
+ {organization &&
{organization.id}
}
+
+
+ ) : null,
+}));
+
+const mockUseRouter = useRouter as jest.MockedFunction;
+const mockUseSearchParams = useSearchParams as jest.MockedFunction<
+ typeof useSearchParams
+>;
+const mockUseAuth = useAuth as jest.MockedFunction;
+const mockUseAdminOrganizations = useAdminOrganizations as jest.MockedFunction<
+ typeof useAdminOrganizations
+>;
+
+// Import mutation hooks for mocking
+const {
+ useCreateOrganization,
+ useUpdateOrganization,
+ useDeleteOrganization,
+} = require('@/lib/api/hooks/useAdmin');
+
+describe('OrganizationManagementContent', () => {
+ let queryClient: QueryClient;
+
+ const mockOrganizations = [
+ {
+ id: '1',
+ name: 'Organization One',
+ slug: 'org-one',
+ description: 'First organization',
+ is_active: true,
+ created_at: '2025-01-01T00:00:00Z',
+ updated_at: '2025-01-01T00:00:00Z',
+ member_count: 5,
+ },
+ {
+ id: '2',
+ name: 'Organization Two',
+ slug: 'org-two',
+ description: 'Second organization',
+ is_active: false,
+ created_at: '2025-01-02T00:00:00Z',
+ updated_at: '2025-01-02T00:00:00Z',
+ member_count: 3,
+ },
+ ];
+
+ beforeEach(() => {
+ queryClient = new QueryClient({
+ defaultOptions: {
+ queries: { retry: false },
+ },
+ });
+
+ jest.clearAllMocks();
+
+ mockUseRouter.mockReturnValue({
+ push: mockPush,
+ replace: jest.fn(),
+ prefetch: jest.fn(),
+ } as any);
+
+ mockUseSearchParams.mockReturnValue(mockSearchParams as any);
+
+ mockUseAuth.mockReturnValue({
+ user: {
+ id: 'current-user',
+ email: 'admin@example.com',
+ is_superuser: true,
+ } as any,
+ isAuthenticated: true,
+ isLoading: false,
+ login: jest.fn(),
+ logout: jest.fn(),
+ });
+
+ mockUseAdminOrganizations.mockReturnValue({
+ data: {
+ data: mockOrganizations,
+ pagination: {
+ total: 2,
+ page: 1,
+ page_size: 20,
+ total_pages: 1,
+ has_next: false,
+ has_prev: false,
+ },
+ },
+ isLoading: false,
+ isError: false,
+ error: null,
+ refetch: jest.fn(),
+ } as any);
+
+ // Mock mutation hooks
+ useCreateOrganization.mockReturnValue({
+ mutate: jest.fn(),
+ mutateAsync: jest.fn(),
+ isError: false,
+ isPending: false,
+ error: null,
+ } as any);
+
+ useUpdateOrganization.mockReturnValue({
+ mutate: jest.fn(),
+ mutateAsync: jest.fn(),
+ isError: false,
+ isPending: false,
+ error: null,
+ } as any);
+
+ useDeleteOrganization.mockReturnValue({
+ mutate: jest.fn(),
+ mutateAsync: jest.fn(),
+ isError: false,
+ isPending: false,
+ error: null,
+ } as any);
+ });
+
+ const renderWithProviders = (ui: React.ReactElement) => {
+ return render(
+ {ui}
+ );
+ };
+
+ describe('Component Rendering', () => {
+ it('renders header section', () => {
+ renderWithProviders();
+
+ expect(screen.getByText('All Organizations')).toBeInTheDocument();
+ expect(
+ screen.getByText('Manage organizations and their members')
+ ).toBeInTheDocument();
+ });
+
+ it('renders create organization button', () => {
+ renderWithProviders();
+
+ expect(
+ screen.getByRole('button', { name: /Create Organization/i })
+ ).toBeInTheDocument();
+ });
+
+ it('renders OrganizationListTable component', () => {
+ renderWithProviders();
+
+ expect(screen.getByTestId('organization-list-table')).toBeInTheDocument();
+ });
+
+ it('does not render dialog initially', () => {
+ renderWithProviders();
+
+ expect(
+ screen.queryByTestId('organization-form-dialog')
+ ).not.toBeInTheDocument();
+ });
+ });
+
+ describe('Create Organization Flow', () => {
+ it('opens create dialog when create button is clicked', async () => {
+ const user = userEvent.setup();
+ renderWithProviders();
+
+ const createButton = screen.getByRole('button', {
+ name: /Create Organization/i,
+ });
+ await user.click(createButton);
+
+ expect(screen.getByTestId('organization-form-dialog')).toBeInTheDocument();
+ expect(screen.getByTestId('dialog-mode')).toHaveTextContent('create');
+ });
+
+ it('closes dialog when onOpenChange is called', async () => {
+ const user = userEvent.setup();
+ renderWithProviders();
+
+ const createButton = screen.getByRole('button', {
+ name: /Create Organization/i,
+ });
+ await user.click(createButton);
+
+ const closeButton = screen.getByRole('button', { name: 'Close Dialog' });
+ await user.click(closeButton);
+
+ await waitFor(() => {
+ expect(
+ screen.queryByTestId('organization-form-dialog')
+ ).not.toBeInTheDocument();
+ });
+ });
+ });
+
+ describe('Edit Organization Flow', () => {
+ it('opens edit dialog when edit organization is triggered', async () => {
+ const user = userEvent.setup();
+ renderWithProviders();
+
+ const editButton = screen.getByRole('button', { name: 'Edit Organization' });
+ await user.click(editButton);
+
+ expect(screen.getByTestId('organization-form-dialog')).toBeInTheDocument();
+ expect(screen.getByTestId('dialog-mode')).toHaveTextContent('edit');
+ expect(screen.getByTestId('dialog-org-id')).toHaveTextContent('1');
+ });
+
+ it('closes dialog after edit', async () => {
+ const user = userEvent.setup();
+ renderWithProviders();
+
+ const editButton = screen.getByRole('button', { name: 'Edit Organization' });
+ await user.click(editButton);
+
+ const closeButton = screen.getByRole('button', { name: 'Close Dialog' });
+ await user.click(closeButton);
+
+ await waitFor(() => {
+ expect(
+ screen.queryByTestId('organization-form-dialog')
+ ).not.toBeInTheDocument();
+ });
+ });
+ });
+
+ describe('View Members Flow', () => {
+ it('navigates to members page when view members is clicked', async () => {
+ const user = userEvent.setup();
+ renderWithProviders();
+
+ const viewMembersButton = screen.getByRole('button', {
+ name: 'View Members',
+ });
+ await user.click(viewMembersButton);
+
+ expect(mockPush).toHaveBeenCalledWith('/admin/organizations/1/members');
+ });
+ });
+
+ describe('URL State Management', () => {
+ it('reads initial page from URL params', () => {
+ const paramsWithPage = new URLSearchParams('page=2');
+ mockUseSearchParams.mockReturnValue(paramsWithPage as any);
+
+ renderWithProviders();
+
+ expect(mockUseAdminOrganizations).toHaveBeenCalledWith(2, 20);
+ });
+
+ it('defaults to page 1 when no page param', () => {
+ renderWithProviders();
+
+ expect(mockUseAdminOrganizations).toHaveBeenCalledWith(1, 20);
+ });
+ });
+
+ describe('Data Loading States', () => {
+ it('passes loading state to table', () => {
+ mockUseAdminOrganizations.mockReturnValue({
+ data: undefined,
+ isLoading: true,
+ isError: false,
+ error: null,
+ refetch: jest.fn(),
+ } as any);
+
+ renderWithProviders();
+
+ expect(screen.getByTestId('organization-list-table')).toBeInTheDocument();
+ });
+
+ it('handles empty organization list', () => {
+ mockUseAdminOrganizations.mockReturnValue({
+ data: {
+ data: [],
+ pagination: {
+ total: 0,
+ page: 1,
+ page_size: 20,
+ total_pages: 0,
+ has_next: false,
+ has_prev: false,
+ },
+ },
+ isLoading: false,
+ isError: false,
+ error: null,
+ refetch: jest.fn(),
+ } as any);
+
+ renderWithProviders();
+
+ expect(screen.getByTestId('organization-list-table')).toBeInTheDocument();
+ });
+
+ it('handles undefined data gracefully', () => {
+ mockUseAdminOrganizations.mockReturnValue({
+ data: undefined,
+ isLoading: false,
+ isError: false,
+ error: null,
+ refetch: jest.fn(),
+ } as any);
+
+ renderWithProviders();
+
+ expect(screen.getByTestId('organization-list-table')).toBeInTheDocument();
+ });
+ });
+
+ describe('Component Integration', () => {
+ it('provides all required props to OrganizationListTable', () => {
+ renderWithProviders();
+
+ expect(screen.getByTestId('organization-list-table')).toBeInTheDocument();
+ });
+
+ it('provides correct props to OrganizationFormDialog', async () => {
+ const user = userEvent.setup();
+ renderWithProviders();
+
+ const createButton = screen.getByRole('button', {
+ name: /Create Organization/i,
+ });
+ await user.click(createButton);
+
+ expect(screen.getByTestId('dialog-mode')).toHaveTextContent('create');
+ });
+ });
+
+ describe('State Management', () => {
+ it('resets dialog state correctly between create and edit', async () => {
+ const user = userEvent.setup();
+ renderWithProviders();
+
+ // Open create dialog
+ const createButton = screen.getByRole('button', {
+ name: /Create Organization/i,
+ });
+ await user.click(createButton);
+ expect(screen.getByTestId('dialog-mode')).toHaveTextContent('create');
+
+ // Close dialog
+ const closeButton1 = screen.getByRole('button', {
+ name: 'Close Dialog',
+ });
+ await user.click(closeButton1);
+
+ // Open edit dialog
+ const editButton = screen.getByRole('button', { name: 'Edit Organization' });
+ await user.click(editButton);
+ expect(screen.getByTestId('dialog-mode')).toHaveTextContent('edit');
+ expect(screen.getByTestId('dialog-org-id')).toHaveTextContent('1');
+ });
+ });
+
+ describe('Current User Context', () => {
+ it('renders with authenticated user', () => {
+ renderWithProviders();
+
+ expect(screen.getByTestId('organization-list-table')).toBeInTheDocument();
+ });
+
+ it('handles missing current user', () => {
+ mockUseAuth.mockReturnValue({
+ user: null,
+ isAuthenticated: false,
+ isLoading: false,
+ login: jest.fn(),
+ logout: jest.fn(),
+ });
+
+ renderWithProviders();
+
+ expect(screen.getByTestId('organization-list-table')).toBeInTheDocument();
+ });
+ });
+});