Files
minstrel/web/src/lib/api/playlists.test.ts
T
bvandeusenandClaude Opus 4.7 993bcc6a14 fix(web/test): update playlist API tests for api.* call surface (#375)
After e8eff1b migrated playlists.ts from raw apiFetch to the api.*
wrapper, three test files mocked the wrong surface:

- playlists.test.ts: GET case asserted init.method === 'GET' but
  api.get omits init.method entirely (fetch defaults to GET). Fall
  back to 'GET' when init.method is undefined.
- playlists.refresh-discover.test.ts: re-mock ./client to expose
  `api: { post: vi.fn() }` instead of `apiFetch`; assertions check
  api.post call args.
- playlists.refresh-foryou.test.ts: same.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:48:04 -04:00

76 lines
2.7 KiB
TypeScript

import { afterEach, describe, expect, test, vi } from 'vitest';
import {
listPlaylists,
getPlaylist,
createPlaylist,
reorderPlaylist,
removePlaylistTrack
} from './playlists';
function stubFetch(status: number, body: unknown, init: Partial<Response> = {}) {
const res = new Response(
body === null ? null : JSON.stringify(body),
{ status, headers: { 'Content-Type': 'application/json' }, ...init }
);
const spy = vi.fn().mockResolvedValue(res);
vi.stubGlobal('fetch', spy);
return spy;
}
afterEach(() => {
vi.unstubAllGlobals();
});
describe('playlists API helper', () => {
test('listPlaylists GETs /api/playlists with default kind=user', async () => {
const spy = stubFetch(200, { owned: [], public: [] });
const r = await listPlaylists();
expect(r.owned).toEqual([]);
expect(r.public).toEqual([]);
const call = spy.mock.calls[0];
expect(call[0]).toBe('/api/playlists?kind=user');
// GET is fetch's default method; api.get omits init.method entirely.
expect((call[1] as RequestInit | undefined)?.method ?? 'GET').toBe('GET');
});
test('listPlaylists passes kind=system through the query string', async () => {
const spy = stubFetch(200, { owned: [], public: [] });
await listPlaylists('system');
const call = spy.mock.calls[0];
expect(call[0]).toBe('/api/playlists?kind=system');
});
test('createPlaylist POSTs JSON body', async () => {
const spy = stubFetch(200, { id: 'p1', name: 'Test' });
const r = await createPlaylist({ name: 'Test' });
expect(r.id).toBe('p1');
const call = spy.mock.calls[0];
expect((call[1] as RequestInit).method).toBe('POST');
expect(JSON.parse((call[1] as RequestInit).body as string)).toEqual({ name: 'Test' });
});
test('reorderPlaylist PUTs ordered_positions', async () => {
const spy = stubFetch(200, { id: 'p1', tracks: [] });
await reorderPlaylist('p1', [2, 1, 0]);
const call = spy.mock.calls[0];
expect(call[0]).toBe('/api/playlists/p1/tracks');
expect((call[1] as RequestInit).method).toBe('PUT');
expect(JSON.parse((call[1] as RequestInit).body as string)).toEqual({
ordered_positions: [2, 1, 0]
});
});
test('removePlaylistTrack DELETEs by position', async () => {
const spy = stubFetch(200, { id: 'p1', tracks: [] });
await removePlaylistTrack('p1', 3);
const call = spy.mock.calls[0];
expect(call[0]).toBe('/api/playlists/p1/tracks/3');
expect((call[1] as RequestInit).method).toBe('DELETE');
});
test('not_found surfaces as ApiError', async () => {
stubFetch(404, { error: 'not_found' });
await expect(getPlaylist('missing')).rejects.toMatchObject({ code: 'not_found' });
});
});