Files
yellowjacket/frontend/test/components/empty-states.test.ts
T
logan 5830b1ba17 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.
2026-08-12 01:20:03 -04:00

69 lines
2.2 KiB
TypeScript

/**
* "Loading tracks…" used to be what the track list said when it was
* loading, when it was empty, and when the query had failed — including
* on the first screen a new user ever sees, behind the first-run wizard
* (errors.M2, H-12). Three situations, three different things to say.
*/
import { describe, expect, it, beforeEach } from 'vitest';
import type { LitElement } from 'lit';
import '@components/track-list/track-list';
import { Events } from '../../src/events';
import { emit, stub, stubFailure, flush, resetHarness } from '@test/support/harness';
import { fixture, shadow, text } from '@test/support/render';
/** Drop the library store's cache so the list has to fetch. */
async function emptyLibrary(): Promise<void> {
resetHarness();
stub('library.Library.GetAllTracks', []);
stub('library.Library.GetAllAlbums', []);
stub('library.Library.GetAllArtists', []);
stub('library.Library.GetAllGenresWithCounts', []);
emit(Events.LibraryScanComplete);
await flush();
}
describe('<track-list> empty, loading and failed', () => {
beforeEach(async () => {
await emptyLibrary();
});
it('says the library is empty when it is empty', async () => {
const el = await fixture<LitElement>('track-list');
await flush();
await el.updateComplete;
expect(text(el, '[data-testid="track-list-empty"]')).toContain(
'No tracks yet',
);
});
it('says the query failed, and offers to try again', async () => {
stubFailure('library.Library.GetAllTracks', 'sql: database is locked');
emit(Events.LibraryScanComplete);
await flush();
const el = await fixture<LitElement>('track-list');
await flush();
await el.updateComplete;
expect([
text(el, '[data-testid="track-list-error"]'),
shadow(el, '[data-testid="track-list-loading"]'),
]).toEqual([expect.stringContaining('busy'), null]);
});
it('does not claim to be loading a list it was handed', async () => {
const el = await fixture<LitElement>('track-list', { externalTracks: [] });
await flush();
await el.updateComplete;
expect(text(el, '[data-testid="track-list-empty"]')).toBe(
'Nothing here yet.',
);
});
});