high priority medium complexity testing pending testing specialist Tier 3

Acceptance Criteria

All LoadSummary success paths emit [WrappedSummaryLoading, WrappedSummaryLoaded] in that exact order with correct data payloads
LoadSummary network-failure path emits [WrappedSummaryLoading, WrappedSummaryError] and triggers offline cache fallback resulting in WrappedSummaryOffline state
ChangePeriod event triggers a re-fetch sequence emitting [WrappedSummaryLoading, WrappedSummaryLoaded] with the new period's data
NavigateSlide emits updated currentSlideIndex; dispatching at index 0 with direction=previous does NOT emit a new state (boundary condition)
NavigateSlide at last slide index with direction=next does NOT emit a new state (upper boundary condition)
ShareSlide success path emits [WrappedSummarySharing, WrappedSummaryLoaded] without mutating slide content
ShareSlide error path emits [WrappedSummarySharing, WrappedSummaryShareError] with a non-null error message string
All mocked services (aggregation, milestone detection, share, offline cache) are injected via constructor and never instantiated inside the BLoC
Test file achieves 100% branch coverage on the BLoC class as reported by flutter test --coverage
Each test group has a setUp that creates a fresh BLoC instance to prevent state bleed between tests

Technical Requirements

frameworks
Flutter
BLoC
bloc_test
flutter_test
mocktail
data models
WrappedSummaryState
WrappedSummaryEvent
AnnualSummaryAggregate
MilestoneBadge
SummaryPeriod
performance requirements
Full test suite must complete in under 10 seconds
No real async I/O — all service calls must be synchronous stubs via mocktail
security requirements
Test fixtures must not contain real user PII — use anonymised stub data only

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Use blocTest() for every test case — avoid raw bloc.add() + expectLater() patterns as they are harder to read and maintain. Register all mock fallbacks with registerFallbackValue() in setUpAll. Group tests hierarchically: group('LoadSummary', () { group('success', ...) group('network failure', ...) }). For boundary navigation tests, seed the BLoC with a WrappedSummaryLoaded state containing a known slides list length before dispatching NavigateSlide.

Ensure the offline cache mock returns a stale WrappedSummaryAggregate with a lastUpdated timestamp so the WrappedSummaryOffline state payload is fully testable. Do not test UI or widget behaviour in this file — that belongs in widget tests.

Testing Requirements

Unit tests only using bloc_test's blocTest() helper. Each event handler gets its own describe/group block. Use mocktail for all service mocks — avoid mockito to stay consistent with the project's null-safe Dart setup. Cover: (1) happy path per event, (2) error/exception path per event, (3) boundary conditions for NavigateSlide, (4) state sequence assertions with expect: [...], (5) verify() calls confirming mocked services were called with expected arguments.

Run with flutter test --coverage and assert 100% line coverage on the BLoC file.

Component
Wrapped Summary BLoC
service medium
Dependencies (4)
Implement the ChangePeriod event handler in WrappedSummaryBloc. When a new period is selected (e.g., previous year), the BLoC re-triggers the aggregation pipeline for the requested period, writes the new result to the offline cache, and emits a fresh WrappedSummaryLoaded state with updated slide data reflecting the selected period. epic-annual-impact-summary-orchestration-task-003 Implement the NavigateSlide event handler to manage current slide index within WrappedSummaryLoaded state. The handler must enforce boundary conditions (first/last slide), update slide position in emitted state, and record slide-view analytics events. Slide count is derived from the loaded summary payload. epic-annual-impact-summary-orchestration-task-004 Implement the ShareSlide event handler in WrappedSummaryBloc. The handler delegates to the summary share service (screenshot capture + platform share sheet), emits a transient sharing state to show loading feedback in the UI, then returns to the current loaded state. Handle errors gracefully with a WrappedSummaryShareError sub-state. epic-annual-impact-summary-orchestration-task-005 Wire the summary offline cache into WrappedSummaryBloc so that every successful LoadSummary or ChangePeriod call persists the result to the cache. On network failure the BLoC reads the latest cached summary and emits WrappedSummaryOffline with the stale data and a staleness timestamp so the screen can display an offline banner. epic-annual-impact-summary-orchestration-task-006
Epic Risks (3)
medium impact medium prob technical

If the device transitions between online and offline states while the user is mid-session in the wrapped screen, the BLoC may emit conflicting state transitions (loaded → error → offline) that cause visual flickering or an inconsistent UI state such as showing the offline banner over an already-loaded summary.

Mitigation & Contingency

Mitigation: Implement a connectivity stream listener in the BLoC that only triggers a state re-evaluation when transitioning from online to offline, not on every connectivity event. Once a summary is in the Loaded state, the BLoC should not transition to error/offline unless the user explicitly requests a refresh. Store the last-loaded data in BLoC state so it survives connectivity changes.

Contingency: If state flickering is observed in testing, add a minimum 3-second debounce on connectivity state changes before the BLoC reacts, and display a non-blocking top banner rather than replacing the entire screen state.

high impact medium prob integration

The push notification deep-link to the wrapped-summary-screen must work correctly whether the app is in the foreground, background, or terminated state. Handling all three app launch states on both iOS and Android is a common source of edge-case bugs, particularly when authentication state must be restored before the deep link can be resolved.

Mitigation & Contingency

Mitigation: Implement deep-link handling through the existing notification-deep-link-handler component which already manages app-state-aware routing. Define the wrapped-summary route in the navigation config early in the epic so the router is ready before notification dispatch is wired. Test all three app states (foreground, background, terminated) explicitly in the QA checklist.

Contingency: If terminated-state deep-linking fails on specific platforms, fall back to launching the app to the home screen with an in-app notification banner prompting the user to open their summary, rather than direct deep-link navigation.

high impact low prob technical

The wrapped-summary-screen manages a large number of AnimationController instances (one or more per slide) via the wrapped-animation-controller. If disposal is not triggered correctly when the user exits mid-flow (e.g., via system back gesture or deep-link away), memory leaks will accumulate across session navigation.

Mitigation & Contingency

Mitigation: Implement screen disposal via Flutter's dispose() lifecycle method calling a single wrapped-animation-controller.disposeAll() method that iterates the named controller registry. Write a test that navigates to the screen, starts animations, then navigates away and verifies no active AnimationController listeners remain using Flutter's test binding.

Contingency: If disposal bugs are detected in production via memory profiling, patch by converting all AnimationControllers to use AutomaticKeepAliveClientMixin false and wrap each slide in a widget that disposes its own controller when removed from the widget tree.