feat(web): smart empty-states with action affordances (Tier B6)
test-web / test (push) Successful in 32s
test-web / test (push) Successful in 32s
Replace dead-end empty copy with EmptyState cards that include a
clear next step.
- EmptyState.svelte: reusable card with `block` (whole-tab) and
`inline` (sub-section) variants, an actions snippet for buttons.
- Artists / Albums: when the whole library is empty, link to
/settings (the operator can scan a folder there).
- Liked: when all three sub-sections are empty, show a single
whole-tab card ("No likes yet" with Explore Home + Browse albums).
When only one sub-section is empty, an inline hint with a link
to the corresponding library tab.
- History: "Listen to something" button → Home.
- Playlists: "Create a playlist" button calls the existing create
flow; renamed from "New playlist" to avoid colliding with the
header's button-by-name lookup in tests.
Liked tab tests updated to match: the previous "three 'no liked X
yet'" assertion is now a single onboarding card test + a sibling
test for the mixed populated/empty case.
This commit is contained in:
@@ -64,7 +64,7 @@ describe('liked library page', () => {
|
||||
expect(screen.getByRole('heading', { name: /tracks/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('empty section renders "No liked X yet" hint', () => {
|
||||
test('all three sections empty renders the whole-tab onboarding card', () => {
|
||||
(createLikedArtistsInfiniteQuery as ReturnType<typeof vi.fn>).mockReturnValue(
|
||||
mockInfiniteQuery({ pages: [page<ArtistRef>([], 0)] })
|
||||
);
|
||||
@@ -75,7 +75,28 @@ describe('liked library page', () => {
|
||||
mockInfiniteQuery({ pages: [page<TrackRef>([], 0)] })
|
||||
);
|
||||
render(LikedPage);
|
||||
expect(screen.getAllByText(/no liked/i)).toHaveLength(3);
|
||||
// The whole-tab EmptyState replaces the three per-section hints
|
||||
// when every section is empty — single heading + action affordances.
|
||||
expect(screen.getByRole('heading', { name: /no likes yet/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole('link', { name: /explore home/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole('link', { name: /browse albums/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('one populated section + two empty subsections renders inline hints', () => {
|
||||
(createLikedArtistsInfiniteQuery as ReturnType<typeof vi.fn>).mockReturnValue(
|
||||
mockInfiniteQuery({ pages: [page<ArtistRef>([{ id: 'a1', name: 'Miles' } as ArtistRef], 1)] })
|
||||
);
|
||||
(createLikedAlbumsInfiniteQuery as ReturnType<typeof vi.fn>).mockReturnValue(
|
||||
mockInfiniteQuery({ pages: [page<AlbumRef>([], 0)] })
|
||||
);
|
||||
(createLikedTracksInfiniteQuery as ReturnType<typeof vi.fn>).mockReturnValue(
|
||||
mockInfiniteQuery({ pages: [page<TrackRef>([], 0)] })
|
||||
);
|
||||
render(LikedPage);
|
||||
expect(screen.getByText(/no liked albums yet/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/no liked tracks yet/i)).toBeInTheDocument();
|
||||
// Inline variant uses link affordances, not the big card heading.
|
||||
expect(screen.queryByRole('heading', { name: /no likes yet/i })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('Load more calls fetchNextPage on each section that has more', async () => {
|
||||
|
||||
Reference in New Issue
Block a user