• English
  • Testing

    Test structure

    test/
    ├── widget_test.dart              # Placeholder default test
    ├── support/                      # Shared test harness
    │   ├── pump_app.dart             # pumpWidgetUnderTest / pumpScreenUnderTest / mockDio
    │   └── harness_smoke_test.dart   # Validates the harness itself
    ├── core/
    │   ├── network/                  # ~30 tests: API client, handlers, TLS, LAN auth
    │   ├── sync/                     # ~30 tests: accounts, sync worker/scheduler/collector,
    │   │                             #   sync buffer, roster, heartbeat, booking outbox, config
    │   ├── database/
    │   ├── device/
    │   ├── notifications/
    │   ├── observability/
    │   ├── push/
    │   └── utils/
    ├── features/
    │   ├── session/                  # ~15 tests: cash handling, reconciliation, closeout
    │   ├── check_in/                 # ~11 tests: screen behavior, cart, quick checkout
    │   ├── staff/                    # ~7 tests: availability, schedule, permissions, PIN
    │   └── (other feature dirs)
    └── shared/
        ├── models/
        ├── widgets/
        └── tap_target_guideline_test.dart

    Test harness (test/support/pump_app.dart)

    Key utilities

    HelperPurpose
    pumpWidgetUnderTest(tester, child)Pumps a leaf widget wrapped in ScreenUtilInit + MaterialApp + l10n at 1280×800. No providers.
    pumpScreenUnderTest(tester, child, overrides: [...])Pumps a screen that reads Riverpod providers. Returns the ProviderContainer.
    mockDio()Returns ({Dio dio, DioAdapter adapter}) — the test seam for all network calls.
    baseOverrides()Returns [sharedPreferencesProvider, obsLoggerProvider] overrides — needed by nearly every screen.
    expectMeetsTapTargetGuideline(tester)Asserts all tappables meet the 48×48 Android minimum.

    Pattern for widget tests

    testWidgets('screen displays correctly', (tester) async {
      final overrides = [
        ...baseOverrides(),
        myRepositoryProvider.overrideWith((ref) => FakeMyRepository()),
      ];
      final container = await pumpScreenUnderTest(
        tester,
        const MyScreen(),
        overrides: overrides,
      );
      // Assertions...
    });

    Pattern for handler/network tests

    test('handler creates ticket', () async {
      final (dio, adapter) = mockDio();
      // Configure adapter to respond to specific requests
      final repo = MyRepository(dio: dio);
      final result = await repo.createTicket(...);
      expect(result, ...);
    });

    Mock Dio

    The Dio adapter (dio_test_adapter / DioAdapter) is the primary test seam for all network-dependent code. Since every client Repository takes a Dio instance, tests can:

    1. Use a real Dio with a mock adapter to test repository logic against expected HTTP calls
    2. Use fake repository implementations to test screens without any network dependency

    Repository pattern for testability

    All feature repositories follow this pattern:

    class MyRepository {
      final Dio _dio;
      MyRepository({required Dio dio}) : _dio = dio;
    
      Future<MyModel> getSomething(String id) async {
        final response = await _dio.get('/api/my-domain/$id');
        return MyModel.fromJson(response.data);
      }
    }

    This makes repositories trivially testable — pass a real Dio with mock adapter, or pass a fake Dio, or override the Riverpod provider entirely.

    Handler testing

    Shelf handlers are testable by mounting them on a test HTTP server. Each handler (TicketHandler, CustomerHandler, etc.) receives a Shelf.Request and returns a Shelf.Response — no database dependency at the handler level (the handler delegates to a repository/service that is injected).

    Anti-drift tests

    The sync table registry (lib/core/sync/sync_table_registry.dart) has an anti-drift test that ensures every AppDatabase table is either:

    1. Registered in kSyncTableRegistry (syncable), or
    2. In the exclusion set (non-syncable)

    This prevents silent omissions when new tables are added.

    Key areas to test when changing code

    AreaWhat to test
    Database schema changesRun the anti-drift test; verify migrations are guarded
    New API handlersAdd handler unit tests; add repository integration tests with mock Dio
    Checkout/payment logicTest discount, gift card, loyalty, and CodePay terminal flows
    Session closeoutTest cash reconciliation, variance calculation, Z-report generation
    Cloud syncTest collector coalescing, worker batch limits, backoff behavior, conflict handling
    EmergencyBufferTest buffer/replay cycle, subnet scan trigger, role transition
    RBAC/permissionsTest that gated UI elements hide correctly; test privilege escalation flow
    Widget accessibilityRun expectMeetsTapTargetGuideline on new interactive elements