import { beforeEach, describe, expect, it } from 'vitest'; import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { http, HttpResponse } from 'msw'; import { renderApp, mockAuthenticated } from '@/test/utils'; import { server } from '@/mocks/server'; import { API_BASE } from '@/mocks/auth/fixtures'; import { _resetSetupStatusCache } from '@/router'; beforeEach(() => { _resetSetupStatusCache(); }); async function openDialog() { mockAuthenticated(); renderApp('/users'); await screen.findByTestId('users-invite-button', {}, { timeout: 10000 }); await userEvent.click(screen.getByTestId('users-invite-button')); expect(screen.getByTestId('invite-dialog-email')).toBeInTheDocument(); } describe('InviteUserDialog', () => { it('shows email and role fields on open', async () => { await openDialog(); expect(screen.getByTestId('invite-dialog-email')).toBeInTheDocument(); expect(screen.getByTestId('invite-dialog-role')).toBeInTheDocument(); expect(screen.getByTestId('invite-dialog-submit')).toBeInTheDocument(); }); it('shows validation error when submitting with empty email', async () => { await openDialog(); await userEvent.click(screen.getByTestId('invite-dialog-submit')); expect(await screen.findByTestId('invite-email-error')).toBeInTheDocument(); }); it('shows validation error when submitting without selecting a role', async () => { await openDialog(); await userEvent.type(screen.getByTestId('invite-dialog-email'), 'new@example.com'); await userEvent.click(screen.getByTestId('invite-dialog-submit')); expect(await screen.findByTestId('invite-role-error')).toBeInTheDocument(); }); it('advances to step 2 (invite link) after successful submission', async () => { await openDialog(); await userEvent.type(screen.getByTestId('invite-dialog-email'), 'new@example.com'); // Open role dropdown and select "User" (use role=option to avoid ambiguity) await userEvent.click(screen.getByTestId('invite-dialog-role')); const options = await screen.findAllByRole('option'); const userOption = options.find((o) => o.textContent === 'User'); await userEvent.click(userOption!); await userEvent.click(screen.getByTestId('invite-dialog-submit')); expect(await screen.findByTestId('invite-dialog-link-input')).toBeInTheDocument(); expect(screen.getByTestId('invite-dialog-copy')).toBeInTheDocument(); }); it('shows error banner when API returns 400', async () => { server.use( http.post(`${API_BASE}/api/v1/Users/invite`, () => HttpResponse.json({ detail: 'User already exists.' }, { status: 400 }), ), ); await openDialog(); await userEvent.type(screen.getByTestId('invite-dialog-email'), 'dup@example.com'); await userEvent.click(screen.getByTestId('invite-dialog-role')); const opts = await screen.findAllByRole('option'); const userOpt = opts.find((o) => o.textContent === 'User'); await userEvent.click(userOpt!); await userEvent.click(screen.getByTestId('invite-dialog-submit')); expect(await screen.findByTestId('form-error-banner')).toBeInTheDocument(); }); it('resets to step 1 when dialog is closed and reopened', async () => { await openDialog(); await userEvent.type(screen.getByTestId('invite-dialog-email'), 'test@example.com'); // Close the dialog by pressing Escape await userEvent.keyboard('{Escape}'); // Reopen await userEvent.click(screen.getByTestId('users-invite-button')); const emailInput = screen.getByTestId('invite-dialog-email') as HTMLInputElement; expect(emailInput.value).toBe(''); }); });