test: fail on render-phase update warnings; smoke-render every registry metric#1511
Conversation
…ry metric Two harnesses derived from the runtime-verification passes: - src/testSetup/failOnRenderPhaseUpdate.ts (global vitest setup): any test during which React logs 'Cannot update a component while rendering a different component' now fails with the substituted component names. This class shipped silently twice (Transitioner/router rebuild, and the analytics freshness watcher fixed in #1510) and was only ever caught by watching a real browser console. Narrow match; intentional console.error paths are unaffected. Zero offenders in the current suite. - registry-smoke.test.tsx: renders all 24 specRegistry metrics through the real MetricRenderer + PanelErrorBoundary with synthetic records derived from each spec's required fields (quantile columns, confidence-gate- clearing counts, timestamp: 'id' mirroring). Two tiers: no metric may trip the boundary; every line/stacked-area metric (derived from spec.primitive, not a hardcoded list) must emit an actual chart. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UZEaYdKFXuQw2Hf6XpPdEV
Coverage Report
File CoverageNo changed files found. |
There was a problem hiding this comment.
Code Review
This pull request introduces a registry-wide panel smoke test to verify that all registered metrics render correctly, along with a global Vitest setup to fail tests that trigger React render-phase cross-component updates. The reviewer provided valuable feedback, suggesting the use of optional chaining when reading metric specifications to avoid potential runtime errors, and recommending leveraging Vitest's TestContext in the afterEach hook to prevent render-phase update errors from masking primary test assertion failures.
… (review) When the test body already failed, log the render-phase-update detail through the restored console.error instead of throwing from afterEach, so vitest reports the real assertion failure; passing tests still fail hard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UZEaYdKFXuQw2Hf6XpPdEV
kriszyp
left a comment
There was a problem hiding this comment.
Really like these two guards — the render-phase-update tripwire in particular is the right permanent net for the defect class that shipped green twice (the router Transitioner and the #1510 freshness watcher). Verified the mechanism: the afterEach throw genuinely fails the test, console.error is restored on every path (including the already-failed branch) to the true original captured in beforeEach, and the narrow 'Cannot update a component' .includes() match is robust to React's version-dependent Warning: prefix without becoming a blanket console.error ban. The smoke test's Tier-1 copy anchors ('is unavailable', 'Render failed') map exactly to the two failure surfaces, and auto-deriving MUST_RENDER_CHART from spec.primitive is the anti-staleness property that matters. All good to merge — none of the below blocks, purely follow-up material.
1. derivedRegistry isn't smoke-tested, but the framing says "every registry metric." Both tiers iterate Object.keys(specRegistry), yet MetricRenderer also dispatches derivedRegistry[metric] at the top of its body — so mqtt-traffic-sent, mqtt-traffic-received, request-rate, error-rate, and transaction-log-growth get zero coverage, and a newly added derived metric is silently uncovered. The branch name (kyle/verify-derived-test-hardening) suggests derived hardening was actually the focus, so this may be intentional scoping — if so, worth softening the header/PR wording to "every spec-driven metric." Otherwise, looping derivedRegistry too (synthetic records keyed off DERIVED_REQUIRED_FIELDS) would close the gap.
2. The chart-tier SVG selector is effectively dead. registry-smoke.test.tsx:124 — querySelector('.recharts-responsive-container, svg') returns the first match in document order, and the .recharts-responsive-container wrapper <div> is an ancestor of the <svg>, so it always matches first and the svg branch never evaluates. That reduces the assertion to "a ResponsiveContainer mounted." It's not vacuous overall — the analytics setup.ts forces 800×600 so recharts really does draw, and the companion not.toContain('No data in window') is the guard actually biting — but the selector as written doesn't verify the SVG. Collapsing to querySelector('svg') makes it bite (setup guarantees it will).
3. Neither guard directly re-proves #1510. The smoke test feeds records straight into MetricRenderer, bypassing useAnalyticsFreshness/the QueryCache subscription that produced the #1510 warning, so it doesn't exercise that path itself. The global tripwire would catch a reintroduced #1510 — but only via some other test that mounts the freshness-watcher subscription. That's the inherent limitation of console-spy tripwires (they catch what something renders); not a defect here, just worth knowing the tripwire and smoke test guard different things. A tiny targeted mount of the freshness-watcher component would close the loop.
Two minor nits if you're touching it anyway: original?.(...) on the already-failed path can just be console.error(...) (it's already restored two lines up), and MUST_RENDER_CHART vs syntheticRecords disagree on .spec vs ?.spec (both safe — spec is required — just inconsistent).
Nice work — this is exactly the kind of durable regression net worth having.
— reviewed with KrAIs (Claude Opus 4.8)
|
Fresh pass on top of @kriszyp's review (which I agree with — the derived-registry gap, the dead 1. The tripwire is silently disabled in tests that mock 2. The "actually renders" tier excludes the two most complex panels → #1521. 3. Minor (no issue): the tripwire depends on React's dev-mode warning. It does fire in this vitest env (confirmed — it caught the #1510 repro when I reverted that hook), but if the test runtime ever picks up a production React build the tripwire goes silently green on everything. A one-line comment noting the dependency would save a future head-scratch. None of this blocks — agreeing with Kris that it's good-to-merge with follow-ups. Nice durable net. |
…net detection Addresses kriszyp + dawsontoth review feedback on #1511. registry-smoke.test.tsx: - Fix dead SVG selector: assert `svg` directly instead of `.recharts-responsive-container, svg` (the wrapper div is an ancestor of the svg and always matched first, so the svg branch never evaluated). The analytics setup forces 800×600 so recharts genuinely draws (kriszyp). - Make `.spec` access consistent: `specRegistry[metric].spec` non-optionally in syntheticRecords, matching MUST_RENDER_CHART (spec is required on SpecRegistryEntry) (kriszyp nit). - Add a derived-registry no-crash tier iterating Object.keys(derivedRegistry). MetricRenderer dispatches derivedRegistry[metric] before specRegistry, so the five derived metrics (mqtt-traffic-sent/received, request-rate, error-rate, transaction-log-growth) previously had zero coverage. Fed synthetic records carrying the raw source columns each recompute reads directly (kriszyp #1). - Add dedicated real-render cases for the non-cartesian primitives: heatmap (replication-latency) asserts a `[role="grid"]` with gridcells from a 2×2 source/destination matrix; small-multiples (resource-usage) asserts multiple sub-charts. Both assert real DOM, not their empty state (dawson #1521). failOnRenderPhaseUpdate.ts: - Add a tripwire-net self-check: capture the installed wrapper in beforeEach and, in afterEach, detect + defensively restore when a test replaced console.error and left it replaced (net disabled). Severity is a loud console.warn naming the test, not a hard failure, so the tests that legitimately mock console.error keep passing. Refactored into exported installTripwire / detectDisabledNet and added failOnRenderPhaseUpdate.test.ts proving the detection fires (dawson #1520). - Document the DEV-mode dependency: the net relies on React's dev-build warning and silently goes green under a production React build (dawson #3 / kriszyp #3). - Simplify the already-failed path to call console.error directly (restored two lines up) instead of original?.() (kriszyp nit). queryClient.test.ts: - The new self-check surfaced that this suite used mockReset() (leaves a swallowing spy installed at teardown); switch to mockRestore() so the tripwire wrapper is back in place between tests. Fixes #1520 Fixes #1521 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UZEaYdKFXuQw2Hf6XpPdEV
|
Thanks @kriszyp and @dawsontoth for the thorough review — really appreciate the catches. Pushed a80dfec addressing every point: registry-smoke.test.tsx
failOnRenderPhaseUpdate.ts
Deferred: kriszyp #3's separate freshness-watcher mount to re-prove #1510 — that belongs in a freshness-specific test, out of scope for this hardening pass. The relevant part (making the tripwire observable in the subsystems it protects) is covered by the #1520 self-check. Gates: Comment generated by kAIle (Claude Fable 5) |
kriszyp
left a comment
There was a problem hiding this comment.
Approving — the derived-registry coverage, dead-selector, and non-cartesian-tier gaps from the last pass are all closed, CI is green, and this is test-infra-only with no product risk.
One follow-up worth keeping in mind (not blocking): the tripwire's own core "fires" logic still doesn't have a direct test — only the "installs" half is covered. Might be worth a fast follow-up so the detection behavior has its own assertion.
— Claude
…net detection Addresses kriszyp + dawsontoth review feedback on #1511. registry-smoke.test.tsx: - Fix dead SVG selector: assert `svg` directly instead of `.recharts-responsive-container, svg` (the wrapper div is an ancestor of the svg and always matched first, so the svg branch never evaluated). The analytics setup forces 800×600 so recharts genuinely draws (kriszyp). - Make `.spec` access consistent: `specRegistry[metric].spec` non-optionally in syntheticRecords, matching MUST_RENDER_CHART (spec is required on SpecRegistryEntry) (kriszyp nit). - Add a derived-registry no-crash tier iterating Object.keys(derivedRegistry). MetricRenderer dispatches derivedRegistry[metric] before specRegistry, so the five derived metrics (mqtt-traffic-sent/received, request-rate, error-rate, transaction-log-growth) previously had zero coverage. Fed synthetic records carrying the raw source columns each recompute reads directly (kriszyp #1). - Add dedicated real-render cases for the non-cartesian primitives: heatmap (replication-latency) asserts a `[role="grid"]` with gridcells from a 2×2 source/destination matrix; small-multiples (resource-usage) asserts multiple sub-charts. Both assert real DOM, not their empty state (dawson #1521). failOnRenderPhaseUpdate.ts: - Add a tripwire-net self-check: capture the installed wrapper in beforeEach and, in afterEach, detect + defensively restore when a test replaced console.error and left it replaced (net disabled). Severity is a loud console.warn naming the test, not a hard failure, so the tests that legitimately mock console.error keep passing. Refactored into exported installTripwire / detectDisabledNet and added failOnRenderPhaseUpdate.test.ts proving the detection fires (dawson #1520). - Document the DEV-mode dependency: the net relies on React's dev-build warning and silently goes green under a production React build (dawson #3 / kriszyp #3). - Simplify the already-failed path to call console.error directly (restored two lines up) instead of original?.() (kriszyp nit). queryClient.test.ts: - The new self-check surfaced that this suite used mockReset() (leaves a swallowing spy installed at teardown); switch to mockRestore() so the tripwire wrapper is back in place between tests. Fixes #1520 Fixes #1521 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UZEaYdKFXuQw2Hf6XpPdEV
Summary
Test-infrastructure only — turns the two most valuable findings from the recent runtime-verification (Playwright) passes into permanent suite guards:
Render-phase update tripwire (
src/testSetup/failOnRenderPhaseUpdate.ts, registered globally): any test during which React logsCannot update a component while rendering a different componentnow fails, with the actual component names substituted into the failure message. This defect class has shipped silently twice (the router-rebuildTransitionerwarning, and the analytics freshness watcher just fixed in fix(status): stop the freshness watcher updating TimeRangePicker during sibling renders #1510) — the suite stayed green both times while production consoles/RUM filled with warnings. The match is deliberately narrow (that one React message, not a blanket console.error ban), so intentional error-logging paths (PanelErrorBoundary, the query-cache toast handler) are unaffected. Zero offenders in the current suite (220 files / 1529 tests run clean), so there's no ordering dependency on other open PRs.Registry-wide panel smoke test (
registry-smoke.test.tsx): everyspecRegistrymetric renders through the realMetricRendererdispatch inside the realPanelErrorBoundary, fed synthetic records derived from each spec's own required fields (plus quantile columns, confidence-gate-clearing counts, andid-mirrored timestamps). Two tiers: no metric may trip the boundary or the render-failed fallback, and everyline/stacked-areametric — derived fromspec.primitive, not a hardcoded list, so new metrics are automatically held to it — must emit an actual chart, not just its empty state. This is the unit-test form of "do the metrics panels actually load?", the class the recent shim-deletion sweep had to browser-verify.Where to focus
afterEachthrow. If a test both fails an assertion and trips the wire, vitest reports the hook failure — it can shadow the primary assertion in the output (both are real failures; you fix the same code). Accepted trade-off, flagging for awareness.offendersaccumulator is not safe undertest.concurrent, which this repo doesn't use.timestamp: 'id'specs — the chart-tier assertions caught both, which is the behavior you want from it going forward.Comment generated by kAIle (Claude Fable 5)