Files
geointel/frontend/src/components/status/SourceFreshnessPanel.test.tsx
T
JensandClaude Opus 5 f567a459b0 Laat een kapot paneel geen kapotte applicatie meer zijn
Er was nergens een error boundary: geen componentDidCatch, geen
getDerivedStateFromError. Eén component die gooit nam de hele werkbank mee,
inclusief de kaart en de navigatie. Nu blijft de schil staan, ziet de gebruiker
welk werkblad het liet afweten, en wist wegnavigeren de fout.

Daarnaast een patroon dat op zeven plekken half was toegepast: een optionele
keten die te vroeg stopt. report?.items?.filter(...) stond geguard, twintig
regels verderop stond report.items.map(...) zonder. Half geguard is de
eigenlijke fout, want het wekt zekerheid zonder die te bieden. Waar de lijst op
meerdere plekken gebruikt wordt staat nu één normalisatie aan de kop.

De brondetails hadden geen enkele afhandeling en werden met .catch(() => null)
overgeslagen; die melden nu wel. De overige elf catch-blokken bleken bovenop
hooks te staan die de fout al zelf opvangen en tonen, wat ik heb nagemeten door
een verzoek te laten mislukken.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 15:00:08 +02:00

60 lines
2.0 KiB
TypeScript

import { cleanup, render, screen } from '@testing-library/react'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { SourceFreshnessPanel } from './SourceFreshnessPanel'
import type { SourceFreshnessReport } from '../../types'
function props(overschrijf: Partial<Parameters<typeof SourceFreshnessPanel>[0]> = {}) {
return {
report: null,
loading: false,
error: null,
onRefresh: vi.fn(),
catalogReport: null,
catalogLoading: false,
catalogError: null,
onProbeCatalogs: vi.fn(),
grbRefreshPlan: null,
grbRefreshPlanLoading: false,
grbRefreshPlanError: null,
...overschrijf,
}
}
const leegRapport: SourceFreshnessReport = {
project_id: 'p-1',
generated_at: '2026-08-23T09:00:00Z',
summary: {
source_count: 0,
dataset_count: 0,
current_count: 0,
due_count: 0,
review_required_count: 0,
local_count: 0,
sources_with_integrity_issues: 0,
integrity_issue_count: 0,
},
items: [],
limitations: [],
}
describe('SourceFreshnessPanel', () => {
afterEach(() => cleanup())
it('blijft overeind bij een antwoord dat zijn eigen contract schendt', () => {
// Het type zegt dat items altijd meekomt, maar een type is een belofte van
// de compiler en geen garantie van het netwerk. Vandaar de optionele keten
// in de component; deze test legt vast dat die er blijft.
const zonderItems = { ...leegRapport, items: undefined } as unknown as SourceFreshnessReport
expect(() => render(<SourceFreshnessPanel {...props({ report: zonderItems })} />)).not.toThrow()
})
it('toont een leeg rapport zonder te struikelen', () => {
expect(() => render(<SourceFreshnessPanel {...props({ report: leegRapport })} />)).not.toThrow()
})
it('meldt een fout in plaats van hem te verzwijgen', () => {
render(<SourceFreshnessPanel {...props({ error: 'Kon de brondekking niet ophalen' })} />)
expect(screen.getByText(/kon de brondekking niet ophalen/i)).toBeTruthy()
})
})