+
+
+ {#if !q}
+
No query — return to search to start typing.
+ {:else if query?.isError}
+
+ {:else if showSkeleton.value && tracks.length === 0}
+
+ {:else if total === 0}
+
No track matches.
+ {:else}
+
+ {#each tracks as t, i (t.id)}
+
+ {/each}
+
+ {#if query?.hasNextPage}
+
+ {/if}
+ {/if}
+
diff --git a/web/src/routes/search/tracks/tracks.test.ts b/web/src/routes/search/tracks/tracks.test.ts
new file mode 100644
index 00000000..5eb9c300
--- /dev/null
+++ b/web/src/routes/search/tracks/tracks.test.ts
@@ -0,0 +1,80 @@
+import { afterEach, describe, expect, test, vi } from 'vitest';
+import { render, screen, fireEvent } from '@testing-library/svelte';
+import { mockInfiniteQuery } from '../../../test-utils/query';
+import type { TrackRef, Page } from '$lib/api/types';
+
+const state = vi.hoisted(() => ({ pageUrl: new URL('http://localhost/search/tracks?q=miles') }));
+
+vi.mock('$app/state', () => ({
+ page: { get url() { return state.pageUrl; } }
+}));
+
+vi.mock('$lib/api/queries', () => ({
+ createSearchTracksInfiniteQuery: vi.fn()
+}));
+
+vi.mock('$lib/player/store.svelte', () => ({
+ playQueue: vi.fn(),
+ playRadio: vi.fn(),
+ enqueueTrack: vi.fn()
+}));
+
+import TracksOverflow from './+page.svelte';
+import { createSearchTracksInfiniteQuery } from '$lib/api/queries';
+import { playRadio } from '$lib/player/store.svelte';
+
+function page