import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { mount, unmount, tick } from 'svelte' import StormWatch from '../../src/components/StormWatch.svelte' import { storm } from '../../src/lib/stores/storm.svelte.js' import { app } from '../../src/lib/stores/app.svelte.js' const risk = (key, label, short, color) => ({ key, label, short, color, props: null }) describe('StormWatch component', () => { let target let instance beforeEach(() => { app.locations = [{ id: 'x', name: 'Home', lat: 36.3567, lon: -95.8146, isCurrent: true }] app.selectedLocationId = 'x' app.settings = { ...app.settings, units: 'imperial' } app.forecastData = { timezone: 'America/Chicago' } storm.updatedAt = Date.now() target = document.createElement('div') document.body.appendChild(target) }) afterEach(async () => { if (instance) await unmount(instance) target?.remove() instance = null document.body.innerHTML = '' }) it('renders alerts, the outlook and nearby reports', async () => { storm.alerts = [{ id: 'a1', event: 'Tornado Warning', type: 'storm', severity: 'Extreme', severityRank: 3, headline: 'Tornado Warning until 5:30 PM', description: 'Take shelter now.', instruction: 'Go to an interior room.', areaDesc: 'Rogers, OK', senderName: 'NWS Tulsa OK', ends: '2026-10-17T22:30:00Z', expires: '2026-10-17T22:45:00Z', }] storm.outlook = { day1: risk('SLGT', 'Slight risk', 'SLGT', '#ffff00'), day2: risk('TSTM', 'General thunderstorms', 'TSTM', '#55bb55'), day3: risk('NONE', 'No outlook', '—', '#7b8eb5'), probabilities: { tornado: 5, wind: 15, hail: 30 }, issued: '2026-10-17T16:14:00Z', } storm.reports = [{ id: 'r1', type: 'HAIL', label: 'Hail', icon: '🧊', magnitude: '1.75', city: 'Claremore', county: 'Rogers', state: 'OK', source: 'Trained Spotter', remark: 'Quarter size hail on Highway 20', valid: '2026-10-17T21:00:00Z', lat: 36.3, lon: -95.6, distanceKm: 12.4, }] instance = mount(StormWatch, { target }) await tick() const text = target.textContent expect(text).toContain('Storm Watch') expect(text).toContain('Tornado Warning') expect(text).toContain('Active alerts') expect(text).toContain('Slight risk') expect(text).toContain('SLGT') expect(text).toContain('15%') // wind probability expect(text).toContain('Claremore') expect(text).toContain('Hail') expect(text).toContain('Rogers Co.') expect(text).toContain('Quarter size hail') expect(target.querySelectorAll('.alert-item')).toHaveLength(1) expect(target.querySelectorAll('.report-item')).toHaveLength(1) expect(target.querySelectorAll('.outlook-day')).toHaveLength(3) }) it('shows all-clear states when nothing is active', async () => { storm.alerts = [] storm.reports = [] storm.outlook = { day1: risk('NONE', 'No outlook', '—', '#7b8eb5'), day2: risk('NONE', 'No outlook', '—', '#7b8eb5'), day3: risk('NONE', 'No outlook', '—', '#7b8eb5'), probabilities: { tornado: 0, wind: 0, hail: 0 }, issued: null, } instance = mount(StormWatch, { target }) await tick() const text = target.textContent expect(text).toContain('No active watches, warnings or advisories') expect(text).toContain('No severe-weather area covers this location') expect(text).toContain('No storm reports within') }) it('renders the non-US note instead of an outlook', async () => { app.locations = [{ id: 'y', name: 'London', lat: 51.5, lon: -0.12, isCurrent: true }] app.selectedLocationId = 'y' storm.alerts = [] storm.reports = [] storm.outlook = null instance = mount(StormWatch, { target }) await tick() expect(target.textContent).toContain('SPC outlooks cover the United States only') expect(target.textContent).toContain('Local Storm Reports cover the United States only') }) })