Continuous Integration / config (pull_request) Successful in 10s
Continuous Integration / backend-build (pull_request) Successful in 4m48s
Continuous Integration / vulnerability-scan (pull_request) Successful in 4m33s
Continuous Integration / frontend-prepare (pull_request) Successful in 1m47s
Continuous Integration / backend-test (pull_request) Successful in 5m17s
Continuous Integration / frontend-build (pull_request) Successful in 2m13s
Continuous Integration / frontend-test (pull_request) Successful in 4m22s
Continuous Integration / frontend-lint (pull_request) Successful in 2m0s
Continuous Integration / publish-production (pull_request) Skipped
Continuous Integration / deploy-production (pull_request) Skipped
Continuous Integration / publish-test (pull_request) Successful in 6m18s
Deploy (SCP) / deploy (pull_request) Successful in 1m28s
Continuous Integration / deploy-test (pull_request) Successful in 1m29s
5000ms was occasionally too tight on the self-hosted Actions runner for lazy-loaded routes gated behind an async setup-status check (observed on SettingsPage.test.tsx, passed reliably locally). Bumps to 10000ms across all nine test files using that pattern, and raises vite.config.ts's global testTimeout from 15000 to 20000 to keep headroom above it.
91 lines
3.8 KiB
TypeScript
91 lines
3.8 KiB
TypeScript
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('');
|
|
});
|
|
});
|