R38.2 — FileCard, DetailRow and the four formatters, including the "Size unknown" case that prompted #52 #58

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

Child 2 of 8 decomposing #52. Depends on R38.1 (needs internal visibility + the tag table).

Why this one matters most

This is the case that prompted #52. While fixing D5 a stream reported that ConverterScreen's
"Size unknown" had no test and that writing one would be new infrastructure — which was already
false, because D6's Robolectric harness had landed on its own base. InputFile.sizeBytes is nullable
and ConverterScreen.kt:429 renders "Size unknown" for it. Still untested.

Scope

Compose (Robolectric + createComposeRule), against FileCard and DetailRow:

  • All four InputKind branches, :441-476:
    • UNPARSEABLE :442 — the single explanatory line, nothing else
    • IMAGE :447 — Type: Image plus the conditional size row
    • AUDIO_ONLY :452 — asserts "No video track"
    • VIDEO :459 — asserts the W×H composition and the audio row
  • probe == null -> the early return@Column at :437: renders "Reading…", and no divider
    and no kind rows
  • sizeBytes == null -> "Size unknown" :429, which renders regardless of probe state — assert
    both with a probe and without one, because that independence is the actual contract

Pure JVM (no Compose rule, no Robolectric): describe :362, formatDuration :492,
formatBytes :499, EnginePreference.label :406.

Acceptance — the mutation

Change :429 to render "0 B" instead of "Size unknown" -> red.

Second bite: delete the return@Column at :437 -> the "Reading…" test goes red.

Size

~200 lines across two files (one Compose, one pure). Independent of R38.3 and R38.4.


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 2 of 8 decomposing #52. **Depends on R38.1** (needs `internal` visibility + the tag table)._ ### Why this one matters most **This is the case that prompted #52.** While fixing D5 a stream reported that `ConverterScreen`'s `"Size unknown"` had no test and that writing one would be new infrastructure — which was already false, because D6's Robolectric harness had landed on its own base. `InputFile.sizeBytes` is nullable and `ConverterScreen.kt:429` renders `"Size unknown"` for it. Still untested. ### Scope **Compose (Robolectric + `createComposeRule`), against `FileCard` and `DetailRow`:** - All four `InputKind` branches, `:441-476`: - `UNPARSEABLE` :442 — the single explanatory line, nothing else - `IMAGE` :447 — `Type: Image` plus the conditional size row - `AUDIO_ONLY` :452 — asserts `"No video track"` - `VIDEO` :459 — asserts the `W×H` composition and the audio row - **`probe == null` -> the early `return@Column` at :437**: renders `"Reading…"`, and **no** divider and **no** kind rows - **`sizeBytes == null` -> `"Size unknown"` :429**, which renders *regardless* of probe state — assert both with a probe and without one, because that independence is the actual contract **Pure JVM (no Compose rule, no Robolectric):** `describe` :362, `formatDuration` :492, `formatBytes` :499, `EnginePreference.label` :406. ### Acceptance — the mutation Change `:429` to render `"0 B"` instead of `"Size unknown"` -> red. Second bite: delete the `return@Column` at `:437` -> the `"Reading…"` test goes red. ### Size ~200 lines across two files (one Compose, one pure). Independent of R38.3 and R38.4. --- ### 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#58