From dd67f28745d4f4c89da0dd4e3c7c90e86907daa2 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sun, 10 May 2026 15:39:25 -0400 Subject: [PATCH] refactor(web): sweep 8 test files to use pageUrlModule() (#375) Centralizes the inline { page: { get url() { return state.pageUrl } } } mock shape via test-utils/mocks/appState.ts. The vi.hoisted state declaration remains per-file (vitest mock-hoisting requires module- level declaration). Adds a $test-utils alias to svelte.config.js so the helper can be imported without ../../../ chains. SKIPPED (mock exposes more than url): - Shell.test.ts, MobileNavDrawer.test.ts, admin.test.ts: static page: { url: ... } shape, no hoisted state to centralize. - album.test.ts, artist.test.ts: page exposes both params and url. - playlist.test.ts, reset-password.test.ts: page exposes params only. Co-Authored-By: Claude Opus 4.7 (1M context) --- web/src/lib/components/AdminTabs.test.ts | 5 ++-- web/src/lib/components/DiscoverTabs.test.ts | 5 ++-- web/src/lib/components/SearchInput.test.ts | 5 ++-- web/src/routes/login/login.test.ts | 7 ++---- web/src/routes/search/albums/albums.test.ts | 17 +++---------- web/src/routes/search/artists/artists.test.ts | 17 +++---------- web/src/routes/search/search.test.ts | 17 +++---------- web/src/routes/search/tracks/tracks.test.ts | 25 +++++-------------- web/svelte.config.js | 6 +++++ 9 files changed, 32 insertions(+), 72 deletions(-) diff --git a/web/src/lib/components/AdminTabs.test.ts b/web/src/lib/components/AdminTabs.test.ts index a7f152b4..d76ddbdf 100644 --- a/web/src/lib/components/AdminTabs.test.ts +++ b/web/src/lib/components/AdminTabs.test.ts @@ -1,13 +1,12 @@ import { describe, expect, test, vi } from 'vitest'; import { render, screen } from '@testing-library/svelte'; +import { pageUrlModule } from '$test-utils/mocks/appState'; const state = vi.hoisted(() => ({ pageUrl: new URL('http://localhost/admin') })); -vi.mock('$app/state', () => ({ - page: { get url() { return state.pageUrl; } } -})); +vi.mock('$app/state', () => pageUrlModule(state)); import AdminTabs from './AdminTabs.svelte'; diff --git a/web/src/lib/components/DiscoverTabs.test.ts b/web/src/lib/components/DiscoverTabs.test.ts index ebcc9e54..a57757f0 100644 --- a/web/src/lib/components/DiscoverTabs.test.ts +++ b/web/src/lib/components/DiscoverTabs.test.ts @@ -1,13 +1,12 @@ import { describe, expect, test, vi } from 'vitest'; import { render, screen } from '@testing-library/svelte'; +import { pageUrlModule } from '$test-utils/mocks/appState'; const state = vi.hoisted(() => ({ pageUrl: new URL('http://localhost/discover') })); -vi.mock('$app/state', () => ({ - page: { get url() { return state.pageUrl; } } -})); +vi.mock('$app/state', () => pageUrlModule(state)); import DiscoverTabs from './DiscoverTabs.svelte'; diff --git a/web/src/lib/components/SearchInput.test.ts b/web/src/lib/components/SearchInput.test.ts index ca1f97bb..83938331 100644 --- a/web/src/lib/components/SearchInput.test.ts +++ b/web/src/lib/components/SearchInput.test.ts @@ -1,13 +1,12 @@ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; import { render, screen, fireEvent } from '@testing-library/svelte'; +import { pageUrlModule } from '$test-utils/mocks/appState'; const state = vi.hoisted(() => ({ pageUrl: new URL('http://localhost/') })); -vi.mock('$app/state', () => ({ - page: { get url() { return state.pageUrl; } } -})); +vi.mock('$app/state', () => pageUrlModule(state)); vi.mock('$app/navigation', () => ({ goto: vi.fn() diff --git a/web/src/routes/login/login.test.ts b/web/src/routes/login/login.test.ts index 315fbd73..534c13ba 100644 --- a/web/src/routes/login/login.test.ts +++ b/web/src/routes/login/login.test.ts @@ -1,16 +1,13 @@ import { afterEach, describe, expect, test, vi } from 'vitest'; import { render, screen, fireEvent, waitFor } from '@testing-library/svelte'; +import { pageUrlModule } from '$test-utils/mocks/appState'; // vi.hoisted avoids the temporal-dead-zone hazard: mock factories are lazy // and can run before top-level `let` is initialized, since imports are // hoisted above variable declarations. const state = vi.hoisted(() => ({ pageUrl: new URL('http://localhost/login') })); -vi.mock('$app/state', () => ({ - page: { - get url() { return state.pageUrl; } - } -})); +vi.mock('$app/state', () => pageUrlModule(state)); vi.mock('$app/navigation', () => ({ goto: vi.fn() diff --git a/web/src/routes/search/albums/albums.test.ts b/web/src/routes/search/albums/albums.test.ts index 93fa4c74..21beaeb6 100644 --- a/web/src/routes/search/albums/albums.test.ts +++ b/web/src/routes/search/albums/albums.test.ts @@ -1,14 +1,13 @@ import { afterEach, describe, expect, test, vi } from 'vitest'; import { render, screen, fireEvent } from '@testing-library/svelte'; -import { readable } from 'svelte/store'; import { mockInfiniteQuery } from '../../../test-utils/query'; +import { emptyLikesMock } from '../../../test-utils/mocks/likes'; +import { pageUrlModule } from '$test-utils/mocks/appState'; import type { AlbumRef, Page } from '$lib/api/types'; const state = vi.hoisted(() => ({ pageUrl: new URL('http://localhost/search/albums?q=miles') })); -vi.mock('$app/state', () => ({ - page: { get url() { return state.pageUrl; } } -})); +vi.mock('$app/state', () => pageUrlModule(state)); vi.mock('$lib/api/queries', () => ({ createSearchAlbumsInfiniteQuery: vi.fn() @@ -21,15 +20,7 @@ vi.mock('$lib/player/store.svelte', () => ({ enqueueTracks: vi.fn() })); -vi.mock('$lib/api/likes', () => ({ - createLikedIdsQuery: () => readable({ - data: { track_ids: [], album_ids: [], artist_ids: [] }, - isPending: false, - isError: false - }), - likeEntity: vi.fn(), - unlikeEntity: vi.fn() -})); +vi.mock('$lib/api/likes', () => emptyLikesMock()); import AlbumsOverflow from './+page.svelte'; import { createSearchAlbumsInfiniteQuery } from '$lib/api/queries'; diff --git a/web/src/routes/search/artists/artists.test.ts b/web/src/routes/search/artists/artists.test.ts index 6893fe18..60e23b7c 100644 --- a/web/src/routes/search/artists/artists.test.ts +++ b/web/src/routes/search/artists/artists.test.ts @@ -1,28 +1,19 @@ import { afterEach, describe, expect, test, vi } from 'vitest'; import { render, screen, fireEvent } from '@testing-library/svelte'; -import { readable } from 'svelte/store'; import { mockInfiniteQuery } from '../../../test-utils/query'; +import { emptyLikesMock } from '../../../test-utils/mocks/likes'; +import { pageUrlModule } from '$test-utils/mocks/appState'; import type { ArtistRef, Page } from '$lib/api/types'; const state = vi.hoisted(() => ({ pageUrl: new URL('http://localhost/search/artists?q=miles') })); -vi.mock('$app/state', () => ({ - page: { get url() { return state.pageUrl; } } -})); +vi.mock('$app/state', () => pageUrlModule(state)); vi.mock('$lib/api/queries', () => ({ createSearchArtistsInfiniteQuery: vi.fn() })); -vi.mock('$lib/api/likes', () => ({ - createLikedIdsQuery: () => readable({ - data: { track_ids: [], album_ids: [], artist_ids: [] }, - isPending: false, - isError: false - }), - likeEntity: vi.fn(), - unlikeEntity: vi.fn() -})); +vi.mock('$lib/api/likes', () => emptyLikesMock()); import ArtistsOverflow from './+page.svelte'; import { createSearchArtistsInfiniteQuery } from '$lib/api/queries'; diff --git a/web/src/routes/search/search.test.ts b/web/src/routes/search/search.test.ts index bf52160c..beec6dac 100644 --- a/web/src/routes/search/search.test.ts +++ b/web/src/routes/search/search.test.ts @@ -1,16 +1,15 @@ import { afterEach, describe, expect, test, vi } from 'vitest'; import { render, screen } from '@testing-library/svelte'; -import { readable } from 'svelte/store'; import { mockQuery } from '../../test-utils/query'; +import { emptyLikesMock } from '../../test-utils/mocks/likes'; +import { pageUrlModule } from '$test-utils/mocks/appState'; import type { ArtistRef, AlbumRef, TrackRef, SearchResponse } from '$lib/api/types'; const state = vi.hoisted(() => ({ pageUrl: new URL('http://localhost/search') })); -vi.mock('$app/state', () => ({ - page: { get url() { return state.pageUrl; } } -})); +vi.mock('$app/state', () => pageUrlModule(state)); vi.mock('$lib/api/queries', () => ({ createSearchQuery: vi.fn() @@ -27,15 +26,7 @@ vi.mock('$lib/api/client', () => ({ api: { get: vi.fn() } })); -vi.mock('$lib/api/likes', () => ({ - createLikedIdsQuery: () => readable({ - data: { track_ids: [], album_ids: [], artist_ids: [] }, - isPending: false, - isError: false - }), - likeEntity: vi.fn(), - unlikeEntity: vi.fn() -})); +vi.mock('$lib/api/likes', () => emptyLikesMock()); import SearchPage from './+page.svelte'; import { createSearchQuery } from '$lib/api/queries'; diff --git a/web/src/routes/search/tracks/tracks.test.ts b/web/src/routes/search/tracks/tracks.test.ts index 9fac2106..d8e769a9 100644 --- a/web/src/routes/search/tracks/tracks.test.ts +++ b/web/src/routes/search/tracks/tracks.test.ts @@ -1,14 +1,14 @@ import { afterEach, describe, expect, test, vi } from 'vitest'; import { render, screen, fireEvent } from '@testing-library/svelte'; -import { readable } from 'svelte/store'; import { mockInfiniteQuery } from '../../../test-utils/query'; +import { emptyLikesMock } from '../../../test-utils/mocks/likes'; +import { emptyQuarantineMock } from '../../../test-utils/mocks/quarantine'; +import { pageUrlModule } from '$test-utils/mocks/appState'; 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('$app/state', () => pageUrlModule(state)); vi.mock('$lib/api/queries', () => ({ createSearchTracksInfiniteQuery: vi.fn() @@ -20,22 +20,9 @@ vi.mock('$lib/player/store.svelte', () => ({ enqueueTrack: vi.fn() })); -vi.mock('$lib/api/likes', () => ({ - createLikedIdsQuery: () => readable({ - data: { track_ids: [], album_ids: [], artist_ids: [] }, - isPending: false, - isError: false - }), - likeEntity: vi.fn(), - unlikeEntity: vi.fn() -})); +vi.mock('$lib/api/likes', () => emptyLikesMock()); -vi.mock('$lib/api/quarantine', () => ({ - flagTrack: vi.fn(), - unflagTrack: vi.fn(), - listMyQuarantine: vi.fn().mockResolvedValue([]), - createMyQuarantineQuery: vi.fn() -})); +vi.mock('$lib/api/quarantine', () => emptyQuarantineMock()); import TracksOverflow from './+page.svelte'; import { createSearchTracksInfiniteQuery } from '$lib/api/queries'; diff --git a/web/svelte.config.js b/web/svelte.config.js index 8fa8490d..a4028e2e 100644 --- a/web/svelte.config.js +++ b/web/svelte.config.js @@ -13,6 +13,12 @@ export default { }), files: { assets: 'static' + }, + alias: { + // test-only helper barrel; resolves the same way $lib does so test + // files can import shared mock factories without ../../../ chains. + '$test-utils': 'src/test-utils', + '$test-utils/*': 'src/test-utils/*' } } };