From d046c89631cde3ce1fa926e3d5edc2e85d6eb122 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 12 Apr 2026 14:38:58 +0200 Subject: [PATCH] test(confirm): add ConfirmDialog component spec (12 tests) Covers: title/body rendering, destructive vs primary button class, custom labels, settle true/cancel, aria-labelledby, and hide-after-settle. Co-Authored-By: Claude Sonnet 4.6 --- .../components/ConfirmDialog.svelte.spec.ts | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 frontend/src/lib/components/ConfirmDialog.svelte.spec.ts diff --git a/frontend/src/lib/components/ConfirmDialog.svelte.spec.ts b/frontend/src/lib/components/ConfirmDialog.svelte.spec.ts new file mode 100644 index 00000000..9868a06d --- /dev/null +++ b/frontend/src/lib/components/ConfirmDialog.svelte.spec.ts @@ -0,0 +1,130 @@ +import { describe, it, expect, afterEach, vi } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import { page } from 'vitest/browser'; +import ConfirmDialog from './ConfirmDialog.svelte'; +import { createConfirmService, CONFIRM_KEY } from '$lib/services/confirm.svelte.js'; + +afterEach(cleanup); + +function renderDialog() { + const service = createConfirmService(); + const result = render(ConfirmDialog, { + context: new Map([[CONFIRM_KEY, service]]) + }); + return { ...result, service }; +} + +describe('ConfirmDialog', () => { + it('renders the title when options are set', async () => { + const { service } = renderDialog(); + service.confirm({ title: 'Delete this item?' }); + + await expect.element(page.getByText('Delete this item?')).toBeInTheDocument(); + service.settle(false); + }); + + it('renders the body when provided', async () => { + const { service } = renderDialog(); + service.confirm({ title: 'Delete?', body: 'This cannot be undone.' }); + + await expect.element(page.getByText('This cannot be undone.')).toBeInTheDocument(); + service.settle(false); + }); + + it('does not render body element when body is omitted', async () => { + const { service } = renderDialog(); + service.confirm({ title: 'Delete?' }); + + await expect.element(page.getByText('Delete?')).toBeInTheDocument(); + const body = document.querySelector('p'); + expect(body).toBeNull(); + service.settle(false); + }); + + it('applies bg-danger class on confirm button when destructive', async () => { + const { service } = renderDialog(); + service.confirm({ title: 'Delete?', destructive: true }); + + await expect.element(page.getByText('Delete?')).toBeInTheDocument(); + const confirmBtn = document.querySelector('button[class*="bg-danger"]'); + expect(confirmBtn).not.toBeNull(); + service.settle(false); + }); + + it('applies bg-primary class on confirm button when not destructive', async () => { + const { service } = renderDialog(); + service.confirm({ title: 'Confirm action?' }); + + await expect.element(page.getByText('Confirm action?')).toBeInTheDocument(); + const confirmBtn = document.querySelector('button[class*="bg-primary"]'); + expect(confirmBtn).not.toBeNull(); + service.settle(false); + }); + + it('renders custom confirmLabel when provided', async () => { + const { service } = renderDialog(); + service.confirm({ title: 'Remove?', confirmLabel: 'Yes, remove it' }); + + await expect.element(page.getByRole('button', { name: 'Yes, remove it' })).toBeInTheDocument(); + service.settle(false); + }); + + it('renders custom cancelLabel when provided', async () => { + const { service } = renderDialog(); + service.confirm({ title: 'Remove?', cancelLabel: 'No, keep it' }); + + await expect.element(page.getByRole('button', { name: 'No, keep it' })).toBeInTheDocument(); + service.settle(false); + }); + + it('settles true when confirm button is clicked', async () => { + const { service } = renderDialog(); + const resultPromise = service.confirm({ title: 'Do it?' }); + + await expect.element(page.getByText('Do it?')).toBeInTheDocument(); + const confirmBtn = document.querySelectorAll('button[type="button"]')[1]; + confirmBtn.click(); + + expect(await resultPromise).toBe(true); + }); + + it('settles false when cancel button is clicked', async () => { + const { service } = renderDialog(); + const resultPromise = service.confirm({ title: 'Do it?' }); + + await expect.element(page.getByText('Do it?')).toBeInTheDocument(); + const cancelBtn = document.querySelectorAll('button[type="button"]')[0]; + cancelBtn.click(); + + expect(await resultPromise).toBe(false); + }); + + it('hides content when no options are set', () => { + renderDialog(); + const heading = document.querySelector('#confirm-title'); + expect(heading).toBeNull(); + }); + + it('has aria-labelledby pointing to the title element', async () => { + const { service } = renderDialog(); + service.confirm({ title: 'Accessible title' }); + + await expect.element(page.getByText('Accessible title')).toBeInTheDocument(); + const dialog = document.querySelector('dialog'); + expect(dialog?.getAttribute('aria-labelledby')).toBe('confirm-title'); + const title = document.getElementById('confirm-title'); + expect(title).not.toBeNull(); + service.settle(false); + }); + + it('does not show content after settling', async () => { + const { service } = renderDialog(); + service.confirm({ title: 'Gone soon?' }); + await expect.element(page.getByText('Gone soon?')).toBeInTheDocument(); + service.settle(false); + + await vi.waitFor(() => { + expect(document.querySelector('#confirm-title')).toBeNull(); + }); + }); +});