95 lines
3.1 KiB
JavaScript
95 lines
3.1 KiB
JavaScript
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)
|
|
})
|
|
})
|