feat(invites): group picker in new-invite form
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 3m11s
CI / OCR Service Tests (pull_request) Successful in 16s
CI / Backend Unit Tests (pull_request) Successful in 4m57s
CI / fail2ban Regex (pull_request) Successful in 38s
CI / Compose Bucket Idempotency (pull_request) Successful in 57s

- load() fetches /api/groups in parallel with /api/invites; returns
  sorted groups array and groupsLoadError for partial failures
- create action forwards groupIds[] to POST /api/invites so invited
  users are placed in the selected groups on registration
- +page.svelte: group checkboxes via UserGroupsSection inside the form;
  amber warning banner when groups could not be loaded
- page.svelte.test.ts: groups checkboxes + warning banner tests
- page.server.test.ts: parallel fetch, sorting, error fallback,
  groupIds in POST body

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-05-14 15:20:53 +02:00
parent a1b319a535
commit 82e61291d4
4 changed files with 324 additions and 12 deletions

View File

@@ -7,12 +7,15 @@ afterEach(cleanup);
const makeInvite = (overrides: Record<string, unknown> = {}) => ({
id: 'i-1',
code: 'XYZ1234567',
displayCode: 'XYZ-1234',
label: 'Familie',
useCount: 0,
maxUses: 5,
expiresAt: '2027-01-01T00:00:00Z',
revoked: false,
status: 'active' as string,
createdAt: '2025-01-01T00:00:00Z',
shareableUrl: 'http://example.com/i/i-1',
...overrides
});
@@ -22,11 +25,15 @@ const baseData = (
invites: ReturnType<typeof makeInvite>[];
status: string;
loadError: string | null;
groups: { id: string; name: string; permissions: string[] }[];
groupsLoadError: string | null;
}> = {}
) => ({
invites: [],
status: 'active',
loadError: null,
groups: [],
groupsLoadError: null,
...overrides
});
@@ -253,4 +260,43 @@ describe('admin/invites page', () => {
const banner = document.querySelector('.bg-red-50');
expect(banner).not.toBeNull();
});
// ─── groups section ───────────────────────────────────────────────────────
it('shows a groups-load warning banner when data.groupsLoadError is set', async () => {
render(AdminInvitesPage, {
props: { data: { ...baseData(), groups: [], groupsLoadError: 'INTERNAL_ERROR' } }
});
await page
.getByRole('button', { name: /neue einladung/i })
.first()
.click();
const banner = document.querySelector('.bg-amber-50');
expect(banner).not.toBeNull();
});
it('renders group checkboxes inside the new-invite form when groups are provided', async () => {
render(AdminInvitesPage, {
props: {
data: {
...baseData(),
groups: [
{ id: 'g-1', name: 'Administratoren', permissions: ['ADMIN'] },
{ id: 'g-2', name: 'Familie', permissions: ['READ_ALL'] }
],
groupsLoadError: null
}
}
});
await page
.getByRole('button', { name: /neue einladung/i })
.first()
.click();
await expect.element(page.getByRole('checkbox', { name: 'Administratoren' })).toBeVisible();
await expect.element(page.getByRole('checkbox', { name: 'Familie' })).toBeVisible();
});
});