mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 21:21:10 +02:00
test(mukke): add vitest setup and 34 frontend tests for player & library stores
- Set up vitest with jsdom, testing-library/svelte, and SvelteKit mocks - Player store: 16 tests covering playSong, queue, shuffle, repeat, volume, error handling, clearQueue, removeFromQueue - Library store: 18 tests covering loadSongs, loadCoverUrls (including non-image path filtering), albums, artists, genres, stats, favorites, tabs, upload, and delete Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
67326b738a
commit
70b1c4429d
9 changed files with 658 additions and 2 deletions
|
|
@ -11,7 +11,9 @@
|
|||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"lint": "eslint .",
|
||||
"format": "prettier --write .",
|
||||
"type-check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json"
|
||||
"type-check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@manacore/shared-pwa": "workspace:*",
|
||||
|
|
@ -20,6 +22,7 @@
|
|||
"@sveltejs/kit": "^2.47.1",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||
"@tailwindcss/vite": "^4.1.7",
|
||||
"@testing-library/svelte": "^5.2.6",
|
||||
"@types/node": "^20.0.0",
|
||||
"@vite-pwa/sveltekit": "^1.1.0",
|
||||
"prettier": "^3.1.1",
|
||||
|
|
@ -29,7 +32,8 @@
|
|||
"tailwindcss": "^4.1.7",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^6.0.0"
|
||||
"vite": "^6.0.0",
|
||||
"vitest": "^4.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@manacore/shared-api-client": "workspace:*",
|
||||
|
|
|
|||
264
apps/mukke/apps/web/src/lib/stores/library.svelte.test.ts
Normal file
264
apps/mukke/apps/web/src/lib/stores/library.svelte.test.ts
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
|
||||
// Mock auth store
|
||||
vi.mock('./auth.svelte', () => ({
|
||||
authStore: {
|
||||
getAuthHeaders: vi.fn().mockResolvedValue({ Authorization: 'Bearer test-token' }),
|
||||
},
|
||||
}));
|
||||
|
||||
let libraryStore: typeof import('./library.svelte').libraryStore;
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks();
|
||||
vi.resetModules();
|
||||
const mod = await import('./library.svelte');
|
||||
libraryStore = mod.libraryStore;
|
||||
});
|
||||
|
||||
function mockFetchResponse(data: unknown) {
|
||||
(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: () => Promise.resolve(data),
|
||||
});
|
||||
}
|
||||
|
||||
function mockFetchError(message = 'Request failed') {
|
||||
(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
ok: false,
|
||||
json: () => Promise.resolve({ message }),
|
||||
});
|
||||
}
|
||||
|
||||
describe('libraryStore', () => {
|
||||
describe('initial state', () => {
|
||||
it('starts empty', () => {
|
||||
expect(libraryStore.songs).toEqual([]);
|
||||
expect(libraryStore.albums).toEqual([]);
|
||||
expect(libraryStore.artists).toEqual([]);
|
||||
expect(libraryStore.genres).toEqual([]);
|
||||
expect(libraryStore.stats).toBeNull();
|
||||
expect(libraryStore.activeTab).toBe('songs');
|
||||
expect(libraryStore.isLoading).toBe(false);
|
||||
expect(libraryStore.error).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadSongs', () => {
|
||||
it('fetches songs and sets state', async () => {
|
||||
const songs = [
|
||||
{ id: '1', title: 'Song 1', coverArtPath: null },
|
||||
{ id: '2', title: 'Song 2', coverArtPath: null },
|
||||
];
|
||||
mockFetchResponse({ songs });
|
||||
|
||||
await libraryStore.loadSongs();
|
||||
|
||||
expect(libraryStore.songs).toEqual(songs);
|
||||
expect(libraryStore.isLoading).toBe(false);
|
||||
expect(libraryStore.error).toBeNull();
|
||||
});
|
||||
|
||||
it('sets error on failure', async () => {
|
||||
mockFetchError('Server error');
|
||||
|
||||
await libraryStore.loadSongs();
|
||||
|
||||
expect(libraryStore.songs).toEqual([]);
|
||||
expect(libraryStore.error).toBe('Server error');
|
||||
expect(libraryStore.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
it('loads cover URLs for songs with cover art', async () => {
|
||||
const songs = [
|
||||
{ id: '1', title: 'Song 1', coverArtPath: 'users/test/covers/1.jpg' },
|
||||
{ id: '2', title: 'Song 2', coverArtPath: null },
|
||||
];
|
||||
mockFetchResponse({ songs });
|
||||
// Cover URLs fetch
|
||||
mockFetchResponse({ urls: { 'users/test/covers/1.jpg': 'https://minio.test/cover.jpg' } });
|
||||
|
||||
await libraryStore.loadSongs();
|
||||
|
||||
// Wait for cover URLs to load (async, non-blocking)
|
||||
await vi.waitFor(() => {
|
||||
expect(libraryStore.coverUrls['users/test/covers/1.jpg']).toBe(
|
||||
'https://minio.test/cover.jpg'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadCoverUrls', () => {
|
||||
it('filters out non-image paths', async () => {
|
||||
// Should not make any fetch for .mp3 paths
|
||||
await libraryStore.loadCoverUrls(['users/test/song.mp3', 'users/test/audio.wav']);
|
||||
|
||||
expect(global.fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('loads valid image paths', async () => {
|
||||
mockFetchResponse({
|
||||
urls: { 'users/test/covers/1.jpg': 'https://minio.test/cover.jpg' },
|
||||
});
|
||||
|
||||
await libraryStore.loadCoverUrls(['users/test/covers/1.jpg']);
|
||||
|
||||
expect(libraryStore.coverUrls['users/test/covers/1.jpg']).toBe(
|
||||
'https://minio.test/cover.jpg'
|
||||
);
|
||||
});
|
||||
|
||||
it('does not refetch cached URLs', async () => {
|
||||
mockFetchResponse({
|
||||
urls: { 'users/test/covers/1.jpg': 'https://minio.test/cover.jpg' },
|
||||
});
|
||||
await libraryStore.loadCoverUrls(['users/test/covers/1.jpg']);
|
||||
|
||||
// Second call with same path should not fetch
|
||||
await libraryStore.loadCoverUrls(['users/test/covers/1.jpg']);
|
||||
|
||||
expect(global.fetch).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('silently handles errors', async () => {
|
||||
(global.fetch as ReturnType<typeof vi.fn>).mockRejectedValueOnce(new Error('Network error'));
|
||||
|
||||
await libraryStore.loadCoverUrls(['users/test/covers/1.png']);
|
||||
|
||||
// Should not throw or set error
|
||||
expect(libraryStore.error).toBeNull();
|
||||
});
|
||||
|
||||
it('accepts various image extensions', async () => {
|
||||
const paths = [
|
||||
'covers/1.jpg',
|
||||
'covers/2.jpeg',
|
||||
'covers/3.png',
|
||||
'covers/4.webp',
|
||||
'covers/5.gif',
|
||||
'covers/6.avif',
|
||||
'covers/7.svg',
|
||||
];
|
||||
const urls: Record<string, string> = {};
|
||||
paths.forEach((p) => (urls[p] = `https://minio.test/${p}`));
|
||||
mockFetchResponse({ urls });
|
||||
|
||||
await libraryStore.loadCoverUrls(paths);
|
||||
|
||||
expect(global.fetch).toHaveBeenCalledTimes(1);
|
||||
paths.forEach((p) => {
|
||||
expect(libraryStore.coverUrls[p]).toBe(`https://minio.test/${p}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadAlbums', () => {
|
||||
it('fetches albums and sets state', async () => {
|
||||
const albums = [{ album: 'Album 1', songCount: 5, coverArtPath: null }];
|
||||
mockFetchResponse({ albums });
|
||||
|
||||
await libraryStore.loadAlbums();
|
||||
|
||||
expect(libraryStore.albums).toEqual(albums);
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadArtists', () => {
|
||||
it('fetches artists and sets state', async () => {
|
||||
const artists = [{ artist: 'Artist 1', songCount: 3, albumCount: 1 }];
|
||||
mockFetchResponse({ artists });
|
||||
|
||||
await libraryStore.loadArtists();
|
||||
|
||||
expect(libraryStore.artists).toEqual(artists);
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadGenres', () => {
|
||||
it('fetches genres and sets state', async () => {
|
||||
const genres = [{ genre: 'Rock', songCount: 10 }];
|
||||
mockFetchResponse({ genres });
|
||||
|
||||
await libraryStore.loadGenres();
|
||||
|
||||
expect(libraryStore.genres).toEqual(genres);
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadStats', () => {
|
||||
it('fetches stats and sets state', async () => {
|
||||
const stats = { totalSongs: 50, totalArtists: 10, totalAlbums: 5, totalGenres: 3 };
|
||||
mockFetchResponse({ stats });
|
||||
|
||||
await libraryStore.loadStats();
|
||||
|
||||
expect(libraryStore.stats).toEqual(stats);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleFavorite', () => {
|
||||
it('updates song in list', async () => {
|
||||
const songs = [{ id: '1', title: 'Song 1', favorite: false, coverArtPath: null }];
|
||||
mockFetchResponse({ songs });
|
||||
await libraryStore.loadSongs();
|
||||
|
||||
mockFetchResponse({ song: { ...songs[0], favorite: true } });
|
||||
await libraryStore.toggleFavorite('1');
|
||||
|
||||
expect(libraryStore.songs[0].favorite).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setActiveTab', () => {
|
||||
it('changes active tab', () => {
|
||||
expect(libraryStore.activeTab).toBe('songs');
|
||||
libraryStore.setActiveTab('albums');
|
||||
expect(libraryStore.activeTab).toBe('albums');
|
||||
});
|
||||
|
||||
it('triggers load for empty tabs', async () => {
|
||||
mockFetchResponse({ albums: [] });
|
||||
libraryStore.setActiveTab('albums');
|
||||
|
||||
// setActiveTab triggers an async load internally
|
||||
await vi.waitFor(() => {
|
||||
expect(global.fetch).toHaveBeenCalled();
|
||||
});
|
||||
expect(libraryStore.activeTab).toBe('albums');
|
||||
});
|
||||
});
|
||||
|
||||
describe('uploadSong', () => {
|
||||
it('creates song and uploads file', async () => {
|
||||
const song = { id: '1', title: 'New Song' };
|
||||
mockFetchResponse({ song, uploadUrl: 'https://minio.test/upload' });
|
||||
// Upload PUT
|
||||
(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ ok: true });
|
||||
|
||||
const file = new File(['audio data'], 'song.mp3', { type: 'audio/mpeg' });
|
||||
const result = await libraryStore.uploadSong(file);
|
||||
|
||||
expect(result).toEqual(song);
|
||||
expect(libraryStore.songs).toContainEqual(song);
|
||||
expect(global.fetch).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteSong', () => {
|
||||
it('removes song from list', async () => {
|
||||
const songs = [
|
||||
{ id: '1', title: 'Song 1', coverArtPath: null },
|
||||
{ id: '2', title: 'Song 2', coverArtPath: null },
|
||||
];
|
||||
mockFetchResponse({ songs });
|
||||
await libraryStore.loadSongs();
|
||||
|
||||
mockFetchResponse({});
|
||||
await libraryStore.deleteSong('1');
|
||||
|
||||
expect(libraryStore.songs).toHaveLength(1);
|
||||
expect(libraryStore.songs[0].id).toBe('2');
|
||||
});
|
||||
});
|
||||
});
|
||||
254
apps/mukke/apps/web/src/lib/stores/player.svelte.test.ts
Normal file
254
apps/mukke/apps/web/src/lib/stores/player.svelte.test.ts
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
|
||||
// Mock auth store before importing player store
|
||||
vi.mock('./auth.svelte', () => ({
|
||||
authStore: {
|
||||
getAuthHeaders: vi.fn().mockResolvedValue({ Authorization: 'Bearer test-token' }),
|
||||
},
|
||||
}));
|
||||
|
||||
// Dynamic import to allow mock setup first
|
||||
let playerStore: typeof import('./player.svelte').playerStore;
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
// Reset module registry so each test gets a fresh store
|
||||
vi.resetModules();
|
||||
const mod = await import('./player.svelte');
|
||||
playerStore = mod.playerStore;
|
||||
});
|
||||
|
||||
function makeSong(overrides: Partial<{ id: string; title: string; artist: string }> = {}) {
|
||||
return {
|
||||
id: overrides.id ?? '1',
|
||||
title: overrides.title ?? 'Test Song',
|
||||
artist: overrides.artist ?? 'Test Artist',
|
||||
album: null,
|
||||
albumArtist: null,
|
||||
genre: null,
|
||||
trackNumber: null,
|
||||
year: null,
|
||||
duration: 180,
|
||||
storagePath: 'users/test/1.mp3',
|
||||
coverArtPath: null,
|
||||
fileSize: null,
|
||||
bpm: null,
|
||||
favorite: false,
|
||||
playCount: 0,
|
||||
lastPlayedAt: null,
|
||||
addedAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
userId: 'user-1',
|
||||
} as any;
|
||||
}
|
||||
|
||||
describe('playerStore', () => {
|
||||
describe('initial state', () => {
|
||||
it('starts with no song playing', () => {
|
||||
expect(playerStore.currentSong).toBeNull();
|
||||
expect(playerStore.isPlaying).toBe(false);
|
||||
expect(playerStore.currentTime).toBe(0);
|
||||
expect(playerStore.duration).toBe(0);
|
||||
expect(playerStore.error).toBeNull();
|
||||
});
|
||||
|
||||
it('starts with default settings', () => {
|
||||
expect(playerStore.volume).toBe(1);
|
||||
expect(playerStore.repeatMode).toBe('off');
|
||||
expect(playerStore.shuffleOn).toBe(false);
|
||||
expect(playerStore.queue).toEqual([]);
|
||||
expect(playerStore.showFullPlayer).toBe(false);
|
||||
expect(playerStore.showQueue).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('playSong', () => {
|
||||
it('sets current song and fetches download URL', async () => {
|
||||
const song = makeSong();
|
||||
(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: () => Promise.resolve({ url: 'https://minio.test/song.mp3' }),
|
||||
});
|
||||
|
||||
await playerStore.playSong(song);
|
||||
|
||||
expect(playerStore.currentSong).toEqual(song);
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
expect.stringContaining('/songs/1/download-url'),
|
||||
expect.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
it('sets up queue when provided', async () => {
|
||||
const songs = [makeSong({ id: '1' }), makeSong({ id: '2' }), makeSong({ id: '3' })];
|
||||
(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: () => Promise.resolve({ url: 'https://minio.test/song.mp3' }),
|
||||
});
|
||||
|
||||
await playerStore.playSong(songs[1], songs, 1);
|
||||
|
||||
expect(playerStore.currentSong?.id).toBe('2');
|
||||
expect(playerStore.queue).toHaveLength(3);
|
||||
expect(playerStore.currentIndex).toBe(1);
|
||||
});
|
||||
|
||||
it('sets error when download URL fetch fails', async () => {
|
||||
const song = makeSong();
|
||||
(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
ok: false,
|
||||
json: () => Promise.resolve({ message: 'Not found' }),
|
||||
});
|
||||
|
||||
await playerStore.playSong(song);
|
||||
|
||||
expect(playerStore.isPlaying).toBe(false);
|
||||
expect(playerStore.error).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('togglePlay', () => {
|
||||
it('does nothing when no song is loaded', () => {
|
||||
playerStore.togglePlay();
|
||||
expect(playerStore.isPlaying).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleRepeat', () => {
|
||||
it('cycles through off -> all -> one -> off', () => {
|
||||
expect(playerStore.repeatMode).toBe('off');
|
||||
|
||||
playerStore.toggleRepeat();
|
||||
expect(playerStore.repeatMode).toBe('all');
|
||||
|
||||
playerStore.toggleRepeat();
|
||||
expect(playerStore.repeatMode).toBe('one');
|
||||
|
||||
playerStore.toggleRepeat();
|
||||
expect(playerStore.repeatMode).toBe('off');
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleShuffle', () => {
|
||||
it('toggles shuffle state', () => {
|
||||
expect(playerStore.shuffleOn).toBe(false);
|
||||
playerStore.toggleShuffle();
|
||||
expect(playerStore.shuffleOn).toBe(true);
|
||||
playerStore.toggleShuffle();
|
||||
expect(playerStore.shuffleOn).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleFullPlayer', () => {
|
||||
it('toggles full player visibility', () => {
|
||||
expect(playerStore.showFullPlayer).toBe(false);
|
||||
playerStore.toggleFullPlayer();
|
||||
expect(playerStore.showFullPlayer).toBe(true);
|
||||
playerStore.toggleFullPlayer();
|
||||
expect(playerStore.showFullPlayer).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleQueue', () => {
|
||||
it('toggles queue panel visibility', () => {
|
||||
expect(playerStore.showQueue).toBe(false);
|
||||
playerStore.toggleQueue();
|
||||
expect(playerStore.showQueue).toBe(true);
|
||||
playerStore.toggleQueue();
|
||||
expect(playerStore.showQueue).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('error handling', () => {
|
||||
it('clearError resets error state', async () => {
|
||||
const song = makeSong();
|
||||
(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
ok: false,
|
||||
json: () => Promise.resolve({ message: 'Not found' }),
|
||||
});
|
||||
|
||||
await playerStore.playSong(song);
|
||||
expect(playerStore.error).toBeTruthy();
|
||||
|
||||
playerStore.clearError();
|
||||
expect(playerStore.error).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('clearQueue', () => {
|
||||
it('resets all state', async () => {
|
||||
const song = makeSong();
|
||||
(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: () => Promise.resolve({ url: 'https://minio.test/song.mp3' }),
|
||||
});
|
||||
|
||||
await playerStore.playSong(song, [song], 0);
|
||||
playerStore.clearQueue();
|
||||
|
||||
expect(playerStore.currentSong).toBeNull();
|
||||
expect(playerStore.isPlaying).toBe(false);
|
||||
expect(playerStore.queue).toEqual([]);
|
||||
expect(playerStore.error).toBeNull();
|
||||
expect(playerStore.showFullPlayer).toBe(false);
|
||||
expect(playerStore.showQueue).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('playQueue', () => {
|
||||
it('starts playing from given index', async () => {
|
||||
const songs = [makeSong({ id: '1' }), makeSong({ id: '2' }), makeSong({ id: '3' })];
|
||||
(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: () => Promise.resolve({ url: 'https://minio.test/song2.mp3' }),
|
||||
});
|
||||
|
||||
await playerStore.playQueue(songs, 1);
|
||||
|
||||
expect(playerStore.currentSong?.id).toBe('2');
|
||||
expect(playerStore.queue).toHaveLength(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setVolume', () => {
|
||||
it('clamps volume between 0 and 1', () => {
|
||||
playerStore.setVolume(0.5);
|
||||
expect(playerStore.volume).toBe(0.5);
|
||||
|
||||
playerStore.setVolume(-1);
|
||||
expect(playerStore.volume).toBe(0);
|
||||
|
||||
playerStore.setVolume(2);
|
||||
expect(playerStore.volume).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeFromQueue', () => {
|
||||
it('does not remove current song', async () => {
|
||||
const songs = [makeSong({ id: '1' }), makeSong({ id: '2' })];
|
||||
(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: () => Promise.resolve({ url: 'https://minio.test/song.mp3' }),
|
||||
});
|
||||
|
||||
await playerStore.playSong(songs[0], songs, 0);
|
||||
playerStore.removeFromQueue(0);
|
||||
|
||||
expect(playerStore.queue).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('removes non-current song from queue', async () => {
|
||||
const songs = [makeSong({ id: '1' }), makeSong({ id: '2' }), makeSong({ id: '3' })];
|
||||
(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: () => Promise.resolve({ url: 'https://minio.test/song.mp3' }),
|
||||
});
|
||||
|
||||
await playerStore.playSong(songs[0], songs, 0);
|
||||
playerStore.removeFromQueue(2);
|
||||
|
||||
expect(playerStore.queue).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
4
apps/mukke/apps/web/src/test/mocks/app/environment.ts
Normal file
4
apps/mukke/apps/web/src/test/mocks/app/environment.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export const browser = true;
|
||||
export const building = false;
|
||||
export const dev = true;
|
||||
export const version = 'test';
|
||||
9
apps/mukke/apps/web/src/test/mocks/app/navigation.ts
Normal file
9
apps/mukke/apps/web/src/test/mocks/app/navigation.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { vi } from 'vitest';
|
||||
|
||||
export const goto = vi.fn();
|
||||
export const invalidate = vi.fn();
|
||||
export const invalidateAll = vi.fn();
|
||||
export const prefetch = vi.fn();
|
||||
export const prefetchRoutes = vi.fn();
|
||||
export const beforeNavigate = vi.fn();
|
||||
export const afterNavigate = vi.fn();
|
||||
17
apps/mukke/apps/web/src/test/mocks/app/stores.ts
Normal file
17
apps/mukke/apps/web/src/test/mocks/app/stores.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { writable, readable } from 'svelte/store';
|
||||
|
||||
export const page = readable({
|
||||
url: new URL('http://localhost'),
|
||||
params: {},
|
||||
route: { id: '/' },
|
||||
status: 200,
|
||||
error: null,
|
||||
data: {},
|
||||
form: null,
|
||||
});
|
||||
|
||||
export const navigating = readable(null);
|
||||
export const updated = {
|
||||
subscribe: writable(false).subscribe,
|
||||
check: async () => false,
|
||||
};
|
||||
2
apps/mukke/apps/web/src/test/mocks/env/static/public.ts
vendored
Normal file
2
apps/mukke/apps/web/src/test/mocks/env/static/public.ts
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export const PUBLIC_BACKEND_URL = 'http://localhost:3010';
|
||||
export const PUBLIC_MANA_CORE_AUTH_URL = 'http://localhost:3001';
|
||||
82
apps/mukke/apps/web/src/test/setup.ts
Normal file
82
apps/mukke/apps/web/src/test/setup.ts
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import { vi, beforeEach } from 'vitest';
|
||||
|
||||
// Mock localStorage
|
||||
const localStorageMock = {
|
||||
getItem: vi.fn(),
|
||||
setItem: vi.fn(),
|
||||
removeItem: vi.fn(),
|
||||
clear: vi.fn(),
|
||||
};
|
||||
Object.defineProperty(global, 'localStorage', { value: localStorageMock });
|
||||
|
||||
// Mock fetch
|
||||
global.fetch = vi.fn();
|
||||
|
||||
// Mock Audio
|
||||
class MockAudio {
|
||||
src = '';
|
||||
currentTime = 0;
|
||||
duration = 0;
|
||||
volume = 1;
|
||||
paused = true;
|
||||
error: MediaError | null = null;
|
||||
private listeners: Record<string, Function[]> = {};
|
||||
|
||||
addEventListener(event: string, handler: Function) {
|
||||
if (!this.listeners[event]) this.listeners[event] = [];
|
||||
this.listeners[event].push(handler);
|
||||
}
|
||||
|
||||
removeEventListener(event: string, handler: Function) {
|
||||
if (this.listeners[event]) {
|
||||
this.listeners[event] = this.listeners[event].filter((h) => h !== handler);
|
||||
}
|
||||
}
|
||||
|
||||
dispatchEvent(event: Event) {
|
||||
const handlers = this.listeners[event.type] || [];
|
||||
handlers.forEach((h) => h(event));
|
||||
return true;
|
||||
}
|
||||
|
||||
emit(eventName: string) {
|
||||
const handlers = this.listeners[eventName] || [];
|
||||
handlers.forEach((h) => h());
|
||||
}
|
||||
|
||||
async play() {
|
||||
this.paused = false;
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
pause() {
|
||||
this.paused = true;
|
||||
}
|
||||
}
|
||||
global.Audio = MockAudio as unknown as typeof Audio;
|
||||
|
||||
// Mock MediaError
|
||||
if (typeof global.MediaError === 'undefined') {
|
||||
(global as Record<string, unknown>).MediaError = {
|
||||
MEDIA_ERR_ABORTED: 1,
|
||||
MEDIA_ERR_NETWORK: 2,
|
||||
MEDIA_ERR_DECODE: 3,
|
||||
MEDIA_ERR_SRC_NOT_SUPPORTED: 4,
|
||||
};
|
||||
}
|
||||
|
||||
// Mock MediaSession
|
||||
if (typeof navigator !== 'undefined' && !('mediaSession' in navigator)) {
|
||||
Object.defineProperty(navigator, 'mediaSession', {
|
||||
value: {
|
||||
metadata: null,
|
||||
setActionHandler: vi.fn(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Reset mocks before each test
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
localStorageMock.getItem.mockReturnValue(null);
|
||||
});
|
||||
20
apps/mukke/apps/web/vitest.config.ts
Normal file
20
apps/mukke/apps/web/vitest.config.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { defineConfig } from 'vitest/config';
|
||||
import { svelte } from '@sveltejs/vite-plugin-svelte';
|
||||
import path from 'path';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [svelte({ hot: !process.env.VITEST })],
|
||||
test: {
|
||||
include: ['src/**/*.{test,spec}.{js,ts}'],
|
||||
environment: 'jsdom',
|
||||
globals: true,
|
||||
setupFiles: ['./src/test/setup.ts'],
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
$lib: path.resolve(__dirname, './src/lib'),
|
||||
$app: path.resolve(__dirname, './src/test/mocks/app'),
|
||||
'$env/static/public': path.resolve(__dirname, './src/test/mocks/env/static/public.ts'),
|
||||
},
|
||||
},
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue