test(frontend): cover the lifecycle, the voice and the repaints
Component and store cases for everything in this series, several of which exist because the thing they pin is invisible everywhere else: - `view-lifecycle` and `keyboard-reach` — a document listener count that does not grow across a simulated navigate cycle, and a tab sequence that reaches the sidebar and plays a row without a mouse. - `notifications`, `notification-store`, `confirm-dialog`, `empty-states` — the four levels, the (level, region, key) coalescing window, and loading/failed/empty as three states. - `card-grid-repaint` — fails if `artists-view`'s or `genres-view`'s per-render arrow functions are hoisted to stable fields, which is the audit's own recommendation and takes the cards from 1 highlighted to 0. It exists for no other reason. - `lazy-track-details` — reads the five sources and fails on a returning static import, the same shape as `TestNoDirectRuntimeEmits` and for the same reason: the invariant is about what the code does *not* say. - `now-playing` — a position report that changes nothing must not touch the DOM again, and a track change must. The first fails against the old unconditional `updated()`. - `playlist-virtualization`, `list-render-cost`, `selection`, `icons`, and the store cases for the library-filter race, the never-settling waiter and the per-playlist patch.
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* The one "are you sure?".
|
||||
*
|
||||
* Three destructive actions had none at all — including a multi-select
|
||||
* loop that deleted N playlists on one click (errors.M6, M7, m4) — so
|
||||
* what matters here is that the promise a call site awaits cannot
|
||||
* resolve true unless somebody said so.
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
confirmAction,
|
||||
type ConfirmDialog,
|
||||
} from '@components/confirm-dialog/confirm-dialog';
|
||||
|
||||
/** The singleton the helper attaches to the document on first use. */
|
||||
function host(): ConfirmDialog {
|
||||
const el = document.querySelector<ConfirmDialog>('confirm-dialog');
|
||||
|
||||
if (!el) throw new Error('confirm-dialog did not mount itself');
|
||||
|
||||
return el;
|
||||
}
|
||||
|
||||
async function press(testid: string): Promise<void> {
|
||||
const el = host();
|
||||
|
||||
await el.updateComplete;
|
||||
el.shadowRoot?.querySelector<HTMLButtonElement>(`[data-testid="${testid}"]`)
|
||||
?.click();
|
||||
await el.updateComplete;
|
||||
}
|
||||
|
||||
describe('confirmAction', () => {
|
||||
it('resolves true only when the user accepts', async () => {
|
||||
const answer = confirmAction({
|
||||
title: 'Delete “Chill”?',
|
||||
message: 'The playlist is deleted; the audio files are not.',
|
||||
confirmLabel: 'Delete playlist',
|
||||
danger: true,
|
||||
});
|
||||
|
||||
await press('confirm-accept');
|
||||
|
||||
await expect(answer).resolves.toBe(true);
|
||||
});
|
||||
|
||||
it('resolves false on cancel, which is the default answer', async () => {
|
||||
const answer = confirmAction({ title: 'Delete?', message: 'Gone for good.' });
|
||||
|
||||
await press('confirm-cancel');
|
||||
|
||||
await expect(answer).resolves.toBe(false);
|
||||
});
|
||||
|
||||
it('says what will happen before asking', async () => {
|
||||
const answer = confirmAction({
|
||||
title: 'Remove “Lidarr”?',
|
||||
message: 'YellowJacket will stop using this client.',
|
||||
impact: 'Its stored credentials are deleted and cannot be recovered.',
|
||||
});
|
||||
const el = host();
|
||||
|
||||
await el.updateComplete;
|
||||
|
||||
expect(el.shadowRoot?.textContent).toContain('cannot be recovered');
|
||||
|
||||
await press('confirm-cancel');
|
||||
await answer;
|
||||
});
|
||||
|
||||
it('does not leave a second question hanging', async () => {
|
||||
const first = confirmAction({ title: 'One?', message: 'First.' });
|
||||
const second = confirmAction({ title: 'Two?', message: 'Second.' });
|
||||
|
||||
await press('confirm-accept');
|
||||
|
||||
// The first was superseded and answered "no" rather than never
|
||||
// settling — a call site awaiting it would otherwise hang forever.
|
||||
await expect(Promise.all([first, second])).resolves.toEqual([false, true]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user