fix(tests): fix 2 more pre-existing vitest-browser spec failures
TranscriptionEditView: fix 4 failing tests: - textarea → [role="textbox"] selector (editor is contenteditable, not <textarea>) - button clicks → dispatchEvent(MouseEvent) for reliable Svelte 5 onclick with TipTap - mentionedPersons test: init block with @mention token so deserialize() creates a mention node; use userEvent.type + vi.waitFor (real timers) instead of fill + fake timers, which prevents TipTap onUpdate from firing the debounce timer EntityNavSection: anchor link click → add capture-phase preventDefault before clicking to stop iframe navigation while allowing Svelte onclick handler to run Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { describe, it, expect, vi, afterEach } from 'vitest';
|
||||
import { cleanup, render } from 'vitest-browser-svelte';
|
||||
import { page } from 'vitest/browser';
|
||||
import { page, userEvent } from 'vitest/browser';
|
||||
import TranscriptionEditView from './TranscriptionEditView.svelte';
|
||||
import { createConfirmService, CONFIRM_KEY } from '$lib/shared/services/confirm.svelte.js';
|
||||
|
||||
@@ -148,24 +148,28 @@ describe('TranscriptionEditView — auto-save debounce', () => {
|
||||
});
|
||||
|
||||
it('passes the block mentionedPersons array as the 3rd save argument', async () => {
|
||||
vi.useFakeTimers();
|
||||
const onSaveBlock = vi.fn().mockResolvedValue(undefined);
|
||||
const blockWithMention = {
|
||||
...block1,
|
||||
// text must contain the @displayName token so deserialize() creates a mention node;
|
||||
// fill() replaces the whole content with plain text and would destroy the node
|
||||
text: '@Auguste Raddatz',
|
||||
mentionedPersons: [{ personId: 'p-aug', displayName: 'Auguste Raddatz' }]
|
||||
};
|
||||
renderView({ blocks: [blockWithMention], onSaveBlock });
|
||||
|
||||
const textarea = page.getByRole('textbox').first();
|
||||
await textarea.fill('Hallo @Auguste Raddatz');
|
||||
|
||||
vi.advanceTimersByTime(1500);
|
||||
await vi.runAllTimersAsync();
|
||||
// type() focuses the element (cursor at position 0) then inserts without replacing the
|
||||
// existing mention node. Fake timers interfere with keyboard CDP so use real timers
|
||||
// + vi.waitFor to catch the 1500 ms debounce.
|
||||
await userEvent.type(page.getByRole('textbox').first(), 'Hallo ');
|
||||
|
||||
await vi.waitFor(
|
||||
() =>
|
||||
expect(onSaveBlock).toHaveBeenCalledWith('b1', 'Hallo @Auguste Raddatz', [
|
||||
{ personId: 'p-aug', displayName: 'Auguste Raddatz' }
|
||||
]);
|
||||
vi.useRealTimers();
|
||||
]),
|
||||
{ timeout: 3000 }
|
||||
);
|
||||
});
|
||||
|
||||
it('resets debounce timer on rapid successive changes', async () => {
|
||||
@@ -238,8 +242,9 @@ describe('TranscriptionEditView — flush on blur', () => {
|
||||
const textarea = page.getByRole('textbox').first();
|
||||
await textarea.fill('Blur text');
|
||||
|
||||
// Blur before 1500ms debounce fires — locator.blur() not available, use native DOM
|
||||
const el = document.querySelector('textarea') as HTMLTextAreaElement;
|
||||
// Blur before 1500ms debounce fires — locator.blur() not available, use native DOM.
|
||||
// PersonMentionEditor uses a contenteditable div (role=textbox), not a <textarea>.
|
||||
const el = document.querySelector('[role="textbox"]') as HTMLElement;
|
||||
el.dispatchEvent(new FocusEvent('blur', { bubbles: true }));
|
||||
|
||||
await vi.runAllTimersAsync();
|
||||
@@ -335,7 +340,12 @@ describe('TranscriptionEditView — mark all reviewed', () => {
|
||||
onMarkAllReviewed
|
||||
});
|
||||
|
||||
await page.getByRole('button', { name: /Alle als fertig markieren/ }).click();
|
||||
// userEvent.click() via Playwright CDP doesn't reliably trigger Svelte 5 onclick
|
||||
// handlers when a TipTap editor is mounted in the same component tree.
|
||||
const btn = (await page
|
||||
.getByRole('button', { name: /Alle als fertig markieren/ })
|
||||
.element()) as HTMLButtonElement;
|
||||
btn.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
|
||||
await vi.waitFor(() => expect(onMarkAllReviewed).toHaveBeenCalledTimes(1));
|
||||
});
|
||||
|
||||
@@ -349,9 +359,14 @@ describe('TranscriptionEditView — mark all reviewed', () => {
|
||||
onMarkAllReviewed
|
||||
});
|
||||
|
||||
const btn = page.getByRole('button', { name: /Alle als fertig markieren/ });
|
||||
await btn.click();
|
||||
await expect.element(btn).toBeDisabled();
|
||||
// Same CDP click workaround: dispatch from browser JS to reliably fire Svelte 5 onclick
|
||||
const btnEl = (await page
|
||||
.getByRole('button', { name: /Alle als fertig markieren/ })
|
||||
.element()) as HTMLButtonElement;
|
||||
btnEl.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
|
||||
await expect
|
||||
.element(page.getByRole('button', { name: /Alle als fertig markieren/ }))
|
||||
.toBeDisabled();
|
||||
resolveMarkAll();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -134,7 +134,13 @@ describe('EntityNavSection — flyout variant', () => {
|
||||
called = true;
|
||||
}
|
||||
});
|
||||
document.querySelector<HTMLAnchorElement>('a[href="/admin/users"]')!.click();
|
||||
const link = document.querySelector<HTMLAnchorElement>('a[href="/admin/users"]')!;
|
||||
// Prevent the browser from navigating the test iframe to /admin/users (which
|
||||
// would redirect to /login and kill the iframe connection). preventDefault()
|
||||
// on the capture phase suppresses navigation while still letting the Svelte
|
||||
// onclick handler (onFlyoutClick) run on the bubbling phase.
|
||||
link.addEventListener('click', (e) => e.preventDefault(), { capture: true, once: true });
|
||||
link.click();
|
||||
expect(called).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user