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:
@@ -7,7 +7,7 @@ import { describe, expect, it, beforeEach } from 'vitest';
|
||||
|
||||
import { searchStore } from '@store/search-store';
|
||||
import { trackListStore } from '@store/tracklist-store';
|
||||
import { exploreCache } from '@store/explore-cache';
|
||||
import { exploreCache, ARTIST_IMAGE_CACHE_LIMIT } from '@store/explore-cache';
|
||||
import { Events } from '../../src/events';
|
||||
import { emit, lastCall, flush } from '@test/support/harness';
|
||||
|
||||
@@ -141,14 +141,34 @@ describe('explore cache', () => {
|
||||
expect(exploreCache.getArtist('mbid-2')?.imageURL).toBe('http://x/eno.jpg');
|
||||
});
|
||||
|
||||
it('caches an artist’s release groups and top tracks separately', () => {
|
||||
exploreCache.setArtistAlbums('mbid-3', [{ mbid: 'rg-1' }] as never);
|
||||
exploreCache.setArtistTopTracks('mbid-3', [{ mbid: 'rec-1' }] as never);
|
||||
// `perf.M8`. An artist entry holds the artist photo's base64 data URL
|
||||
// — ~128 kB measured — so an unbounded map on a view that never
|
||||
// unmounts grows for the life of the process.
|
||||
it('evicts the least recently used artist past its cap', () => {
|
||||
const over = ARTIST_IMAGE_CACHE_LIMIT + 10;
|
||||
|
||||
expect([
|
||||
exploreCache.getArtistAlbums('mbid-3')?.length,
|
||||
exploreCache.getArtistTopTracks('mbid-3')?.length,
|
||||
]).toEqual([1, 1]);
|
||||
for (let i = 0; i < over; i++) {
|
||||
exploreCache.setArtist(`cap-${i}`, { mbid: `cap-${i}`, name: `A${i}` });
|
||||
}
|
||||
|
||||
expect(exploreCache.stats().artists.entries).toBe(ARTIST_IMAGE_CACHE_LIMIT);
|
||||
// The first inserted is gone; the last is not.
|
||||
expect(exploreCache.getArtist('cap-0')).toBeUndefined();
|
||||
expect(exploreCache.getArtist(`cap-${over - 1}`)?.name).toBe(`A${over - 1}`);
|
||||
});
|
||||
|
||||
it('keeps an artist alive by reading it', () => {
|
||||
// Recency is what makes the cap safe: the entry being rendered must
|
||||
// not be the one evicted, or the render refetches it immediately.
|
||||
for (let i = 0; i < ARTIST_IMAGE_CACHE_LIMIT; i++) {
|
||||
exploreCache.setArtist(`lru-${i}`, { mbid: `lru-${i}`, name: `A${i}` });
|
||||
}
|
||||
|
||||
exploreCache.getArtist('lru-0');
|
||||
exploreCache.setArtist('lru-new', { mbid: 'lru-new', name: 'New' });
|
||||
|
||||
expect(exploreCache.getArtist('lru-0')?.name).toBe('A0');
|
||||
expect(exploreCache.getArtist('lru-1')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('populates artists and albums from one search result', () => {
|
||||
|
||||
Reference in New Issue
Block a user