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 { describe, it, expect, vi, afterEach } from 'vitest';
|
||||||
import { cleanup, render } from 'vitest-browser-svelte';
|
import { cleanup, render } from 'vitest-browser-svelte';
|
||||||
import { page } from 'vitest/browser';
|
import { page, userEvent } from 'vitest/browser';
|
||||||
import TranscriptionEditView from './TranscriptionEditView.svelte';
|
import TranscriptionEditView from './TranscriptionEditView.svelte';
|
||||||
import { createConfirmService, CONFIRM_KEY } from '$lib/shared/services/confirm.svelte.js';
|
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 () => {
|
it('passes the block mentionedPersons array as the 3rd save argument', async () => {
|
||||||
vi.useFakeTimers();
|
|
||||||
const onSaveBlock = vi.fn().mockResolvedValue(undefined);
|
const onSaveBlock = vi.fn().mockResolvedValue(undefined);
|
||||||
const blockWithMention = {
|
const blockWithMention = {
|
||||||
...block1,
|
...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' }]
|
mentionedPersons: [{ personId: 'p-aug', displayName: 'Auguste Raddatz' }]
|
||||||
};
|
};
|
||||||
renderView({ blocks: [blockWithMention], onSaveBlock });
|
renderView({ blocks: [blockWithMention], onSaveBlock });
|
||||||
|
|
||||||
const textarea = page.getByRole('textbox').first();
|
// type() focuses the element (cursor at position 0) then inserts without replacing the
|
||||||
await textarea.fill('Hallo @Auguste Raddatz');
|
// 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 ');
|
||||||
|
|
||||||
vi.advanceTimersByTime(1500);
|
await vi.waitFor(
|
||||||
await vi.runAllTimersAsync();
|
() =>
|
||||||
|
expect(onSaveBlock).toHaveBeenCalledWith('b1', 'Hallo @Auguste Raddatz', [
|
||||||
expect(onSaveBlock).toHaveBeenCalledWith('b1', 'Hallo @Auguste Raddatz', [
|
{ personId: 'p-aug', displayName: 'Auguste Raddatz' }
|
||||||
{ personId: 'p-aug', displayName: 'Auguste Raddatz' }
|
]),
|
||||||
]);
|
{ timeout: 3000 }
|
||||||
vi.useRealTimers();
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('resets debounce timer on rapid successive changes', async () => {
|
it('resets debounce timer on rapid successive changes', async () => {
|
||||||
@@ -238,8 +242,9 @@ describe('TranscriptionEditView — flush on blur', () => {
|
|||||||
const textarea = page.getByRole('textbox').first();
|
const textarea = page.getByRole('textbox').first();
|
||||||
await textarea.fill('Blur text');
|
await textarea.fill('Blur text');
|
||||||
|
|
||||||
// Blur before 1500ms debounce fires — locator.blur() not available, use native DOM
|
// Blur before 1500ms debounce fires — locator.blur() not available, use native DOM.
|
||||||
const el = document.querySelector('textarea') as HTMLTextAreaElement;
|
// 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 }));
|
el.dispatchEvent(new FocusEvent('blur', { bubbles: true }));
|
||||||
|
|
||||||
await vi.runAllTimersAsync();
|
await vi.runAllTimersAsync();
|
||||||
@@ -335,7 +340,12 @@ describe('TranscriptionEditView — mark all reviewed', () => {
|
|||||||
onMarkAllReviewed
|
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));
|
await vi.waitFor(() => expect(onMarkAllReviewed).toHaveBeenCalledTimes(1));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -349,9 +359,14 @@ describe('TranscriptionEditView — mark all reviewed', () => {
|
|||||||
onMarkAllReviewed
|
onMarkAllReviewed
|
||||||
});
|
});
|
||||||
|
|
||||||
const btn = page.getByRole('button', { name: /Alle als fertig markieren/ });
|
// Same CDP click workaround: dispatch from browser JS to reliably fire Svelte 5 onclick
|
||||||
await btn.click();
|
const btnEl = (await page
|
||||||
await expect.element(btn).toBeDisabled();
|
.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();
|
resolveMarkAll();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -134,7 +134,13 @@ describe('EntityNavSection — flyout variant', () => {
|
|||||||
called = true;
|
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);
|
expect(called).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user