W4: the seven settings edits, and three tests that did not bite until they did #164

Merged
JMR-dev merged 1 commits from test/viewmodel-setters into main 2026-08-29 15:57:15 +00:00
JMR-dev commented 2026-08-29 15:46:50 +00:00 (Migrated from github.com)

Closes #157. Independent of the rest of wave 2 (#153).

The gap

setPreset was covered. The six beside it — setContainer, setVideoCodec, setAudioCodec, applySuggestion, setQuality, setEnginePreference — and cancel() had no coverage at all. That asymmetry is the tell: they are reachable from the JVM suite by exactly the route setPreset already takes, and nothing had asked.

What is asserted

Not "the setter sets something". Each copies into a nested OutputSpec, so the failure worth catching is a setter that writes the right value into the wrong field, or that rebuilds the spec and quietly discards the other two. Every test asserts the field it changed and that the rest survived.

Three tests did not bite, and the reason is the interesting part

The first mutation pass came back with two green:

mutation first run
setContainer rebuilds the spec from OutputFormat.MP4_H265.spec GREEN
setQuality also resets enginePreference to AUTO GREEN

Both for one mistake of mine. ConversionSettings starts at MP4_H265.spec, QualityTier.FAST and EnginePreference.AUTO — and I asserted "the rest survived" against values still sitting at those defaults. So a mutation that RESET a neighbouring field to its default was indistinguishable from one that left it alone. The tests were checking a value, not a behaviour.

Fixed by moving each neighbour off its default before the call under test. A third test had the same latent hazard — it asserted quality == FAST — and was corrected with the others rather than left to fail later on someone else's change.

This is exactly the shape CLAUDE.md warns about:

A review of this codebase ran 46 mutations against a 257-test suite and 9 were vacuous — five of them passing the whole suite over a completely unguarded code path. Green is not evidence.

Second time in this wave the mutation pass has earned its keep.

Mutations, after the fix

mutation red
setContainer rebuilds from a preset 1 test
setQuality resets the engine preference 1 test
setEnginePreference resets the quality 2 tests
applySuggestion resets quality 1 test
setVideoCodec writes nothing 1 test
setAudioCodec writes nothing 2 tests
setEnginePreference writes nothing 2 tests
cancel() dereferences a null activeWorkId 1 test

cancel()'s null side matters on its own: a user can reach Cancel through a state that has already finished, and taking the app down for it would be worse than doing nothing.

516 → 525 tests, 87.7% → 88.0% line, 70.4% → 70.5% branch. Gate green: assembleDebug, testDebugUnitTest, compileDebugAndroidTestKotlin, ktlintCheck, detekt, lintDebug.

Note the modest branch movement, in contrast to W1 and W2: these are one-line assignments with no decisions in them, so line coverage is what moves. That is the expected shape here, and the inverse of what #153's parent records for decision code.

Closes #157. Independent of the rest of wave 2 (#153). ## The gap `setPreset` was covered. The six beside it — `setContainer`, `setVideoCodec`, `setAudioCodec`, `applySuggestion`, `setQuality`, `setEnginePreference` — and `cancel()` had **no coverage at all**. That asymmetry is the tell: they are reachable from the JVM suite by exactly the route `setPreset` already takes, and nothing had asked. ## What is asserted Not "the setter sets something". Each copies into a nested `OutputSpec`, so the failure worth catching is **a setter that writes the right value into the wrong field, or that rebuilds the spec and quietly discards the other two.** Every test asserts the field it changed *and* that the rest survived. ## Three tests did not bite, and the reason is the interesting part The first mutation pass came back with two green: | mutation | first run | |---|---| | `setContainer` rebuilds the spec from `OutputFormat.MP4_H265.spec` | **GREEN** | | `setQuality` also resets `enginePreference` to `AUTO` | **GREEN** | Both for one mistake of mine. `ConversionSettings` starts at `MP4_H265.spec`, `QualityTier.FAST` and `EnginePreference.AUTO` — and I asserted "the rest survived" against values still sitting at those defaults. **So a mutation that RESET a neighbouring field to its default was indistinguishable from one that left it alone.** The tests were checking a value, not a behaviour. Fixed by moving each neighbour off its default before the call under test. A third test had the same latent hazard — it asserted `quality == FAST` — and was corrected with the others rather than left to fail later on someone else's change. This is exactly the shape `CLAUDE.md` warns about: > A review of this codebase ran 46 mutations against a 257-test suite and **9 were vacuous** — five of them passing the whole suite over a completely unguarded code path. Green is not evidence. Second time in this wave the mutation pass has earned its keep. ## Mutations, after the fix | mutation | red | |---|---| | `setContainer` rebuilds from a preset | 1 test | | `setQuality` resets the engine preference | 1 test | | `setEnginePreference` resets the quality | 2 tests | | `applySuggestion` resets quality | 1 test | | `setVideoCodec` writes nothing | 1 test | | `setAudioCodec` writes nothing | 2 tests | | `setEnginePreference` writes nothing | 2 tests | | `cancel()` dereferences a null `activeWorkId` | 1 test | `cancel()`'s null side matters on its own: a user can reach Cancel through a state that has already finished, and taking the app down for it would be worse than doing nothing. **516 → 525 tests, 87.7% → 88.0% line, 70.4% → 70.5% branch.** Gate green: `assembleDebug`, `testDebugUnitTest`, `compileDebugAndroidTestKotlin`, `ktlintCheck`, `detekt`, `lintDebug`. Note the modest branch movement, in contrast to W1 and W2: these are one-line assignments with no decisions in them, so line coverage is what moves. That is the expected shape here, and the inverse of what #153's parent records for decision code.
Sign in to join this conversation.