test(260805-d0r): rewrite modal test fixtures to { groups, modules } shape

- Regression: group without any module grant stays visible as a chip
- New: mixed empty-modules + populated-groups case, LDAP/MANUAL badge case
This commit is contained in:
2026-08-05 09:34:42 +02:00
parent ecadf69e14
commit 771f680034
@@ -23,6 +23,10 @@ const messages: Record<string, Record<string, unknown>> = {
'{module} direkt für {user} {granted, select, true {freigeben} other {entziehen}}',
saveError: 'Freigabe konnte nicht gespeichert werden. Bitte erneut versuchen.',
},
'admin.groups.members': {
sourceManual: 'Manuell',
sourceLdap: 'LDAP',
},
common: {
loading: 'Laden...',
close: 'Schliessen',
@@ -58,7 +62,9 @@ vi.mock('next-intl', () => ({
import { UserAccessModal } from './components/UserAccessModal';
const mockAccessRows = [
const mockAccess = {
groups: [{ id: 'g1', name: 'Alle Benutzer', source: 'MANUAL' as const }],
modules: [
{
module: { id: 'm1', name: 'Ausschreibungs-Radar', category: 'procurement' },
viaGroups: ['Alle Benutzer'],
@@ -69,7 +75,8 @@ const mockAccessRows = [
viaGroups: [],
direct: true,
},
];
],
};
afterEach(() => {
cleanup();
@@ -80,7 +87,7 @@ describe('UserAccessModal', () => {
it('renders the group-membership chip list, and the empty state when the user has none', async () => {
vi.stubGlobal(
'fetch',
vi.fn(() => Promise.resolve({ ok: true, json: () => Promise.resolve(mockAccessRows) })),
vi.fn(() => Promise.resolve({ ok: true, json: () => Promise.resolve(mockAccess) })),
);
render(<UserAccessModal userId="u1" username="Maxi Musterfrau" onClose={vi.fn()} />);
@@ -96,10 +103,7 @@ describe('UserAccessModal', () => {
vi.fn(() =>
Promise.resolve({
ok: true,
json: () =>
Promise.resolve(
mockAccessRows.map((row) => ({ ...row, viaGroups: [] as string[] })),
),
json: () => Promise.resolve({ groups: [], modules: mockAccess.modules }),
}),
),
);
@@ -113,10 +117,44 @@ describe('UserAccessModal', () => {
});
});
it('REGRESSION: a group without any module grant stays visible as a chip', async () => {
// Deliberately no module row references the group name -- viaGroups is
// empty everywhere, so any found text is unambiguously the chip and not
// the "Über Gruppe(n)" column.
vi.stubGlobal(
'fetch',
vi.fn(() =>
Promise.resolve({
ok: true,
json: () =>
Promise.resolve({
groups: [{ id: 'g1', name: 'Gruppe A', source: 'MANUAL' as const }],
modules: [
{
module: { id: 'm1', name: 'Ausschreibungs-Radar', category: 'procurement' },
viaGroups: [],
direct: false,
},
],
}),
}),
),
);
render(<UserAccessModal userId="u1" username="Maxi Musterfrau" onClose={vi.fn()} />);
await waitFor(() => {
expect(screen.getByText('Gruppe A')).toBeInTheDocument();
});
expect(
screen.queryByText('Dieser Benutzer ist keiner Gruppe zugeordnet.'),
).not.toBeInTheDocument();
});
it('renders the module access table with one inherited and one non-inherited module', async () => {
vi.stubGlobal(
'fetch',
vi.fn(() => Promise.resolve({ ok: true, json: () => Promise.resolve(mockAccessRows) })),
vi.fn(() => Promise.resolve({ ok: true, json: () => Promise.resolve(mockAccess) })),
);
render(<UserAccessModal userId="u1" username="Maxi Musterfrau" onClose={vi.fn()} />);
@@ -125,21 +163,43 @@ describe('UserAccessModal', () => {
expect(screen.getByText('Ausschreibungs-Radar')).toBeInTheDocument();
});
expect(screen.getByText('DKV Flotte')).toBeInTheDocument();
// m1 is inherited via 'Alle Benutzer' -- shown as a chip in the via-groups column.
// m1 is inherited via 'Alle Benutzer' -- shown as a chip in the via-groups column
// AND as a group-membership chip.
expect(screen.getAllByText('Alle Benutzer').length).toBeGreaterThan(0);
// m2 has no inheriting groups -- renders the em-dash placeholder.
expect(screen.getByText('–')).toBeInTheDocument();
});
it('shows both the group chips and the no-active-modules hint when modules is empty but groups is not', async () => {
vi.stubGlobal(
'fetch',
vi.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ groups: mockAccess.groups, modules: [] }),
}),
),
);
render(<UserAccessModal userId="u1" username="Maxi Musterfrau" onClose={vi.fn()} />);
await waitFor(() => {
expect(screen.getAllByText('Alle Benutzer').length).toBeGreaterThan(0);
});
expect(
screen.getByText('Für diesen Mandanten sind keine Module aktiviert.'),
).toBeInTheDocument();
});
it('rolls back the direct checkbox and shows a visible error when the request fails', async () => {
const fetchMock = vi.fn((url: string, init?: RequestInit) => {
if (typeof url === 'string' && url.endsWith('/module-grants/users/u1')) {
return Promise.resolve({ ok: true, json: () => Promise.resolve(mockAccessRows) });
return Promise.resolve({ ok: true, json: () => Promise.resolve(mockAccess) });
}
if (typeof url === 'string' && url.endsWith('/module-grants') && init?.method === 'POST') {
return Promise.resolve({ ok: false, status: 500, text: () => Promise.resolve('boom') });
}
return Promise.resolve({ ok: true, json: () => Promise.resolve(mockAccessRows) });
return Promise.resolve({ ok: true, json: () => Promise.resolve(mockAccess) });
});
vi.stubGlobal('fetch', fetchMock);
@@ -164,7 +224,9 @@ describe('UserAccessModal', () => {
it('shows the hint text when the tenant has no active modules', async () => {
vi.stubGlobal(
'fetch',
vi.fn(() => Promise.resolve({ ok: true, json: () => Promise.resolve([]) })),
vi.fn(() =>
Promise.resolve({ ok: true, json: () => Promise.resolve({ groups: [], modules: [] }) }),
),
);
render(<UserAccessModal userId="u1" username="Maxi Musterfrau" onClose={vi.fn()} />);
@@ -179,7 +241,7 @@ describe('UserAccessModal', () => {
it('gives every direct checkbox an aria-label', async () => {
vi.stubGlobal(
'fetch',
vi.fn(() => Promise.resolve({ ok: true, json: () => Promise.resolve(mockAccessRows) })),
vi.fn(() => Promise.resolve({ ok: true, json: () => Promise.resolve(mockAccess) })),
);
render(<UserAccessModal userId="u1" username="Maxi Musterfrau" onClose={vi.fn()} />);
@@ -194,4 +256,32 @@ describe('UserAccessModal', () => {
expect(box).toHaveAccessibleName();
}
});
it('shows the LDAP origin badge on a chip with source LDAP and the Manuell badge on source MANUAL', async () => {
vi.stubGlobal(
'fetch',
vi.fn(() =>
Promise.resolve({
ok: true,
json: () =>
Promise.resolve({
groups: [
{ id: 'g1', name: 'Manuelle Gruppe', source: 'MANUAL' as const },
{ id: 'g2', name: 'LDAP-Gruppe', source: 'LDAP' as const },
],
modules: [],
}),
}),
),
);
render(<UserAccessModal userId="u1" username="Maxi Musterfrau" onClose={vi.fn()} />);
await waitFor(() => {
expect(screen.getByText('Manuelle Gruppe')).toBeInTheDocument();
});
expect(screen.getByText('LDAP-Gruppe')).toBeInTheDocument();
expect(screen.getByText('Manuell')).toBeInTheDocument();
expect(screen.getByText('LDAP')).toBeInTheDocument();
});
});