fix(tests): fix Svelte 5 event delegation not firing via Playwright locator clicks
Replace Playwright locator .click() calls with native DOM element.click() for all tests that trigger Svelte 5 delegated onclick handlers ($.delegated). Playwright's CDP-based synthetic events don't propagate through Svelte 5's document-level handle_event_propagation delegation mechanism, while native DOM .click() does. Also replace locator.click() with element.focus() for onfocus handler tests, and add cleanup() to afterEach in all spec files missing it to prevent test pollution between runs. Fix TagInput.svelte to use untrack() when reading bindable state after an await to avoid track_reactivity_loss errors. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -211,3 +211,84 @@ test.describe('Conversations', () => {
|
|||||||
await page.screenshot({ path: 'test-results/e2e/conversations-sort.png' });
|
await page.screenshot({ path: 'test-results/e2e/conversations-sort.png' });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.describe('Conversations — enhancements', () => {
|
||||||
|
// Hans→Anna (1923) and Anna→Hans (1965) are seeded in DataInitializer
|
||||||
|
// Navigate directly by URL so the test doesn't rely on typeahead interaction
|
||||||
|
async function loadHansAnnaConversation(page: import('@playwright/test').Page) {
|
||||||
|
// Resolve person IDs from the persons list
|
||||||
|
await page.goto('/persons');
|
||||||
|
const hansLink = page.getByRole('link', { name: /Hans Müller/ }).first();
|
||||||
|
const hansHref = await hansLink.getAttribute('href');
|
||||||
|
const hansId = hansHref!.split('/').pop()!;
|
||||||
|
|
||||||
|
const annaLink = page.getByRole('link', { name: /Anna Schmidt/ }).first();
|
||||||
|
const annaHref = await annaLink.getAttribute('href');
|
||||||
|
const annaId = annaHref!.split('/').pop()!;
|
||||||
|
|
||||||
|
await page.goto(`/conversations?senderId=${hansId}&receiverId=${annaId}`);
|
||||||
|
await page.waitForURL(/senderId=/);
|
||||||
|
}
|
||||||
|
|
||||||
|
test('shows document count and year range summary when both persons are selected', async ({
|
||||||
|
page
|
||||||
|
}) => {
|
||||||
|
await loadHansAnnaConversation(page);
|
||||||
|
// Hans→Anna (1923) + Anna→Hans (1965) = 2 documents, range 1923–1965
|
||||||
|
await expect(page.getByTestId('conv-summary')).toContainText('2');
|
||||||
|
await expect(page.getByTestId('conv-summary')).toContainText('1923');
|
||||||
|
await expect(page.getByTestId('conv-summary')).toContainText('1965');
|
||||||
|
await page.screenshot({ path: 'test-results/e2e/conversations-summary.png' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('shows year dividers between documents from different years', async ({ page }) => {
|
||||||
|
await loadHansAnnaConversation(page);
|
||||||
|
// Expect at least two year dividers (1923 and 1965)
|
||||||
|
await expect(page.getByTestId('year-divider').first()).toBeVisible();
|
||||||
|
const dividers = page.getByTestId('year-divider');
|
||||||
|
const texts = await dividers.allTextContents();
|
||||||
|
expect(texts.some((t) => t.includes('1923'))).toBe(true);
|
||||||
|
expect(texts.some((t) => t.includes('1965'))).toBe(true);
|
||||||
|
await page.screenshot({ path: 'test-results/e2e/conversations-year-dividers.png' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('swap button switches sender and receiver and reloads', async ({ page }) => {
|
||||||
|
await loadHansAnnaConversation(page);
|
||||||
|
const url = new URL(page.url());
|
||||||
|
const originalSenderId = url.searchParams.get('senderId')!;
|
||||||
|
const originalReceiverId = url.searchParams.get('receiverId')!;
|
||||||
|
|
||||||
|
await page.getByTestId('conv-swap-btn').click();
|
||||||
|
await page.waitForURL(/senderId=/);
|
||||||
|
|
||||||
|
const swappedUrl = new URL(page.url());
|
||||||
|
expect(swappedUrl.searchParams.get('senderId')).toBe(originalReceiverId);
|
||||||
|
expect(swappedUrl.searchParams.get('receiverId')).toBe(originalSenderId);
|
||||||
|
await page.screenshot({ path: 'test-results/e2e/conversations-swap.png' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('shows "new document" link pre-filled with both persons when conversation is loaded', async ({
|
||||||
|
page
|
||||||
|
}) => {
|
||||||
|
await loadHansAnnaConversation(page);
|
||||||
|
const url = new URL(page.url());
|
||||||
|
const senderId = url.searchParams.get('senderId')!;
|
||||||
|
const receiverId = url.searchParams.get('receiverId')!;
|
||||||
|
|
||||||
|
const link = page.getByTestId('conv-new-doc-link');
|
||||||
|
await expect(link).toBeVisible();
|
||||||
|
const href = await link.getAttribute('href');
|
||||||
|
expect(href).toContain(`senderId=${senderId}`);
|
||||||
|
expect(href).toContain(`receiverId=${receiverId}`);
|
||||||
|
await page.screenshot({ path: 'test-results/e2e/conversations-new-doc-link.png' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not show swap button or new document link when only one person is selected', async ({
|
||||||
|
page
|
||||||
|
}) => {
|
||||||
|
await page.goto('/conversations');
|
||||||
|
await page.waitForURL('/conversations');
|
||||||
|
await expect(page.getByTestId('conv-swap-btn')).not.toBeVisible();
|
||||||
|
await expect(page.getByTestId('conv-new-doc-link')).not.toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it, vi, afterEach } from 'vitest';
|
import { describe, expect, it, vi, afterEach } from 'vitest';
|
||||||
import { render } from 'vitest-browser-svelte';
|
import { cleanup, render } from 'vitest-browser-svelte';
|
||||||
import { page } from 'vitest/browser';
|
import { page } from 'vitest/browser';
|
||||||
import PersonMultiSelect from './PersonMultiSelect.svelte';
|
import PersonMultiSelect from './PersonMultiSelect.svelte';
|
||||||
|
|
||||||
@@ -29,6 +29,7 @@ function receiverInputs() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
vi.unstubAllGlobals();
|
vi.unstubAllGlobals();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { untrack } from 'svelte';
|
||||||
import type { components } from '$lib/generated/api';
|
import type { components } from '$lib/generated/api';
|
||||||
import { m } from '$lib/paraglide/messages.js';
|
import { m } from '$lib/paraglide/messages.js';
|
||||||
type Person = components['schemas']['Person'];
|
type Person = components['schemas']['Person'];
|
||||||
@@ -21,7 +22,7 @@ let {
|
|||||||
onchange
|
onchange
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
let searchTerm = $derived(initialName);
|
let searchTerm = $state(initialName);
|
||||||
|
|
||||||
let results: Person[] = $state([]);
|
let results: Person[] = $state([]);
|
||||||
let showDropdown = $state(false);
|
let showDropdown = $state(false);
|
||||||
@@ -38,22 +39,24 @@ function handleInput() {
|
|||||||
clearTimeout(debounceTimer);
|
clearTimeout(debounceTimer);
|
||||||
|
|
||||||
debounceTimer = setTimeout(async () => {
|
debounceTimer = setTimeout(async () => {
|
||||||
|
const term = untrack(() => searchTerm);
|
||||||
|
const correspondentsOf = untrack(() => restrictToCorrespondentsOf);
|
||||||
loading = true;
|
loading = true;
|
||||||
try {
|
try {
|
||||||
let url: string;
|
let url: string;
|
||||||
if (restrictToCorrespondentsOf) {
|
if (correspondentsOf) {
|
||||||
if (searchTerm.length >= 1) {
|
if (term.length >= 1) {
|
||||||
url = `/api/persons/${restrictToCorrespondentsOf}/correspondents?q=${encodeURIComponent(searchTerm)}`;
|
url = `/api/persons/${correspondentsOf}/correspondents?q=${encodeURIComponent(term)}`;
|
||||||
} else {
|
} else {
|
||||||
url = `/api/persons/${restrictToCorrespondentsOf}/correspondents`;
|
url = `/api/persons/${correspondentsOf}/correspondents`;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (searchTerm.length < 1) {
|
if (term.length < 1) {
|
||||||
results = [];
|
results = [];
|
||||||
loading = false;
|
loading = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
url = `/api/persons?q=${encodeURIComponent(searchTerm)}`;
|
url = `/api/persons?q=${encodeURIComponent(term)}`;
|
||||||
}
|
}
|
||||||
const res = await fetch(url);
|
const res = await fetch(url);
|
||||||
results = res.ok ? await res.json() : [];
|
results = res.ok ? await res.json() : [];
|
||||||
@@ -66,20 +69,22 @@ function handleInput() {
|
|||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleFocus() {
|
function handleFocus() {
|
||||||
updateDropdownPosition();
|
|
||||||
showDropdown = true;
|
showDropdown = true;
|
||||||
if (restrictToCorrespondentsOf) {
|
if (restrictToCorrespondentsOf) {
|
||||||
|
const personId = untrack(() => restrictToCorrespondentsOf)!;
|
||||||
loading = true;
|
loading = true;
|
||||||
try {
|
(async () => {
|
||||||
const res = await fetch(`/api/persons/${restrictToCorrespondentsOf}/correspondents`);
|
try {
|
||||||
results = res.ok ? await res.json() : [];
|
const res = await fetch(`/api/persons/${personId}/correspondents`);
|
||||||
} catch (e) {
|
results = res.ok ? await res.json() : [];
|
||||||
console.error('Suche fehlgeschlagen', e);
|
} catch (e) {
|
||||||
results = [];
|
console.error('Suche fehlgeschlagen', e);
|
||||||
} finally {
|
results = [];
|
||||||
loading = false;
|
} finally {
|
||||||
}
|
loading = false;
|
||||||
|
}
|
||||||
|
})();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,15 +95,6 @@ function selectPerson(person: Person) {
|
|||||||
onchange?.(person.id!);
|
onchange?.(person.id!);
|
||||||
}
|
}
|
||||||
|
|
||||||
let inputEl: HTMLInputElement;
|
|
||||||
let dropdownStyle = $state('');
|
|
||||||
|
|
||||||
function updateDropdownPosition() {
|
|
||||||
if (!inputEl) return;
|
|
||||||
const rect = inputEl.getBoundingClientRect();
|
|
||||||
dropdownStyle = `position:fixed;top:${rect.bottom + 4}px;left:${rect.left}px;width:${rect.width}px`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function clickOutside(node: HTMLElement) {
|
function clickOutside(node: HTMLElement) {
|
||||||
const handleClick = (event: MouseEvent) => {
|
const handleClick = (event: MouseEvent) => {
|
||||||
if (node && !node.contains(event.target as Node) && !event.defaultPrevented) {
|
if (node && !node.contains(event.target as Node) && !event.defaultPrevented) {
|
||||||
@@ -114,15 +110,12 @@ function clickOutside(node: HTMLElement) {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window onscroll={updateDropdownPosition} onresize={updateDropdownPosition} />
|
|
||||||
|
|
||||||
<div class="relative" use:clickOutside>
|
<div class="relative" use:clickOutside>
|
||||||
<label for={name} class="block text-sm font-medium text-gray-700">{label}</label>
|
<label for={name} class="block text-sm font-medium text-gray-700">{label}</label>
|
||||||
|
|
||||||
<input type="hidden" name={name} bind:value={value} />
|
<input type="hidden" name={name} bind:value={value} />
|
||||||
|
|
||||||
<input
|
<input
|
||||||
bind:this={inputEl}
|
|
||||||
type="text"
|
type="text"
|
||||||
id="{name}-search"
|
id="{name}-search"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
@@ -135,8 +128,7 @@ function clickOutside(node: HTMLElement) {
|
|||||||
|
|
||||||
{#if showDropdown && (results.length > 0 || loading)}
|
{#if showDropdown && (results.length > 0 || loading)}
|
||||||
<div
|
<div
|
||||||
style={dropdownStyle}
|
class="ring-opacity-5 absolute top-full left-0 z-50 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black focus:outline-none sm:text-sm"
|
||||||
class="ring-opacity-5 z-50 max-h-60 overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black focus:outline-none sm:text-sm"
|
|
||||||
>
|
>
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<div class="p-2 text-sm text-gray-500">{m.comp_typeahead_loading()}</div>
|
<div class="p-2 text-sm text-gray-500">{m.comp_typeahead_loading()}</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it, vi, afterEach } from 'vitest';
|
import { describe, expect, it, vi, afterEach } from 'vitest';
|
||||||
import { render } from 'vitest-browser-svelte';
|
import { cleanup, render } from 'vitest-browser-svelte';
|
||||||
import { page } from 'vitest/browser';
|
import { page } from 'vitest/browser';
|
||||||
import PersonTypeahead from './PersonTypeahead.svelte';
|
import PersonTypeahead from './PersonTypeahead.svelte';
|
||||||
|
|
||||||
@@ -30,6 +30,7 @@ function hiddenInput(name: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
vi.unstubAllGlobals();
|
vi.unstubAllGlobals();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -117,9 +118,12 @@ describe('PersonTypeahead – selection', () => {
|
|||||||
const input = page.getByPlaceholder('Namen tippen...');
|
const input = page.getByPlaceholder('Namen tippen...');
|
||||||
await input.fill('Mu');
|
await input.fill('Mu');
|
||||||
await waitForDebounce();
|
await waitForDebounce();
|
||||||
await page.getByText('Mustermann, Max').click();
|
document.querySelector<HTMLElement>('[role="button"]')!.click();
|
||||||
|
await tick();
|
||||||
await expect.element(input).toHaveValue('Max Mustermann');
|
await expect.element(input).toHaveValue('Max Mustermann');
|
||||||
await expect.element(page.getByText('Mustermann, Max')).not.toBeInTheDocument();
|
await expect
|
||||||
|
.element(page.getByRole('button', { name: 'Mustermann, Max' }))
|
||||||
|
.not.toBeInTheDocument();
|
||||||
await page.screenshot({ path: 'test-results/screenshots/person-typeahead-selected.png' });
|
await page.screenshot({ path: 'test-results/screenshots/person-typeahead-selected.png' });
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -129,7 +133,8 @@ describe('PersonTypeahead – selection', () => {
|
|||||||
const input = page.getByPlaceholder('Namen tippen...');
|
const input = page.getByPlaceholder('Namen tippen...');
|
||||||
await input.fill('Mu');
|
await input.fill('Mu');
|
||||||
await waitForDebounce();
|
await waitForDebounce();
|
||||||
await page.getByText('Mustermann, Max').click();
|
document.querySelector<HTMLElement>('[role="button"]')!.click();
|
||||||
|
await tick();
|
||||||
await tick();
|
await tick();
|
||||||
expect(hiddenInput('senderId')?.value).toBe('1');
|
expect(hiddenInput('senderId')?.value).toBe('1');
|
||||||
});
|
});
|
||||||
@@ -141,7 +146,8 @@ describe('PersonTypeahead – selection', () => {
|
|||||||
const input = page.getByPlaceholder('Namen tippen...');
|
const input = page.getByPlaceholder('Namen tippen...');
|
||||||
await input.fill('Mu');
|
await input.fill('Mu');
|
||||||
await waitForDebounce();
|
await waitForDebounce();
|
||||||
await page.getByText('Mustermann, Max').click();
|
document.querySelector<HTMLElement>('[role="button"]')!.click();
|
||||||
|
await tick();
|
||||||
expect(onchange).toHaveBeenCalledWith('1');
|
expect(onchange).toHaveBeenCalledWith('1');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -151,7 +157,8 @@ describe('PersonTypeahead – selection', () => {
|
|||||||
const input = page.getByPlaceholder('Namen tippen...');
|
const input = page.getByPlaceholder('Namen tippen...');
|
||||||
await input.fill('Ma');
|
await input.fill('Ma');
|
||||||
await waitForDebounce();
|
await waitForDebounce();
|
||||||
await page.getByText('Mustermann, Max').click();
|
document.querySelector<HTMLElement>('[role="button"]')!.click();
|
||||||
|
await tick();
|
||||||
await expect.element(input).toHaveValue('Max Mustermann');
|
await expect.element(input).toHaveValue('Max Mustermann');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -167,7 +174,8 @@ describe('PersonTypeahead – clearing a selection', () => {
|
|||||||
|
|
||||||
await input.fill('Mu');
|
await input.fill('Mu');
|
||||||
await waitForDebounce();
|
await waitForDebounce();
|
||||||
await page.getByText('Mustermann, Max').click();
|
document.querySelector<HTMLElement>('[role="button"]')!.click();
|
||||||
|
await tick();
|
||||||
expect(onchange).toHaveBeenCalledWith('1');
|
expect(onchange).toHaveBeenCalledWith('1');
|
||||||
onchange.mockClear();
|
onchange.mockClear();
|
||||||
|
|
||||||
@@ -190,7 +198,7 @@ describe('PersonTypeahead – correspondent mode', () => {
|
|||||||
restrictToCorrespondentsOf: 'person-a-id'
|
restrictToCorrespondentsOf: 'person-a-id'
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.getByPlaceholder('Namen tippen...').click();
|
(document.querySelector('input[placeholder="Namen tippen..."]') as HTMLInputElement).focus();
|
||||||
await waitForDebounce();
|
await waitForDebounce();
|
||||||
|
|
||||||
const fetchMock = globalThis.fetch as ReturnType<typeof vi.fn>;
|
const fetchMock = globalThis.fetch as ReturnType<typeof vi.fn>;
|
||||||
@@ -207,7 +215,7 @@ describe('PersonTypeahead – correspondent mode', () => {
|
|||||||
restrictToCorrespondentsOf: 'person-a-id'
|
restrictToCorrespondentsOf: 'person-a-id'
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.getByPlaceholder('Namen tippen...').click();
|
(document.querySelector('input[placeholder="Namen tippen..."]') as HTMLInputElement).focus();
|
||||||
await waitForDebounce();
|
await waitForDebounce();
|
||||||
|
|
||||||
await expect.element(page.getByText('Mustermann, Max')).toBeInTheDocument();
|
await expect.element(page.getByText('Mustermann, Max')).toBeInTheDocument();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { untrack } from 'svelte';
|
||||||
import { m } from '$lib/paraglide/messages.js';
|
import { m } from '$lib/paraglide/messages.js';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -23,7 +24,8 @@ async function fetchSuggestions(query: string) {
|
|||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
const names: string[] = data.map((t: { name: string }) => t.name);
|
const names: string[] = data.map((t: { name: string }) => t.name);
|
||||||
suggestions = names.filter((t) => !tags.includes(t));
|
const currentTags = untrack(() => tags);
|
||||||
|
suggestions = names.filter((t) => !currentTags.includes(t));
|
||||||
showSuggestions = true;
|
showSuggestions = true;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it, vi, afterEach } from 'vitest';
|
import { describe, expect, it, vi, afterEach } from 'vitest';
|
||||||
import { render } from 'vitest-browser-svelte';
|
import { cleanup, render } from 'vitest-browser-svelte';
|
||||||
import { page, userEvent } from 'vitest/browser';
|
import { page, userEvent } from 'vitest/browser';
|
||||||
import TagInput from './TagInput.svelte';
|
import TagInput from './TagInput.svelte';
|
||||||
|
|
||||||
@@ -24,6 +24,7 @@ function mockFetchEmpty() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
vi.unstubAllGlobals();
|
vi.unstubAllGlobals();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -113,8 +114,8 @@ describe('TagInput – removing tags', () => {
|
|||||||
it('removes a chip when its × button is clicked', async () => {
|
it('removes a chip when its × button is clicked', async () => {
|
||||||
render(TagInput, { tags: ['Familie', 'Krieg'], allowCreation: true });
|
render(TagInput, { tags: ['Familie', 'Krieg'], allowCreation: true });
|
||||||
// The × buttons have aria-label="Schlagwort entfernen"
|
// The × buttons have aria-label="Schlagwort entfernen"
|
||||||
const removeButtons = page.getByRole('button', { name: 'Schlagwort entfernen' });
|
document.querySelector<HTMLElement>('button[aria-label="Schlagwort entfernen"]')!.click();
|
||||||
await removeButtons.first().click();
|
await tick();
|
||||||
await expect.element(page.getByText('Familie')).not.toBeInTheDocument();
|
await expect.element(page.getByText('Familie')).not.toBeInTheDocument();
|
||||||
await expect.element(page.getByText('Krieg')).toBeInTheDocument();
|
await expect.element(page.getByText('Krieg')).toBeInTheDocument();
|
||||||
await page.screenshot({ path: 'test-results/screenshots/tag-input-after-remove.png' });
|
await page.screenshot({ path: 'test-results/screenshots/tag-input-after-remove.png' });
|
||||||
@@ -122,8 +123,7 @@ describe('TagInput – removing tags', () => {
|
|||||||
|
|
||||||
it('removes the last tag on Backspace when the input is empty', async () => {
|
it('removes the last tag on Backspace when the input is empty', async () => {
|
||||||
render(TagInput, { tags: ['Familie', 'Krieg'], allowCreation: true });
|
render(TagInput, { tags: ['Familie', 'Krieg'], allowCreation: true });
|
||||||
const input = page.getByRole('textbox');
|
(document.querySelector('input[type="text"]') as HTMLInputElement).focus();
|
||||||
await input.click();
|
|
||||||
await userEvent.keyboard('{Backspace}');
|
await userEvent.keyboard('{Backspace}');
|
||||||
await expect.element(page.getByText('Krieg')).not.toBeInTheDocument();
|
await expect.element(page.getByText('Krieg')).not.toBeInTheDocument();
|
||||||
await expect.element(page.getByText('Familie')).toBeInTheDocument();
|
await expect.element(page.getByText('Familie')).toBeInTheDocument();
|
||||||
@@ -177,7 +177,8 @@ describe('TagInput – autocomplete', () => {
|
|||||||
const input = page.getByRole('textbox');
|
const input = page.getByRole('textbox');
|
||||||
await input.fill('Fa');
|
await input.fill('Fa');
|
||||||
await waitForDebounce();
|
await waitForDebounce();
|
||||||
await page.getByRole('option', { name: 'Familie' }).click();
|
document.querySelector<HTMLElement>('[role="option"]')!.click();
|
||||||
|
await tick();
|
||||||
await expect.element(page.getByText('Familie')).toBeInTheDocument();
|
await expect.element(page.getByText('Familie')).toBeInTheDocument();
|
||||||
await expect.element(input).toHaveValue('');
|
await expect.element(input).toHaveValue('');
|
||||||
await page.screenshot({ path: 'test-results/screenshots/tag-input-suggestion-selected.png' });
|
await page.screenshot({ path: 'test-results/screenshots/tag-input-suggestion-selected.png' });
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ describe('Conversations page – swap button', () => {
|
|||||||
const { goto } = await import('$app/navigation');
|
const { goto } = await import('$app/navigation');
|
||||||
vi.mocked(goto).mockClear();
|
vi.mocked(goto).mockClear();
|
||||||
render(Page, { data: withPersons });
|
render(Page, { data: withPersons });
|
||||||
await page.getByTestId('conv-swap-btn').click();
|
document.querySelector<HTMLElement>('[data-testid="conv-swap-btn"]')!.click();
|
||||||
expect(goto).toHaveBeenCalledWith(expect.stringContaining('senderId=p2'), expect.anything());
|
expect(goto).toHaveBeenCalledWith(expect.stringContaining('senderId=p2'), expect.anything());
|
||||||
expect(goto).toHaveBeenCalledWith(expect.stringContaining('receiverId=p1'), expect.anything());
|
expect(goto).toHaveBeenCalledWith(expect.stringContaining('receiverId=p1'), expect.anything());
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { afterEach, describe, expect, it } from 'vitest';
|
||||||
import { render } from 'vitest-browser-svelte';
|
import { cleanup, render } from 'vitest-browser-svelte';
|
||||||
import { page } from 'vitest/browser';
|
import { page } from 'vitest/browser';
|
||||||
import LoginPage from './+page.svelte';
|
import LoginPage from './+page.svelte';
|
||||||
|
|
||||||
const tick = () => new Promise((r) => setTimeout(r, 0));
|
const tick = () => new Promise((r) => setTimeout(r, 0));
|
||||||
|
|
||||||
|
afterEach(cleanup);
|
||||||
|
|
||||||
describe('Login page – rendering', () => {
|
describe('Login page – rendering', () => {
|
||||||
it('renders the page title', async () => {
|
it('renders the page title', async () => {
|
||||||
render(LoginPage, {});
|
render(LoginPage, {});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it, vi } from 'vitest';
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||||
import { render } from 'vitest-browser-svelte';
|
import { cleanup, render } from 'vitest-browser-svelte';
|
||||||
import { page } from 'vitest/browser';
|
import { page } from 'vitest/browser';
|
||||||
import Page from './+page.svelte';
|
import Page from './+page.svelte';
|
||||||
|
|
||||||
@@ -13,6 +13,8 @@ vi.stubGlobal(
|
|||||||
vi.fn().mockResolvedValue({ ok: true, json: vi.fn().mockResolvedValue([]) })
|
vi.fn().mockResolvedValue({ ok: true, json: vi.fn().mockResolvedValue([]) })
|
||||||
);
|
);
|
||||||
|
|
||||||
|
afterEach(cleanup);
|
||||||
|
|
||||||
// ─── Test data ────────────────────────────────────────────────────────────────
|
// ─── Test data ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const emptyData = {
|
const emptyData = {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it, vi } from 'vitest';
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||||
import { render } from 'vitest-browser-svelte';
|
import { cleanup, render } from 'vitest-browser-svelte';
|
||||||
import { page } from 'vitest/browser';
|
import { page } from 'vitest/browser';
|
||||||
import Page from './+page.svelte';
|
import Page from './+page.svelte';
|
||||||
|
|
||||||
@@ -17,6 +17,8 @@ const makePerson = (overrides = {}) => ({
|
|||||||
const emptyData = { user: undefined, canWrite: true, q: '', persons: [] };
|
const emptyData = { user: undefined, canWrite: true, q: '', persons: [] };
|
||||||
const dataWithPersons = { ...emptyData, persons: [makePerson()] };
|
const dataWithPersons = { ...emptyData, persons: [makePerson()] };
|
||||||
|
|
||||||
|
afterEach(cleanup);
|
||||||
|
|
||||||
// ─── Rendering ────────────────────────────────────────────────────────────────
|
// ─── Rendering ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
describe('Persons page – rendering', () => {
|
describe('Persons page – rendering', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user