@@ -42,6 +42,10 @@ vi.mock("@/components/depth-chip", () => ({
4242} ) ) ;
4343
4444vi . mock ( "@/components/badges" , ( ) => ( {
45+ // Mirror the REAL Badge contract: a "?" label means no-data and the real
46+ // Badge returns null (hides). Replicating that here keeps these tests from
47+ // being overfit to a mock that renders unconditionally — a gated D6 routed
48+ // through label "?" would vanish in production but pass against a naive mock.
4549 Badge : vi . fn (
4650 ( {
4751 name,
@@ -50,11 +54,12 @@ vi.mock("@/components/badges", () => ({
5054 name : string ;
5155 state : { tone : string ; label : string } ;
5256 title ?: string ;
53- } ) => (
54- < span data-testid = { `mock-badge-${ name } ` } data-tone = { state . tone } >
55- { name } { state . label }
56- </ span >
57- ) ,
57+ } ) =>
58+ state . label === "?" ? null : (
59+ < span data-testid = { `mock-badge-${ name } ` } data-tone = { state . tone } >
60+ { name } { state . label }
61+ </ span >
62+ ) ,
5863 ) ,
5964 FlashOnChange : vi . fn (
6065 ( { children } : { children : React . ReactNode ; tone : string } ) => (
@@ -166,6 +171,7 @@ function makeModel(overrides?: Partial<CellModel>): CellModel {
166171 d4 : makeLevel ( true , "green" ) ,
167172 d5 : makeLevel ( true , "green" ) ,
168173 d6 : makeLevel ( false ) ,
174+ d6Effective : null ,
169175 achievedDepth : 5 ,
170176 ceilingDepth : 5 ,
171177 chipColor : "green" ,
@@ -412,7 +418,15 @@ describe("UnifiedCell", () => {
412418 // d6 pill surfaces the depth chip + health badges (incl. the D6 badge).
413419 it ( "renders depth + health content (not blank) for a {d6}-only overlay set" , ( ) => {
414420 const ctx = makeCtx ( ) ;
415- const model = makeModel ( { d6 : makeLevel ( true , "green" ) } ) ;
421+ // Fully-intact ladder (D3-D5 green by default) with a present green D6 →
422+ // d6Effective passes through as green, so the D6 badge renders visibly.
423+ // (The default makeModel d6Effective is null, which models a no-data
424+ // ladder — that would correctly suppress the D6 badge, defeating this
425+ // test's intent, so set it consistently here.)
426+ const model = makeModel ( {
427+ d6 : makeLevel ( true , "green" ) ,
428+ d6Effective : "green" ,
429+ } ) ;
416430 const { getByTestId, queryByTestId } = render (
417431 < UnifiedCell ctx = { ctx } model = { model } overlays = { overlaySet ( "d6" ) } /> ,
418432 ) ;
@@ -427,6 +441,83 @@ describe("UnifiedCell", () => {
427441 } ) ;
428442 } ) ;
429443
444+ // ── D6 badge ladder-gating (D6 never green if D5 fails) ────────────
445+ describe ( "D6 badge reflects ladder-gated d6Effective" , ( ) => {
446+ it ( "renders a VISIBLE gated D6 badge ('—', gray) when D5 is red even though raw d6 is green" , ( ) => {
447+ const ctx = makeCtx ( ) ;
448+ // Raw D6 dimension is green, but D5 is red so the ladder is broken
449+ // below D6 → d6Effective null. The D6 badge must render a real, VISIBLE
450+ // not-achieved indicator ("—", gray) — NOT a "?" that the real Badge
451+ // hides, and never green; CV stays per-dimension red (diagnostic).
452+ const model = makeModel ( {
453+ d3 : makeLevel ( true , "green" ) ,
454+ d4 : makeLevel ( true , "green" ) ,
455+ d5 : makeLevel ( true , "red" ) ,
456+ d6 : makeLevel ( true , "green" ) ,
457+ d6Effective : null ,
458+ chipColor : "red" ,
459+ achievedDepth : 4 ,
460+ } ) ;
461+ const { getByTestId } = render (
462+ < UnifiedCell ctx = { ctx } model = { model } overlays = { overlaySet ( "health" ) } /> ,
463+ ) ;
464+
465+ // The gated D6 badge is PRESENT (does not vanish) and renders the
466+ // em-dash. With the mock honoring the real "?"→null rule, this would
467+ // FAIL against a naive "?"-emitting implementation — proving the fix.
468+ const d6Badge = getByTestId ( "mock-badge-D6" ) ;
469+ expect ( d6Badge ) . toBeInTheDocument ( ) ;
470+ expect ( d6Badge . getAttribute ( "data-tone" ) ) . toBe ( "gray" ) ;
471+ expect ( d6Badge . getAttribute ( "data-tone" ) ) . not . toBe ( "green" ) ;
472+ expect ( d6Badge . textContent ) . toContain ( "—" ) ;
473+
474+ // CV badge still shows the real per-dimension D5 failure (red).
475+ expect ( getByTestId ( "mock-badge-CV" ) . getAttribute ( "data-tone" ) ) . toBe (
476+ "red" ,
477+ ) ;
478+ } ) ;
479+
480+ it ( "hides the D6 badge entirely when D6 has no data (does not exist)" , ( ) => {
481+ const ctx = makeCtx ( ) ;
482+ // D6 not mapped/no row → does not exist. Gated rendering must NOT kick
483+ // in; the badge is simply absent (no "—" for a non-existent rung).
484+ const model = makeModel ( {
485+ d3 : makeLevel ( true , "green" ) ,
486+ d4 : makeLevel ( true , "green" ) ,
487+ d5 : makeLevel ( true , "green" ) ,
488+ d6 : makeLevel ( false ) ,
489+ d6Effective : null ,
490+ chipColor : "amber" ,
491+ achievedDepth : 5 ,
492+ } ) ;
493+ const { queryByTestId } = render (
494+ < UnifiedCell ctx = { ctx } model = { model } overlays = { overlaySet ( "health" ) } /> ,
495+ ) ;
496+
497+ expect ( queryByTestId ( "mock-badge-D6" ) ) . not . toBeInTheDocument ( ) ;
498+ } ) ;
499+
500+ it ( "renders D6 badge green on a fully-intact ladder (d6Effective green)" , ( ) => {
501+ const ctx = makeCtx ( ) ;
502+ const model = makeModel ( {
503+ d3 : makeLevel ( true , "green" ) ,
504+ d4 : makeLevel ( true , "green" ) ,
505+ d5 : makeLevel ( true , "green" ) ,
506+ d6 : makeLevel ( true , "green" ) ,
507+ d6Effective : "green" ,
508+ chipColor : "green" ,
509+ achievedDepth : 6 ,
510+ } ) ;
511+ const { getByTestId } = render (
512+ < UnifiedCell ctx = { ctx } model = { model } overlays = { overlaySet ( "health" ) } /> ,
513+ ) ;
514+
515+ expect ( getByTestId ( "mock-badge-D6" ) . getAttribute ( "data-tone" ) ) . toBe (
516+ "green" ,
517+ ) ;
518+ } ) ;
519+ } ) ;
520+
430521 // ── Additional coverage ───────────────────────────────────────────
431522 describe ( "badge tone mapping" , ( ) => {
432523 it ( "maps green status to green tone with check mark" , ( ) => {
@@ -445,20 +536,19 @@ describe("UnifiedCell", () => {
445536 expect ( badge . textContent ) . toContain ( "✓" ) ;
446537 } ) ;
447538
448- it ( "maps null status to gray tone with question mark " , ( ) => {
539+ it ( "hides a no-data ( null status) rung — the real Badge nulls a '?' label " , ( ) => {
449540 const ctx = makeCtx ( ) ;
450541 const model = makeModel ( {
451- d3 : makeLevel ( true , null ) , // exists but no status yet
542+ d3 : makeLevel ( true , null ) , // exists but no status yet → label "?"
452543 d4 : makeLevel ( false ) ,
453544 d5 : makeLevel ( false ) ,
454545 } ) ;
455- const { getByTestId } = render (
546+ const { queryByTestId } = render (
456547 < UnifiedCell ctx = { ctx } model = { model } overlays = { overlaySet ( "health" ) } /> ,
457548 ) ;
458549
459- const badge = getByTestId ( "mock-badge-API" ) ;
460- expect ( badge . getAttribute ( "data-tone" ) ) . toBe ( "gray" ) ;
461- expect ( badge . textContent ) . toContain ( "?" ) ;
550+ // A no-data rung emits label "?", which the real Badge hides → no badge.
551+ expect ( queryByTestId ( "mock-badge-API" ) ) . not . toBeInTheDocument ( ) ;
462552 } ) ;
463553 } ) ;
464554} ) ;
0 commit comments