high priority high complexity testing pending testing specialist Tier 7

Acceptance Criteria

Test suite covers all 6 specified flow scenarios: deep-link entry, slide loading, slide navigation, share action, period change with data reload, and offline cache display
Deep-link scenario: test simulates a notification tap (by programmatically triggering the deep-link handler with a seeded payload) and asserts WrappedSummaryScreen is displayed with the correct year heading
Slide loading scenario: after deep-link entry, test asserts that the loading skeleton is shown briefly and then the first slide widget renders with seeded data values
Slide navigation scenario: test swipes or taps next/previous controls and asserts that slide index changes, slide content updates, and progress indicator reflects current position
Share action scenario: test taps the share button and asserts that the share sheet is invoked (mock share_plus to verify the call) and that the sharing loading overlay appears and dismisses correctly
Period change scenario: test selects a different year/period from the period selector and asserts that a new LoadSummary event is dispatched and the slide data refreshes with the new period's seeded values
Offline cache scenario: test disables network connectivity (via a network condition mock or interceptor), navigates to WrappedSummaryScreen, and asserts that cached data is displayed alongside the offline banner with a stale timestamp
All tests pass deterministically across 3 consecutive runs against the seeded Supabase test environment
Tests are tagged appropriately (e.g. @Tags(['integration', 'wrapped'])) and can be run in isolation from the full test suite
Test execution time does not exceed 3 minutes for the full suite on CI

Technical Requirements

frameworks
flutter_test (core test utilities)
integration_test package (Flutter integration test runner)
BLoC test (bloc_test package for state stream assertions within integration context)
Riverpod (ProviderScope overrides for injecting test providers)
apis
Supabase PostgreSQL — seeded test schema with annual_summary rows for deterministic test data
Supabase Auth — test user with known credentials and organisation_id seeded in test environment
Firebase Cloud Messaging — mocked; deep-link handler called directly without real FCM payload
data models
annual_summary (seeded rows for test peer_mentor_id, multiple years and period types)
assignment (seeded assignment linking test peer mentor to test organisation)
activity (seeded activity records contributing to the summary's activity_count and hours values)
performance requirements
Full test suite completes within 3 minutes on a standard CI runner (GitHub Actions or equivalent)
Each individual test scenario completes within 30 seconds including Supabase round-trips to the test environment
security requirements
Supabase test environment credentials (URL, anon key) stored in CI/CD secrets — never hardcoded in test files
Test user accounts use non-production credentials and are scoped to a dedicated test organisation that cannot access real user data
Test environment RLS policies match production to ensure tests validate real access control behaviour
ui components
WrappedSummaryScreen (under test)
Slide widgets (asserted for correct content rendering)
Progress indicator (asserted for correct position)
Period selector (interacted with to trigger reload)
Offline banner (asserted for visibility in offline scenario)
Share button and loading overlay (interacted with and asserted)

Execution Context

Execution Tier
Tier 7

Tier 7 - 84 tasks

Can start after Tier 6 completes

Implementation Notes

Structure the test file as a single testWidgets suite with setUp/tearDown for Supabase test data seeding and cleanup. Use a dedicated test_helpers/wrapped_test_seed.dart file to encapsulate all data seeding logic (annual_summary rows, assignments, activities) so seeds can be reused by future tests. For the deep-link scenario, directly call the notification handler function with a constructed RemoteMessage object rather than relying on real FCM — this makes the test deterministic and CI-safe. For slide navigation, use WidgetTester.drag() on the PageView or tap on arrow/dot controls depending on the slide navigation implementation.

For the offline scenario, override the repository's HTTP client or Supabase client with a mock that returns cached data (from the previously loaded state) and throws a connectivity exception on the subsequent load — assert the offline banner widget key is present. Ensure tests do not share state between scenarios: use separate testWidgets blocks rather than one monolithic test to allow individual failures to be diagnosed.

Testing Requirements

This task IS the testing requirement. Integration tests must use the integration_test package with flutter_test. Use testWidgets with a real app pump (pumpWidget(MyApp())) backed by ProviderScope overrides for the network layer and feature flags. Seed the Supabase test environment using a setup script or Supabase migrations applied to a dedicated test project.

Network interception for the offline scenario can be achieved by overriding the Supabase client provider with a mock that throws a SocketException after initial data load. Use share_plus's platform channel mock to assert share invocation without triggering the real share sheet on CI. All integration tests must run on both iOS Simulator and Android Emulator in CI. TestFlight distribution is used for physical device validation but is not required for CI pass.

Component
Wrapped Summary Screen
ui high
Dependencies (5)
Add an offline banner widget inside WrappedSummaryScreen that appears when WrappedSummaryBloc emits WrappedSummaryOffline state. The banner must display a stale-data warning with the last-updated timestamp, remain dismissible, and not obstruct the slide content. Use the design token color palette for warning styling. epic-annual-impact-summary-orchestration-task-010 Integrate the SummaryPeriodSelector widget into WrappedSummaryScreen so peer mentors can switch between available annual periods. Tapping a period dispatches ChangePeriod to the BLoC and shows an inline loading state. The selector should only be shown when multiple periods are available in the loaded state payload. epic-annual-impact-summary-orchestration-task-011 Integrate the SummaryShareOverlay into WrappedSummaryScreen. A share button on each slide opens the overlay; confirming dispatches ShareSlide to the BLoC. The screen must show a loading indicator while the BLoC processes sharing and gracefully display an error snackbar if ShareSlide fails. The overlay must be dismissed after a successful share. epic-annual-impact-summary-orchestration-task-012 Wire the push notification deep-link handler to navigate directly into WrappedSummaryScreen when a summary-ready notification is tapped. The entry point must dispatch LoadSummary on screen init so the BLoC loads the correct year. Handle cold-start (app not running) and warm-start (app backgrounded) deep-link scenarios using the existing notification deep-link handler infrastructure. epic-annual-impact-summary-orchestration-task-013 Implement the PageView inside WrappedSummaryScreen that renders each slide widget (StatCard, MilestoneBadge, ActivityTypeBreakdown) based on the current BLoC state. Add a segmented progress indicator at the top of the screen that highlights the active slide and responds to NavigateSlide BLoC events. Integrate the WrappedAnimationController for entrance animations per slide. epic-annual-impact-summary-orchestration-task-009
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.