The three #52 test PRs landed 56 new tests and coverage moved 29.8% → 29.7%. That looked like the tests being worthless. It was the measurement.
Robolectric loads classes through its own sandbox classloader, and those classes arrive with no source location. JaCoCo skips no-location classes unless told otherwise, and nothing here told it — so not one Robolectric test has ever contributed coverage in this repo, and Robolectric is what exercises the framework edge: both workers, the publisher, both ViewModels, every Compose screen.
Measured — same commit, same 335 tests, same 0 failures
before
after
LINE
29.7% (652/2194)
69.2% (1519/2194)
BRANCH
29.8% (425/1424)
53.2% (758/1424)
OutputPublisher
0.0%
97.5%
ConversionViewModel
0.0%
85.4%
MainActivityKt
6.8%
86.4%
ConverterScreenKt
6.6%
62.8%
The discriminator, so this isn't cargo cult
Inside ConverterScreenKt, describe is the one non-@Composable and is exercised by a plain JVM test. It reported 8/8 covered while every @Composable in the same class reported 0 — including ones whose mutations demonstrably failed the build when reverted. Across files the split is exactly Robolectric-vs-not: StagingSweep (pure) 100%; OutputPublisher, ConversionViewModel, FailureOutcome (Robolectric) 0%.
excludes = listOf("jdk.internal.*") is not decoration — without it JaCoCo walks JDK-internal classes Robolectric has no location for either and the test JVM dies rather than reporting a number.
CLAUDE.md was wrong twice over
The figure was an artifact, and the explanation attached to it described a cause that doesn't exist: that coverage fell as the suite grew from 11 test files to 43 because the denominator outran the numerator on framework-edge code "the JVM cannot reach". The JVM reaches that code fine. The new tests were disproportionately Robolectric, so each added denominator and no numerator — the measurement was punishing precisely the tests that were hardest to write, and the conclusion drawn was that writing them hadn't helped.
Mutation
Run both ways on this branch: remove the block and jacocoTestReport collapses to 29.7% / 29.8%; restore it and it returns to 69.2% / 53.2%.
Still no coverage gate, and a floor still needs a settled baseline — this one just moved 39 points in one build change.
Closes #75.
The three #52 test PRs landed 56 new tests and coverage moved **29.8% → 29.7%**. That looked like the tests being worthless. It was the measurement.
Robolectric loads classes through its own sandbox classloader, and those classes arrive with **no source location**. JaCoCo skips no-location classes unless told otherwise, and nothing here told it — so **not one Robolectric test has ever contributed coverage in this repo**, and Robolectric is what exercises the framework edge: both workers, the publisher, both ViewModels, every Compose screen.
## Measured — same commit, same 335 tests, same 0 failures
| | before | after |
|---|---|---|
| **LINE** | 29.7% (652/2194) | **69.2% (1519/2194)** |
| **BRANCH** | 29.8% (425/1424) | **53.2% (758/1424)** |
| `OutputPublisher` | 0.0% | **97.5%** |
| `ConversionViewModel` | 0.0% | **85.4%** |
| `MainActivityKt` | 6.8% | **86.4%** |
| `ConverterScreenKt` | 6.6% | **62.8%** |
## The discriminator, so this isn't cargo cult
Inside `ConverterScreenKt`, `describe` is the one **non**-`@Composable` and is exercised by a plain JVM test. It reported **8/8 covered** while every `@Composable` in the same class reported **0** — including ones whose mutations demonstrably failed the build when reverted. Across files the split is exactly Robolectric-vs-not: `StagingSweep` (pure) 100%; `OutputPublisher`, `ConversionViewModel`, `FailureOutcome` (Robolectric) 0%.
`excludes = listOf("jdk.internal.*")` is not decoration — without it JaCoCo walks JDK-internal classes Robolectric has no location for either and the test JVM dies rather than reporting a number.
## CLAUDE.md was wrong twice over
The figure was an artifact, **and the explanation attached to it described a cause that doesn't exist**: that coverage fell as the suite grew from 11 test files to 43 because the denominator outran the numerator on framework-edge code *"the JVM cannot reach"*. The JVM reaches that code fine. The new tests were disproportionately Robolectric, so each added denominator and no numerator — the measurement was punishing precisely the tests that were hardest to write, and the conclusion drawn was that writing them hadn't helped.
## Mutation
Run both ways on this branch: remove the block and `jacocoTestReport` collapses to 29.7% / 29.8%; restore it and it returns to 69.2% / 53.2%.
Gate green: `assembleDebug + testDebugUnitTest + compileDebugAndroidTestKotlin + ktlintCheck + detekt + lintDebug --continue`.
Still no coverage gate, and a floor still needs a settled baseline — this one just moved 39 points in one build change.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #75.
The three #52 test PRs landed 56 new tests and coverage moved 29.8% → 29.7%. That looked like the tests being worthless. It was the measurement.
Robolectric loads classes through its own sandbox classloader, and those classes arrive with no source location. JaCoCo skips no-location classes unless told otherwise, and nothing here told it — so not one Robolectric test has ever contributed coverage in this repo, and Robolectric is what exercises the framework edge: both workers, the publisher, both ViewModels, every Compose screen.
Measured — same commit, same 335 tests, same 0 failures
OutputPublisherConversionViewModelMainActivityKtConverterScreenKtThe discriminator, so this isn't cargo cult
Inside
ConverterScreenKt,describeis the one non-@Composableand is exercised by a plain JVM test. It reported 8/8 covered while every@Composablein the same class reported 0 — including ones whose mutations demonstrably failed the build when reverted. Across files the split is exactly Robolectric-vs-not:StagingSweep(pure) 100%;OutputPublisher,ConversionViewModel,FailureOutcome(Robolectric) 0%.excludes = listOf("jdk.internal.*")is not decoration — without it JaCoCo walks JDK-internal classes Robolectric has no location for either and the test JVM dies rather than reporting a number.CLAUDE.md was wrong twice over
The figure was an artifact, and the explanation attached to it described a cause that doesn't exist: that coverage fell as the suite grew from 11 test files to 43 because the denominator outran the numerator on framework-edge code "the JVM cannot reach". The JVM reaches that code fine. The new tests were disproportionately Robolectric, so each added denominator and no numerator — the measurement was punishing precisely the tests that were hardest to write, and the conclusion drawn was that writing them hadn't helped.
Mutation
Run both ways on this branch: remove the block and
jacocoTestReportcollapses to 29.7% / 29.8%; restore it and it returns to 69.2% / 53.2%.Gate green:
assembleDebug + testDebugUnitTest + compileDebugAndroidTestKotlin + ktlintCheck + detekt + lintDebug --continue.Still no coverage gate, and a floor still needs a settled baseline — this one just moved 39 points in one build change.