Files
yellowjacket/frontend/test/components/album-track-request.test.ts
T
yonluandClaude Opus 5 1062b7c0bc fix(explore): tell Lit that a track request changed something
The album page's tracklist badges read `libraryStatusFor(false,
track.mbid)` at render time, which is a dependency on `downloadStore`
that Lit cannot see. The page did subscribe to that store, but its
callback only assigned `canDownload` and `isRequested` — neither of
which a *track* request changes — so no reactive field moved and the
component never re-rendered. The request was filed, the plus stayed a
plus, and clicking again cancelled it.

The other three hosts rendering these badges have always asked for the
repaint in the same place, which is what made this one look correct on
inspection.

Closes #33

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 08:04:39 -04:00

150 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Issue #33: "Want track" filed the request and left the badge alone.
*
* The tracklist's badges read `libraryStatusFor(false, track.mbid)` at
* render time, which is a dependency on `downloadStore` that Lit cannot
* see. `explore-album-details` did subscribe to that store, but its
* callback only assigned `canDownload` and `isRequested` — neither of
* which a *track* request changes — so nothing in the component's
* reactive state moved and the page never re-rendered. The request was
* real, the plus stayed a plus, and clicking again cancelled it.
*
* The other three hosts rendering these badges (`explore-artist-
* details`, `explore-view`, `top-results-row`) have always asked for
* the repaint in the same place, which is what made this one look
* correct on inspection.
*/
import { describe, expect, it, beforeEach } from 'vitest';
import type { LitElement } from 'lit';
import '@components/explore-album-details/explore-album-details';
import type { Request } from '@store/download-store';
import { Events } from '../../src/events';
import { stub, flush, resetHarness, emit } from '@test/support/harness';
import { fixture, shadowAll } from '@test/support/render';
type RequestOverrides = Partial<Omit<Request, 'state' | 'entity'>> & {
state?: `${Request['state']}`;
entity?: `${Request['entity']}`;
};
function request(overrides: RequestOverrides): Request {
return {
id: 1,
mbid: 'mbid-1',
entity: 'recording',
libraryId: 1,
artist: 'An Artist',
title: 'Track 1',
scope: 'future',
secondary: false,
state: 'wanted',
attempts: 0,
...overrides,
} as Request;
}
/** Put a request list into the store the way the backend does. */
async function withRequests(rows: Request[]): Promise<void> {
stub('download.Service.ListRequests', rows);
emit(Events.RequestsChanged);
await flush();
}
function track(n: number) {
return {
position: n,
discNumber: 1,
title: `Track ${n}`,
length: 200000,
mbid: `mbid-${n}`,
inLibrary: false,
};
}
/** An unowned catalog tracklist, which is the only case with badges:
* an owned row renders none, there being nothing left to ask for. */
async function withTracklist(count: number): Promise<LitElement> {
const el = await fixture<LitElement>('explore-album-details', {
albumName: 'Glass Harbour',
});
Object.assign(el, {
versionEntries: [
{
key: 'v1',
label: '2019',
sublabel: `${count} tracks`,
tracks: Array.from({ length: count }, (_, i) => track(i + 1)),
},
],
selectedVersionKey: 'v1',
loadingReleases: false,
loadingInfo: false,
});
el.requestUpdate();
await flush();
await el.updateComplete;
return el;
}
/** The status of each track badge, in tracklist order. */
function badgeStatuses(el: LitElement): string[] {
return shadowAll(el, 'library-status-indicator.track-request').map(
(b) => b.getAttribute('status') ?? '',
);
}
describe('the album tracklists request badges', () => {
beforeEach(async () => {
resetHarness();
stub('library.Library.GetFilePathsByRecordingMBIDs', {});
stub('library.Library.GetFilePathsByAlbums', {});
stub('library.Library.GetAlbumTracks', []);
stub('library.Library.GetAllLibrariesWithTrackCounts', []);
await withRequests([]);
});
it('starts as a plus on every unowned row', async () => {
const el = await withTracklist(3);
expect(badgeStatuses(el)).toEqual([
'not-in-library',
'not-in-library',
'not-in-library',
]);
});
it('repaints the row whose track has been requested', async () => {
const el = await withTracklist(3);
await withRequests([request({ mbid: 'mbid-2' })]);
await el.updateComplete;
// Only the requested row moves. A request is by MBID, so the two
// rows either side of it are still a plus.
expect(badgeStatuses(el)).toEqual([
'not-in-library',
'queued',
'not-in-library',
]);
});
it('repaints again when the request is cancelled', async () => {
const el = await withTracklist(3);
await withRequests([request({ mbid: 'mbid-2' })]);
await el.updateComplete;
await withRequests([]);
await el.updateComplete;
expect(badgeStatuses(el)).toEqual([
'not-in-library',
'not-in-library',
'not-in-library',
]);
});
});