Widget visual regression and animation snapshot tests
epic-annual-impact-summary-ui-components-task-017 — Create golden file snapshot tests for all four widgets covering: stat card in locked/counting/completed animation states, milestone badge in locked and unlocked states, activity breakdown with 3 and 6 segment variants, and the share overlay open state. Include reduced-motion variants for all animated widgets. Run tests on both light and dark token themes.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 5 - 253 tasks
Can start after Tier 4 completes
Implementation Notes
Wrap each widget under test in a MaterialApp with the appropriate ThemeData (light/dark design tokens injected). Fix the widget size with SizedBox(width: 375, height: X) to match the iPhone SE viewport width for consistency. Use a FakeAsync or explicit pump durations rather than pumpAndSettle for animation states — pumpAndSettle will skip to the end, so use tester.pump(const Duration(milliseconds: 300)) for the mid-animation counting state. Store all golden files in test/goldens/ with snake_case names.
If using golden_toolkit, use loadAppFonts() in setUpAll to ensure text renders correctly. For the share overlay golden, mock ScreenshotCaptureUtility to return a fixed 1x1 transparent image so the preview renders without async gaps.
Testing Requirements
All tests use flutter_test's matchesGoldenFile. Use tester.pump(Duration.zero) to advance to the initial frame, and for mid-animation state use tester.pump(animationDuration * 0.5) to capture counting state. For completed state use tester.pumpAndSettle(). For reduced-motion, inject SummaryAccessibilityProvider with reducedMotionEnabled: true and use tester.pumpAndSettle() — the widget should immediately show final state.
Run golden tests on a Linux CI runner (Flutter's canonical golden comparison platform) to ensure deterministic rendering. Document the update command: flutter test --update-goldens test/goldens/. Enforce that golden files are never committed with --update-goldens in CI — use a diff check on PRs.
Simultaneous count-up animations across multiple stat cards and chart draw-in animations on lower-end Android devices may cause frame drops below 60fps, degrading the premium Wrapped experience and making the feature feel unpolished.
Mitigation & Contingency
Mitigation: Stagger animation starts using AnimationController with staggered intervals rather than starting all animations simultaneously. Use RepaintBoundary around each animated widget to isolate rasterisation. Profile on a mid-range Android device (e.g., equivalent to Pixel 4a) during development, not just at QA.
Contingency: If frame rate targets cannot be met on low-end devices, implement a device-capability check at startup and substitute simpler fade-in animations for the count-up and chart draw-in on devices below a CPU performance threshold.
The activity-type-breakdown-widget must render organisation-specific activity type labels sourced from the terminology system. If the terminology provider is not yet integrated at the time this widget is built, the widget will display hardcoded system labels, which is a regression risk for multi-org support.
Mitigation & Contingency
Mitigation: Accept activity type labels as a typed parameter in the widget constructor rather than reading from the terminology provider directly inside the widget. The BLoC or repository layer resolves labels before passing them to the widget, maintaining clean separation and testability.
Contingency: If terminology resolution is unavailable at widget integration time, display internal activity type keys as a temporary fallback with a localised suffix '(label pending)' visible only in non-production builds so QA can identify unresolved labels.