fix(web): Formularweg und Listenladen der Benutzerverwaltung melden Fehler sichtbar

WINDOWS #36, Aufgabe 2/3: handleSubmit und fetchUsers verschluckten
abgewiesene Antworten und Verbindungsfehler ebenso wie der Loeschweg aus
Aufgabe 1. formError zeigt jetzt den Servertext oder eine Ersatzmeldung
im offenen Formulardialog; loadError verhindert die irrefuehrende
Meldung "Keine Benutzer gefunden", wenn das Laden selbst gescheitert
ist. Beide Zustaende werden beim Oeffnen eines neuen Dialogs
zurueckgesetzt, damit eine alte Meldung nicht in den naechsten Aufruf
hinueberwandert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
This commit is contained in:
2026-09-21 07:31:47 +02:00
parent 38d2586466
commit 51bff7564f
2 changed files with 220 additions and 4 deletions
@@ -285,3 +285,189 @@ describe('AdminUsersPage — Loeschweg (WINDOWS #36, Aufgabe 1)', () => {
expect(usersCalls.length).toBeGreaterThanOrEqual(2);
});
});
describe('AdminUsersPage — Formularweg und Listenladen (WINDOWS #36, Aufgabe 2)', () => {
it('zeigt den Servertext im offenen Formular, wenn das Speichern abgewiesen wird', async () => {
stubAdmin();
const fetchMock = vi.fn((url: string, init?: RequestInit) => {
if (typeof url === 'string' && url.endsWith('/users') && (!init || init.method === undefined)) {
return Promise.resolve({ ok: true, json: () => Promise.resolve(mockUsers) });
}
if (typeof url === 'string' && init?.method === 'PATCH') {
return Promise.resolve({
ok: false,
status: 403,
json: () => Promise.resolve({ message: 'Cannot modify a SUPER_ADMIN user' }),
});
}
return Promise.resolve({ ok: true, json: () => Promise.resolve([]) });
});
vi.stubGlobal('fetch', fetchMock);
render(<AdminUsersPage />);
await waitFor(() => {
expect(screen.getByText('user.zwei')).toBeInTheDocument();
});
const editButtons = screen.getAllByText('Bearbeiten');
await userEvent.click(editButtons[0]);
const usernameInput = await screen.findByDisplayValue('admin.eins');
const saveButton = screen.getByText('Speichern');
await userEvent.click(saveButton);
await waitFor(() => {
expect(
screen.getByText('Der Server hat die Aktion abgelehnt: Cannot modify a SUPER_ADMIN user'),
).toBeInTheDocument();
});
// Formular bleibt offen.
expect(usernameInput).toBeInTheDocument();
});
it('verbindet Pruefmeldungen aus einem Feld mit Komma im Rahmensatz', async () => {
stubAdmin();
const fetchMock = vi.fn((url: string, init?: RequestInit) => {
if (typeof url === 'string' && url.endsWith('/users') && (!init || init.method === undefined)) {
return Promise.resolve({ ok: true, json: () => Promise.resolve(mockUsers) });
}
if (typeof url === 'string' && init?.method === 'PATCH') {
return Promise.resolve({
ok: false,
status: 400,
json: () =>
Promise.resolve({ message: ['username must be longer', 'email must be an email'] }),
});
}
return Promise.resolve({ ok: true, json: () => Promise.resolve([]) });
});
vi.stubGlobal('fetch', fetchMock);
render(<AdminUsersPage />);
await waitFor(() => {
expect(screen.getByText('user.zwei')).toBeInTheDocument();
});
const editButtons = screen.getAllByText('Bearbeiten');
await userEvent.click(editButtons[0]);
await screen.findByDisplayValue('admin.eins');
await userEvent.click(screen.getByText('Speichern'));
await waitFor(() => {
expect(
screen.getByText(
'Der Server hat die Aktion abgelehnt: username must be longer, email must be an email',
),
).toBeInTheDocument();
});
});
it('zeigt die Netzmeldung im offenen Formular, wenn die Verbindung beim Speichern scheitert', async () => {
stubAdmin();
const fetchMock = vi.fn((url: string, init?: RequestInit) => {
if (typeof url === 'string' && url.endsWith('/users') && (!init || init.method === undefined)) {
return Promise.resolve({ ok: true, json: () => Promise.resolve(mockUsers) });
}
if (typeof url === 'string' && init?.method === 'PATCH') {
return Promise.reject(new Error('network down'));
}
return Promise.resolve({ ok: true, json: () => Promise.resolve([]) });
});
vi.stubGlobal('fetch', fetchMock);
render(<AdminUsersPage />);
await waitFor(() => {
expect(screen.getByText('user.zwei')).toBeInTheDocument();
});
const editButtons = screen.getAllByText('Bearbeiten');
await userEvent.click(editButtons[0]);
const usernameInput = await screen.findByDisplayValue('admin.eins');
await userEvent.click(screen.getByText('Speichern'));
await waitFor(() => {
expect(
screen.getByText('Der Server ist nicht erreichbar. Bitte erneut versuchen.'),
).toBeInTheDocument();
});
expect(usernameInput).toBeInTheDocument();
});
it('meldet ein gescheitertes Laden im Listenkopf statt faelschlich "Keine Benutzer gefunden" zu zeigen', async () => {
stubAdmin();
const fetchMock = vi.fn((url: string, init?: RequestInit) => {
if (typeof url === 'string' && url.endsWith('/users') && (!init || init.method === undefined)) {
return Promise.resolve({
ok: false,
status: 500,
json: () => Promise.reject(new Error('not json')),
});
}
return Promise.resolve({ ok: true, json: () => Promise.resolve([]) });
});
vi.stubGlobal('fetch', fetchMock);
render(<AdminUsersPage />);
await waitFor(() => {
expect(
screen.getByText('Die Benutzerliste konnte nicht geladen werden. Bitte laden Sie die Seite neu.'),
).toBeInTheDocument();
});
expect(screen.queryByText('Keine Benutzer gefunden')).not.toBeInTheDocument();
});
it('laesst eine Meldung nicht das Schliessen und erneute Oeffnen des Formulars ueberdauern', async () => {
stubAdmin();
const fetchMock = vi.fn((url: string, init?: RequestInit) => {
if (typeof url === 'string' && url.endsWith('/users') && (!init || init.method === undefined)) {
return Promise.resolve({ ok: true, json: () => Promise.resolve(mockUsers) });
}
if (typeof url === 'string' && init?.method === 'PATCH') {
return Promise.resolve({
ok: false,
status: 403,
json: () => Promise.resolve({ message: 'Cannot modify a SUPER_ADMIN user' }),
});
}
return Promise.resolve({ ok: true, json: () => Promise.resolve([]) });
});
vi.stubGlobal('fetch', fetchMock);
render(<AdminUsersPage />);
await waitFor(() => {
expect(screen.getByText('user.zwei')).toBeInTheDocument();
});
const editButtons = screen.getAllByText('Bearbeiten');
await userEvent.click(editButtons[0]);
await screen.findByDisplayValue('admin.eins');
await userEvent.click(screen.getByText('Speichern'));
await waitFor(() => {
expect(
screen.getByText('Der Server hat die Aktion abgelehnt: Cannot modify a SUPER_ADMIN user'),
).toBeInTheDocument();
});
await userEvent.click(screen.getByText('Abbrechen'));
await waitFor(() => {
expect(screen.queryByDisplayValue('admin.eins')).not.toBeInTheDocument();
});
const editButtonsAgain = screen.getAllByText('Bearbeiten');
await userEvent.click(editButtonsAgain[0]);
await screen.findByDisplayValue('admin.eins');
expect(
screen.queryByText('Der Server hat die Aktion abgelehnt: Cannot modify a SUPER_ADMIN user'),
).not.toBeInTheDocument();
});
});