diff --git a/android/app/src/main/java/com/fabledsword/minstrel/models/Discover.kt b/android/app/src/main/java/com/fabledsword/minstrel/models/Discover.kt index 4c1b39e4..817eb0aa 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/models/Discover.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/models/Discover.kt @@ -98,13 +98,14 @@ data class SuggestionSnoozeRef( * better than rendering an empty line. */ fun returnsIn(nowMs: Long = System.currentTimeMillis()): String { - val untilMs = runCatching { Instant.parse(snoozedUntil).toEpochMilliseconds() } - .getOrNull() ?: return "shortly" - // Already lapsed by our clock, yet the server still returned it — the - // two disagree. Say something plausible rather than "today", which - // would read as a real prediction. - if (untilMs <= nowMs) return "shortly" - val days = ((untilMs - nowMs).toDouble() / MILLIS_PER_DAY).roundToInt() + val remainingMs = runCatching { Instant.parse(snoozedUntil).toEpochMilliseconds() } + .getOrNull()?.minus(nowMs) + // Two ways to have nothing to state: an unparseable timestamp, or one + // already lapsed by our clock though the server still returned the row + // (the two disagree). Neither is "today", which would read as a real + // prediction. + if (remainingMs == null || remainingMs <= 0) return "shortly" + val days = (remainingMs.toDouble() / MILLIS_PER_DAY).roundToInt() return when { days < 1 -> "today" days == 1 -> "tomorrow" diff --git a/web/src/routes/discover/discover.test.ts b/web/src/routes/discover/discover.test.ts index c8cf2351..45b77c53 100644 --- a/web/src/routes/discover/discover.test.ts +++ b/web/src/routes/discover/discover.test.ts @@ -21,8 +21,16 @@ vi.mock('$lib/api/lidarr', () => ({ createLidarrSearchQuery: vi.fn() })); +// SuggestionFeed reaches for the snooze surface too (#2375). These are +// stubbed here even though this page-level suite asserts nothing about +// snoozing: a factory-shaped module mock must export everything the +// component tree imports, or rendering the feed throws before any +// assertion runs. vi.mock('$lib/api/suggestions', () => ({ - createSuggestionsQuery: vi.fn() + createSuggestionsQuery: vi.fn(), + createSnoozesQuery: vi.fn(), + snoozeSuggestion: vi.fn().mockResolvedValue(undefined), + unsnoozeSuggestion: vi.fn().mockResolvedValue(undefined) })); vi.mock('$lib/api/requests', () => ({ @@ -42,11 +50,12 @@ vi.mock('@tanstack/svelte-query', async (importOriginal) => { import DiscoverPage from './+page.svelte'; import { createLidarrSearchQuery } from '$lib/api/lidarr'; -import { createSuggestionsQuery } from '$lib/api/suggestions'; +import { createSuggestionsQuery, createSnoozesQuery } from '$lib/api/suggestions'; import { createRequest } from '$lib/api/requests'; const mockedCreateQuery = createLidarrSearchQuery as ReturnType; const mockedCreateSuggestionsQuery = createSuggestionsQuery as ReturnType; +const mockedCreateSnoozesQuery = createSnoozesQuery as ReturnType; const mockedCreateRequest = createRequest as ReturnType; function result(over: Partial = {}): LidarrSearchResult { @@ -69,6 +78,9 @@ beforeEach(() => { // Default: empty suggestion feed so its empty-state copy renders without // interfering with search-mode tests. mockedCreateSuggestionsQuery.mockReturnValue(mockQuery({ data: [] })); + // Nothing parked, which keeps the feed's empty-state copy on the + // "no signal yet" branch that this suite's assertions expect. + mockedCreateSnoozesQuery.mockReturnValue(mockQuery({ data: [] })); }); afterEach(() => {