test(260907-ipz): failing test fuer Einzahl im Loeschdialog

Der Loeschdialog schreibt bei genau einem Mitglied und einer Freigabe
faelschlich "1 Mitglieder und 1 Modul-Freigaben". Der Mock-Uebersetzer
in groups-page.test.tsx wird um eine ICU-Plural-Aufloesung erweitert
(nach dem Vorbild der bestehenden select-Aufloesung in
grants-matrix.test.tsx), und ein neuer Test mit
{ memberCount: 1, grantCount: 1 } nagelt den korrekten Einzahl-Text
fest. Der Test ist an dieser Stelle rot, da der Uebersetzungstext noch
nicht auf ICU-Plural umgestellt ist.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
This commit is contained in:
2026-09-07 13:39:09 +02:00
parent b70161717c
commit dee52a2b00
@@ -3,8 +3,11 @@ import userEvent from '@testing-library/user-event';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
// Namespace-aware next-intl mock: dot-path keys resolve against the
// namespace passed to useTranslations(ns), and {param} placeholders are
// substituted the same way next-intl does for plain (non-ICU) values.
// namespace passed to useTranslations(ns), {param} placeholders are
// substituted the same way next-intl does for plain (non-ICU) values, and
// ICU `plural` templates (`{name, plural, one {...} other {...}}`) are
// resolved against the numeric param, mirroring next-intl's own behaviour
// closely enough for text assertions.
const messages: Record<string, Record<string, unknown>> = {
'admin.groups': {
title: 'Gruppen',
@@ -72,6 +75,13 @@ function resolve(ns: string, key: string, params?: Record<string, unknown>): str
}
if (typeof val !== 'string') return key;
if (params) {
val = val.replace(
/\{(\w+),\s*plural,\s*one\s*\{([^}]*)\}\s*other\s*\{([^}]*)\}\}/g,
(_match: string, name: string, one: string, other: string) => {
const branch = Number(params[name]) === 1 ? one : other;
return branch.replace(/#/g, String(params[name]));
},
);
for (const [k, v] of Object.entries(params)) {
val = val.replace(`{${k}}`, String(v));
}
@@ -477,6 +487,24 @@ describe('DeleteGroupDialog (via AdminGroupsPage)', () => {
expect(screen.getByText(/2 Modul-Freigaben/)).toBeInTheDocument();
});
it('uses singular wording for exactly one member and one module grant', async () => {
vi.stubGlobal('fetch', buildRouterFetchMock({ impact: { memberCount: 1, grantCount: 1 } }));
render(<AdminGroupsPage />);
await waitFor(() => {
expect(screen.getByText('Buchhaltung')).toBeInTheDocument();
});
const deleteButtons = screen.getAllByText('Löschen');
await userEvent.click(deleteButtons[deleteButtons.length - 1]);
await waitFor(() => {
expect(screen.getByText(/hat 1 Mitglied und 1 Modul-Freigabe\./)).toBeInTheDocument();
});
expect(screen.queryByText(/1 Mitglieder/)).not.toBeInTheDocument();
});
it('stays open and shows a visible error when DELETE /groups/:id fails', async () => {
vi.stubGlobal(
'fetch',