Adds user management

This commit is contained in:
2026-06-22 21:14:22 +02:00
parent 5331be4279
commit 6976eb4337
40 changed files with 3392 additions and 146 deletions
@@ -0,0 +1,90 @@
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: 5000 });
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('');
});
});