| name | fs-game-score-testing-workflow |
| description | Unit, widget, and integration testing patterns for this app: ProviderScope overrides, integration_test helpers (launchApp, waitForScoreTable, pumpUntilFound), fake sync transports, widget-key lookups in tests, and accessibility/semantics checks. Use whenever writing, fixing, or reviewing any test, debugging a flaky integration flow or splash-clear race, or changing screen-reader/a11y behavior.
|
FS Score Card — Testing and accessibility
See docs/How-To-Riverpod.md — Integration and widget testing.
Always run tests with fvm flutter test per AGENTS.md.
Test types and skills
| Type | Location | Skill |
|---|
| Unit | test/ | dart-add-unit-test |
| Widget | test/ | flutter-add-widget-test |
| Integration | integration_test/ | flutter-add-integration-test |
| Mocks | test/ | dart-generate-test-mocks |
Use Arrange-Act-Assert. Prefer fakes/stubs over mocks where possible (FakeGameSyncTransport).
New tests may use package:checks (dart-migrate-to-checks-package skill); existing tests still use package:matcher / expect().
Unit / widget test setup
test/flutter_test_config.dart — SharedPreferences.setMockInitialValues({}) before each run.
- Wrap widgets under test in
ProviderScope and override sharedPreferencesProvider when code touches repositories.
Read notifier state:
final container = ProviderScope.containerOf(tester.element(find.byType(MyWidget)));
container.read(gameNotifierProvider);
Works with UncontrolledProviderScope from bootstrapApp().
Project unit tests (examples)
Snapshot of representative tests — verify against test/ before citing; this list drifts.
| File | Covers |
|---|
test/players_notifier_persist_test.dart | Coalesced persist burst, splash clear, in-flight + splash race |
test/game_sync_protocol_test.dart | Wire messages, version matching |
test/game_sync_connection_label_test.dart | Banner / host labels |
test/game_sync_mapper_test.dart | Snapshot ↔ domain mapping |
test/game_sync_qr_test.dart, test/game_sync_platform_test.dart | QR URLs, platform gates |
test/game_serialization_test.dart | Game.fromJson / new gameId behavior |
Integration tests
Helpers: integration_test/app_test_helpers.dart
| Helper | Purpose |
|---|
clearPersistedGameState() | setUp / tearDown — real prefs on devices |
await launchApp(tester) | await bootstrapApp() — never unawaited main() |
await launchAppOnSplash(tester) | Launch + splash Continue + waitForSplashReady |
waitForSplashReady(tester) | Await splash prepareForSplashEntry before Continue |
waitForScoreTable(tester) | Poll until DataTable2 and app bar new-scorecard control |
tapNewScoreCardControlIconButton(tester) | Tap home icon; uses AppBar overflow on narrow screens |
pumpUntilFound(tester, finder) | Slow emulators |
waitForSplashPlayersCleared(tester) | After navigating to splash — coalesced persist race |
Splash player clear
After New Score Card or back to splash, assert cleared roster with waitForSplashPlayersCleared(tester) — not a raw prefs read immediately after pumpAndSettle. See State-Management.md — Splash entry and coalesced persist race.
Widget keys in tests
Use static key functions from presentation widgets — see fs-game-score-widgets-holding-player-game-data.
await tester.tap(find.byKey(PlayerGameCell.nameKey(0)));
await tester.enterText(find.byKey(PlayerRoundModal.scoreFieldKey(0, 1)), '42');
Integration tests demonstrate patterns in integration_test/*_test.dart.
Live sync testing
Override transport factory:
gameSyncTransportFactoryProvider.overrideWith(
(ref) => () => fakeTransport,
);
FakeGameSyncTransport — lib/sync/fake_game_sync_transport.dart. Toggle pinAccepted, appVersionAccepted, expectedHostAppVersion.
Details: fs-game-score-live-sync, docs/Game-Sync.md.
Accessibility
- Wrap player-data widgets in
Semantics or set semanticLabel.
- Target 4.5:1 text contrast; verify UI at increased system font scale.
- Semantic labels are screen-reader text and must be localized — canonical rule in
fs-game-score-flutter-patterns → Localization. The localize_semantic_labels custom lint (fvm dart run custom_lint) flags hardcoded labels in-editor.