Skip to content

CI: file-pair existence guard (every page/widget needs a test or an allow-list entry) #551

@TaprootFreak

Description

@TaprootFreak

Context

#544 (page tests), #545 (widget tests), and #547 (visual regression) all reference a "CI check that lists *_page.dart / lib/widgets/**/*.dart without a matching *_test.dart / *_golden_test.dart" as an enforcement deliverable. None of those three issues own that check. This issue owns it.

Without an enforcement guard, every new page or widget added after the test backlog is closed will silently reopen the gap.

This guard is a complement to #544/#545/#547, not a prerequisite — those issues can ship without it; the guard's value is forward-looking regression prevention.

Constraint: combined and parent-test coverage

A naive basename-match (find lib/screens -name '*_page.dart' | while read p; do test -f "test/${p#lib/}" || echo MISSING; done) produces false positives in three ways:

Combined test files (one test, multiple source files)

  • test/screens/kyc/steps/financial_data/subpages/kyc_financial_data_loading_failure_pages_test.dart covers kyc_financial_data_loading_page.dart + kyc_financial_data_failure_page.dart
  • test/widgets/buttons/app_buttons_test.dart covers app_filled_button.dart + app_text_button.dart
  • test/widgets/iban_formatters_test.dart covers iban_input_formatter.dart + iban_text_formatter.dart
  • test/widgets/tag_tab_selectors_test.dart covers tab_selector.dart + tag_selection.dart
  • test/widgets/outlined_tile_handlebars_test.dart covers outlined_tile.dart + handlebars.dart
  • test/widgets/form/form_fields_test.dart covers dropdown_field.dart + labeled_text_field.dart

Parent-test coverage (page rendered + asserted in another page's test)

  • kyc_financial_data_questions_page.dart — asserted in test/screens/kyc/steps/kyc_financial_data_page_test.dart:130,154,178,202 via find.byType
  • settings_edit_pending_page.dart, settings_edit_failure_page.dart — asserted in test/screens/settings_user_data/subpages/settings_edit_name_page_test.dart and settings_edit_address_page_test.dart

part of files (not independently testable)

  • lib/widgets/mnemonic_input_field.dart, mnemonic_input_field_controller.dart, mnemonic_field_base.dart, mnemonic_read_only_field.dart — all part of 'mnemonic_field.dart'; (line 1 of each). They cannot be imported by a test directly; the test imports the parent.

The guard must combine import-detection with these three exemptions.

Bottom-sheet glob exception

lib/screens/pin/widgets/enable_biometric_bottom_sheet.dart, lib/screens/pin/widgets/forgot_pin_bottom_sheet.dart, and lib/screens/hardware_connect_bitbox/show_bitbox_reconnect_sheet.dart should be in scope but don't match *_page.dart. Need either a separate glob or an explicit include-list.

Scope

Guard implementation

  • Script under tool/check_test_coverage.sh (or tool/check_test_coverage.dart) that:
    1. Source globs: lib/screens/**/*_page.dart + lib/screens/**/*_bottom_sheet.dart + lib/screens/hardware_connect_bitbox/show_bitbox_reconnect_sheet.dart + lib/widgets/**/*.dart
    2. Excludes:
      • *.g.dart (Drift codegen — only database.g.dart today)
      • lib/generated/** (i18n + release_info codegen — 3 files)
      • Files whose line 1 is part of '...'; (part-of pattern, e.g. all 4 mnemonic part files)
      • Note: *.freezed.dart and *.mocks.dart do not exist in this tree today (verified via find); they're listed in the existing lcov --remove step at .github/workflows/pull-request.yaml:113-116 as preemptive, harmless to mirror but unnecessary
    3. For each remaining source file, finds either:
      • A test/**/*_test.dart that imports the source file by package:realunit_wallet/... path (covers basename and combined cases), or
      • An entry in .test-coverage-allowlist
    4. Lists source files with neither match
  • Output: GitHub Actions warning per missing-pair, fail step with summary count
  • Allow-list mechanism: .test-coverage-allowlist flat-file in repo root with one path per line + one-line rationale (e.g. lib/screens/web_view/web_view_page.dart # InAppWebView wrapper, see #544)

Variants (later, not load-bearing for #544/#545)

CI wiring

  • New job Test Coverage Pairs in .github/workflows/pull-request.yaml, runs in parallel with Analyze & Test
  • Job runs on pull_request and on push to develop
  • Failure is hard (job fails) for the page/widget testWidgets guard; the golden guard can start as warning-only until Visual regression: scale from 5-screen pilot to every *_page.dart #547 closes the gap

Documentation

  • Add a "Test-coverage guards" section to docs/testing.md
  • README "Coverage infrastructure roadmap" gets a new checkbox row

Acceptance criteria

Estimated effort

Sub-task Days
Detection script (Dart preferred; Bash workable) — import-grep + part-of exclusion + allow-list reader 0.5
.test-coverage-allowlist initial entries for current coverage:ignore-file candidates (web_view, debug_auth, legal_document, image_picker_sheet) 0.25
New Test Coverage Pairs CI job + parallel wiring 0.25
Documentation in docs/testing.md + README roadmap row 0.25
Testing the guard against the existing tree (zero false positives target) 0.5
Total ~1.5-2 engineer-days

Why this is its own issue, not embedded in #544/#545/#547

The script is a one-time deliverable that all three issues reference. Embedding it in any one of them creates an ownership conflict; embedding it in all three duplicates the work. Note: this is a forward-looking complement#544/#545/#547 can ship before this lands.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions