high priority medium complexity testing pending testing specialist Tier 7

Acceptance Criteria

NotificationFilterBar test: tapping each filter chip emits the correct FilterNotifications(type) BLoC event and the chip renders as selected/active
NotificationTabBadge test: renders '5' for count=5, '99+' for count=100, and is not present in the widget tree for count=0
NotificationDeepLinkHandler test: valid payload with an existing entityId navigates to the correct named route
NotificationDeepLinkHandler test: payload referencing a deleted entity shows a SnackBar with a non-empty message and dispatches MarkNotificationRead
NotificationDeepLinkHandler test: unknown route type in payload triggers fallback snackbar without throwing
NotificationCentreScreen test: BLoC in loading state renders skeleton widgets and no list items
NotificationCentreScreen test: BLoC in empty state renders the empty-state widget and no skeleton or list items
NotificationCentreScreen test: BLoC in loaded state renders the correct number of NotificationListItem widgets
Pull-to-refresh test: fling the list downward triggers RefreshIndicator and the BLoC receives a RefreshNotifications event
Infinite scroll test: programmatically scroll near the bottom of a loaded list and verify LoadNextPage is dispatched exactly once
Test coverage report shows >= 80% branch coverage across NotificationFilterBar, NotificationTabBadge, NotificationDeepLinkHandler, and NotificationCentreScreen
All tests pass in CI without flakiness (no sleep/delay hacks; use pumpAndSettle or pump(Duration) as appropriate)

Technical Requirements

frameworks
Flutter
flutter_test
BLoC
bloc_test
apis
MockNotificationBloc (via mocktail or bloc_test MockBloc)
MockNotificationDeepLinkHandler
MockGoRouter or NavigatorObserver for route assertions
data models
Notification
NotificationFilterType
NotificationPayload
NotificationState (loading, empty, loaded, error)
performance requirements
Each test file must complete within 30 seconds on CI to avoid pipeline timeouts
Use pump() with explicit durations rather than pumpAndSettle() for animation-heavy widgets to avoid infinite loop timeouts
security requirements
Test fixtures must not contain real user data or real notification payloads — use anonymized placeholder content
ui components
MockBloc via bloc_test for injecting controlled BLoC states
WidgetTester for gesture simulation (tap, fling, drag)
find.byType / find.byKey for widget assertions
expect(find.text('99+'), findsOneWidget) pattern for badge overflow assertions

Execution Context

Execution Tier
Tier 7

Tier 7 - 84 tasks

Can start after Tier 6 completes

Implementation Notes

For BLoC widget tests, wrap the widget under test in a `BlocProvider(create: (_) => mockBloc, child: WidgetUnderTest())`. Use `whenListen(mockBloc, Stream.fromIterable([state1, state2]))` from bloc_test to simulate state transitions. For the infinite scroll test, use `tester.drag(find.byType(ListView), Offset(0, -5000))` followed by `await tester.pump()` to simulate scrolling near the bottom, then verify the BLoC received LoadNextPage via `verify(() => mockBloc.add(LoadNextPage())).called(1)`. For the pull-to-refresh test, use `tester.fling(find.byType(ListView), Offset(0, 300), 1000)` then pump.

For route assertions in deep link tests, use a GoRouter with a NavigatorObserver mock or check the current location via a test GoRouter instance. Group happy-path tests first, then edge cases, then error cases within each file for readability.

Testing Requirements

This task IS the test implementation. Organize tests into four files: `notification_filter_bar_test.dart`, `notification_tab_badge_test.dart`, `notification_deep_link_handler_test.dart`, `notification_centre_screen_test.dart`. Use `group()` to separate test scenarios within each file. Run with `flutter test --coverage` and verify the lcov report shows >= 80% branch coverage.

Include a GitHub Actions step (or equivalent CI config) that fails the build if coverage drops below threshold. Use `mocktail` for dependency mocking. Avoid testing implementation details — test observable widget output and BLoC event emissions only.

Component
Notification Centre Screen
ui medium
Epic Risks (3)
medium impact medium prob integration

If a referenced entity (contact, certification, activity) has been deleted or its RLS policy now excludes the current user, the deep link handler may navigate to a screen that renders in an error state or throws an unhandled exception.

Mitigation & Contingency

Mitigation: The deep link handler must perform a lightweight existence check (HEAD request or minimal SELECT) before pushing the route. Define a contract with each destination screen for how to handle a not-found entity ID passed as a route parameter.

Contingency: If the existence check itself fails (network error), navigate to the destination screen anyway and let it handle the error gracefully with its own error state; do not block navigation for network timeouts.

medium impact low prob technical

If the tab badge widget triggers a full rebuild of the bottom navigation bar on every unread count change, it will cause visible jank on devices with many active Realtime events (e.g., org admins receiving org-wide alerts).

Mitigation & Contingency

Mitigation: Scope the badge widget to a dedicated BlocSelector that rebuilds only when the unread count value changes, not on any BLoC state emission. Use RepaintBoundary to isolate the badge from the rest of the nav bar.

Contingency: If performance issues persist, debounce badge updates to a maximum of one rebuild per 500ms and display the last known count during the debounce window.

high impact medium prob scope

Complex swipe-to-mark-read gestures and dynamic list updates may conflict with VoiceOver/TalkBack navigation patterns, particularly for Blindeforbundet users who rely exclusively on screen readers.

Mitigation & Contingency

Mitigation: Provide a dedicated accessibility action (Semantics.onTap / CustomSemanticsAction) for mark-as-read on each list item so screen reader users do not need the swipe gesture. Test with VoiceOver on iOS and TalkBack on Android before each release.

Contingency: If the swipe gesture proves incompatible with assistive technologies, disable it when a screen reader is detected (via ScreenReaderDetectionService) and rely solely on the tap-to-read and accessible action pathways.