Harden RC9 UX and performance states
This commit is contained in:
@@ -82,6 +82,7 @@ describe('useCoverageResolver', () => {
|
||||
expect(result.current.coverage).toBeNull()
|
||||
expect(result.current.coverageError).toBeNull()
|
||||
expect(result.current.loadingCoverage).toBe(false)
|
||||
expect(result.current.coverageDurationMs).toBeNull()
|
||||
})
|
||||
|
||||
it('exposes provider failures without retaining stale results', async () => {
|
||||
@@ -95,4 +96,28 @@ describe('useCoverageResolver', () => {
|
||||
expect(result.current.coverage).toBeNull()
|
||||
expect(result.current.loadingCoverage).toBe(false)
|
||||
})
|
||||
|
||||
it('records duration and exposes a slow coverage response', async () => {
|
||||
let resolveRequest: ((value: CoverageResolveResponse) => void) | null = null
|
||||
mocks.resolveCoverage.mockImplementationOnce(
|
||||
() => new Promise<CoverageResolveResponse>((resolve) => {
|
||||
resolveRequest = resolve
|
||||
}),
|
||||
)
|
||||
const { result } = renderHook(() => useCoverageResolver({ projectId: 'project-1', bbox }))
|
||||
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(250)
|
||||
})
|
||||
expect(result.current.loadingCoverage).toBe(true)
|
||||
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(4_001)
|
||||
resolveRequest?.(coverageResult)
|
||||
await Promise.resolve()
|
||||
})
|
||||
|
||||
expect(result.current.coverageDurationMs).toBe(4_001)
|
||||
expect(result.current.coverageBudgetExceeded).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { externalApi } from '../services/api'
|
||||
import type { CoverageResolveResponse, VectorSelectionBBox } from '../types'
|
||||
import {
|
||||
COVERAGE_RESPONSE_BUDGET_MS,
|
||||
exceedsPerformanceBudget,
|
||||
} from '../lib/performanceBudget'
|
||||
|
||||
interface CoverageResolverOptions {
|
||||
projectId: string | null
|
||||
@@ -11,18 +15,22 @@ export function useCoverageResolver({ projectId, bbox }: CoverageResolverOptions
|
||||
const [coverage, setCoverage] = useState<CoverageResolveResponse | null>(null)
|
||||
const [loadingCoverage, setLoadingCoverage] = useState(false)
|
||||
const [coverageError, setCoverageError] = useState<string | null>(null)
|
||||
const [coverageDurationMs, setCoverageDurationMs] = useState<number | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!projectId || !bbox) {
|
||||
setCoverage(null)
|
||||
setCoverageError(null)
|
||||
setLoadingCoverage(false)
|
||||
setCoverageDurationMs(null)
|
||||
return
|
||||
}
|
||||
let cancelled = false
|
||||
const timer = window.setTimeout(() => {
|
||||
const startedAt = Date.now()
|
||||
setLoadingCoverage(true)
|
||||
setCoverageError(null)
|
||||
setCoverageDurationMs(null)
|
||||
externalApi.resolveCoverage({
|
||||
projectId,
|
||||
bbox: {
|
||||
@@ -45,6 +53,7 @@ export function useCoverageResolver({ projectId, bbox }: CoverageResolverOptions
|
||||
})
|
||||
.finally(() => {
|
||||
if (!cancelled) {
|
||||
setCoverageDurationMs(Date.now() - startedAt)
|
||||
setLoadingCoverage(false)
|
||||
}
|
||||
})
|
||||
@@ -59,5 +68,10 @@ export function useCoverageResolver({ projectId, bbox }: CoverageResolverOptions
|
||||
coverage,
|
||||
loadingCoverage,
|
||||
coverageError,
|
||||
coverageDurationMs,
|
||||
coverageBudgetExceeded: exceedsPerformanceBudget(
|
||||
coverageDurationMs,
|
||||
COVERAGE_RESPONSE_BUDGET_MS,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user