test: inject real ConfirmService via context (batch 2/2)
Completes Phase 2a: geschichten/[id], persons/[id]/edit and admin/tags/[id] page specs now provide a real createConfirmService() via render context instead of mocking confirm.svelte. Zero confirm.svelte vi.mocks remain across the client suite (AC#4). Part of #560. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -10,9 +10,7 @@ vi.mock('$app/state', () => ({
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('$lib/shared/services/confirm.svelte', () => ({
|
import { createConfirmService, CONFIRM_KEY } from '$lib/shared/services/confirm.svelte.js';
|
||||||
getConfirmService: () => ({ confirm: async () => false })
|
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock('$app/navigation', () => ({
|
vi.mock('$app/navigation', () => ({
|
||||||
beforeNavigate: () => {},
|
beforeNavigate: () => {},
|
||||||
@@ -49,13 +47,17 @@ const baseData = (overrides: Record<string, unknown> = {}) => ({
|
|||||||
|
|
||||||
describe('admin/tags/[id] page', () => {
|
describe('admin/tags/[id] page', () => {
|
||||||
it('renders the edit heading with the tag name', async () => {
|
it('renders the edit heading with the tag name', async () => {
|
||||||
render(AdminTagEditPage, { props: { data: baseData(), form: undefined } });
|
render(AdminTagEditPage, {
|
||||||
|
context: new Map([[CONFIRM_KEY, createConfirmService()]]),
|
||||||
|
props: { data: baseData(), form: undefined }
|
||||||
|
});
|
||||||
|
|
||||||
await expect.element(page.getByRole('heading', { name: /personen/i })).toBeVisible();
|
await expect.element(page.getByRole('heading', { name: /personen/i })).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('hydrates the name input from data.tag.name', async () => {
|
it('hydrates the name input from data.tag.name', async () => {
|
||||||
render(AdminTagEditPage, {
|
render(AdminTagEditPage, {
|
||||||
|
context: new Map([[CONFIRM_KEY, createConfirmService()]]),
|
||||||
props: { data: baseData({ tag: baseTag({ name: 'Reisen' }) }), form: undefined }
|
props: { data: baseData({ tag: baseTag({ name: 'Reisen' }) }), form: undefined }
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -64,14 +66,20 @@ describe('admin/tags/[id] page', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('renders the color picker for top-level tags (no parentId)', async () => {
|
it('renders the color picker for top-level tags (no parentId)', async () => {
|
||||||
render(AdminTagEditPage, { props: { data: baseData(), form: undefined } });
|
render(AdminTagEditPage, {
|
||||||
|
context: new Map([[CONFIRM_KEY, createConfirmService()]]),
|
||||||
|
props: { data: baseData(), form: undefined }
|
||||||
|
});
|
||||||
|
|
||||||
const colorPicker = document.querySelector('[data-testid="color-picker"]');
|
const colorPicker = document.querySelector('[data-testid="color-picker"]');
|
||||||
expect(colorPicker).not.toBeNull();
|
expect(colorPicker).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders one color swatch per palette entry', async () => {
|
it('renders one color swatch per palette entry', async () => {
|
||||||
render(AdminTagEditPage, { props: { data: baseData(), form: undefined } });
|
render(AdminTagEditPage, {
|
||||||
|
context: new Map([[CONFIRM_KEY, createConfirmService()]]),
|
||||||
|
props: { data: baseData(), form: undefined }
|
||||||
|
});
|
||||||
|
|
||||||
const swatches = document.querySelectorAll('[data-testid^="color-swatch-"]');
|
const swatches = document.querySelectorAll('[data-testid^="color-swatch-"]');
|
||||||
expect(swatches.length).toBeGreaterThanOrEqual(10);
|
expect(swatches.length).toBeGreaterThanOrEqual(10);
|
||||||
@@ -79,6 +87,7 @@ describe('admin/tags/[id] page', () => {
|
|||||||
|
|
||||||
it('marks the active color swatch as aria-pressed', async () => {
|
it('marks the active color swatch as aria-pressed', async () => {
|
||||||
render(AdminTagEditPage, {
|
render(AdminTagEditPage, {
|
||||||
|
context: new Map([[CONFIRM_KEY, createConfirmService()]]),
|
||||||
props: { data: baseData({ tag: baseTag({ color: 'amber' }) }), form: undefined }
|
props: { data: baseData({ tag: baseTag({ color: 'amber' }) }), form: undefined }
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -88,6 +97,7 @@ describe('admin/tags/[id] page', () => {
|
|||||||
|
|
||||||
it('shows the form-success banner when form.success is true', async () => {
|
it('shows the form-success banner when form.success is true', async () => {
|
||||||
render(AdminTagEditPage, {
|
render(AdminTagEditPage, {
|
||||||
|
context: new Map([[CONFIRM_KEY, createConfirmService()]]),
|
||||||
props: { data: baseData(), form: { success: true } }
|
props: { data: baseData(), form: { success: true } }
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -97,6 +107,7 @@ describe('admin/tags/[id] page', () => {
|
|||||||
|
|
||||||
it('shows the form-error banner when form.error is set', async () => {
|
it('shows the form-error banner when form.error is set', async () => {
|
||||||
render(AdminTagEditPage, {
|
render(AdminTagEditPage, {
|
||||||
|
context: new Map([[CONFIRM_KEY, createConfirmService()]]),
|
||||||
props: { data: baseData(), form: { error: 'Tag name already in use' } }
|
props: { data: baseData(), form: { error: 'Tag name already in use' } }
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -106,6 +117,7 @@ describe('admin/tags/[id] page', () => {
|
|||||||
|
|
||||||
it('shows the merge-success banner when data.mergeSuccess is set', async () => {
|
it('shows the merge-success banner when data.mergeSuccess is set', async () => {
|
||||||
render(AdminTagEditPage, {
|
render(AdminTagEditPage, {
|
||||||
|
context: new Map([[CONFIRM_KEY, createConfirmService()]]),
|
||||||
props: { data: baseData({ mergeSuccess: 'old-id' }), form: undefined }
|
props: { data: baseData({ mergeSuccess: 'old-id' }), form: undefined }
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -115,6 +127,7 @@ describe('admin/tags/[id] page', () => {
|
|||||||
|
|
||||||
it('hides the color picker for child tags (parentId set)', async () => {
|
it('hides the color picker for child tags (parentId set)', async () => {
|
||||||
render(AdminTagEditPage, {
|
render(AdminTagEditPage, {
|
||||||
|
context: new Map([[CONFIRM_KEY, createConfirmService()]]),
|
||||||
props: {
|
props: {
|
||||||
data: baseData({ tag: baseTag({ parentId: 't-parent' }) }),
|
data: baseData({ tag: baseTag({ parentId: 't-parent' }) }),
|
||||||
form: undefined
|
form: undefined
|
||||||
@@ -126,7 +139,10 @@ describe('admin/tags/[id] page', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('does not show form-success banner when form is undefined', async () => {
|
it('does not show form-success banner when form is undefined', async () => {
|
||||||
render(AdminTagEditPage, { props: { data: baseData(), form: undefined } });
|
render(AdminTagEditPage, {
|
||||||
|
context: new Map([[CONFIRM_KEY, createConfirmService()]]),
|
||||||
|
props: { data: baseData(), form: undefined }
|
||||||
|
});
|
||||||
|
|
||||||
const banners = document.querySelectorAll('.bg-green-50');
|
const banners = document.querySelectorAll('.bg-green-50');
|
||||||
// Some other green elements may exist, but the form-success specifically
|
// Some other green elements may exist, but the form-success specifically
|
||||||
@@ -139,6 +155,7 @@ describe('admin/tags/[id] page', () => {
|
|||||||
|
|
||||||
it('hides the merge-success banner when mergeSuccess is null', async () => {
|
it('hides the merge-success banner when mergeSuccess is null', async () => {
|
||||||
render(AdminTagEditPage, {
|
render(AdminTagEditPage, {
|
||||||
|
context: new Map([[CONFIRM_KEY, createConfirmService()]]),
|
||||||
props: { data: baseData({ mergeSuccess: null }), form: undefined }
|
props: { data: baseData({ mergeSuccess: null }), form: undefined }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import { describe, it, expect, vi, afterEach } from 'vitest';
|
import { describe, it, expect, afterEach } from 'vitest';
|
||||||
import { cleanup, render } from 'vitest-browser-svelte';
|
import { cleanup, render } from 'vitest-browser-svelte';
|
||||||
import { page } from 'vitest/browser';
|
import { page } from 'vitest/browser';
|
||||||
|
|
||||||
vi.mock('$lib/shared/services/confirm.svelte', () => ({
|
import { createConfirmService, CONFIRM_KEY } from '$lib/shared/services/confirm.svelte.js';
|
||||||
getConfirmService: () => ({ confirm: async () => false })
|
|
||||||
}));
|
|
||||||
|
|
||||||
const { default: GeschichtePage } = await import('./+page.svelte');
|
const { default: GeschichtePage } = await import('./+page.svelte');
|
||||||
|
|
||||||
@@ -33,7 +31,10 @@ const baseData = (overrides: Record<string, unknown> = {}) => ({
|
|||||||
|
|
||||||
describe('geschichten/[id] page', () => {
|
describe('geschichten/[id] page', () => {
|
||||||
it('renders the geschichte title as the level-1 heading', async () => {
|
it('renders the geschichte title as the level-1 heading', async () => {
|
||||||
render(GeschichtePage, { props: { data: baseData() } });
|
render(GeschichtePage, {
|
||||||
|
context: new Map([[CONFIRM_KEY, createConfirmService()]]),
|
||||||
|
props: { data: baseData() }
|
||||||
|
});
|
||||||
|
|
||||||
await expect
|
await expect
|
||||||
.element(page.getByRole('heading', { level: 1, name: /reise nach berlin/i }))
|
.element(page.getByRole('heading', { level: 1, name: /reise nach berlin/i }))
|
||||||
@@ -41,13 +42,17 @@ describe('geschichten/[id] page', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('renders the author full name from firstName + lastName', async () => {
|
it('renders the author full name from firstName + lastName', async () => {
|
||||||
render(GeschichtePage, { props: { data: baseData() } });
|
render(GeschichtePage, {
|
||||||
|
context: new Map([[CONFIRM_KEY, createConfirmService()]]),
|
||||||
|
props: { data: baseData() }
|
||||||
|
});
|
||||||
|
|
||||||
await expect.element(page.getByText(/Anna Schmidt/)).toBeVisible();
|
await expect.element(page.getByText(/Anna Schmidt/)).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('falls back to author email when no name is set', async () => {
|
it('falls back to author email when no name is set', async () => {
|
||||||
render(GeschichtePage, {
|
render(GeschichtePage, {
|
||||||
|
context: new Map([[CONFIRM_KEY, createConfirmService()]]),
|
||||||
props: {
|
props: {
|
||||||
data: baseData({
|
data: baseData({
|
||||||
geschichte: baseGeschichte({
|
geschichte: baseGeschichte({
|
||||||
@@ -62,6 +67,7 @@ describe('geschichten/[id] page', () => {
|
|||||||
|
|
||||||
it('renders an empty author when author is null', async () => {
|
it('renders an empty author when author is null', async () => {
|
||||||
render(GeschichtePage, {
|
render(GeschichtePage, {
|
||||||
|
context: new Map([[CONFIRM_KEY, createConfirmService()]]),
|
||||||
props: { data: baseData({ geschichte: baseGeschichte({ author: null }) }) }
|
props: { data: baseData({ geschichte: baseGeschichte({ author: null }) }) }
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -69,13 +75,17 @@ describe('geschichten/[id] page', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('renders the publishedAt date suffix when publishedAt is set', async () => {
|
it('renders the publishedAt date suffix when publishedAt is set', async () => {
|
||||||
render(GeschichtePage, { props: { data: baseData() } });
|
render(GeschichtePage, {
|
||||||
|
context: new Map([[CONFIRM_KEY, createConfirmService()]]),
|
||||||
|
props: { data: baseData() }
|
||||||
|
});
|
||||||
|
|
||||||
await expect.element(page.getByText(/veröffentlicht am/i)).toBeVisible();
|
await expect.element(page.getByText(/veröffentlicht am/i)).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('omits the publishedAt suffix when publishedAt is null', async () => {
|
it('omits the publishedAt suffix when publishedAt is null', async () => {
|
||||||
render(GeschichtePage, {
|
render(GeschichtePage, {
|
||||||
|
context: new Map([[CONFIRM_KEY, createConfirmService()]]),
|
||||||
props: { data: baseData({ geschichte: baseGeschichte({ publishedAt: null }) }) }
|
props: { data: baseData({ geschichte: baseGeschichte({ publishedAt: null }) }) }
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -83,13 +93,17 @@ describe('geschichten/[id] page', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('omits the persons section when there are no linked persons', async () => {
|
it('omits the persons section when there are no linked persons', async () => {
|
||||||
render(GeschichtePage, { props: { data: baseData() } });
|
render(GeschichtePage, {
|
||||||
|
context: new Map([[CONFIRM_KEY, createConfirmService()]]),
|
||||||
|
props: { data: baseData() }
|
||||||
|
});
|
||||||
|
|
||||||
await expect.element(page.getByText(/Personen in dieser Geschichte/i)).not.toBeInTheDocument();
|
await expect.element(page.getByText(/Personen in dieser Geschichte/i)).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders the persons section when there are linked persons', async () => {
|
it('renders the persons section when there are linked persons', async () => {
|
||||||
render(GeschichtePage, {
|
render(GeschichtePage, {
|
||||||
|
context: new Map([[CONFIRM_KEY, createConfirmService()]]),
|
||||||
props: {
|
props: {
|
||||||
data: baseData({
|
data: baseData({
|
||||||
geschichte: baseGeschichte({
|
geschichte: baseGeschichte({
|
||||||
@@ -108,13 +122,17 @@ describe('geschichten/[id] page', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('omits the documents section when there are no linked documents', async () => {
|
it('omits the documents section when there are no linked documents', async () => {
|
||||||
render(GeschichtePage, { props: { data: baseData() } });
|
render(GeschichtePage, {
|
||||||
|
context: new Map([[CONFIRM_KEY, createConfirmService()]]),
|
||||||
|
props: { data: baseData() }
|
||||||
|
});
|
||||||
|
|
||||||
await expect.element(page.getByText('Erwähnte Dokumente')).not.toBeInTheDocument();
|
await expect.element(page.getByText('Erwähnte Dokumente')).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders the documents section when there are linked documents', async () => {
|
it('renders the documents section when there are linked documents', async () => {
|
||||||
render(GeschichtePage, {
|
render(GeschichtePage, {
|
||||||
|
context: new Map([[CONFIRM_KEY, createConfirmService()]]),
|
||||||
props: {
|
props: {
|
||||||
data: baseData({
|
data: baseData({
|
||||||
geschichte: baseGeschichte({
|
geschichte: baseGeschichte({
|
||||||
@@ -129,7 +147,10 @@ describe('geschichten/[id] page', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('renders edit and delete actions when canBlogWrite is true', async () => {
|
it('renders edit and delete actions when canBlogWrite is true', async () => {
|
||||||
render(GeschichtePage, { props: { data: baseData({ canBlogWrite: true }) } });
|
render(GeschichtePage, {
|
||||||
|
context: new Map([[CONFIRM_KEY, createConfirmService()]]),
|
||||||
|
props: { data: baseData({ canBlogWrite: true }) }
|
||||||
|
});
|
||||||
|
|
||||||
await expect
|
await expect
|
||||||
.element(page.getByRole('link', { name: /bearbeiten/i }))
|
.element(page.getByRole('link', { name: /bearbeiten/i }))
|
||||||
@@ -138,7 +159,10 @@ describe('geschichten/[id] page', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('hides edit and delete actions when canBlogWrite is false', async () => {
|
it('hides edit and delete actions when canBlogWrite is false', async () => {
|
||||||
render(GeschichtePage, { props: { data: baseData({ canBlogWrite: false }) } });
|
render(GeschichtePage, {
|
||||||
|
context: new Map([[CONFIRM_KEY, createConfirmService()]]),
|
||||||
|
props: { data: baseData({ canBlogWrite: false }) }
|
||||||
|
});
|
||||||
|
|
||||||
await expect.element(page.getByRole('link', { name: /bearbeiten/i })).not.toBeInTheDocument();
|
await expect.element(page.getByRole('link', { name: /bearbeiten/i })).not.toBeInTheDocument();
|
||||||
await expect.element(page.getByRole('button', { name: /löschen/i })).not.toBeInTheDocument();
|
await expect.element(page.getByRole('button', { name: /löschen/i })).not.toBeInTheDocument();
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import { describe, it, expect, vi, afterEach } from 'vitest';
|
import { describe, it, expect, afterEach } from 'vitest';
|
||||||
import { cleanup, render } from 'vitest-browser-svelte';
|
import { cleanup, render } from 'vitest-browser-svelte';
|
||||||
import { page } from 'vitest/browser';
|
import { page } from 'vitest/browser';
|
||||||
|
|
||||||
vi.mock('$lib/shared/services/confirm.svelte', () => ({
|
import { createConfirmService, CONFIRM_KEY } from '$lib/shared/services/confirm.svelte.js';
|
||||||
getConfirmService: () => ({ confirm: async () => false })
|
|
||||||
}));
|
|
||||||
|
|
||||||
const { default: PersonEditPage } = await import('./+page.svelte');
|
const { default: PersonEditPage } = await import('./+page.svelte');
|
||||||
|
|
||||||
@@ -29,19 +27,26 @@ const baseData = (overrides: Record<string, unknown> = {}) => ({
|
|||||||
|
|
||||||
describe('persons/[id]/edit page', () => {
|
describe('persons/[id]/edit page', () => {
|
||||||
it('renders the edit heading', async () => {
|
it('renders the edit heading', async () => {
|
||||||
render(PersonEditPage, { props: { data: baseData(), form: undefined } });
|
render(PersonEditPage, {
|
||||||
|
context: new Map([[CONFIRM_KEY, createConfirmService()]]),
|
||||||
|
props: { data: baseData(), form: undefined }
|
||||||
|
});
|
||||||
|
|
||||||
await expect.element(page.getByRole('heading', { name: /person bearbeiten/i })).toBeVisible();
|
await expect.element(page.getByRole('heading', { name: /person bearbeiten/i })).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders the persons-section heading', async () => {
|
it('renders the persons-section heading', async () => {
|
||||||
render(PersonEditPage, { props: { data: baseData(), form: undefined } });
|
render(PersonEditPage, {
|
||||||
|
context: new Map([[CONFIRM_KEY, createConfirmService()]]),
|
||||||
|
props: { data: baseData(), form: undefined }
|
||||||
|
});
|
||||||
|
|
||||||
await expect.element(page.getByRole('heading', { name: /angaben zur person/i })).toBeVisible();
|
await expect.element(page.getByRole('heading', { name: /angaben zur person/i })).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows the form-error banner when form.updateError is set', async () => {
|
it('shows the form-error banner when form.updateError is set', async () => {
|
||||||
render(PersonEditPage, {
|
render(PersonEditPage, {
|
||||||
|
context: new Map([[CONFIRM_KEY, createConfirmService()]]),
|
||||||
props: { data: baseData(), form: { updateError: 'Last name is required' } }
|
props: { data: baseData(), form: { updateError: 'Last name is required' } }
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -49,14 +54,20 @@ describe('persons/[id]/edit page', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('does not show the form-error banner when form is undefined', async () => {
|
it('does not show the form-error banner when form is undefined', async () => {
|
||||||
render(PersonEditPage, { props: { data: baseData(), form: undefined } });
|
render(PersonEditPage, {
|
||||||
|
context: new Map([[CONFIRM_KEY, createConfirmService()]]),
|
||||||
|
props: { data: baseData(), form: undefined }
|
||||||
|
});
|
||||||
|
|
||||||
const banner = document.querySelector('.bg-red-50.border-red-200');
|
const banner = document.querySelector('.bg-red-50.border-red-200');
|
||||||
expect(banner).toBeNull();
|
expect(banner).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders the save bar with the discard href pointing to the person detail', async () => {
|
it('renders the save bar with the discard href pointing to the person detail', async () => {
|
||||||
render(PersonEditPage, { props: { data: baseData(), form: undefined } });
|
render(PersonEditPage, {
|
||||||
|
context: new Map([[CONFIRM_KEY, createConfirmService()]]),
|
||||||
|
props: { data: baseData(), form: undefined }
|
||||||
|
});
|
||||||
|
|
||||||
const link = document.querySelector('a[href="/persons/p-1"]');
|
const link = document.querySelector('a[href="/persons/p-1"]');
|
||||||
expect(link).not.toBeNull();
|
expect(link).not.toBeNull();
|
||||||
|
|||||||
Reference in New Issue
Block a user