feat: legibility wave — veil conditions, first-run hints, kinder errors
Workstream B of the usability wave (#2 hints, #3 conditions, #4 errors): - GET /api/conditions (new backend/app/routes/conditions.py): composes celestial veil_thinness with the cached NOAA Kp reading; lenient lon parsing (junk degrades to moon-only, never 422); geomagnetic may be null on a cold cache. Route tests stub the cache — no live NOAA calls. - VeilConditions strip in the séance side column: moon glyph + phase, % lit, veil-thinness phrase, Kp line only when data exists. Polls every 10 min; renders nothing while loading; no error state. - ModeHint: per-mode in-fiction one-liner after ~15s idle, suppressed once the mode's sensor runs this session, dismissal persisted in localStorage (qm_hint_<mode>). One mount line per panel. - Error copy upgraded to detect-and-redirect: mic denied points at site settings and the ouija board/wire; WebUSB-unsupported suggests EVP. - i18n en/es parity for every new string; coverage-check template domains extended for the new template-key call sites. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
50
frontend/src/components/VeilConditions.test.tsx
Normal file
50
frontend/src/components/VeilConditions.test.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { render, screen, waitFor } from '@testing-library/react'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import '../i18n'
|
||||
import { VeilConditions } from './VeilConditions'
|
||||
|
||||
const sky = {
|
||||
moon_phase: 0.5,
|
||||
moon_name: 'full moon',
|
||||
moon_illumination: 0.98,
|
||||
witching_proximity: null,
|
||||
thinness: 0.98,
|
||||
}
|
||||
|
||||
function stubFetch(body: unknown, ok = true) {
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn().mockResolvedValue({ ok, json: () => Promise.resolve(body) }),
|
||||
)
|
||||
}
|
||||
|
||||
describe('VeilConditions', () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
it('renders moon, thinness phrase and storm line from the endpoint', async () => {
|
||||
stubFetch({ sky, geomagnetic: { kp: 6.3, label: 'moderate geomagnetic storm', storm: true } })
|
||||
render(<VeilConditions />)
|
||||
expect(await screen.findByText('full moon')).toBeInTheDocument()
|
||||
expect(screen.getByText('98% lit')).toBeInTheDocument()
|
||||
expect(screen.getByText('the veil runs thin tonight')).toBeInTheDocument()
|
||||
expect(screen.getByText('geomagnetic storm')).toBeInTheDocument()
|
||||
expect(screen.getByText('Kp 6')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('omits the field line when geomagnetic is null', async () => {
|
||||
stubFetch({ sky, geomagnetic: null })
|
||||
render(<VeilConditions />)
|
||||
expect(await screen.findByText('full moon')).toBeInTheDocument()
|
||||
expect(screen.queryByText(/field:/)).toBeNull()
|
||||
expect(screen.queryByText(/Kp/)).toBeNull()
|
||||
})
|
||||
|
||||
it('renders nothing when the fetch fails', async () => {
|
||||
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('down')))
|
||||
const { container } = render(<VeilConditions />)
|
||||
await waitFor(() => expect(fetch).toHaveBeenCalled())
|
||||
expect(container.firstChild).toBeNull()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user