Five test files mocked $lib/shared/services/confirm.svelte under BOTH spellings (.svelte and .svelte.js) within the same file; two more mocked only the .svelte.js form. Both resolve to the same module URL but register two distinct Playwright route handlers in @vitest/browser-playwright. The cleanup logic only removes one, leaving an orphan that fires when the next session loads the module — crashing the run with "[birpc] rpc is closed, cannot call resolveManualMock". This is the exact trigger fixed upstream by vitest PR #10267 (issue #9957). Normalise every confirm.svelte mock to the no-extension form, matching production imports and the source file basename (confirm.svelte.ts). After this commit: 8 confirm.svelte mocks across 8 spec files, all under one canonical ID. A meta-test (next commit) prevents the duplicate-id pattern from reappearing. Refs: #553 · vitest-dev/vitest#9957 · vitest-dev/vitest#10267 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
126 lines
4.2 KiB
TypeScript
126 lines
4.2 KiB
TypeScript
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
import { cleanup, render } from 'vitest-browser-svelte';
|
|
import { page } from 'vitest/browser';
|
|
|
|
vi.mock('$lib/shared/services/confirm.svelte', () => ({
|
|
getConfirmService: () => ({ confirm: async () => false })
|
|
}));
|
|
|
|
const { default: AdminGroupEditPage } = await import('./+page.svelte');
|
|
|
|
afterEach(cleanup);
|
|
|
|
const baseGroup = (overrides: Record<string, unknown> = {}) => ({
|
|
id: 'g1',
|
|
name: 'Familie',
|
|
permissions: ['READ_ALL'] as string[],
|
|
...overrides
|
|
});
|
|
|
|
describe('admin/groups/[id] page', () => {
|
|
it('renders the edit heading with the group name', async () => {
|
|
render(AdminGroupEditPage, { props: { data: { group: baseGroup() }, form: undefined } });
|
|
|
|
await expect.element(page.getByRole('heading', { name: /familie/i })).toBeVisible();
|
|
});
|
|
|
|
it('hydrates the name input from data.group.name', async () => {
|
|
render(AdminGroupEditPage, {
|
|
props: { data: { group: baseGroup({ name: 'Admins' }) }, form: undefined }
|
|
});
|
|
|
|
const input = document.querySelector('input[name="name"]') as HTMLInputElement;
|
|
expect(input.value).toBe('Admins');
|
|
});
|
|
|
|
it('checks the permission checkboxes that are in data.group.permissions', async () => {
|
|
render(AdminGroupEditPage, {
|
|
props: {
|
|
data: { group: baseGroup({ permissions: ['READ_ALL', 'ADMIN_TAG'] }) },
|
|
form: undefined
|
|
}
|
|
});
|
|
|
|
const readAll = document.querySelector(
|
|
'input[name="permissions"][value="READ_ALL"]'
|
|
) as HTMLInputElement;
|
|
const adminTag = document.querySelector(
|
|
'input[name="permissions"][value="ADMIN_TAG"]'
|
|
) as HTMLInputElement;
|
|
const writeAll = document.querySelector(
|
|
'input[name="permissions"][value="WRITE_ALL"]'
|
|
) as HTMLInputElement;
|
|
expect(readAll.checked).toBe(true);
|
|
expect(adminTag.checked).toBe(true);
|
|
expect(writeAll.checked).toBe(false);
|
|
});
|
|
|
|
it('shows the success banner when form.success is true', async () => {
|
|
render(AdminGroupEditPage, {
|
|
props: { data: { group: baseGroup() }, form: { success: true } }
|
|
});
|
|
|
|
await expect.element(page.getByText('Gruppe gespeichert.')).toBeVisible();
|
|
});
|
|
|
|
it('shows the error banner when form.error is set', async () => {
|
|
render(AdminGroupEditPage, {
|
|
props: {
|
|
data: { group: baseGroup() },
|
|
form: { error: 'Name darf nicht leer sein.' }
|
|
}
|
|
});
|
|
|
|
await expect.element(page.getByText('Name darf nicht leer sein.')).toBeVisible();
|
|
});
|
|
|
|
it('renders the cancel link to /admin/groups', async () => {
|
|
render(AdminGroupEditPage, { props: { data: { group: baseGroup() }, form: undefined } });
|
|
|
|
const links = document.querySelectorAll('a[href="/admin/groups"]');
|
|
expect(links.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('renders the delete and save buttons', async () => {
|
|
render(AdminGroupEditPage, { props: { data: { group: baseGroup() }, form: undefined } });
|
|
|
|
await expect.element(page.getByRole('button', { name: /löschen/i })).toBeVisible();
|
|
await expect.element(page.getByRole('button', { name: /speichern/i })).toBeVisible();
|
|
});
|
|
|
|
it('does not render success banner when form is undefined', async () => {
|
|
render(AdminGroupEditPage, { props: { data: { group: baseGroup() }, form: undefined } });
|
|
|
|
const banner = document.querySelector('.bg-green-50');
|
|
expect(banner).toBeNull();
|
|
});
|
|
|
|
it('does not render error-banner div when form.success is true (success path only)', async () => {
|
|
render(AdminGroupEditPage, {
|
|
props: { data: { group: baseGroup() }, form: { success: true } }
|
|
});
|
|
|
|
// Error banner is the <div> with bg-red-50 — the delete button is also red but is a button
|
|
const errorBanner = document.querySelector('div.bg-red-50');
|
|
expect(errorBanner).toBeNull();
|
|
});
|
|
|
|
it('renders all 8 permission checkboxes (4 standard + 4 admin)', async () => {
|
|
render(AdminGroupEditPage, { props: { data: { group: baseGroup() }, form: undefined } });
|
|
|
|
const checkboxes = document.querySelectorAll('input[name="permissions"]');
|
|
expect(checkboxes.length).toBe(8);
|
|
});
|
|
|
|
it('handles a group with empty permissions array', async () => {
|
|
render(AdminGroupEditPage, {
|
|
props: { data: { group: baseGroup({ permissions: [] }) }, form: undefined }
|
|
});
|
|
|
|
const checkboxes = Array.from(
|
|
document.querySelectorAll('input[name="permissions"]')
|
|
) as HTMLInputElement[];
|
|
expect(checkboxes.every((c) => !c.checked)).toBe(true);
|
|
});
|
|
});
|