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>
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
import { cleanup, render, screen } from '@testing-library/react'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { ProviderPanel } from './ProviderPanel'
|
||||
import type { ProviderCapability } from '../../types'
|
||||
|
||||
function props(overschrijf: Partial<Parameters<typeof ProviderPanel>[0]> = {}) {
|
||||
return {
|
||||
selectedProjectId: 'p-1',
|
||||
providers: [] as ProviderCapability[],
|
||||
loadingCapabilities: false,
|
||||
capabilitiesError: null,
|
||||
onRefresh: vi.fn(),
|
||||
onOpenSources: vi.fn(),
|
||||
onOpenStatus: vi.fn(),
|
||||
onOpenMap: vi.fn(),
|
||||
...overschrijf,
|
||||
}
|
||||
}
|
||||
|
||||
describe('ProviderPanel', () => {
|
||||
afterEach(() => cleanup())
|
||||
|
||||
it('blijft overeind bij een antwoord dat zijn eigen contract schendt', () => {
|
||||
// Het type zegt dat providers altijd meekomt. Dit paneel gebruikte die
|
||||
// lijst op drie plekken en had er op één een guard; nu wordt hij aan de
|
||||
// kop genormaliseerd. Een type is een belofte van de compiler, niet van
|
||||
// het netwerk.
|
||||
const zonder = props({ providers: undefined as unknown as ProviderCapability[] })
|
||||
expect(() => render(<ProviderPanel {...zonder} />)).not.toThrow()
|
||||
})
|
||||
|
||||
it('toont een lege toestand zonder koppelingen', () => {
|
||||
render(<ProviderPanel {...props()} />)
|
||||
expect(screen.getByText(/geen databronnen gemeld/i)).toBeTruthy()
|
||||
})
|
||||
|
||||
it('meldt het wanneer de status niet opgehaald kon worden', () => {
|
||||
render(<ProviderPanel {...props({ capabilitiesError: 'Bronservice niet bereikbaar' })} />)
|
||||
expect(screen.getByText(/bronservice niet bereikbaar/i)).toBeTruthy()
|
||||
})
|
||||
})
|
||||
@@ -52,7 +52,10 @@ export function ProviderPanel({
|
||||
onOpenStatus,
|
||||
onOpenMap,
|
||||
}: ProviderPanelProps): JSX.Element {
|
||||
const configuredCount = providers.filter((provider) => provider.configured).length
|
||||
// Eén normalisatie aan de kop, in plaats van op elke gebruiksplek een guard.
|
||||
// Een antwoord zonder deze lijst liet het paneel eerder vallen.
|
||||
const koppelingen = providers ?? []
|
||||
const configuredCount = koppelingen.filter((provider) => provider.configured).length
|
||||
const [operations, setOperations] = useState<AoiOperation[]>([])
|
||||
const [operationsError, setOperationsError] = useState<string | null>(null)
|
||||
const [loadingOperations, setLoadingOperations] = useState(false)
|
||||
@@ -93,7 +96,7 @@ export function ProviderPanel({
|
||||
</div>
|
||||
|
||||
<div className="system-command-surface" aria-label="Systeemacties">
|
||||
<div><span>Bronkoppelingen</span><strong>{configuredCount} van {providers.length} actief</strong></div>
|
||||
<div><span>Bronkoppelingen</span><strong>{configuredCount} van {koppelingen.length} actief</strong></div>
|
||||
<div><span>Werkmodus</span><strong>Begrensde bronopvraging</strong></div>
|
||||
<div className="system-command-actions">
|
||||
<button type="button" className="primary-action" onClick={onOpenMap}>Open operationele kaart</button>
|
||||
@@ -115,7 +118,7 @@ export function ProviderPanel({
|
||||
<p>{capabilitiesError}</p>
|
||||
</div>
|
||||
) : null}
|
||||
{providers.length === 0 && !loadingCapabilities ? (
|
||||
{koppelingen.length === 0 && !loadingCapabilities ? (
|
||||
<div className="result-state result-state-empty">
|
||||
<strong>Geen databronnen gemeld.</strong>
|
||||
<p>Vernieuw de status zodra de backend bereikbaar is.</p>
|
||||
@@ -157,7 +160,7 @@ export function ProviderPanel({
|
||||
</div>
|
||||
</div>
|
||||
<ul className="system-provider-list">
|
||||
{providers.map((provider) => (
|
||||
{koppelingen.map((provider) => (
|
||||
<li className="system-provider-card" key={provider.provider_name}>
|
||||
<div className="system-provider-header">
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user