Files
yellowjacket/frontend/test/components/catalog-scope-notice.test.ts
T
logan ba35858208 feat(explore): say whether a page is the catalog or your own copy
The album and artist pages draw from two sources and rendered
identically either way. An album showing one track because that is all
you own was indistinguishable from an album that has one track, and
both were indistinguishable from a page still waiting on a background
catalog fetch — so the answer to "is more coming?" was to keep
reloading and find out.

<catalog-scope-notice> names the source in one line: silent for full
catalog data, "still loading" while a fetch may land, "library only"
for an entity with no MBID (which will never fill in, so it points at
Autotag), and a retryable notice when the catalog had nothing to say.

Both pages needed a new distinction to drive it. loadingReleases and
its artist-side equivalents mean "something is renderable", which a
library stand-in satisfies — so catalogPending/catalogLoaded track the
different question of whether the catalog has actually answered.

Also fixes the artist page clobbering its library-hydrated discography
with an empty catalog result. An empty BrowseReleaseGroups means the
index has not built this artist yet, not that they released nothing.
2026-08-11 01:15:11 -04:00

67 lines
2.2 KiB
TypeScript

/**
* The scope notice exists because an album or artist page looked
* identical whether it was showing the catalog, a library stand-in, or
* nothing yet. So the assertions here are about the one thing it must
* never do — stay silent when the page is not the whole story — and
* about staying out of the way when it is.
*/
import { describe, expect, it } from 'vitest';
import '@components/catalog-scope-notice/catalog-scope-notice';
import { fixture, shadow, text } from '@test/support/render';
describe('catalog scope notice', () => {
it('renders nothing at all for full catalog data', async () => {
const el = await fixture('catalog-scope-notice', { scope: 'catalog' });
expect(shadow(el, '.notice')).toBeNull();
});
it('names the entity it is talking about', async () => {
const album = await fixture('catalog-scope-notice', {
scope: 'library',
entityType: 'album',
});
const artist = await fixture('catalog-scope-notice', {
scope: 'library',
entityType: 'artist',
});
expect(text(album, '.text')).toContain('album');
expect(text(artist, '.text')).toContain('artist');
});
it('distinguishes "still loading" from "this is all there is"', async () => {
const loading = await fixture('catalog-scope-notice', { scope: 'loading' });
const library = await fixture('catalog-scope-notice', { scope: 'library' });
expect(text(loading, '.text')).toContain('load');
expect(text(library, '.text')).toContain('Library only');
});
it('offers a retry only where retrying could change anything', async () => {
for (const scope of ['loading', 'library']) {
const el = await fixture('catalog-scope-notice', { scope });
expect(shadow(el, 'button')).toBeNull();
}
const el = await fixture('catalog-scope-notice', { scope: 'unavailable' });
expect(shadow(el, 'button')).not.toBeNull();
});
it('asks its host to retry rather than fetching anything itself', async () => {
const el = await fixture('catalog-scope-notice', { scope: 'unavailable' });
let asked = 0;
el.addEventListener('catalog-retry', () => {
asked += 1;
});
shadow<HTMLElement>(el, 'button')!.click();
expect(asked).toBe(1);
});
});