{ "output": "\nIMPORTANT: The file content has been truncated.\nStatus: Showing lines 310-523 of 523 total lines.\nAction: To read more of the file, you can use the 'start_line' and 'end_line' parameters in a subsequent 'read_file' call. For example, to read the next section of the file, use start_line: 524.\n\n--- FILE CONTENT (truncated) ---\n1. Launch on `VehicleListScreen`.\n2. Tap the \"Add Vehicle\" FAB.\n3. Verify the upgrade info sheet is displayed (assert by `testTag = \"paywall-info-sheet\"`).\n4. Verify \"Upgrade to Premium\" and \"Maybe later\" buttons are present.\n5. Tap \"Maybe later\".\n6. Verify the sheet dismisses and `VehicleListScreen` is still showing 1 vehicle.\n\n**Verifies:** Free-tier gate triggers at `vehicleCount >= 1 && !isPremium`; `PaywallInfoSheet` renders on the real Compose pipeline; dismiss restores screen state.\n\n---\n\n### S19: Read-Only Status Card and Paywall Enforcement (Paywall)\n\n**Precondition:** Vault configured; 2 vehicles (\"Car A\", \"Car B\") pre-populated via test helper; `setBillingOverride(FORCE_FREE)`.\n\n**Flow:**\n1. Launch on `VehicleListScreen`.\n2. Verify the read-only status card is displayed (`testTag = \"paywall-status-card\"`); verify the \"Learn more\" action is present in the card.\n3. Attempt swipe-to-dismiss on a vehicle row; verify the vehicle is still present (swipe-delete disabled in read-only).\n4. Tap \"Car A\" → `VehicleDetailScreen`. Verify the \"Add Record\" FAB is hidden.\n5. Tap the Settings icon → `SettingsScreen`. Verify the \"Premium\" section shows the \"Upgrade to Premium\" entry.\n\n**Verifies:** `PaywallStatusCard` correctly signals read-only state on `VehicleListScreen`; `AppGate` propagates `isReadOnly` through all ViewModels; write-action UI is suppressed.\n\n---\n\n### S20: Read-Only Lifts on Premium Activation (Paywall)\n\n**Precondition:** Vault configured; 2 vehicles present; `setBillingOverride(FORCE_FREE)`.\n\n**Flow:**\n1. Launch on `VehicleListScreen`. Verify the read-only status card is displayed.\n2. From the test process, call `setBillingOverride(FORCE_PREMIUM)`.\n3. `waitUntil` the status card is no longer displayed (timeout 5s — involves DataStore write + Flow combine + recomposition).\n4. Tap the \"Add Vehicle\" FAB and verify it navigates to `AddEditVehicleScreen` (no upgrade sheet).\n5. Press back; tap \"Car A\" → `VehicleDetailScreen`. Verify the banner is absent and the \"Add Record\" FAB is visible.\n\n**Verifies:** Reactive lift of read-only mode end-to-end through `BillingManagerImpl` → `AppGate` → ViewModels → UI on the real Compose pipeline. Catches stale-state bugs in the gate's `combine` pipeline that unit tests can miss when scope/dispatcher choices differ from production.\n\n---\n\n### S17: Back Navigation\n\n**Precondition:** App has navigated several screens deep (e.g., VehicleList -> VehicleDetail -> RecordDetail).\n\n**Flow:**\n1. Starting from `RecordDetailScreen`\n2. Press the system back button or toolbar back arrow\n3. Verify `VehicleDetailScreen` is displayed\n4. Press back again\n5. Verify `VehicleListScreen` is displayed\n\n**Verifies:** Back-stack is maintained correctly through the real Activity lifecycle, navigation transitions render without crashes.\n\n---\n\n## Scenarios Explicitly Out of Scope\n\nThe following are **not** covered by smoke tests and are handled by Robolectric integration tests or manual testing:\n\n| Area | Reason |\n|---|---|\n| **Form validation errors** (empty required fields, VIN duplicates, character limits) | Covered exhaustively by ViewModel unit tests and screen integration tests |\n| **Image viewer gestures** (pinch-zoom, pan, double-tap) | Gesture simulation on GMD is unreliable; covered by screenshot tests and Robolectric. Navigation to ImageViewer is covered by S08 |\n| **Real Google Play Billing purchase sheet** | Requires the device to be signed into a license-tester account with the app on a Play track. Covered manually per `docs/plan-paywall.md` Phase 8 Track B. |\n| **Odometer unit / currency switching** | Settings persistence covered by unit tests; display rendering covered by Robolectric |\n| **Data folder relocation** | SAF folder picker interaction; high complexity for smoke tests |\n| **Edge cases** (200+ records, 20 vehicles, large attachments) | Performance testing, not smoke testing |\n| **Error states** (corrupted JSON, missing files, permission denied) | Defensive behavior covered by unit tests per ADR-006 |\n\n---\n\n## Phased Implementation Plan\n\n### Step 1: Test Infrastructure & Helpers\n\n**Goal:** Set up the `SmokeTest.kt` file with all necessary test infrastructure before writing scenario logic.\n\n1. **Rename entry point:** Rename `MainActivityTest.kt` to `SmokeTest.kt` (or create `SmokeTest.kt` and delete `MainActivityTest.kt`). Update the class name to `SmokeTest`. Keep the `@RunWith(AndroidJUnit4::class)` runner and `createAndroidComposeRule()` rule.\n2. **Add UiAutomator dependency:** Add `androidx.test.uiautomator:uiautomator` to `androidTestImplementation` in `app/build.gradle.kts` for system UI interactions (SAF picker, camera).\n3. **Add test ordering:** Annotate the class with `@FixMethodOrder(MethodSorters.NAME_ASCENDING)` so scenarios execute in `test_01_`, `test_02_` order.\n4. **Vault setup helper:** Create a helper method `configureVaultForTesting()` that creates a temporary vault directory on the emulator's internal storage, persists its URI through the app's DataStore-backed preferences (for example via `AppPreferencesStore.setVaultUri(...)`, or by editing the DataStore directly), and then calls `ServiceBookApplication.applyVaultUri()` to bypass the SAF picker for most tests.\n5. **Navigation helpers:** Create small helper methods for common navigation steps — `navigateToVehicleDetail(name)`, `navigateToAddRecord()`, `navigateToSettings()` — using Compose test semantics (`onNodeWithText`, `performClick`). These reduce duplication across scenarios.\n6. **Attachment fixtures:** Create `app/src/androidTest/assets/` directory with a small test image (`test_receipt.jpg`, ~10KB) and a test PDF (`test_invoice.pdf`, ~5KB). Add a `@Before` or helper that copies these into a picker-visible location for SAF simulation — on API 33, prefer inserting them into Downloads via `MediaStore` (with unique test-specific names) rather than using an app-private directory such as `context.cacheDir`, which DocumentsUI generally cannot browse.\n7. **Cleanup rule:** Add a `@After` (or `TestWatcher` rule) that wipes the temporary vault directory and deletes any test fixture entries created in the picker-visible location (for example, the Downloads `MediaStore` items created for the run) to prevent cross-run pollution.\n8. **Billing override helper (added with paywall feature, see `docs/plan-paywall.md` Phase 7.5):** Add `setBillingOverride(mode: DebugBillingOverride.Mode)` that calls `(application as ServiceBookApplication).debugBillingOverride.setMode(mode)` from the test process. Add an `@After` rule that resets the override to `NONE` to prevent state leaking across scenarios. Add `addVehicleForTesting(nickname)` helper that persists a vehicle directly via `VehicleRepository`, so paywall scenarios can start from a known multi-vehicle state without depending on S03 having run.\n9. **Local verification:** Run `./gradlew pixel2api33DebugAndroidTest` to confirm the empty test class compiles, deploys, and the infrastructure helpers initialize without errors.\n\n**Deliverables:** `SmokeTest.kt` with infrastructure, helpers, fixtures, and cleanup — no scenario logic yet. `MainActivityTest.kt` removed.\n\n---\n\n### Step 2: Core Happy Path (S01–S06)\n\n**Goal:** Implement the primary user journey — from first launch through viewing a record — as a single multi-step flow test.\n\n1. **S01 — First launch:** In `test_01_coreHappyPath_flow()`, start from first launch and assert \"Welcome\" text and \"Choose Folder\" button are displayed.\n2. **S02 — Vault configuration:** In the same flow test, use the hybrid approach: verify the picker intent fires on button tap, then call `configureVaultForTesting()` programmatically and navigate to `VehicleListScreen`. Assert empty state is shown.\n3. **S03 — Add vehicle:** Continue in the same flow test. Navigate to AddEditVehicle, fill the nickname field, save, and assert the vehicle appears in the list.\n4. **S04 — View vehicle detail:** Continue in the same flow test. Tap the vehicle card and assert the detail screen renders with the vehicle name and empty records state.\n5. **S05 — Add service record:** Continue in the same flow test. Navigate to AddEditRecord, fill odometer/category/performer, save, and assert the record appears in the vehicle detail list.\n6. **S06 — View record detail:** Finish the same flow test by tapping the record and asserting all fields render on `RecordDetailScreen`.\n7. **Local verification:** Run the full suite on GMD. Ensure the single S01–S06 flow test passes reliably end-to-end. Address any timing issues with `waitUntil` / `waitForIdle`.\n\n**Deliverables:** 1 passing multi-step flow test covering the core CRUD happy path from S01 through S06. This is the minimum viable smoke suite.\n\n---\n\n### Step 3: Attachment Scenarios (S07–S10)\n\n**Goal:** Add attachment lifecycle coverage — the primary reason these tests must run on a real device.\n\n1. **S07 — Add attachment via file picker:** Implement `test_07_addAttachmentViaFilePicker()`. Start by creating or editing a record. Use UiAutomator to interact with the system file picker (or use `ActivityResultLauncher` test doubles via `ActivityScenario` if UiAutomator is too fragile). Select the fixture image from the known device path. Assert the attachment thumbnail appears. Save the record.\n2. **S08 — View attachment on record detail:** Implement `test_08_viewAttachmentOnRecordDetail()`. Navigate to RecordDetailScreen for the record with attachments. Assert the \"Attachments (1)\" header and attachment card are displayed. Tap the image attachment, assert `ImageViewerScreen` is displayed, press back.\n3. **S09 — Remove attachment:** Implement `test_09_removeAttachment()`. Edit the record, tap \"Remove\" on the attachment, confirm the dialog, assert the attachment is gone, save. Navigate to RecordDetail and verify the Attachments section is absent or empty.\n4. **S10 — Take photo attachment:** Implement `test_10_takePhotoAttachment()`. On AddEditRecordScreen, tap \"Take photo\", interact with the emulated camera (or stub the camera result). Assert the photo thumbnail appears. Save.\n5. **Flakiness assessment:** Run the attachment tests 5 times locally. If S10 (camera) fails >20% of the time, downgrade it to a \"picker launch verification only\" test and document the limitation.\n6. **Local verification:** Full suite (S01–S10) passes on GMD.\n\n**Deliverables:** 4 attachment tests. Camera test may be marked as best-effort depending on emulator reliability.\n\n---\n\n### Step 4: Navigation & Settings (S11–S12, S17)\n\n**Goal:** Cover settings screens and back-stack navigation.\n\n1. **S11 — Navigate to Settings:** Implement `test_11_navigateToSettings()`. From VehicleListScreen, tap Settings, assert the settings screen renders with odometer unit, currency, and vault location.\n2. **S12 — Navigate to Categories:** Implement `test_12_navigateToCategories()`. From SettingsScreen, tap Categories, assert predefined categories are listed, navigate back.\n3. **S17 — Back navigation:** Implement `test_17_backNavigation()`. Navigate VehicleList → VehicleDetail → RecordDetail, then press back twice, asserting each intermediate screen.\n4. **Local verification:** Full suite passes.\n\n**Deliverables:** 3 tests covering settings/categories screens and back-stack integrity.\n\n---\n\n### Step 5: Edit & Delete Flows (S13–S16)\n\n**Goal:** Cover mutation and deletion paths with confirmation guards.\n\n1. **S13 — Edit vehicle:** Implement `test_13_editVehicle()`. Navigate to VehicleDetail, tap Edit, verify pre-filled nickname, change it, save, assert updated name on VehicleDetail.\n2. **S14 — Edit service record:** Implement `test_14_editServiceRecord()`. Navigate to RecordDetail, tap Edit, verify pre-filled odometer, change it, save, assert updated value on RecordDetail.\n3. **S15 — Delete service record:** Implement `test_15_deleteServiceRecord()`. On RecordDetail, tap Delete, confirm dialog, assert return to VehicleDetail with record removed.\n4. **S16 — Delete vehicle:** Implement `test_16_deleteVehicle()`. Initiate vehicle deletion, confirm dialog, assert return to VehicleList with vehicle removed and empty state shown.\n5. **Ordering consideration:** S15 and S16 are destructive. If using shared state (approach 1), these must run last. If the test data from S03/S05 was already consumed by prior tests, create fresh test data in a setup helper before running the delete scenarios.\n6. **Local verification:** Full suite (S01–S17) passes on GMD.\n\n**Deliverables:** 4 tests covering edit and delete flows. Complete smoke suite of 17 scenarios.\n\n---\n\n### Step 6: Paywall Scenarios (S18–S20)\n\n**Goal:** Cover paywall gate, read-only banner propagation, and reactive lift on premium activation. Depends on `DebugBillingOverride` shipping with the paywall feature (`docs/plan-paywall.md` Phase 3.3) — sequence this step **after** that work.\n\n1. **S18 — Upgrade dialog on free tier:** Implement `test_18_upgradeDialogOnFreeTier()`. Setup: 1 vehicle + `FORCE_FREE`. Tap FAB; assert dialog renders; dismiss; assert dialog gone.\n2. **S19 — Read-only banner across screens:** Implement `test_19_readOnlyBannerAcrossScreens()`. Setup: 2 vehicles + `FORCE_FREE`. Visit VehicleList → VehicleDetail → Settings; assert banner present on each; assert write actions suppressed.\n3. **S20 — Read-only lifts on premium activation:** Implement `test_20_readOnlyLiftsOnPremium()`. Setup: 2 vehicles + `FORCE_FREE`. Switch override to `FORCE_PREMIUM` from the test process; `waitUntil` banner disappears (timeout 5s); assert FAB now navigates to AddVehicle.\n4. **Override reset:** Confirm `@After` resets override to `NONE` so subsequent tests are unaffected.\n5. **Local verification:** Full suite (S01–S20) passes on GMD; verify paywall tests do not contaminate non-paywall scenarios.\n\n**Deliverables:** 3 paywall smoke tests. Suite total: 20 scenarios.\n\n---\n\n### Step 7: Coverage Baseline & Final Verification\n\n**Goal:** Generate the coverage baseline and validate the complete suite.\n\n1. **Full local run:** Run `./gradlew pixel2api33DebugAndroidTest` and confirm all 17 tests pass.\n2. **Timing check:** Verify the entire suite completes within the 2-minute budget. If it exceeds the budget, identify the slowest scenarios and consider merging related scenarios (e.g., combine S11+S12 into a single settings navigation test).\n3. **Generate coverage:** Run `./gradlew generateGmdCoverage` to produce the updated `gmd_smoke.ec` baseline.\n4. **Coverage report:** Run `./gradlew verifyWithCoverage` and review the report. Confirm the smoke tests contribute meaningful coverage to areas not covered by Robolectric (Activity lifecycle, SAF integration, real filesystem paths).\n5. **Commit:** Commit `SmokeTest.kt`, attachment fixtures, dependency changes, and the updated `gmd_smoke.ec` to Git LFS.\n6. **Update documentation:** Update `TESTING_SETUP.md` to document the smoke test suite, its scenarios, and how to run/maintain it.\n\n**Deliverables:** Final smoke suite committed, coverage baseline refreshed, documentation updated. Phase 3 complete.\n\n---\n\n## Implementation Notes\n\n### Shared State vs. Independent Tests\n\nTwo viable strategies:\n\n1. **Single flow test** — implement the core happy-path journey as one `@Test` method with multiple sequential steps. With `createAndroidComposeRule()`, this is the only approach that truly keeps a single Activity launch for the entire chain. Fastest for the end-to-end path, but failures stop the remainder of that flow.\n\n2. **Independent tests with setup helpers** — each `@Test` launches a fresh `MainActivity` and uses helper methods to reach the required precondition state. If tests are ordered, they may still share persisted state such as vault/files on disk, but they do **not** share one in-memory Activity instance. Slower (multiple launches) but more robust.\n\n**Recommendation:** Use approach 1 for the core happy-path chain (S01-S06) as a single multi-step flow test, and approach 2 for isolated scenarios (S07-S17) that should remain independent of prior in-memory UI state.\n\n### SAF Picker Interaction\n\nThe SAF folder picker (S02) is a system UI outside the app's Compose tree. Options:\n- **UiAutomator** (`UiDevice.findObject()`) to interact with the system picker — most realistic but fragile across API levels\n- **Pre-configure vault** in a `@Before` / test fixture by writing the vault URI via DataStore (for example, `AppPreferencesStore.setVaultUri`, backed by `Context.dataStore`) and then calling `ServiceBookApplication.applyVaultUri()` — skips SAF but tests everything after it\n- **Hybrid** — one dedicated test for SAF launch verification, remaining tests use the DataStore-preconfigured vault\n\n**Recommendation:** Use the hybrid approach. One test verifies the picker launches; all others pre-configure the vault via DataStore.\n\n### Attachment Test Fixtures\n\nAttachment scenarios (S07-S10) require files to be available on the emulator. Options:\n- **Pre-push via Gradle:** Add a task that runs `adb push` to place a small test image (e.g., `test_receipt.jpg`) and a test PDF into the emulator's Downloads folder before tests execute\n- **Bundle in `androidTest/assets`:** Place fixture files in `app/src/androidTest/assets/` and copy them to a known device path in `@Before`\n- **Programmatic creation:** Generate a minimal bitmap or text file in `@Before` and write it to the device filesystem\n\n**Recommendation:** Bundle fixtures in `androidTest/assets` — this is self-contained, doesn't require `adb`, and works reliably on GMD.\n\n### Test Data Cleanup\n\nTests that create vehicles/records should clean up after themselves or use a temporary vault directory that is wiped in `@After`. This prevents test pollution across runs and ensures `generateGmdCoverage` produces consistent results.\n" }