feat(web/playlists): For-You tile play button refreshes and plays
Click on the For-You tile's play button now triggers a synchronous refresh, then enqueues + auto-plays the freshly-built playlist. Two-click-target tile pattern: tile BODY click still navigates to the playlist detail page (existing behavior); the play button is the new generate-and-play action. Behavior is gated on playlist.system_variant === 'for_you' — every other playlist (user, Discover, Songs-like-X) keeps the existing "fetch detail, enqueue, play" flow. The atomic-replace BuildSystem- Playlists creates a new playlist_id, so the play handler uses the refresh response's id for the follow-up getPlaylist call rather than the stale tile prop's id. Empty-library response (playlist_id: null) is a no-op — clicking play in a degenerate-empty library doesn't error, it just doesn't start playback. Caches are invalidated post-refresh so the home view re-fetches and the tile re-renders with the new id, avoiding the corner case of a click-then-tile-body 404. Tests cover the refresh-and-play happy path, the empty-library no-op, and that non-For-You playlists keep the existing flow. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { refreshForYou } from './playlists';
|
||||
|
||||
vi.mock('./client', () => ({
|
||||
apiFetch: vi.fn()
|
||||
}));
|
||||
|
||||
import { apiFetch } from './client';
|
||||
|
||||
describe('refreshForYou', () => {
|
||||
beforeEach(() => vi.clearAllMocks());
|
||||
|
||||
it('POSTs the correct path and returns the response', async () => {
|
||||
const sample = { playlist_id: 'pl-abc', track_count: 25, track_ids: ['t1', 't2'] };
|
||||
(apiFetch as unknown as ReturnType<typeof vi.fn>).mockResolvedValueOnce(sample);
|
||||
const got = await refreshForYou();
|
||||
expect(apiFetch).toHaveBeenCalledWith(
|
||||
'/api/playlists/system/for-you/refresh',
|
||||
expect.objectContaining({ method: 'POST' })
|
||||
);
|
||||
expect(got).toEqual(sample);
|
||||
});
|
||||
|
||||
it('handles empty-library response', async () => {
|
||||
const sample = { playlist_id: null, track_count: 0, track_ids: [] };
|
||||
(apiFetch as unknown as ReturnType<typeof vi.fn>).mockResolvedValueOnce(sample);
|
||||
const got = await refreshForYou();
|
||||
expect(got.playlist_id).toBe(null);
|
||||
expect(got.track_ids).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -108,3 +108,24 @@ export async function refreshDiscover(): Promise<RefreshDiscoverResponse> {
|
||||
body: JSON.stringify({})
|
||||
})) as RefreshDiscoverResponse;
|
||||
}
|
||||
|
||||
// For-You refresh ------------------------------------------------------------
|
||||
|
||||
export type RefreshForYouResponse = {
|
||||
playlist_id: string | null;
|
||||
track_count: number;
|
||||
track_ids: string[];
|
||||
};
|
||||
|
||||
// POST /api/playlists/system/for-you/refresh — synchronously
|
||||
// rebuilds the calling user's For-You playlist and returns the new
|
||||
// id, track count, and track id list.
|
||||
//
|
||||
// Used by the home Playlists row's For-You tile play button: click
|
||||
// triggers refresh, the frontend then calls getPlaylist(new_id) to
|
||||
// get full TrackRef snapshots and enqueues for playback.
|
||||
export async function refreshForYou(): Promise<RefreshForYouResponse> {
|
||||
return (await apiFetch('/api/playlists/system/for-you/refresh', {
|
||||
method: 'POST'
|
||||
})) as RefreshForYouResponse;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user