high priority low complexity testing pending testing specialist Tier 3

Acceptance Criteria

Test file covers all 3 states × 2 display modes = 6 primary rendering combinations
Each combination asserts the correct background color is applied to the badge container by inspecting the widget tree decoration
Each combination asserts the correct foreground (text/icon) color is applied
Contrast ratio between foreground and background for each state is validated to meet WCAG 2.2 AA (4.5:1) using a pure-Dart contrast calculation helper in the test file
Semantics node is found for each combination using find.bySemanticsLabel with the expected label string
Full mode with null expiryDateLabel renders without assertion errors or RenderFlex overflow
Full mode with a provided expiryDateLabel includes the date in the Semantics label
Widget renders without layout overflow at textScaleFactor 1.0, 1.5, and 2.0 for both modes
All tests pass in CI (flutter test) without device or emulator
Test file uses descriptive group() and test() names that read as living documentation

Technical Requirements

frameworks
Flutter (flutter_test)
Dart
data models
CertificateExpiryStatus
CertificateExpiryTokens
ExpiryDisplayMode
performance requirements
Full test suite runs in under 10 seconds
ui components
ExpiryStatusIndicator (under test)

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

The contrast ratio helper should implement the exact WCAG 2.2 formula: relative luminance L = 0.2126R + 0.7152G + 0.0722B (after gamma correction), then ratio = (L1 + 0.05) / (L2 + 0.05) where L1 is the lighter color. Flutter's Color stores values as integers — extract red, green, blue as doubles in [0,1] range before applying gamma correction. Do not import a third-party contrast library — implement inline. For Semantics testing, ensure the test MaterialApp has debugShowCheckedModeBanner: false and Overlay support so Semantics traversal works correctly.

Use tester.pumpWidget(MaterialApp(home: Scaffold(body: widget))) as the minimal host. To test dark mode colors, wrap with Theme(data: ThemeData.dark(), child: ...). Keep all test assertions focused on the public contract (colors, semantics, layout safety) — do not test private methods or internal widget structure that may change during refactoring.

Testing Requirements

All tests are Flutter widget tests using flutter_test. Structure the file with nested group() blocks: outer group per display mode (compact, full), inner group per expiry state. Use a shared helper pumpIndicator(tester, status, mode, {dateLabel}) to reduce boilerplate. For color assertions, retrieve the BoxDecoration from the badge Container using tester.widget(...) and assert .color or .decoration.color equals the expected token value.

For contrast ratio, implement a static double _contrastRatio(Color fg, Color bg) function using the WCAG relative luminance formula directly in the test file. For Semantics assertions, use tester.getSemantics(find.byType(ExpiryStatusIndicator)) or SemanticsHandle. For overflow detection, wrap the widget in a SizedBox(width: 320) and use tester.takeException() — any RenderFlex overflow throws an exception in test mode. Target 100% line coverage of ExpiryStatusIndicator build paths.

Epic Risks (2)
medium impact medium prob technical

The persistent banner must remain visible across app sessions and only disappear when a specific backend condition is met (renewal or coordinator acknowledgement). If the BLoC state is not properly sourced from the notification record repository on every app launch, the banner may disappear prematurely or fail to reappear after a session restart.

Mitigation & Contingency

Mitigation: Drive the banner's visibility exclusively from a Supabase real-time subscription on the notification records table filtered by mentor_id and acknowledged_at IS NULL. Never persist banner visibility state locally. Write an integration test that restarts the BLoC and verifies the banner reappears from the database source.

Contingency: If real-time subscriptions introduce latency or connection reliability issues in offline-first scenarios, add a local cache flag that is only cleared when the repository confirms the acknowledgement write succeeded, with a cache TTL of 24 hours as a fallback.

high impact low prob security

The notification detail view must conditionally render coordinator-specific actions based on the authenticated user's role. Incorrect role resolution could expose the 'Acknowledge Lapse' action to peer mentors or hide it from coordinators, breaking the workflow and potentially allowing unauthorised state changes.

Mitigation & Contingency

Mitigation: Source the role check from the existing role_state_manager BLoC that is already authenticated against Supabase role claims. Do not rely on a local flag. The coordinator acknowledgement service backend also validates role server-side, providing defence in depth. Add widget tests that render the detail view with mentor and coordinator role fixtures and assert the presence or absence of coordinator actions.

Contingency: If a role resolution bug is found in production, immediately disable the acknowledge action via a feature flag and patch the role check in a hotfix release. The server-side validation in the coordinator acknowledgement service ensures no actual state change can occur even if the button is incorrectly rendered.