Files
Psychotoxical-psysonic/src/hooks/useBrowseAlbumTextSearch.test.ts
T
Maxim Isaev 0feb54ac33 fix(browse): album text search union, scope, and local-first index
Merge album/FTS/track sources for title search (any-word LIKE + prefix FTS),
dedupe cluster text hits by album id instead of album_key, route scoped
cluster browse through per-server advanced search, and prefer the local
index over network race when the library index is enabled.
2026-06-05 22:20:41 +03:00

102 lines
3.5 KiB
TypeScript

import { renderHook, waitFor } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
const runLocalBrowseAlbums = vi.fn();
const runNetworkBrowseAlbums = vi.fn();
const isClusterMultiLibraryScopeBrowse = vi.fn();
const isClusterMode = vi.fn();
vi.mock('../utils/library/browseTextSearch', () => ({
BROWSE_TEXT_DEBOUNCE_RACE_MS: 0,
BROWSE_TEXT_DEBOUNCE_NETWORK_MS: 0,
runLocalBrowseAlbums: (...args: unknown[]) => runLocalBrowseAlbums(...args),
runNetworkBrowseAlbums: (...args: unknown[]) => runNetworkBrowseAlbums(...args),
}));
vi.mock('../utils/serverCluster/clusterScope', () => ({
isClusterMode: () => isClusterMode(),
}));
vi.mock('../utils/serverCluster/clusterLibraryScopes', () => ({
isClusterMultiLibraryScopeBrowse: () => isClusterMultiLibraryScopeBrowse(),
}));
import { useBrowseAlbumTextSearch } from './useBrowseAlbumTextSearch';
beforeEach(() => {
vi.clearAllMocks();
isClusterMultiLibraryScopeBrowse.mockReturnValue(false);
isClusterMode.mockReturnValue(false);
runLocalBrowseAlbums.mockResolvedValue([
{ id: 'al-1', name: 'Local', artist: 'A', artistId: 'a', songCount: 1, duration: 1 },
]);
runNetworkBrowseAlbums.mockResolvedValue([
{ id: 'net-1', name: 'Net', artist: 'B', artistId: 'b', songCount: 1, duration: 1 },
]);
});
describe('useBrowseAlbumTextSearch', () => {
it('uses local cluster index only when multi-library scope is active', async () => {
isClusterMultiLibraryScopeBrowse.mockReturnValue(true);
const { result } = renderHook(() =>
useBrowseAlbumTextSearch('beatles', true, 'srv-a'),
);
await waitFor(() => {
expect(result.current.textSearchLoading).toBe(false);
expect(runLocalBrowseAlbums).toHaveBeenCalled();
});
expect(runLocalBrowseAlbums).toHaveBeenCalledWith('srv-a', 'beatles', undefined, false, true);
expect(runNetworkBrowseAlbums).not.toHaveBeenCalled();
expect(result.current.textSearchAlbums).toHaveLength(1);
expect(result.current.textSearchAlbums![0].id).toBe('al-1');
});
it('uses local index only in cluster mode', async () => {
isClusterMode.mockReturnValue(true);
const { result } = renderHook(() =>
useBrowseAlbumTextSearch('beatles', true, 'srv-a'),
);
await waitFor(() => {
expect(result.current.textSearchLoading).toBe(false);
expect(runLocalBrowseAlbums).toHaveBeenCalled();
});
expect(runNetworkBrowseAlbums).not.toHaveBeenCalled();
});
it('prefers local index when enabled and falls back to network only when local is unavailable', async () => {
const { result } = renderHook(() =>
useBrowseAlbumTextSearch('beatles', true, 'srv-a'),
);
await waitFor(() => {
expect(result.current.textSearchLoading).toBe(false);
expect(runLocalBrowseAlbums).toHaveBeenCalled();
});
expect(runLocalBrowseAlbums).toHaveBeenCalledWith('srv-a', 'beatles', undefined, false, false);
expect(runNetworkBrowseAlbums).not.toHaveBeenCalled();
expect(result.current.textSearchAlbums![0].id).toBe('al-1');
});
it('falls back to network when the local index cannot serve the query', async () => {
runLocalBrowseAlbums.mockResolvedValueOnce(null);
const { result } = renderHook(() =>
useBrowseAlbumTextSearch('beatles', true, 'srv-a'),
);
await waitFor(() => {
expect(result.current.textSearchLoading).toBe(false);
expect(runNetworkBrowseAlbums).toHaveBeenCalled();
});
expect(result.current.textSearchAlbums![0].id).toBe('net-1');
});
});