import { describe, it, expect, beforeEach, vi } from 'vitest' import { AppStore } from '../../../src/lib/stores/app.svelte.js' import { fetchForecast } from '../../../src/lib/api/weather.js' import { clearForecastCache } from '../../../src/lib/stores/forecast-cache.js' vi.mock('../../../src/lib/api/weather.js', () => ({ fetchForecast: vi.fn(), searchLocations: vi.fn() })) vi.mock('../../../src/lib/api/nws.js', () => ({ fetchForecastNWS: vi.fn(), mergeFallbackData: vi.fn((a) => a) })) describe('AppStore', () => { let app beforeEach(() => { app = new AppStore() }) it('initializes with default state', () => { expect(app.locations).toEqual([]) expect(app.selectedLocationId).toBeNull() expect(app.forecastData).toBeNull() expect(app.loading).toBe(false) expect(app.error).toBe('') expect(app.showAddLocation).toBe(false) expect(app.showSettings).toBe(false) expect(app.sidebarOpen).toBe(false) }) it('has default settings', () => { expect(app.settings.units).toBe('metric') expect(app.settings.alertsEnabled).toBe(true) }) it('selectedLocation is derived correctly', () => { expect(app.selectedLocation).toBeNull() app.locations = [ { id: 'loc1', name: 'Berlin', lat: 52.52, lon: 13.41, isCurrent: false, order: 1 }, ] app.selectedLocationId = 'loc1' expect(app.selectedLocation).toBeTruthy() expect(app.selectedLocation.name).toBe('Berlin') }) it('selectedLocation returns null when id not found', () => { app.locations = [{ id: 'loc1', name: 'Berlin', lat: 52.52, lon: 13.41, isCurrent: false, order: 1 }] app.selectedLocationId = 'nonexistent' expect(app.selectedLocation).toBeNull() }) }) const LOC = { id: 'loc1', name: 'Home', lat: 36.35, lon: -95.81, isCurrent: false } describe('AppStore forecast caching', () => { let app beforeEach(() => { vi.clearAllMocks() clearForecastCache() app = new AppStore() app.locations = [LOC] app.selectedLocationId = LOC.id fetchForecast.mockResolvedValue({ current: { temperature_2m: 20 } }) }) it('fetches once then serves the cache on the next call', async () => { await app.refreshForecast() expect(fetchForecast).toHaveBeenCalledTimes(1) await app.refreshForecast() expect(fetchForecast).toHaveBeenCalledTimes(1) // cache hit, no second fetch }) it('sets loading=true while a cache-miss fetch is in flight, then false', async () => { const p = app.refreshForecast() expect(app.loading).toBe(true) await p expect(app.loading).toBe(false) expect(app.forecastData).toEqual({ current: { temperature_2m: 20 } }) }) it('a cache hit does not set loading', async () => { await app.refreshForecast() const p = app.refreshForecast() expect(app.loading).toBe(false) // served from cache synchronously await p expect(fetchForecast).toHaveBeenCalledTimes(1) }) it('force=true bypasses a fresh cache entry', async () => { await app.refreshForecast() await app.refreshForecast(null, { force: true }) expect(fetchForecast).toHaveBeenCalledTimes(2) }) })