feat(web): add home/albums/artists API clients for M6a

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-01 20:13:49 -04:00
parent b1f2227d62
commit e674869e06
6 changed files with 145 additions and 0 deletions
+33
View File
@@ -0,0 +1,33 @@
import { afterEach, describe, expect, test, vi } from 'vitest';
vi.mock('./client', () => ({
api: { get: vi.fn() }
}));
import { listAlbumsAlpha } from './albums';
import { qk } from './queries';
import { api } from './client';
afterEach(() => vi.clearAllMocks());
describe('albums client', () => {
test('listAlbumsAlpha hits /api/library/albums with paging', async () => {
(api.get as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
items: [], total: 0, limit: 50, offset: 0
});
await listAlbumsAlpha(50, 0);
expect(api.get).toHaveBeenCalledWith('/api/library/albums?limit=50&offset=0');
});
test('listAlbumsAlpha honors custom limit and offset', async () => {
(api.get as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
items: [], total: 0, limit: 25, offset: 100
});
await listAlbumsAlpha(25, 100);
expect(api.get).toHaveBeenCalledWith('/api/library/albums?limit=25&offset=100');
});
test('qk.albumsAlpha key is stable', () => {
expect(qk.albumsAlpha()).toEqual(['albumsAlpha']);
});
});
+22
View File
@@ -0,0 +1,22 @@
import { createInfiniteQuery } from '@tanstack/svelte-query';
import { api } from './client';
import { qk } from './queries';
import type { AlbumRef, Page } from './types';
export const ALBUM_PAGE_SIZE = 50;
export async function listAlbumsAlpha(limit: number, offset: number): Promise<Page<AlbumRef>> {
return api.get<Page<AlbumRef>>(`/api/library/albums?limit=${limit}&offset=${offset}`);
}
export function createAlbumsAlphaInfiniteQuery() {
return createInfiniteQuery({
queryKey: qk.albumsAlpha(),
queryFn: ({ pageParam = 0 }) => listAlbumsAlpha(ALBUM_PAGE_SIZE, pageParam),
initialPageParam: 0,
getNextPageParam: (last) => {
const loaded = last.offset + last.items.length;
return loaded >= last.total ? undefined : loaded;
}
});
}
+23
View File
@@ -0,0 +1,23 @@
import { afterEach, describe, expect, test, vi } from 'vitest';
vi.mock('./client', () => ({
api: { get: vi.fn() }
}));
import { listArtistTracks } from './artists';
import { qk } from './queries';
import { api } from './client';
afterEach(() => vi.clearAllMocks());
describe('artists client', () => {
test('listArtistTracks hits /api/artists/:id/tracks', async () => {
(api.get as ReturnType<typeof vi.fn>).mockResolvedValueOnce([]);
await listArtistTracks('art-1');
expect(api.get).toHaveBeenCalledWith('/api/artists/art-1/tracks');
});
test('qk.artistTracks key includes the artist id', () => {
expect(qk.artistTracks('art-1')).toEqual(['artistTracks', 'art-1']);
});
});
+16
View File
@@ -0,0 +1,16 @@
import { createQuery } from '@tanstack/svelte-query';
import { api } from './client';
import { qk } from './queries';
import type { TrackRef } from './types';
export async function listArtistTracks(artistId: string): Promise<TrackRef[]> {
return api.get<TrackRef[]>(`/api/artists/${artistId}/tracks`);
}
export function createArtistTracksQuery(artistId: string) {
return createQuery({
queryKey: qk.artistTracks(artistId),
queryFn: () => listArtistTracks(artistId),
enabled: artistId.length > 0
});
}
+32
View File
@@ -0,0 +1,32 @@
import { afterEach, describe, expect, test, vi } from 'vitest';
vi.mock('./client', () => ({
api: { get: vi.fn() }
}));
import { listHome } from './home';
import { qk } from './queries';
import { api } from './client';
import type { HomePayload } from './types';
afterEach(() => vi.clearAllMocks());
describe('home client', () => {
test('listHome hits /api/home', async () => {
const fixture: HomePayload = {
recently_added_albums: [],
rediscover_albums: [],
rediscover_artists: [],
most_played_tracks: [],
last_played_artists: []
};
(api.get as ReturnType<typeof vi.fn>).mockResolvedValueOnce(fixture);
const got = await listHome();
expect(api.get).toHaveBeenCalledWith('/api/home');
expect(got).toEqual(fixture);
});
test('qk.home key is stable', () => {
expect(qk.home()).toEqual(['home']);
});
});
+19
View File
@@ -0,0 +1,19 @@
import { createQuery } from '@tanstack/svelte-query';
import { api } from './client';
import { qk } from './queries';
import type { HomePayload } from './types';
export async function listHome(): Promise<HomePayload> {
return api.get<HomePayload>('/api/home');
}
// staleTime = 60s — repeat home-page mounts within a minute reuse cached
// data. Like-state changes, new plays, freshly-added albums surface on
// the next refetch.
export function createHomeQuery() {
return createQuery({
queryKey: qk.home(),
queryFn: listHome,
staleTime: 60_000
});
}