R38.6 — Every ConversionState renders its own affordances #62

Closed
opened 2026-08-23 23:07:00 +00:00 by JMR-dev · 0 comments
JMR-dev commented 2026-08-23 23:07:00 +00:00 (Migrated from github.com)

Child 6 of 8 decomposing #52. Depends on R38.5 (needs ConverterScreenContent).

Scope

Seven states against ConverterScreenContent. No ViewModel, no WorkManager, no async — the state is
a constructor argument.

state what the test pins
Idle "Pick a file to convert." + "Choose file", and no FileCard
Ready all five pickers present; Convert enabled iff validation.isValid (:149)
Converting progress reads percent; Cancel present
Waiting the Paused paragraph; Cancel present
Converted Save + Start over; route chip present iff routeReason.isNotBlank() (:196)
Saved "Convert another", and no FileCard
Failed the message renders, in the error colour; Start over

The two "no FileCard" rows are assertions, not filler: Saved and Failed are the only states
that drop it, and nothing currently stops someone re-adding it.

Do not "fix" this while you are in here

requestNotifications' callback at :86 calls convert() on both grant and deny. The KDoc at
:80-83 says that is deliberate. Pin the documented intent — a test asserting "Convert only runs
when permitted" would assert the opposite of it. If that intent looks wrong, it is its own ticket,
not a change smuggled inside a test PR.

Also do not chase ConverterScreen.kt:128 (is Idle -> Unit in the nested when). The outer when
at :103 already peeled Idle off, so that line is permanently unreachable and jacoco will
report it missed forever.

Acceptance — the mutation

Change :149 enabled = validation.isValid to enabled = true -> the invalid-spec test goes red.

Second bite: delete the Cancel button from the Waiting branch -> red.

Size

~180 lines, one new test file. Independent of R38.7.


Traps (shared across the R38 children)

  • The import pair is mixed: androidx.compose.ui.test.junit4.v2.createComposeRule (v2) but
    androidx.compose.ui.test.junit4.StateRestorationTester (non-v2). Every tutorial shows the
    non-v2 rule. Copy both lines from AppRootRestorationTest.
  • @UnstableApi propagates. Both screens carry it, so a test class touching them needs it or the
    build fails on UnsafeOptInUsageError.
  • @RunWith(RobolectricTestRunner::class) and nothing else. There is no @Config in this repo;
    sdk=36 is set once in app/src/test/resources/robolectric.properties because Robolectric 4.16.1
    has no android-all jar for API 37.
  • DetailRow renders "$label: $value" as one node (ConverterScreen.kt:486).
    onNodeWithText("Container") will not match — use the full string or substring = true.
  • Typographic characters retyped as ASCII fail silently: … U+2026, — U+2014, × U+00D7,
    · U+00B7. Copy them out of the source.
  • Assertions are org.junit.Assert.*, statically imported one per symbol. No kotlin.test, no
    Truth, no mockk — test doubles are hand-written subclasses.
  • Open the test class with a KDoc naming the defect it bites on. House style, and it is what makes
    the mutation check reviewable by someone else.

Done means

./gradlew :app:assembleDebug :app:testDebugUnitTest :app:compileDebugAndroidTestKotlin :app:ktlintCheck :app:detekt :app:lintDebug --continue

then the mutation above: revert the named line, watch the named test go red, restore, and quote what
the failure said. Per CLAUDE.md, own branch, own PR, never on main.

Parent: #52

_Child 6 of 8 decomposing #52. **Depends on R38.5** (needs `ConverterScreenContent`)._ ### Scope Seven states against `ConverterScreenContent`. No ViewModel, no WorkManager, no async — the state is a constructor argument. | state | what the test pins | |---|---| | `Idle` | `"Pick a file to convert."` + `"Choose file"`, and **no** `FileCard` | | `Ready` | all five pickers present; **Convert enabled iff `validation.isValid`** (`:149`) | | `Converting` | progress reads `percent`; Cancel present | | `Waiting` | the Paused paragraph; Cancel present | | `Converted` | Save + Start over; **route chip present iff `routeReason.isNotBlank()`** (`:196`) | | `Saved` | `"Convert another"`, and **no** `FileCard` | | `Failed` | the message renders, in the error colour; Start over | The two "no `FileCard`" rows are assertions, not filler: `Saved` and `Failed` are the only states that drop it, and nothing currently stops someone re-adding it. ### Do not "fix" this while you are in here `requestNotifications`' callback at `:86` calls `convert()` on **both grant and deny**. The KDoc at `:80-83` says that is deliberate. **Pin the documented intent** — a test asserting "Convert only runs when permitted" would assert the opposite of it. If that intent looks wrong, it is its own ticket, not a change smuggled inside a test PR. Also do not chase `ConverterScreen.kt:128` (`is Idle -> Unit` in the nested `when`). The outer `when` at `:103` already peeled `Idle` off, so that line is **permanently unreachable** and jacoco will report it missed forever. ### Acceptance — the mutation Change `:149` `enabled = validation.isValid` to `enabled = true` -> the invalid-spec test goes red. Second bite: delete the `Cancel` button from the `Waiting` branch -> red. ### Size ~180 lines, one new test file. Independent of R38.7. --- ### Traps (shared across the R38 children) - **The import pair is mixed**: `androidx.compose.ui.test.junit4.v2.createComposeRule` (**v2**) but `androidx.compose.ui.test.junit4.StateRestorationTester` (**non-v2**). Every tutorial shows the non-v2 rule. Copy both lines from `AppRootRestorationTest`. - **`@UnstableApi` propagates.** Both screens carry it, so a test class touching them needs it or the build fails on `UnsafeOptInUsageError`. - **`@RunWith(RobolectricTestRunner::class)` and nothing else.** There is no `@Config` in this repo; `sdk=36` is set once in `app/src/test/resources/robolectric.properties` because Robolectric 4.16.1 has no `android-all` jar for API 37. - **`DetailRow` renders `"$label: $value"` as one node** (`ConverterScreen.kt:486`). `onNodeWithText("Container")` will not match — use the full string or `substring = true`. - **Typographic characters retyped as ASCII fail silently**: `…` U+2026, `—` U+2014, `×` U+00D7, `·` U+00B7. Copy them out of the source. - **Assertions are `org.junit.Assert.*`, statically imported one per symbol.** No `kotlin.test`, no Truth, no mockk — test doubles are hand-written subclasses. - **Open the test class with a KDoc naming the defect it bites on.** House style, and it is what makes the mutation check reviewable by someone else. ### Done means `./gradlew :app:assembleDebug :app:testDebugUnitTest :app:compileDebugAndroidTestKotlin :app:ktlintCheck :app:detekt :app:lintDebug --continue` then the mutation above: revert the named line, watch the named test go red, restore, and quote what the failure said. Per `CLAUDE.md`, own branch, own PR, never on `main`. Parent: #52
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: JMR-dev/LibreMediaConverter#62