Files
Psychotoxical-psysonic/src/features/offline/utils/offlineLocalBrowse.test.ts
T
Psychotoxical 7e9cb60763 refactor(api): co-locate server-protocol clients into lib/api
Move the Subsonic + Navidrome protocol clients and the library IPC facade
(subsonic*, navidrome*, library.ts + tests) from src/api/ into src/lib/api/,
the feature-free infra layer (plan M4, decision §10.3: shared client core → lib/).
Pure move: deep-path import specifiers @/api/* -> @/lib/api/* across ~150
consumers; no behaviour change. Domain REST (lyrics/events) and cover/analysis
infra stay in src/api/ pending their own M4 placement.

tsc 0, lint 0/0, full suite 319 files / 2353 tests green.
2026-06-30 07:53:25 +02:00

96 lines
3.2 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { LibraryTrackDto } from '@/lib/api/library';
import { useAuthStore } from '@/store/authStore';
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
import { useLocalPlaybackStore } from '@/store/localPlaybackStore';
import {
countLocalBrowsableTracks,
fetchOfflineLocalBrowsableSongPage,
offlineLocalBrowseEnabled,
} from '@/features/offline/utils/offlineLocalBrowse';
const { libraryGetTracksBatchChunkedMock } = vi.hoisted(() => ({
libraryGetTracksBatchChunkedMock: vi.fn(async (): Promise<LibraryTrackDto[]> => []),
}));
vi.mock('@/lib/api/library', () => ({
libraryGetTracksBatchChunked: libraryGetTracksBatchChunkedMock,
libraryGetTracksByAlbum: vi.fn(async () => []),
libraryAdvancedSearch: vi.fn(async () => ({ albums: [], artists: [], tracks: [] })),
}));
describe('offlineLocalBrowse', () => {
beforeEach(() => {
useAuthStore.setState({
activeServerId: 'srv-a',
servers: [{ id: 'srv-a', name: 'A', url: 'https://a.test', username: 'u', password: 'p' }],
});
useLibraryIndexStore.setState({ masterEnabled: true });
useLocalPlaybackStore.setState({ entries: {} });
libraryGetTracksBatchChunkedMock.mockReset();
libraryGetTracksBatchChunkedMock.mockResolvedValue([]);
});
it('offlineLocalBrowseEnabled requires index and local bytes', () => {
expect(offlineLocalBrowseEnabled('srv-a')).toBe(false);
useLocalPlaybackStore.setState({
entries: {
'a.test:t1': {
serverIndexKey: 'a.test',
trackId: 't1',
localPath: '/media/library/a.test/a/al/t1.mp3',
layoutFingerprint: 'fp',
sizeBytes: 1,
tier: 'library',
cachedAt: 1,
suffix: 'mp3',
},
},
});
expect(countLocalBrowsableTracks('srv-a')).toBe(1);
expect(offlineLocalBrowseEnabled('srv-a')).toBe(true);
});
it('fetchOfflineLocalBrowsableSongPage pages local bytes alphabetically', async () => {
useLocalPlaybackStore.setState({
entries: {
'a.test:t1': {
serverIndexKey: 'a.test',
trackId: 't1',
localPath: '/media/library/a.test/a/al/t1.mp3',
layoutFingerprint: 'fp',
sizeBytes: 1,
tier: 'library',
cachedAt: 1,
suffix: 'mp3',
},
'a.test:t2': {
serverIndexKey: 'a.test',
trackId: 't2',
localPath: '/media/library/a.test/a/al/t2.mp3',
layoutFingerprint: 'fp',
sizeBytes: 1,
tier: 'library',
cachedAt: 1,
suffix: 'mp3',
},
},
});
libraryGetTracksBatchChunkedMock.mockResolvedValue([
{
id: 't2', title: 'Beta', artist: 'A', album: 'Al', albumId: 'al-1',
durationSec: 1, serverId: 'srv-a', syncedAt: 1, rawJson: {},
},
{
id: 't1', title: 'Alpha', artist: 'A', album: 'Al', albumId: 'al-1',
durationSec: 1, serverId: 'srv-a', syncedAt: 1, rawJson: {},
},
]);
const page = await fetchOfflineLocalBrowsableSongPage('srv-a', 0, 1);
expect(page?.songs.map(s => s.id)).toEqual(['t1']);
expect(page?.hasMore).toBe(true);
});
});