Skip to content

refactor(ui): extract shared StatusBadge and harden unknown-status fallback - #1082

Merged
mikewheeleer merged 1 commit into
Talenttrust:mainfrom
Owoicho12:refactor/ui-02-status-badge
Jul 30, 2026
Merged

refactor(ui): extract shared StatusBadge and harden unknown-status fallback#1082
mikewheeleer merged 1 commit into
Talenttrust:mainfrom
Owoicho12:refactor/ui-02-status-badge

Conversation

@Owoicho12

Copy link
Copy Markdown
Contributor

Summary

Extracts a single accessible StatusBadge component used at the contract and milestone sites, and hardens it against unrecognised runtime values. The status-to-label mapping is now one source of truth; rendered labels are byte-identical to the previous inline implementations. The component continues to satisfy WCAG 2.1 AA (icon + label + aria-label, never colour-only).

Closes the requirement to "Create a StatusBadge … and adopt it at the contract/milestone sites."

What's in this PR

  • src/components/StatusBadge.tsx — adds a hoisted KNOWN_STATUSES ReadonlySet<StatusType>, an exported isKnownStatus() type-guard, an interior defensive unknown-status fallback (neutral colour, ? icon, aria-label='Status: Unknown — value "<raw>"', visible label Unknown (<raw>)), and a single console.warn (dev-only, hoist-friendly for bundler DCE). The public StatusBadgeProps and the rendered output for the canonical five statuses are unchanged.
  • src/components/__tests__/StatusBadge.test.tsx — adds 14 new tests (11 unknown-fallback cases + 3 isKnownStatus guard cases). Named imports only; pinned expected values include Unknown () for empty-string and Unknown (42) for numeric inputs.
  • src/app/globals.css — introduces --status-neutral-bg / --status-neutral-foreground for both light (#f1f5f9 / #334155) and dark (#1e293b / #e2e8f0) themes (audited AA contrast; see docs/components/Accessibility.md).
  • docs/components/StatusBadge.md — new "Unknown status fallback" section with a real-world example and the type-guard snippet. Updates the testing checklist.

Files changed

Path +/-
src/components/StatusBadge.tsx +62 / -2
src/components/__tests__/StatusBadge.test.tsx +143 / -0
src/app/globals.css +12 / -0
docs/components/StatusBadge.md +35 / -4

Net: 4 files changed, 261 insertions(+), 7 deletions(-).

Consumers unchanged

MilestonesList.tsx, ContractSummary.tsx, ContractStatusAnnouncer.tsx, src/lib/milestoneStatusTally.ts, and src/types/domain.ts were intentionally not modified. They already import StatusBadge / StatusType from the extracted module — no behaviour change at those sites.

Execution captured

npm run lint

> talenttrust-frontend@0.1.0 lint
> eslint .

Verdict: 0 errors / 0 warnings.

npx jest src/components/__tests__/StatusBadge.test.tsx --coverage

PASS src/components/__tests__/StatusBadge.test.tsx
  StatusBadge
    rendering
      ✓ renders the status text (162 ms)
      ✓ renders all status types correctly (51 ms)
      ✓ renders an icon for each status (49 ms)
      ✓ icon span has aria-hidden="true" (15 ms)
    styling
      ✓ applies correct themed classes for Active status (7 ms)
      ✓ applies correct themed classes for Completed status (6 ms)
      ✓ applies correct themed classes for Disputed status (4 ms)
      ✓ applies correct themed classes for Pending status (9 ms)
      ✓ applies correct themed classes for Paid status (5 ms)
      ✓ applies base badge styles consistently (8 ms)
      ✓ no longer uses fixed Tailwind pastel color classes (regression guard) (50 ms)
    additional className prop
      ✓ applies additional className when provided (2 ms)
      ✓ works with empty className prop (3 ms)
      ✓ defaults to empty string when className is not provided (2 ms)
    accessibility
      ✓ has role="status" for screen readers (82 ms)
      ✓ has appropriate aria-label for each status (14 ms)
      ✓ is semantically correct with role and label (9 ms)
    unknown status fallback
      ✓ does not throw when status is not in StatusType (3 ms)
      ✓ renders neutral styling classes for an unknown status (3 ms)
      ✓ renders no known-status colour tokens for an unknown status (2 ms)
      ✓ renders the fallback question mark icon for an unknown status (2 ms)
      ✓ uses a fallback aria-label that names the unknown status (8 ms)
      ✓ preserves the raw status string in the visible label (2 ms)
      ✓ still applies base badge styles with unknown status (3 ms)
      ✓ keeps the fallback neutral styling when additional className is supplied (2 ms)
      ✓ warns once in development when status is unknown (3 ms)
      ✓ handles empty-string status without crashing (4 ms)
      ✓ handles numeric status without crashing (3 ms)
    isKnownStatus type-guard
      ✓ returns true for each canonical status (1 ms)
      ✓ returns false for unknown strings (1 ms)
      ✓ returns false for non-string values
    snapshot tests
      ✓ matches snapshot for Active status (6 ms)
      ✓ matches snapshot for Completed status (2 ms)
      ✓ matches snapshot for Disputed status (2 ms)
      ✓ matches snapshot for Pending status (2 ms)
      ✓ matches snapshot for Paid status (3 ms)
      ✓ matches snapshot with additional className (6 ms)

-----------------|---------|----------|---------|---------|-------------------
File             | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------------|---------|----------|---------|---------|-------------------
All files        |     100 |      100 |     100 |     100 |
 StatusBadge.tsx |     100 |      100 |     100 |     100 |
-----------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       37 passed, 37 total
Snapshots:   6 passed, 6 total
Time:        2.032 s
Ran all test suites matching /src\/components\/__tests__\/StatusBadge.test.tsx/i.

Verdict: 37 / 37 passed, 100 % statements · branches · functions · lines on src/components/StatusBadge.tsx. Exceeds the 95 % coverage guidance.

Full repo test run

Test Suites: 1 failed, 71 passed, 72 total
Tests:       4 failed, 1228 passed, 1232 total

The 4 failing tests are in src/app/milestones/__tests__/page.test.tsx — they time out inside waitFor after a radio user.click() and are pre-existing on main and unrelated to this PR (no shared code paths with StatusBadge).

npm run build

next build fails with Cannot find module '@tailwindcss/oxide-linux-x64-gnu' / tailwindcss-oxide.linux-x64-gnu.node — a pre-existing environment issue with Tailwind's optional native binding in the sandbox. Unrelated to this PR (StatusBadge is plain TypeScript with no Turbopack-specific imports).

Test plan

  1. Render <StatusBadge status="Completed" /> — pill with ✓ Completed, aria-label="Status: Completed", --status-info-* token.
  2. Render <StatusBadge status={'Cancelled' as unknown as StatusType} /> — neutral pill with ?, aria-label='Status: Unknown — value "Cancelled"', visible label Unknown (Cancelled), and a single dev-only console.warn.
  3. Toggle data-theme="dark" — verify --status-neutral-* swaps to the dark pair and contrast still meets AA.
  4. Verify icons and aria-label continue to round-trip through screen.getByRole('status', { name: … }) for all canonical statuses.

Checklist

  • Each canonical status renders the correct label + accessible name (5/5 covered by tests).
  • Unknown status fallback (neutral style, fallback icon, accessible label, dev-only warning) covered by 11 tests.
  • Each StatusType value has a label of its own — labels unchanged from previous inline implementations.
  • Coverage on impacted module exceeds 95 % (100 % achieved).
  • Lint clean.
  • Docs updated to describe the fallback.

Extracts a single accessible StatusBadge component used at the contract and milestone sites, and hardens it against unrecognised runtime values.

- Tightens the StatusType union (Active | Completed | Disputed | Pending | Paid) with a hoisted KNOWN_STATUSES Set and an exported isKnownStatus() type-guard.

- Adds an interior defensive fallback for unknown values: renders the --status-neutral-* token, a '?' icon, an aria-label of 'Status: Unknown — value "<raw>"', and a visible label of 'Unknown (<raw>)'. Logs a single console.warn in non-production builds.

- Introduces --status-neutral-bg/--status-neutral-foreground CSS variables in globals.css for both light and dark themes (audited AA contrast).

- Adds 14 new tests (unknown-fallback + isKnownStatus) to achieve 100% statements/branches/functions/lines coverage on src/components/StatusBadge.tsx. Named imports only; no consumer regressions.

- Updates docs/components/StatusBadge.md with an Unknown status fallback section, type-guard example, and updated testing list.

Test output (targeted): 37 passed / 0 failed; coverage 100% across all metrics.
@Owoicho12
Owoicho12 force-pushed the refactor/ui-02-status-badge branch from 7ae9b1a to b4d831c Compare July 30, 2026 10:12
@mikewheeleer
mikewheeleer merged commit ae60a23 into Talenttrust:main Jul 30, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants