From dee52a2b00228389dfd3eb66e9aa369d37ae783a Mon Sep 17 00:00:00 2001 From: Schalli Date: Mon, 7 Sep 2026 13:39:09 +0200 Subject: [PATCH] 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) Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq --- .../admin/groups/groups-page.test.tsx | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/apps/web/src/app/(portal)/admin/groups/groups-page.test.tsx b/apps/web/src/app/(portal)/admin/groups/groups-page.test.tsx index 7f5a06f..93e2506 100644 --- a/apps/web/src/app/(portal)/admin/groups/groups-page.test.tsx +++ b/apps/web/src/app/(portal)/admin/groups/groups-page.test.tsx @@ -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> = { 'admin.groups': { title: 'Gruppen', @@ -72,6 +75,13 @@ function resolve(ns: string, key: string, params?: Record): 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(); + + 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',