refactor(search): adapt UI to use ResourceList

This commit is contained in:
Edouard Vanbelle
2026-07-26 16:38:49 +02:00
parent c22741bc7f
commit c946cf4333
11 changed files with 739 additions and 309 deletions
+18 -3
View File
@@ -2,16 +2,16 @@ import { it, expect, vi, beforeEach } from 'vitest';
vi.mock('$lib/api/client', () => ({ apiFetch: vi.fn(), apiJson: vi.fn() }));
vi.mock('$lib/api/csrf', () => ({ getCsrfHeaders: () => ({}) }));
import { apiFetch, apiJson } from '$lib/api/client';
import { searchFiles, searchSuggest, clearSearchCache } from './search';
import { searchResources, searchSuggest, clearSearchCache } from './search';
const f = apiFetch as unknown as ReturnType<typeof vi.fn>;
const j = apiJson as unknown as ReturnType<typeof vi.fn>;
beforeEach(() => {
vi.clearAllMocks();
f.mockResolvedValue({ ok: true, status: 200, json: async () => ({}) });
j.mockResolvedValue({ files: [], folders: [] });
j.mockResolvedValue({ items: [], query_time_ms: 0 });
});
it('builds search requests including filters', async () => {
await searchFiles('q', {
await searchResources('q', {
recursive: true,
fileTypes: ['mp3', 'wav'],
minSize: 1,
@@ -19,7 +19,22 @@ it('builds search requests including filters', async () => {
sortBy: 'date'
}).catch(() => {});
expect(j).toHaveBeenCalledWith(expect.stringContaining('type=mp3%2Cwav'), expect.anything());
// Sort dimension is sent on the wire as `order_by`, matching the
// backend's `SearchResourcesQuery` (post-normalization).
expect(j).toHaveBeenCalledWith(expect.stringContaining('order_by=date'), expect.anything());
await searchSuggest('q').catch(() => {});
await clearSearchCache().catch(() => {});
expect(f.mock.calls.length + j.mock.calls.length).toBeGreaterThan(1);
});
it('forwards cursor pagination and resource-type filter', async () => {
await searchResources('q', {
cursor: 'abc',
resourceTypes: ['file'],
limit: 25
}).catch(() => {});
const call = j.mock.calls.at(-1)?.[0] as string;
expect(call).toContain('cursor=abc');
expect(call).toContain('resource_types=file');
expect(call).toContain('limit=25');
});
+26 -8
View File
@@ -1,6 +1,6 @@
/** Search endpoint — ported from features/files/search.js. */
// Search endpoint — hits the normalized `/*/resources` envelope shape.
import { apiFetch, apiJson } from '$lib/api/client';
import type { SearchResults, SortBy } from '$lib/api/types';
import type { ItemType, SearchResourcesResponse, SortBy } from '$lib/api/types';
export interface SearchOptions {
folderId?: string;
@@ -16,14 +16,31 @@ export interface SearchOptions {
modifiedAfter?: number;
/** Unix-seconds upper bound on modified time. */
modifiedBefore?: number;
/** Page size (1–200 server-side; default 50). */
limit?: number;
offset?: number;
/**
* Cursor from a previous response's `next_cursor`. Absent → first page.
* The wire uses cursor pagination now; the old `offset` param is gone.
*/
cursor?: string;
/** Sort dimension; maps to backend `order_by`. */
sortBy?: SortBy;
/** Restrict to files, folders, or both (default). */
resourceTypes?: ItemType[];
/** Abort the request when a newer search supersedes it. */
signal?: AbortSignal;
}
export function searchFiles(query: string, opts: SearchOptions = {}): Promise<SearchResults> {
/**
* Cursor-paginated search. Returns the shared envelope
* `{ items[], next_cursor?, query_time_ms, total? }` — same shape as
* favorites / recent / trash / folder listings so `ResourceList`
* consumes the items without a demux step.
*/
export function searchResources(
query: string,
opts: SearchOptions = {}
): Promise<SearchResourcesResponse> {
const params = new URLSearchParams();
params.append('query', query);
if (opts.folderId) params.append('folder_id', opts.folderId);
@@ -37,10 +54,11 @@ export function searchFiles(query: string, opts: SearchOptions = {}): Promise<Se
if (opts.createdBefore != null) params.append('created_before', String(opts.createdBefore));
if (opts.modifiedAfter != null) params.append('modified_after', String(opts.modifiedAfter));
if (opts.modifiedBefore != null) params.append('modified_before', String(opts.modifiedBefore));
params.append('limit', String(opts.limit ?? 100));
params.append('offset', String(opts.offset ?? 0));
params.append('sort_by', opts.sortBy ?? 'relevance');
return apiJson<SearchResults>(`/api/search?${params.toString()}`, {
if (opts.resourceTypes?.length) params.append('resource_types', opts.resourceTypes.join(','));
if (opts.limit != null) params.append('limit', String(opts.limit));
if (opts.cursor) params.append('cursor', opts.cursor);
if (opts.sortBy) params.append('order_by', opts.sortBy);
return apiJson<SearchResourcesResponse>(`/api/search?${params.toString()}`, {
credentials: 'same-origin',
signal: opts.signal
});