mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 07:15:47 +00:00
refactor(api): pull feature-resident subsonic clients into lib/api
Move the 5 subsonic api modules that M3 had co-located into features (subsonicAlbumInfo/Artists/Playlists/Radio/Statistics) into src/lib/api/, the feature-free infra layer, and drop their feature-barrel re-exports. Consumers now import these protocol calls directly from @/lib/api/subsonic*; the feature barrels export only domain UI/hooks/state. This is the consistent api placement (plan §10.3, revised: ALL subsonic protocol clients are feature-free infra, not per-feature) and it kills the documented api-induced feature<->feature cycles: offline->artist/playlist (getArtist/ getPlaylist*), album->artist (getArtistInfo), deviceSync/nowPlaying/orbit->*. Remaining artist<->album refs are UI-only (OpenArtistRefInline/coerceOpenArtist Refs) and offline->playlist is store-level (usePlaylistStore) — both deeper, tracked for M5, not api placement. Pure move: import specifiers + vi.mock/spy targets repointed; no behaviour change. tsc 0, lint 0/0, full suite 319 files / 2353 tests green. Tooling note: barrel-imported and dynamic-import api symbols were split out of @/features/* to @/lib/api/* (tsc-driven); 12 vi.mock barrels retargeted to the deep lib module (mock-collapse sweep), AlbumHeader's UI mock left untouched.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { getArtist, getArtistInfo } from '@/features/artist';
|
||||
import { getArtist, getArtistInfo } from '@/lib/api/subsonicArtists';
|
||||
import { filterAlbumsToActiveLibrary } from '@/lib/api/subsonicLibrary';
|
||||
import { resolveAlbum, resolveMediaServerId } from '@/features/offline';
|
||||
import type { SubsonicAlbum } from '@/lib/api/subsonicTypes';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Play } from 'lucide-react';
|
||||
import { updatePlaylist } from '@/features/playlist';
|
||||
import { updatePlaylist } from '@/lib/api/subsonicPlaylists';
|
||||
import { resolvePlaylist, resolveMediaServerId } from '@/features/offline';
|
||||
import { songToTrack } from '../utils/playback/songToTrack';
|
||||
import type { Track } from '../store/playerStoreTypes';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ListMusic, Plus } from 'lucide-react';
|
||||
import { getPlaylist, updatePlaylist } from '@/features/playlist';
|
||||
import { getPlaylist, updatePlaylist } from '@/lib/api/subsonicPlaylists';
|
||||
import type { SubsonicPlaylist } from '@/lib/api/subsonicTypes';
|
||||
import { usePlaylistStore } from '@/features/playlist';
|
||||
import { showToast } from '../../utils/ui/toast';
|
||||
|
||||
@@ -39,7 +39,7 @@ export function MultiAlbumToPlaylistSubmenu({ albumIds, onDone, triggerId: _trig
|
||||
}, [albumIds]);
|
||||
|
||||
const handleAddWithToast = async (pl: SubsonicPlaylist, songIds: string[]) => {
|
||||
const { updatePlaylist } = await import('@/features/playlist');
|
||||
const { updatePlaylist } = await import('@/lib/api/subsonicPlaylists');
|
||||
const touchPlaylist = usePlaylistStore.getState().touchPlaylist;
|
||||
|
||||
try {
|
||||
@@ -125,7 +125,7 @@ export function MultiAlbumToPlaylistSubmenu({ albumIds, onDone, triggerId: _trig
|
||||
const handleCreate = async () => {
|
||||
const name = newName.trim() || t('playlists.unnamed');
|
||||
try {
|
||||
const { createPlaylist } = await import('@/features/playlist');
|
||||
const { createPlaylist } = await import('@/lib/api/subsonicPlaylists');
|
||||
const pl = await createPlaylist(name, songIds);
|
||||
if (pl?.id) {
|
||||
usePlaylistStore.getState().touchPlaylist(pl.id);
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ListMusic, Plus } from 'lucide-react';
|
||||
import { resolveAlbum, resolveArtist, resolveMediaServerId, resolvePlaylist } from '@/features/offline';
|
||||
import { getPlaylists } from '@/features/playlist';
|
||||
import { getPlaylists } from '@/lib/api/subsonicPlaylists';
|
||||
import type { SubsonicPlaylist } from '@/lib/api/subsonicTypes';
|
||||
import { usePlaylistStore } from '@/features/playlist';
|
||||
import { showToast } from '../../utils/ui/toast';
|
||||
@@ -53,7 +53,7 @@ export function MultiArtistToPlaylistSubmenu({ artistIds, onDone, triggerId: _tr
|
||||
}, [artistIds]);
|
||||
|
||||
const handleAddWithToast = async (pl: SubsonicPlaylist, songIds: string[]) => {
|
||||
const { updatePlaylist } = await import('@/features/playlist');
|
||||
const { updatePlaylist } = await import('@/lib/api/subsonicPlaylists');
|
||||
const touchPlaylist = usePlaylistStore.getState().touchPlaylist;
|
||||
|
||||
try {
|
||||
@@ -139,7 +139,7 @@ export function MultiArtistToPlaylistSubmenu({ artistIds, onDone, triggerId: _tr
|
||||
const handleCreate = async () => {
|
||||
const name = newName.trim() || t('playlists.unnamed');
|
||||
try {
|
||||
const { createPlaylist } = await import('@/features/playlist');
|
||||
const { createPlaylist } = await import('@/lib/api/subsonicPlaylists');
|
||||
const pl = await createPlaylist(name, songIds);
|
||||
if (pl?.id) {
|
||||
usePlaylistStore.getState().touchPlaylist(pl.id);
|
||||
|
||||
@@ -65,7 +65,7 @@ export default function PlaylistContextItems(props: ContextMenuItemsProps) {
|
||||
<div className="context-menu-divider" />
|
||||
<div className="context-menu-item" style={{ color: 'var(--danger)' }} onClick={() => handleAction(async () => {
|
||||
const { showToast } = await import('../../utils/ui/toast');
|
||||
const { deletePlaylist } = await import('@/features/playlist');
|
||||
const { deletePlaylist } = await import('@/lib/api/subsonicPlaylists');
|
||||
const { removeId } = usePlaylistStore.getState();
|
||||
try {
|
||||
await deletePlaylist(playlist.id);
|
||||
@@ -113,7 +113,7 @@ export default function PlaylistContextItems(props: ContextMenuItemsProps) {
|
||||
{offlinePolicy.canEditPlaylist && (
|
||||
<div className="context-menu-item" style={{ color: 'var(--danger)' }} onClick={() => handleAction(async () => {
|
||||
const { showToast } = await import('../../utils/ui/toast');
|
||||
const { deletePlaylist } = await import('@/features/playlist');
|
||||
const { deletePlaylist } = await import('@/lib/api/subsonicPlaylists');
|
||||
const { removeId } = usePlaylistStore.getState();
|
||||
const deletedIds: string[] = [];
|
||||
for (const pl of selectedPlaylists) {
|
||||
|
||||
@@ -41,7 +41,7 @@ export function SinglePlaylistToPlaylistSubmenu({ playlist, onDone, triggerId }:
|
||||
|
||||
const handleCreate = async () => {
|
||||
if (!newName.trim()) return;
|
||||
const { createPlaylist } = await import('@/features/playlist');
|
||||
const { createPlaylist } = await import('@/lib/api/subsonicPlaylists');
|
||||
try {
|
||||
const newPl = await createPlaylist(newName.trim(), []);
|
||||
if (newPl?.id) {
|
||||
@@ -55,7 +55,7 @@ export function SinglePlaylistToPlaylistSubmenu({ playlist, onDone, triggerId }:
|
||||
};
|
||||
|
||||
const handleAddToNewPlaylist = async (targetId: string, targetName: string) => {
|
||||
const { getPlaylist, updatePlaylist } = await import('@/features/playlist');
|
||||
const { getPlaylist, updatePlaylist } = await import('@/lib/api/subsonicPlaylists');
|
||||
const touchPlaylist = usePlaylistStore.getState().touchPlaylist;
|
||||
|
||||
try {
|
||||
@@ -73,7 +73,7 @@ export function SinglePlaylistToPlaylistSubmenu({ playlist, onDone, triggerId }:
|
||||
};
|
||||
|
||||
const handleAdd = async (targetId: string, targetName: string) => {
|
||||
const { getPlaylist, updatePlaylist } = await import('@/features/playlist');
|
||||
const { getPlaylist, updatePlaylist } = await import('@/lib/api/subsonicPlaylists');
|
||||
const touchPlaylist = usePlaylistStore.getState().touchPlaylist;
|
||||
|
||||
try {
|
||||
@@ -180,7 +180,7 @@ export function MultiPlaylistToPlaylistSubmenu({ playlists, onDone, triggerId }:
|
||||
|
||||
const handleCreate = async () => {
|
||||
if (!newName.trim()) return;
|
||||
const { createPlaylist } = await import('@/features/playlist');
|
||||
const { createPlaylist } = await import('@/lib/api/subsonicPlaylists');
|
||||
try {
|
||||
const newPl = await createPlaylist(newName.trim(), []);
|
||||
if (newPl?.id) {
|
||||
@@ -194,7 +194,7 @@ export function MultiPlaylistToPlaylistSubmenu({ playlists, onDone, triggerId }:
|
||||
};
|
||||
|
||||
const handleMergeToNewPlaylist = async (targetId: string, targetName: string) => {
|
||||
const { getPlaylist, updatePlaylist } = await import('@/features/playlist');
|
||||
const { getPlaylist, updatePlaylist } = await import('@/lib/api/subsonicPlaylists');
|
||||
const touchPlaylist = usePlaylistStore.getState().touchPlaylist;
|
||||
|
||||
try {
|
||||
@@ -223,7 +223,7 @@ export function MultiPlaylistToPlaylistSubmenu({ playlists, onDone, triggerId }:
|
||||
};
|
||||
|
||||
const handleMerge = async (targetId: string, targetName: string) => {
|
||||
const { getPlaylist, updatePlaylist } = await import('@/features/playlist');
|
||||
const { getPlaylist, updatePlaylist } = await import('@/lib/api/subsonicPlaylists');
|
||||
const touchPlaylist = usePlaylistStore.getState().touchPlaylist;
|
||||
|
||||
try {
|
||||
|
||||
@@ -178,7 +178,7 @@ export default function SongContextItems(props: ContextMenuItemsProps) {
|
||||
</div>
|
||||
{offlinePolicy.canEditPlaylist && playlistId && playlistSongIndex !== undefined && (
|
||||
<div className="context-menu-item" style={{ color: 'var(--danger)' }} onClick={() => handleAction(async () => {
|
||||
const { updatePlaylist } = await import('@/features/playlist');
|
||||
const { updatePlaylist } = await import('@/lib/api/subsonicPlaylists');
|
||||
const { showToast } = await import('../../utils/ui/toast');
|
||||
const touchPlaylist = usePlaylistStore.getState().touchPlaylist;
|
||||
try {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Play, X, Trash2, ListPlus } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { getPlaylists, deletePlaylist } from '@/features/playlist';
|
||||
import { getPlaylists, deletePlaylist } from '@/lib/api/subsonicPlaylists';
|
||||
import type { SubsonicPlaylist } from '@/lib/api/subsonicTypes';
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
* sync, core), the album context-menu items (context-menu subsystem), the
|
||||
* shared `TracklistColumnPicker` (also used by favorites), and `cover/*`.
|
||||
*/
|
||||
export * from './api/subsonicAlbumInfo';
|
||||
export * from './hooks/useAlbumBrowseData';
|
||||
export * from './hooks/useAlbumBrowseFilters';
|
||||
export * from './hooks/useAlbumBrowseScrollReset';
|
||||
|
||||
@@ -2,7 +2,7 @@ import { buildDownloadUrl } from '@/lib/api/subsonicStreamUrl';
|
||||
import { setRating, star, unstar } from '@/lib/api/subsonicStarRating';
|
||||
import { queueSongStar, queueSongRating } from '@/store/pendingStarSync';
|
||||
import { getAlbumForServer } from '@/lib/api/subsonicLibrary';
|
||||
import { getArtistInfo } from '@/features/artist';
|
||||
import { getArtistInfo } from '@/lib/api/subsonicArtists';
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { songToTrack } from '@/utils/playback/songToTrack';
|
||||
import { shuffleArray } from '@/utils/playback/shuffleArray';
|
||||
|
||||
@@ -15,10 +15,10 @@ import { MemoryRouter } from 'react-router-dom';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { SubsonicArtistInfo } from '@/lib/api/subsonicTypes';
|
||||
|
||||
vi.mock('@/features/artist/api/subsonicArtists');
|
||||
vi.mock('@/lib/api/subsonicArtists');
|
||||
vi.mock('@/lib/api/subsonicSearch');
|
||||
|
||||
import { getArtist, getArtistInfo, getTopSongs } from '@/features/artist/api/subsonicArtists';
|
||||
import { getArtist, getArtistInfo, getTopSongs } from '@/lib/api/subsonicArtists';
|
||||
import { search } from '@/lib/api/subsonicSearch';
|
||||
import { useArtistDetailData } from '@/features/artist/hooks/useArtistDetailData';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { search } from '@/lib/api/subsonicSearch';
|
||||
import { getArtist, getArtistForServer, getArtistInfo, getTopSongs } from '@/features/artist/api/subsonicArtists';
|
||||
import { getArtist, getArtistForServer, getArtistInfo, getTopSongs } from '@/lib/api/subsonicArtists';
|
||||
import type {
|
||||
SubsonicAlbum, SubsonicArtist, SubsonicArtistInfo, SubsonicSong,
|
||||
} from '@/lib/api/subsonicTypes';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { getArtistInfoForServer } from '@/features/artist/api/subsonicArtists';
|
||||
import { getArtistInfoForServer } from '@/lib/api/subsonicArtists';
|
||||
import type { SubsonicArtistInfo, SubsonicOpenArtistRef } from '@/lib/api/subsonicTypes';
|
||||
import { makeCache } from '@/utils/cache/nowPlayingCache';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getArtists } from '@/features/artist/api/subsonicArtists';
|
||||
import { getArtists } from '@/lib/api/subsonicArtists';
|
||||
import type { SubsonicArtist } from '@/lib/api/subsonicTypes';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { dedupeById } from '@/utils/dedupeById';
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
* `*ContextItems`/`*ToPlaylistSubmenu` items (the cross-cutting context-menu
|
||||
* subsystem, shared with album), and `PlaylistArtistCell` (playlist).
|
||||
*/
|
||||
export * from './api/subsonicArtists';
|
||||
export * from './hooks/useArtistDetailData';
|
||||
export * from './hooks/useArtistInfoBatch';
|
||||
export * from './hooks/useArtistOfflineState';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type React from 'react';
|
||||
import type { TFunction } from 'i18next';
|
||||
import { uploadArtistImage } from '@/features/playlist';
|
||||
import { uploadArtistImage } from '@/lib/api/subsonicPlaylists';
|
||||
import { setRating, star, unstar } from '@/lib/api/subsonicStarRating';
|
||||
import type { SubsonicArtist } from '@/lib/api/subsonicTypes';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { TFunction } from 'i18next';
|
||||
import { getSimilarSongs2, getTopSongs } from '@/features/artist/api/subsonicArtists';
|
||||
import { getSimilarSongs2, getTopSongs } from '@/lib/api/subsonicArtists';
|
||||
import type { SubsonicAlbum, SubsonicArtist, SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import type { Track } from '@/store/playerStoreTypes';
|
||||
import { songToTrack } from '@/utils/playback/songToTrack';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { getPlaylists } from '@/features/playlist';
|
||||
import { getArtists, getArtist } from '@/features/artist';
|
||||
import { getPlaylists } from '@/lib/api/subsonicPlaylists';
|
||||
import { getArtists, getArtist } from '@/lib/api/subsonicArtists';
|
||||
import { getAlbumList } from '@/lib/api/subsonicLibrary';
|
||||
import { search as searchSubsonic } from '@/lib/api/subsonicSearch';
|
||||
import type {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { getInternetRadioStations } from '@/features/radio';
|
||||
import { getInternetRadioStations } from '@/lib/api/subsonicRadio';
|
||||
import { getStarred } from '@/lib/api/subsonicStarRating';
|
||||
import type {
|
||||
InternetRadioStation, SubsonicAlbum, SubsonicArtist, SubsonicSong,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getSongForServer } from '@/lib/api/subsonicLibrary';
|
||||
import { getArtistInfoForServer } from '@/features/artist';
|
||||
import { getArtistInfoForServer } from '@/lib/api/subsonicArtists';
|
||||
import type { SubsonicArtistInfo, SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -13,7 +13,7 @@ import { renderHook, act, waitFor } from '@testing-library/react';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { SubsonicArtistInfo, SubsonicSong, SubsonicAlbum, SubsonicArtist } from '@/lib/api/subsonicTypes';
|
||||
|
||||
vi.mock('@/features/artist');
|
||||
vi.mock('@/lib/api/subsonicArtists');
|
||||
vi.mock('@/lib/api/subsonicLibrary');
|
||||
vi.mock('@/api/bandsintown');
|
||||
vi.mock('@/utils/network/subsonicNetworkGuard', () => ({
|
||||
@@ -21,7 +21,7 @@ vi.mock('@/utils/network/subsonicNetworkGuard', () => ({
|
||||
}));
|
||||
|
||||
import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard';
|
||||
import { getArtistForServer, getArtistInfoForServer, getTopSongsForServer } from '@/features/artist';
|
||||
import { getArtistForServer, getArtistInfoForServer, getTopSongsForServer } from '@/lib/api/subsonicArtists';
|
||||
import { getAlbumForServer, getSongForServer } from '@/lib/api/subsonicLibrary';
|
||||
import { fetchBandsintownEvents } from '@/api/bandsintown';
|
||||
import { useNowPlayingFetchers, type NowPlayingFetchersDeps } from '@/features/nowPlaying/hooks/useNowPlayingFetchers';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { getArtistInfoForServer } from '@/features/artist';
|
||||
import { getArtistInfoForServer } from '@/lib/api/subsonicArtists';
|
||||
import type { SubsonicAlbum, SubsonicArtistInfo, SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { resolveNpAlbum, resolveNpDiscography, resolveNpSongMeta, resolveNpTopSongs } from '@/features/nowPlaying/utils/nowPlayingMetadataResolve';
|
||||
import { fetchBandsintownEvents, type BandsintownEvent } from '@/api/bandsintown';
|
||||
|
||||
@@ -9,7 +9,7 @@ import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { onInvoke } from '@/test/mocks/tauri';
|
||||
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
|
||||
import type { LibraryAdvancedSearchResponse } from '@/lib/api/library';
|
||||
import * as subsonicArtists from '@/features/artist';
|
||||
import * as subsonicArtists from '@/lib/api/subsonicArtists';
|
||||
import * as subsonicLibrary from '@/lib/api/subsonicLibrary';
|
||||
|
||||
// Network reachability is decided by the guard; mock it so we can test both arms.
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* is intentionally absent here.
|
||||
*/
|
||||
import { libraryGetTrack, libraryGetTracksByAlbum } from '@/lib/api/library';
|
||||
import { getArtistForServer, getTopSongsForServer } from '@/features/artist';
|
||||
import { getArtistForServer, getTopSongsForServer } from '@/lib/api/subsonicArtists';
|
||||
import { getAlbumForServer, getSongForServer } from '@/lib/api/subsonicLibrary';
|
||||
import type { SubsonicAlbum, SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { libraryUpsertSongsFromApi } from '@/lib/api/library';
|
||||
import { buildStreamUrl } from '@/lib/api/subsonicStreamUrl';
|
||||
import { getAlbum } from '@/lib/api/subsonicLibrary';
|
||||
import { getArtist } from '@/features/artist';
|
||||
import { getArtist } from '@/lib/api/subsonicArtists';
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { create } from 'zustand';
|
||||
import { persist, createJSONStorage } from 'zustand/middleware';
|
||||
|
||||
@@ -28,7 +28,7 @@ vi.mock('@/lib/api/subsonicLibrary', () => ({
|
||||
getAlbumForServer: vi.fn(async () => ({ songs: [] })),
|
||||
}));
|
||||
|
||||
vi.mock('@/features/artist', () => ({
|
||||
vi.mock('@/lib/api/subsonicArtists', () => ({
|
||||
getArtistForServer: vi.fn(async () => ({ albums: [] })),
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { libraryUpsertSongsFromApi } from '@/lib/api/library';
|
||||
import { librarySqlServerId } from '@/api/coverCache';
|
||||
import { getAlbumForServer } from '@/lib/api/subsonicLibrary';
|
||||
import { getArtistForServer } from '@/features/artist';
|
||||
import { getArtistForServer } from '@/lib/api/subsonicArtists';
|
||||
import { getStarredForServer } from '@/lib/api/subsonicStarRating';
|
||||
import { buildStreamUrlForServer } from '@/lib/api/subsonicStreamUrl';
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
|
||||
@@ -53,11 +53,11 @@ vi.mock('@/lib/api/subsonicLibrary', () => ({
|
||||
getAlbumForServer: (serverId: string, albumId: string) => getAlbumForServerMock(serverId, albumId),
|
||||
}));
|
||||
|
||||
vi.mock('@/features/artist', () => ({
|
||||
vi.mock('@/lib/api/subsonicArtists', () => ({
|
||||
getArtistForServer: (serverId: string, artistId: string) => getArtistForServerMock(serverId, artistId),
|
||||
}));
|
||||
|
||||
vi.mock('@/features/playlist', () => ({
|
||||
vi.mock('@/lib/api/subsonicPlaylists', () => ({
|
||||
getPlaylistForServer: (serverId: string, playlistId: string) =>
|
||||
getPlaylistForServerMock(serverId, playlistId),
|
||||
}));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getAlbumForServer } from '@/lib/api/subsonicLibrary';
|
||||
import { getArtistForServer } from '@/features/artist';
|
||||
import { getPlaylistForServer } from '@/features/playlist';
|
||||
import { getArtistForServer } from '@/lib/api/subsonicArtists';
|
||||
import { getPlaylistForServer } from '@/lib/api/subsonicPlaylists';
|
||||
import type {
|
||||
SubsonicAlbum,
|
||||
SubsonicArtist,
|
||||
|
||||
@@ -29,7 +29,7 @@ vi.mock('@/utils/network/activeServerReachability', () => ({
|
||||
onActiveServerBecameReachable: () => () => {},
|
||||
}));
|
||||
|
||||
vi.mock('@/features/playlist/api/subsonicPlaylists', () => ({
|
||||
vi.mock('@/lib/api/subsonicPlaylists', () => ({
|
||||
getPlaylistForServer: (serverId: string, id: string) => getPlaylistMock(serverId, id),
|
||||
}));
|
||||
|
||||
@@ -38,7 +38,7 @@ vi.mock('@/lib/api/subsonicLibrary', () => ({
|
||||
filterSongsToServerLibrary: (songs: SubsonicSong[]) => filterSongsMock(songs),
|
||||
}));
|
||||
|
||||
vi.mock('@/features/artist', () => ({
|
||||
vi.mock('@/lib/api/subsonicArtists', () => ({
|
||||
getArtistForServer: (serverId: string, artistId: string) => getArtistForServerMock(serverId, artistId),
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { libraryGetTracksByAlbum, subscribeLibrarySyncIdle } from '@/lib/api/library';
|
||||
import { getAlbumForServer, filterSongsToServerLibrary } from '@/lib/api/subsonicLibrary';
|
||||
import { getPlaylistForServer } from '@/features/playlist';
|
||||
import { getArtistForServer } from '@/features/artist';
|
||||
import { getPlaylistForServer } from '@/lib/api/subsonicPlaylists';
|
||||
import { getArtistForServer } from '@/lib/api/subsonicArtists';
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { libraryGetTracksBatchChunked } from '@/lib/api/library';
|
||||
import { getPlaylist } from '@/features/playlist';
|
||||
import { getPlaylist } from '@/lib/api/subsonicPlaylists';
|
||||
import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import type { OfflineAlbumMeta } from '@/features/offline/store/offlineStore';
|
||||
|
||||
@@ -19,7 +19,7 @@ const { authState, orbitState } = vi.hoisted(() => ({
|
||||
orbitState: { sessionId: null as string | null },
|
||||
}));
|
||||
|
||||
vi.mock('@/features/playlist', () => ({ getPlaylists, deletePlaylist }));
|
||||
vi.mock('@/lib/api/subsonicPlaylists', () => ({ getPlaylists, deletePlaylist }));
|
||||
vi.mock('@/store/authStore', () => ({
|
||||
useAuthStore: {
|
||||
getState: () => ({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { deletePlaylist, getPlaylists } from '@/features/playlist';
|
||||
import { deletePlaylist, getPlaylists } from '@/lib/api/subsonicPlaylists';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
|
||||
import { ORBIT_PLAYLIST_PREFIX, parseOrbitState } from '@/features/orbit/api/orbit';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createPlaylist, deletePlaylist, getPlaylist, getPlaylists, updatePlaylist } from '@/features/playlist';
|
||||
import { createPlaylist, deletePlaylist, getPlaylist, getPlaylists, updatePlaylist } from '@/lib/api/subsonicPlaylists';
|
||||
import { getSong } from '@/lib/api/subsonicLibrary';
|
||||
import { songToTrack } from '@/utils/playback/songToTrack';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
|
||||
@@ -24,7 +24,7 @@ vi.mock('@/features/orbit/utils/remote', () => ({
|
||||
vi.mock('@/features/orbit/store/orbitStore', () => ({ useOrbitStore: { getState: () => orbitStore } }));
|
||||
vi.mock('@/store/authStore', () => ({ useAuthStore: { getState: () => ({}) } }));
|
||||
vi.mock('@/store/playerStore', () => ({ usePlayerStore: { getState: () => ({ enqueue: vi.fn() }) } }));
|
||||
vi.mock('@/features/playlist', () => ({ createPlaylist: vi.fn(), deletePlaylist: vi.fn() }));
|
||||
vi.mock('@/lib/api/subsonicPlaylists', () => ({ createPlaylist: vi.fn(), deletePlaylist: vi.fn() }));
|
||||
vi.mock('@/lib/api/subsonicLibrary', () => ({ getSong: vi.fn() }));
|
||||
vi.mock('@/utils/playback/songToTrack', () => ({ songToTrack: vi.fn() }));
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createPlaylist, deletePlaylist } from '@/features/playlist';
|
||||
import { createPlaylist, deletePlaylist } from '@/lib/api/subsonicPlaylists';
|
||||
import { getSong } from '@/lib/api/subsonicLibrary';
|
||||
import { songToTrack } from '@/utils/playback/songToTrack';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { deletePlaylist, getPlaylists } from '@/features/playlist';
|
||||
import { deletePlaylist, getPlaylists } from '@/lib/api/subsonicPlaylists';
|
||||
import { useOrbitStore } from '@/features/orbit/store/orbitStore';
|
||||
import { orbitOutboxPlaylistName, type OrbitState } from '@/features/orbit/api/orbit';
|
||||
import { writeOrbitState } from '@/features/orbit/utils/remote';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getPlaylist, getPlaylists, updatePlaylistMeta } from '@/features/playlist';
|
||||
import { getPlaylist, getPlaylists, updatePlaylistMeta } from '@/lib/api/subsonicPlaylists';
|
||||
import {
|
||||
orbitSessionPlaylistName,
|
||||
parseOrbitState,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getPlaylist, getPlaylists, updatePlaylist } from '@/features/playlist';
|
||||
import { getPlaylist, getPlaylists, updatePlaylist } from '@/lib/api/subsonicPlaylists';
|
||||
import { type OrbitOutboxMeta } from '@/features/orbit/api/orbit';
|
||||
import { parseOutboxPlaylistName } from '@/features/orbit/utils/helpers';
|
||||
import { type OutboxSnapshot } from '@/features/orbit/utils/stateMath';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { filterSongsToActiveLibrary } from '@/lib/api/subsonicLibrary';
|
||||
import { getPlaylist } from '@/features/playlist/api/subsonicPlaylists';
|
||||
import { getPlaylist } from '@/lib/api/subsonicPlaylists';
|
||||
import type { SubsonicPlaylist } from '@/lib/api/subsonicTypes';
|
||||
import { useOfflineBrowseContext } from '@/features/offline';
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
* `playlistDetailHelpers` (shared with offline + favorites; keeping it here
|
||||
* would create a playlist⟷offline barrel cycle → lib/shared in M4).
|
||||
*/
|
||||
export * from './api/subsonicPlaylists';
|
||||
export * from './hooks/usePlaylistBulkPlayCallbacks';
|
||||
export * from './hooks/usePlaylistCovers';
|
||||
export * from './hooks/usePlaylistDerived';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { updatePlaylist } from '@/features/playlist/api/subsonicPlaylists';
|
||||
import { updatePlaylist } from '@/lib/api/subsonicPlaylists';
|
||||
import type { SubsonicPlaylist, SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import React, { useEffect, useState, useCallback, useMemo } from 'react';
|
||||
import { useParams, useNavigate, useLocation } from 'react-router-dom';
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { getPlaylists } from '@/features/playlist/api/subsonicPlaylists';
|
||||
import { getPlaylists } from '@/lib/api/subsonicPlaylists';
|
||||
import type { SubsonicPlaylist } from '@/lib/api/subsonicTypes';
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { createPlaylist as apiCreatePlaylist } from '@/features/playlist/api/subsonicPlaylists';
|
||||
import { createPlaylist as apiCreatePlaylist } from '@/lib/api/subsonicPlaylists';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { isOfflineBrowseActive } from '@/features/offline';
|
||||
import { fetchOfflineBrowsablePlaylists } from '@/features/offline';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getPlaylist } from '@/features/playlist/api/subsonicPlaylists';
|
||||
import { getPlaylist } from '@/lib/api/subsonicPlaylists';
|
||||
import { songToTrack } from '@/utils/playback/songToTrack';
|
||||
import { usePlayerStore } from '@/store/playerStore';
|
||||
import { playPlaylistAll } from '@/features/playlist/utils/playlistBulkPlayActions';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type React from 'react';
|
||||
import { getPlaylist } from '@/features/playlist/api/subsonicPlaylists';
|
||||
import { getPlaylist } from '@/lib/api/subsonicPlaylists';
|
||||
import { filterSongsToActiveLibrary } from '@/lib/api/subsonicLibrary';
|
||||
import type { SubsonicPlaylist, SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { TFunction } from 'i18next';
|
||||
import { getPlaylist, updatePlaylistMeta, uploadPlaylistCoverArt } from '@/features/playlist/api/subsonicPlaylists';
|
||||
import { getPlaylist, updatePlaylistMeta, uploadPlaylistCoverArt } from '@/lib/api/subsonicPlaylists';
|
||||
import type { SubsonicPlaylist } from '@/lib/api/subsonicTypes';
|
||||
import { showToast } from '@/utils/ui/toast';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type React from 'react';
|
||||
import type { TFunction } from 'i18next';
|
||||
import { deletePlaylist, getPlaylist, updatePlaylist } from '@/features/playlist/api/subsonicPlaylists';
|
||||
import { deletePlaylist, getPlaylist, updatePlaylist } from '@/lib/api/subsonicPlaylists';
|
||||
import type { SubsonicPlaylist } from '@/lib/api/subsonicTypes';
|
||||
import { usePlaylistStore } from '@/features/playlist/store/playlistStore';
|
||||
import { showToast } from '@/utils/ui/toast';
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Cast, Check, Loader2, Plus, X } from 'lucide-react';
|
||||
import {
|
||||
createInternetRadioStation, fetchUrlBytes, getInternetRadioStations,
|
||||
getTopRadioStations, searchRadioBrowser, uploadRadioCoverArtBytes,
|
||||
} from '@/features/radio/api/subsonicRadio';
|
||||
} from '@/lib/api/subsonicRadio';
|
||||
import {
|
||||
type InternetRadioStation, type RadioBrowserStation, RADIO_PAGE_SIZE,
|
||||
} from '@/lib/api/subsonicTypes';
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
* playback/audio core — the player core drives them, so they are not part of
|
||||
* this UI feature.
|
||||
*/
|
||||
export { getInternetRadioStations } from './api/subsonicRadio';
|
||||
export { useRadioMetadata } from './hooks/useRadioMetadata';
|
||||
export type { RadioMetadata } from './hooks/useRadioMetadata';
|
||||
export { useRadioMprisSync } from './hooks/useRadioMprisSync';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getInternetRadioStations, createInternetRadioStation, updateInternetRadioStation, deleteInternetRadioStation, uploadRadioCoverArt, deleteRadioCoverArt } from '@/features/radio/api/subsonicRadio';
|
||||
import { getInternetRadioStations, createInternetRadioStation, updateInternetRadioStation, deleteInternetRadioStation, uploadRadioCoverArt, deleteRadioCoverArt } from '@/lib/api/subsonicRadio';
|
||||
import { type InternetRadioStation } from '@/lib/api/subsonicTypes';
|
||||
import React, { useEffect, useState, useMemo, useCallback } from 'react';
|
||||
import { Plus, Search } from 'lucide-react';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { fetchStatisticsFormatSample, fetchStatisticsLibraryAggregates, fetchStatisticsOverview } from '@/features/stats/api/subsonicStatistics';
|
||||
import { fetchStatisticsFormatSample, fetchStatisticsLibraryAggregates, fetchStatisticsOverview } from '@/lib/api/subsonicStatistics';
|
||||
import { getAlbumList } from '@/lib/api/subsonicLibrary';
|
||||
import type { SubsonicAlbum, SubsonicGenre } from '@/lib/api/subsonicTypes';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useEffect } from 'react';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import type { NavigateFunction } from 'react-router-dom';
|
||||
import { getSimilarSongs } from '@/features/artist';
|
||||
import { getSimilarSongs } from '@/lib/api/subsonicArtists';
|
||||
import { getMusicFolders } from '@/lib/api/subsonicLibrary';
|
||||
import { search as subsonicSearch } from '@/lib/api/subsonicSearch';
|
||||
import { filterSongsForLuckyMixRatings, getMixMinRatingsConfigFromAuth } from '../../utils/mix/mixRatingFilter';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect } from 'react';
|
||||
import type React from 'react';
|
||||
import { getPlaylist } from '@/features/playlist';
|
||||
import { getPlaylist } from '@/lib/api/subsonicPlaylists';
|
||||
import type { SubsonicPlaylist } from '@/lib/api/subsonicTypes';
|
||||
import { usePlaylistStore } from '@/features/playlist';
|
||||
import type { PendingSmartPlaylist } from '@/features/playlist';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { filterSongsToActiveLibrary } from '@/lib/api/subsonicLibrary';
|
||||
import { getPlaylist } from '@/features/playlist';
|
||||
import { getPlaylist } from '@/lib/api/subsonicPlaylists';
|
||||
import type { SubsonicPlaylist } from '@/lib/api/subsonicTypes';
|
||||
import { isSmartPlaylistName } from '@/features/playlist';
|
||||
|
||||
|
||||
@@ -27,11 +27,11 @@ vi.mock('@/utils/network/subsonicNetworkGuard', () => ({
|
||||
|
||||
import axios from 'axios';
|
||||
import { pingWithCredentials, pingWithCredentialsForProfile, ping } from '@/lib/api/subsonic';
|
||||
import { getAlbumInfo2 } from '@/features/album';
|
||||
import { getAlbumInfo2 } from '@/lib/api/subsonicAlbumInfo';
|
||||
import { getStarred } from '@/lib/api/subsonicStarRating';
|
||||
import { search } from '@/lib/api/subsonicSearch';
|
||||
import { getAlbum, getMusicDirectory, getMusicFolders, getMusicIndexes, getRandomSongs, getSong } from '@/lib/api/subsonicLibrary';
|
||||
import { getArtists, getTopSongs } from '@/features/artist';
|
||||
import { getArtists, getTopSongs } from '@/lib/api/subsonicArtists';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { resetAuthStore } from '@/test/helpers/storeReset';
|
||||
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ vi.mock('@/lib/api/subsonicLibrary', () => ({
|
||||
}));
|
||||
|
||||
import { api } from '@/lib/api/subsonicClient';
|
||||
import { fetchSimilarTracksRouted } from '@/features/artist/api/subsonicArtists';
|
||||
import { fetchSimilarTracksRouted } from '@/lib/api/subsonicArtists';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
|
||||
const SID = 'srv-router';
|
||||
@@ -1,12 +1,12 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('@/features/artist', () => ({ getArtist: vi.fn() }));
|
||||
vi.mock('@/lib/api/subsonicArtists', () => ({ getArtist: vi.fn() }));
|
||||
vi.mock('@/lib/api/subsonicLibrary', () => ({ getAlbum: vi.fn() }));
|
||||
vi.mock('@/utils/network/subsonicNetworkGuard', () => ({
|
||||
shouldAttemptSubsonicForActiveServer: vi.fn(() => true),
|
||||
}));
|
||||
|
||||
import { getArtist } from '@/features/artist';
|
||||
import { getArtist } from '@/lib/api/subsonicArtists';
|
||||
import { invalidateEntityUserRatingCaches, prefetchArtistUserRatings } from '@/lib/api/subsonicRatings';
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getArtist } from '@/features/artist';
|
||||
import { getArtist } from '@/lib/api/subsonicArtists';
|
||||
import { getAlbum } from '@/lib/api/subsonicLibrary';
|
||||
import { shouldAttemptSubsonicForActiveServer } from '@/utils/network/subsonicNetworkGuard';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { genreTagsFor } from '@/utils/library/genreTags';
|
||||
import { getArtists } from '@/features/artist';
|
||||
import { getArtists } from '@/lib/api/subsonicArtists';
|
||||
import { getAlbumList, getRandomSongs } from '@/lib/api/subsonicLibrary';
|
||||
import type {
|
||||
StatisticsFormatSample,
|
||||
@@ -1,5 +1,5 @@
|
||||
import { star, unstar } from '@/lib/api/subsonicStarRating';
|
||||
import { getArtist, getArtistInfo } from '@/features/artist';
|
||||
import { getArtist, getArtistInfo } from '@/lib/api/subsonicArtists';
|
||||
import type { SubsonicArtist, SubsonicAlbum, SubsonicArtistInfo } from '@/lib/api/subsonicTypes';
|
||||
import { useEffect, useState, useMemo } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { getArtists } from '@/features/artist';
|
||||
import { getArtists } from '@/lib/api/subsonicArtists';
|
||||
import { getAlbumList, getRandomSongs } from '@/lib/api/subsonicLibrary';
|
||||
import type { SubsonicAlbum, SubsonicArtist, SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { runLocalRandomSongs } from '../utils/library/browseTextSearch';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getSimilarSongs2, getTopSongs } from '@/features/artist';
|
||||
import { getSimilarSongs2, getTopSongs } from '@/lib/api/subsonicArtists';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { buildInfiniteQueueCandidates } from '../utils/playback/buildInfiniteQueueCandidates';
|
||||
import { songToTrack } from '../utils/playback/songToTrack';
|
||||
|
||||
@@ -7,7 +7,7 @@ const { inOrbit, getSimilarSongs2, getTopSongs } = vi.hoisted(() => ({
|
||||
getTopSongs: vi.fn(() => Promise.resolve([])),
|
||||
}));
|
||||
|
||||
vi.mock('@/features/artist', () => ({ getSimilarSongs2, getTopSongs }));
|
||||
vi.mock('@/lib/api/subsonicArtists', () => ({ getSimilarSongs2, getTopSongs }));
|
||||
vi.mock('@tauri-apps/api/core', () => ({ invoke: vi.fn(() => Promise.resolve()) }));
|
||||
vi.mock('@/features/orbit', () => ({ isInOrbitSession: () => inOrbit.value }));
|
||||
vi.mock('./authStore', () => ({
|
||||
|
||||
@@ -43,11 +43,11 @@ vi.mock('@/lib/api/subsonicLibrary', () => ({
|
||||
getSong: vi.fn(async () => null),
|
||||
getRandomSongs: vi.fn(async () => []),
|
||||
}));
|
||||
vi.mock('@/features/artist', () => ({
|
||||
vi.mock('@/lib/api/subsonicArtists', () => ({
|
||||
getSimilarSongs2: vi.fn(async () => []),
|
||||
getTopSongs: vi.fn(async () => []),
|
||||
}));
|
||||
vi.mock('@/features/album', () => ({
|
||||
vi.mock('@/lib/api/subsonicAlbumInfo', () => ({
|
||||
getAlbumInfo2: vi.fn(async () => null),
|
||||
}));
|
||||
vi.mock('@/lib/api/subsonicScrobble', () => ({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { join } from '@tauri-apps/api/path';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { getSimilarSongs2, fetchSimilarTracksRouted, getTopSongs } from '@/features/artist';
|
||||
import { getSimilarSongs2, fetchSimilarTracksRouted, getTopSongs } from '@/lib/api/subsonicArtists';
|
||||
import { filterSongsForLuckyMixRatings, getMixMinRatingsConfigFromAuth } from '../mix/mixRatingFilter';
|
||||
import { buildDownloadUrl } from '@/lib/api/subsonicStreamUrl';
|
||||
import { useAuthStore } from '../../store/authStore';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { fetchSimilarTracksRouted } from '@/features/artist';
|
||||
import { fetchSimilarTracksRouted } from '@/lib/api/subsonicArtists';
|
||||
import { filterSongsToActiveLibrary, getRandomSongs } from '@/lib/api/subsonicLibrary';
|
||||
import type { SubsonicAlbum, SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import type { QueueItemRef } from '../../store/playerStoreTypes';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getTopSongs } from '@/features/artist';
|
||||
import { getTopSongs } from '@/lib/api/subsonicArtists';
|
||||
import { filterSongsToActiveLibrary, getAlbumList, getRandomSongs } from '@/lib/api/subsonicLibrary';
|
||||
import { resolveAlbumForActiveServer } from '@/features/offline';
|
||||
import type { SubsonicAlbum, SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* refactor (2026-05-12). This test pins the artist-first / random-fallback
|
||||
* order, the dedup contract against existingIds, and the autoAdded flag.
|
||||
*/
|
||||
import { getSimilarSongs2, getTopSongs } from '@/features/artist';
|
||||
import { getSimilarSongs2, getTopSongs } from '@/lib/api/subsonicArtists';
|
||||
import { getRandomSongs } from '@/lib/api/subsonicLibrary';
|
||||
import type { Track } from '../../store/playerStoreTypes';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
@@ -14,7 +14,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
// Mock only the artist Subsonic API submodule (the pre-move target was
|
||||
// `api/subsonicArtists`); the barrel re-exports it, so consumers still get the
|
||||
// stubs while `coerceOpenArtistRefs` (used by songToTrack) stays real.
|
||||
vi.mock('@/features/artist/api/subsonicArtists', () => ({
|
||||
vi.mock('@/lib/api/subsonicArtists', () => ({
|
||||
getSimilarSongs2: vi.fn(),
|
||||
getTopSongs: vi.fn(),
|
||||
}));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getSimilarSongs2, getTopSongs } from '@/features/artist';
|
||||
import { getSimilarSongs2, getTopSongs } from '@/lib/api/subsonicArtists';
|
||||
import { getRandomSongs } from '@/lib/api/subsonicLibrary';
|
||||
import type { Track } from '../../store/playerStoreTypes';
|
||||
import {
|
||||
|
||||
@@ -27,7 +27,7 @@ vi.mock('@/lib/api/subsonicLibrary', () => ({
|
||||
getSong: mocks.getSong,
|
||||
}));
|
||||
|
||||
vi.mock('@/features/artist', () => ({
|
||||
vi.mock('@/lib/api/subsonicArtists', () => ({
|
||||
getArtist: mocks.getArtist,
|
||||
}));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user