R38.7 — Every JoinState renders its own affordances #63

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

Child 7 of 8 decomposing #52. Depends on R38.5 (needs JoinScreenContent).

Scope

Seven states against JoinScreenContent, same shape as R38.6:

state what the test pins
Idle "Pick two or more files to join, in the order you want them." + "Choose files"
Ready one FileRow per input, in order; "Join N files" reads the real count
Joining "Joining N files…"; the progress bar is indeterminate, unlike the converter's
Waiting the Paused paragraph
Joined Save + Start over, and both ConcatStrategy branches at :137-144
Saved "Join more"
Failed the message renders, in the error colour; Start over

Ready ordering is worth an explicit assertion — a join is the one flow where input order is
user-visible output, and nothing else in the suite pins it.

Do not chase JoinScreen.kt:94 (is Idle -> Unit in the nested when) — the outer when at :69
already peeled Idle off, so it is permanently unreachable.

Acceptance — the mutation

Replace Text(s.message) in the Failed branch with an empty string -> red.

Second bite: force strategy to STREAM_COPY unconditionally -> the REENCODE copy test goes red.

Size

~140 lines. Independent of R38.6 — different file, different composable.


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 7 of 8 decomposing #52. **Depends on R38.5** (needs `JoinScreenContent`)._ ### Scope Seven states against `JoinScreenContent`, same shape as R38.6: | state | what the test pins | |---|---| | `Idle` | `"Pick two or more files to join, in the order you want them."` + `"Choose files"` | | `Ready` | one `FileRow` per input, in order; `"Join N files"` reads the real count | | `Joining` | `"Joining N files…"`; the progress bar is **indeterminate**, unlike the converter's | | `Waiting` | the Paused paragraph | | `Joined` | Save + Start over, and **both** `ConcatStrategy` branches at `:137-144` | | `Saved` | `"Join more"` | | `Failed` | the message renders, in the error colour; Start over | `Ready` ordering is worth an explicit assertion — a join is the one flow where input order is user-visible output, and nothing else in the suite pins it. Do not chase `JoinScreen.kt:94` (`is Idle -> Unit` in the nested `when`) — the outer `when` at `:69` already peeled `Idle` off, so it is permanently unreachable. ### Acceptance — the mutation Replace `Text(s.message)` in the `Failed` branch with an empty string -> red. Second bite: force `strategy` to `STREAM_COPY` unconditionally -> the `REENCODE` copy test goes red. ### Size ~140 lines. Independent of R38.6 — different file, different composable. --- ### 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#63