Files
Oxicloud/frontend/src/routes/music/page.test.ts
T

120 lines
3.7 KiB
TypeScript
Raw Normal View History

import { it, expect, vi, beforeEach } from 'vitest';
import { render, screen, fireEvent, waitFor } from '@testing-library/svelte';
const { ui, promptDialog, confirmDialog } = vi.hoisted(() => ({
ui: { notify: vi.fn() },
promptDialog: vi.fn(),
confirmDialog: vi.fn()
}));
vi.mock('$lib/stores/ui.svelte', () => ({ ui }));
vi.mock('$lib/stores/dialogs.svelte', () => ({ promptDialog, confirmDialog }));
vi.mock('$lib/api/endpoints/files', () => ({ fileInlineUrl: () => '/in' }));
vi.mock('$lib/api/endpoints/search', () => ({ searchFiles: vi.fn(async () => ({ items: [] })) }));
vi.mock('$lib/api/endpoints/music', () => ({
addTracks: vi.fn(),
createPlaylist: vi.fn(),
deletePlaylist: vi.fn(),
listPlaylists: vi.fn(),
listShares: vi.fn(async () => []),
listTracks: vi.fn(async () => []),
removeShare: vi.fn(),
removeTrack: vi.fn(),
renamePlaylist: vi.fn(),
reorderTracks: vi.fn(),
sharePlaylist: vi.fn(),
updatePlaylist: vi.fn(),
uploadCoverImage: vi.fn()
}));
import {
listPlaylists,
listTracks,
createPlaylist,
renamePlaylist,
deletePlaylist
} from '$lib/api/endpoints/music';
import MusicPage from './+page.svelte';
const m = (fn: unknown) => fn as ReturnType<typeof vi.fn>;
const playlist = {
id: 'p1',
name: 'Roadtrip',
description: 'songs',
owner_id: 'me',
is_public: false,
cover_file_id: null,
track_count: 1,
total_duration_secs: 180,
created_at: 0,
updated_at: 0
};
const track = {
id: 't1',
playlist_id: 'p1',
file_id: 'f1',
position: 0,
added_at: 0,
file_name: 'song.mp3',
file_size: 100,
mime_type: 'audio/mpeg',
title: 'Song',
artist: 'Artist',
album: 'Album',
duration_secs: 180
};
beforeEach(() => {
vi.clearAllMocks();
m(listTracks).mockResolvedValue([track]);
});
it('renders an empty state when there are no playlists', async () => {
m(listPlaylists).mockResolvedValue([]);
render(MusicPage);
await waitFor(() => expect(listPlaylists).toHaveBeenCalled());
await screen.findByTestId('music-create-playlist-empty-btn');
});
it('selects the first playlist and loads its tracks', async () => {
m(listPlaylists).mockResolvedValue([playlist]);
render(MusicPage);
await waitFor(() => expect(listTracks).toHaveBeenCalledWith('p1'));
await waitFor(() => expect(screen.getAllByText('Roadtrip').length).toBeGreaterThan(0));
});
it('creates a playlist from the header button', async () => {
m(listPlaylists).mockResolvedValue([playlist]);
promptDialog.mockResolvedValue('Chill');
m(createPlaylist).mockResolvedValue({ ...playlist, id: 'p2', name: 'Chill' });
render(MusicPage);
await screen.findByTestId('music-create-playlist-btn');
await fireEvent.click(screen.getByTestId('music-create-playlist-btn'));
await waitFor(() => expect(createPlaylist).toHaveBeenCalledWith('Chill'));
});
it('surfaces a load error', async () => {
m(listPlaylists).mockRejectedValue(new Error('boom'));
render(MusicPage);
await waitFor(() => expect(listPlaylists).toHaveBeenCalled());
await waitFor(() => expect(screen.getByText('boom')).toBeTruthy());
});
it('renames the current playlist', async () => {
m(listPlaylists).mockResolvedValue([playlist]);
promptDialog.mockResolvedValue('Renamed');
m(renamePlaylist).mockResolvedValue(undefined);
render(MusicPage);
await fireEvent.click(await screen.findByTestId('music-rename-playlist-btn'));
await waitFor(() => expect(renamePlaylist).toHaveBeenCalledWith('p1', 'Renamed'));
});
it('deletes the current playlist after confirmation', async () => {
m(listPlaylists).mockResolvedValue([playlist]);
confirmDialog.mockResolvedValue(true);
m(deletePlaylist).mockResolvedValue(undefined);
render(MusicPage);
await fireEvent.click(await screen.findByTestId('music-delete-playlist-btn'));
await waitFor(() => expect(deletePlaylist).toHaveBeenCalledWith('p1'));
});