test: increase coverage
This commit is contained in:
@@ -93,3 +93,132 @@ describe('TranscriptionEditView — reorder', () => {
|
||||
expect(handles.length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Auto-save debounce ───────────────────────────────────────────────────────
|
||||
|
||||
describe('TranscriptionEditView — auto-save debounce', () => {
|
||||
it('calls onSaveBlock after 1500ms debounce when text changes', async () => {
|
||||
vi.useFakeTimers();
|
||||
const onSaveBlock = vi.fn().mockResolvedValue(undefined);
|
||||
renderView({ onSaveBlock });
|
||||
|
||||
const textarea = page.getByRole('textbox').first();
|
||||
await textarea.fill('Neue Zeile');
|
||||
|
||||
// Not called immediately
|
||||
expect(onSaveBlock).not.toHaveBeenCalled();
|
||||
|
||||
// Advance past debounce
|
||||
vi.advanceTimersByTime(1500);
|
||||
await vi.runAllTimersAsync();
|
||||
|
||||
expect(onSaveBlock).toHaveBeenCalledWith('b1', 'Neue Zeile');
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('resets debounce timer on rapid successive changes', async () => {
|
||||
vi.useFakeTimers();
|
||||
const onSaveBlock = vi.fn().mockResolvedValue(undefined);
|
||||
renderView({ onSaveBlock });
|
||||
|
||||
const textarea = page.getByRole('textbox').first();
|
||||
await textarea.fill('First');
|
||||
vi.advanceTimersByTime(500);
|
||||
|
||||
await textarea.fill('Second');
|
||||
vi.advanceTimersByTime(500);
|
||||
|
||||
// 1000ms elapsed since first change — should not have saved yet
|
||||
expect(onSaveBlock).not.toHaveBeenCalled();
|
||||
|
||||
vi.advanceTimersByTime(1000);
|
||||
await vi.runAllTimersAsync();
|
||||
|
||||
// Only one save with the final value
|
||||
expect(onSaveBlock).toHaveBeenCalledTimes(1);
|
||||
expect(onSaveBlock).toHaveBeenCalledWith('b1', 'Second');
|
||||
vi.useRealTimers();
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Save state transitions ───────────────────────────────────────────────────
|
||||
|
||||
describe('TranscriptionEditView — save state indicators', () => {
|
||||
it('shows saving indicator while onSaveBlock is in-flight', async () => {
|
||||
vi.useFakeTimers();
|
||||
let resolveSave!: () => void;
|
||||
const onSaveBlock = vi.fn().mockReturnValue(new Promise<void>((r) => (resolveSave = r)));
|
||||
renderView({ onSaveBlock });
|
||||
|
||||
await page.getByRole('textbox').first().fill('Hello');
|
||||
vi.advanceTimersByTime(1500);
|
||||
await vi.runAllTimersAsync();
|
||||
|
||||
await expect.element(page.getByText('Speichere...')).toBeInTheDocument();
|
||||
|
||||
resolveSave();
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('shows error state when onSaveBlock rejects', async () => {
|
||||
vi.useFakeTimers();
|
||||
const onSaveBlock = vi.fn().mockRejectedValue(new Error('network'));
|
||||
renderView({ onSaveBlock });
|
||||
|
||||
await page.getByRole('textbox').first().fill('Fails');
|
||||
vi.advanceTimersByTime(1500);
|
||||
await vi.runAllTimersAsync();
|
||||
|
||||
await expect.element(page.getByText('Nicht gespeichert')).toBeInTheDocument();
|
||||
await expect.element(page.getByText('Erneut versuchen')).toBeInTheDocument();
|
||||
vi.useRealTimers();
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Flush on blur ────────────────────────────────────────────────────────────
|
||||
|
||||
describe('TranscriptionEditView — flush on blur', () => {
|
||||
it('flushes pending save immediately on textarea blur before debounce expires', async () => {
|
||||
vi.useFakeTimers();
|
||||
const onSaveBlock = vi.fn().mockResolvedValue(undefined);
|
||||
renderView({ onSaveBlock });
|
||||
|
||||
const textarea = page.getByRole('textbox').first();
|
||||
await textarea.fill('Blur text');
|
||||
|
||||
// Blur before 1500ms debounce fires
|
||||
await textarea.blur();
|
||||
|
||||
await vi.runAllTimersAsync();
|
||||
expect(onSaveBlock).toHaveBeenCalledWith('b1', 'Blur text');
|
||||
vi.useRealTimers();
|
||||
});
|
||||
});
|
||||
|
||||
// ─── onDeleteBlock callback ───────────────────────────────────────────────────
|
||||
|
||||
describe('TranscriptionEditView — delete block', () => {
|
||||
it('calls onDeleteBlock with correct blockId when delete is confirmed', async () => {
|
||||
const onDeleteBlock = vi.fn().mockResolvedValue(undefined);
|
||||
vi.spyOn(window, 'confirm').mockReturnValue(true);
|
||||
renderView({ onDeleteBlock });
|
||||
|
||||
const deleteBtn = page.getByRole('button', { name: 'Löschen' }).first();
|
||||
await deleteBtn.click();
|
||||
|
||||
expect(onDeleteBlock).toHaveBeenCalledWith('b1');
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('does not call onDeleteBlock when deletion is cancelled', async () => {
|
||||
const onDeleteBlock = vi.fn();
|
||||
vi.spyOn(window, 'confirm').mockReturnValue(false);
|
||||
renderView({ onDeleteBlock });
|
||||
|
||||
const deleteBtn = page.getByRole('button', { name: 'Löschen' }).first();
|
||||
await deleteBtn.click();
|
||||
|
||||
expect(onDeleteBlock).not.toHaveBeenCalled();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user