From 6802b48cd879cda90eea599cf523a1984917e448 Mon Sep 17 00:00:00 2001 From: Schalli Date: Thu, 6 Aug 2026 16:29:15 +0200 Subject: [PATCH] feat(16-04): show internalName with AD-name fallback in groups list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Name column renders group.internalName ?? group.name (nullish, not truthiness, since the backend already normalizes blank values to null) inside a span carrying title={group.name} so the AD name stays discoverable on hover once an internal name is set. Badge column is untouched — it remains the sole imported-vs-local marker per D-07. Adds two component-test cases covering the fallback and its title attribute (D-04). --- .../admin/groups/groups-page.test.tsx | 57 +++++++++++++++++++ .../src/app/(portal)/admin/groups/page.tsx | 4 +- 2 files changed, 60 insertions(+), 1 deletion(-) 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 d78d111..fdea52d 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 @@ -96,6 +96,7 @@ interface Group { tenantId: string; name: string; ldapDn: string | null; + internalName: string | null; isDefault: boolean; createdAt: string; updatedAt: string; @@ -108,6 +109,7 @@ const mockGroups: Group[] = [ tenantId: 't1', name: 'Alle Benutzer', ldapDn: null, + internalName: null, isDefault: true, createdAt: '2026-01-01T00:00:00.000Z', updatedAt: '2026-01-01T00:00:00.000Z', @@ -118,6 +120,32 @@ const mockGroups: Group[] = [ tenantId: 't1', name: 'Buchhaltung', ldapDn: 'CN=Buchhaltung,OU=Gruppen,DC=ctl,DC=local', + internalName: null, + isDefault: false, + createdAt: '2026-01-01T00:00:00.000Z', + updatedAt: '2026-01-01T00:00:00.000Z', + memberCount: 1, + }, +]; + +const mockGroupsWithInternalName: Group[] = [ + { + id: 'g3', + tenantId: 't1', + name: 'AD-Vertrieb', + ldapDn: 'CN=AD-Vertrieb,OU=Gruppen,DC=ctl,DC=local', + internalName: 'Vertrieb', + isDefault: false, + createdAt: '2026-01-01T00:00:00.000Z', + updatedAt: '2026-01-01T00:00:00.000Z', + memberCount: 2, + }, + { + id: 'g4', + tenantId: 't1', + name: 'AD-Support', + ldapDn: 'CN=AD-Support,OU=Gruppen,DC=ctl,DC=local', + internalName: null, isDefault: false, createdAt: '2026-01-01T00:00:00.000Z', updatedAt: '2026-01-01T00:00:00.000Z', @@ -178,6 +206,35 @@ describe('AdminGroupsPage', () => { expect(screen.getByText('1')).toBeInTheDocument(); }); + it('shows the internal name in the name column and the AD name as title when internalName is set (D-04)', async () => { + vi.stubGlobal( + 'fetch', + vi.fn(() => Promise.resolve({ ok: true, json: () => Promise.resolve(mockGroupsWithInternalName) })), + ); + + render(); + + await waitFor(() => { + expect(screen.getByText('Vertrieb')).toBeInTheDocument(); + }); + expect(screen.queryByText('AD-Vertrieb')).not.toBeInTheDocument(); + expect(screen.getByText('Vertrieb').closest('span')).toHaveAttribute('title', 'AD-Vertrieb'); + }); + + it('falls back to the stored name in the name column when internalName is not set (D-04)', async () => { + vi.stubGlobal( + 'fetch', + vi.fn(() => Promise.resolve({ ok: true, json: () => Promise.resolve(mockGroupsWithInternalName) })), + ); + + render(); + + await waitFor(() => { + expect(screen.getByText('AD-Support')).toBeInTheDocument(); + }); + expect(screen.getByText('AD-Support').closest('span')).toHaveAttribute('title', 'AD-Support'); + }); + it('toggles the default-group star optimistically and persists via PATCH', async () => { const fetchMock = vi.fn((url: string, init?: RequestInit) => { if (typeof url === 'string' && url.endsWith('/groups') && (!init || init.method === undefined)) { diff --git a/apps/web/src/app/(portal)/admin/groups/page.tsx b/apps/web/src/app/(portal)/admin/groups/page.tsx index 572e579..5335e51 100644 --- a/apps/web/src/app/(portal)/admin/groups/page.tsx +++ b/apps/web/src/app/(portal)/admin/groups/page.tsx @@ -177,7 +177,9 @@ export default function AdminGroupsPage() { {groups.map((group) => ( - {group.name} + + {group.internalName ?? group.name} +