R38.8 — E2E: the SAF picker round-trip, and rotation against a real Bundle #64
Closed
opened 2026-08-23 23:07:02 +00:00 by JMR-dev
·
2 comments
No Branch/Tag Specified
main
fix/102-picker-back-press-overshoot
fix/268-saf-picker-determinism
feat/ogg-vorbis-libvorbis
feat/expedited-conversion-work
fix/gate-cache-in-worktrees
chore/gate-runs-shellcheck
test/publish-delete-arm-real-provider
docs/e8-instrumented-coverage
docs/api37-carrier-count-drift
docs/e7-second-constraint
test/publish-to-a-real-saf-destination
fix/api37-report-match-line
fix/api37-task-snapshot-crash
test/join-failure-message-on-device
docs/e2e-read-findings-e7
test/cancelling-a-running-export
test/reattach-to-a-running-job
test/content-uri-reaches-ffmpeg
fix/launcher-wiring-waits-for-the-pick
fix/cancel-tests-need-a-slower-encode
test/cancelling-a-running-join
test/cancelling-a-running-session
test/notification-cancel-action
test/ffmpeg-progress-is-observed
test/fallback-asserts-the-path
test/flac-and-opus-assert-their-format
docs/e2e-read-findings
docs/wave4-coverage-numbers
fix/injectable-startup-sweep-scope
test/session-outcome-seam
test/launcher-callback-identity
test/theme-follows-system-dark
test/audio-drop-arm
fix/rotation-waits-for-recreation
fix/convert-guards-on-ready
test/retry-save-mime
test/hardware-progress-reaches-workmanager
test/ffprobe-mapping-seam
test/device-codec-enumeration-seam
test/unknown-container-row
test/null-message-fallbacks
test/cancel-reaches-workmanager
docs/coverage-wave3-recovery
test/concat-engine-seam
docs/coverage-wave3
test/mediaprobe-merge-seam
test/adaptive-shell-wiring
test/aac-audio-args
test/notification-progress-text
test/media3-muxer-guard
test/hardware-fallback-and-cancellation
test/unprobeable-join-clip
test/one-branch-outcomes
test/foreground-type-regimes
fix/bound-wedge-diagnostics
docs/coverage-wave2
test/screen-wiring
test/viewmodel-setters
test/join-state-mapping
test/conversion-state-mapping
test/dedupe-user-messages
fix/restore-stack-merges
test/refused-jobs
test/concatworker-failure-arms
test/container-capabilities-audio
test/readspec-enum-fallbacks
test/outputpublisher-seams
test/mediaprobe-track-seam
test/outputpublisher-partial-branches
test/fake-provider-scaffolding
docs/coverage-read-findings
chore/gitignore-kotlin
test/bound-the-hangs
docs/coverage-remeasure
ci/baseline-counter-precision
fix/invalid-suggestion-chip
ci/wedged-leg-report
fix/reattachment-overwrites-pick
test/theme-live-branches
fix/failed-save-retry
fix/empty-composition-crash
ci/advisory-failure-report
docs/seven-run-counts
test/release-permission-guard
ci/build-workflow-permissions
docs/api37-point-release
docs/benchmark-populate-path
fix/dead-assertion-probe-test
ci/actionlint
test/device-codecs-encode-consequence
fix/sdkmanager-pipefail
docs/readme-restart-claim
fix/saf-picker-root-discovery
fix/probe-dispatcher-seam
test/media3engine-mime-tables
test/mediaprobe-pure-helpers
fix/codec-vocabulary-drift
docs/robolectric-rationale-correction
docs/api37-advisory-counts
test/r38-8-saf-e2e
test/r38-7-join-states
test/r38-6-conversion-states
test/r38-5-state-seam
fix/jacoco-robolectric-coverage
docs/instrumented-tests-correction
test/r38-2-filecard
test/r38-4-advanced-picker
test/r38-3-pickers
tools/file-issue-script
tools/api-37-emulator
fix/review-app-gaps
docs/review-corrections
No results found.
Labels
Clear labels
above-cut
accessibility
backlog
bug
confirmed
documentation
duplicate
enhancement
good first issue
help wanted
invalid
plausible
question
sev:high
sev:low
sev:medium
wontfix
Worked autonomously overnight: local, JVM-verifiable, no product decision
Barrier affecting people with disabilities
Held for manual review: product/UX call, CI/workflow, hardware, or unverifiable here
Something isn't working
Reviewer demonstrated the defect
Improvements or additions to documentation
This issue or pull request already exists
New feature or request
Good for newcomers
Extra attention is needed
This doesn't seem right
Reviewer could not fully demonstrate it; treat as unproven
Further information is requested
High severity
Low severity
Medium severity
This will not be worked on
Milestone
No items
No Milestone
Projects
Clear projects
No projects
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: JMR-dev/LibreMediaConverter#64
Reference in New Issue
Block a user
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.
Child 8 of 8 decomposing #52. Depends on R38.1 for the tag table only — independent of R38.5.
Why the picker and the rotation are one ticket
A rotation test on its own has no mutation of its own to name. Its bite would be
rememberSaveable->remember, whichAppRootRestorationTestalready catches on the JVM. A childwhose acceptance criterion duplicates an existing test's is exactly the vacuous-test failure mode
this decomposition exists to prevent.
Driving the picker first fixes that. It leaves the app in
Readywith a real input and a realActivity-scoped ViewModel, and rotating from there is a bite nothing in the repo has:
AppRootRestorationTestinjects a stubcontentlambda specifically to avoid standing up eitherViewModel, and
StateRestorationTestersaves to an in-memory map rather than aBundle.Scope
androidx.test.uiautomatortogradle/libs.versions.toml. Itsandroidx.group means theexisting
componentSelectionprerelease guard inapp/build.gradle.ktsalready covers it — no newpinning argument needed.
OpenDocument/OpenMultipleDocuments/CreateDocumentthrough the real system picker andassert the round trip lands in the ViewModel. Nothing in either source set exercises SAF as a
picker today; the only SAF coverage is the publish side, in
OutputPublisherPublishTestagainsthand-written
ContentProviderfakes.Bundle, real Activity recreation, realViewModel.
Two environment facts that decide where this runs
androidTest. No test there currently usesActivityScenario,createAndroidComposeRuleor Espresso — thoughcompose-ui-test-junit4andespresso-coreare both already on the classpath andui-test-manifestis ondebugImplementation,so the wiring exists.
be unlocked from a shell, so the picker cannot be driven there. That is precisely why this gap
survived.
tools/local-emulator/run-e2e.shcovers API 33-36 locally.May need
@FailsOnEmulatorApi37— the marker is onmainas of1779f20(PR #56), in the rootorg.libremediaconverterpackage, and CI reads it twice (notAnnotationfor the gating leg,annotationfor the advisory one).Acceptance — the mutation
Change the MIME filter at
ConverterScreen.kt:116fromarrayOf("*/*")to something the fixturedoes not match -> the picker test goes red.
Second bite: make the input ViewModel composition-scoped rather than Activity-scoped -> the rotation
test goes red while every JVM test stays green. That divergence is this ticket's whole reason to
exist; if it does not hold, say so on the ticket rather than shipping a test that proves nothing.
Size
~250 lines plus a build change. The one child that could reasonably be split again if it runs long.
Traps (shared across the R38 children)
androidx.compose.ui.test.junit4.v2.createComposeRule(v2) butandroidx.compose.ui.test.junit4.StateRestorationTester(non-v2). Every tutorial shows thenon-v2 rule. Copy both lines from
AppRootRestorationTest.@UnstableApipropagates. Both screens carry it, so a test class touching them needs it or thebuild fails on
UnsafeOptInUsageError.@RunWith(RobolectricTestRunner::class)and nothing else. There is no@Configin this repo;sdk=36is set once inapp/src/test/resources/robolectric.propertiesbecause Robolectric 4.16.1has no
android-alljar for API 37.DetailRowrenders"$label: $value"as one node (ConverterScreen.kt:486).onNodeWithText("Container")will not match — use the full string orsubstring = true.…U+2026,—U+2014,×U+00D7,·U+00B7. Copy them out of the source.org.junit.Assert.*, statically imported one per symbol. Nokotlin.test, noTruth, no mockk — test doubles are hand-written subclasses.
the mutation check reviewable by someone else.
Done means
./gradlew :app:assembleDebug :app:testDebugUnitTest :app:compileDebugAndroidTestKotlin :app:ktlintCheck :app:detekt :app:lintDebug --continuethen 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 onmain.Parent: #52
Flagging one thing before anyone starts this: the second mutation above is asserted, not verified.
"Make the input ViewModel composition-scoped rather than Activity-scoped" is not a one-line revert.
viewModel()inside a Composable resolves throughLocalViewModelStoreOwner, which is the Activity — changing that scope may not be expressible without restructuring the call site. And if the ViewModel is recreated on rotation,ConversionStatereturns toIdleand the assertion becomes trivially different rather than a genuine mutation.That matters because it is this ticket's whole stated reason for existing: the rotation half was folded in here precisely so it would have a bite that
AppRootRestorationTestdoes not already catch on the JVM.Establish that the mutation is constructible before writing the test. If it is not, the honest outcomes are either a different mutation that only a real
Bundle+ real Activity recreation can catch, or dropping the rotation half and keeping this ticket to the SAF round-trip alone — which has its own solid bite (the MIME filter atConverterScreen.kt:116). Do not ship a rotation test that proves nothing; say which way it went on this ticket.Nothing here blocks the SAF half, and nothing blocks #57–#63.
Finding from #60 (PR #71) that lands directly on this ticket's premise.
What crosses the
Bundleis not aBoolean.rememberSaveable { mutableStateOf(false) }passes nostateSaver, soautoSaversaves theMutableStateobject itself — the registry hands back anandroidx.compose.runtime.ParcelableSnapshotMutableState. It survives a rotation only becausemutableStateOfon Android returns aParcelableimplementation; the plain-JVM one is not.This ticket is scoped as "rotation against a real
Bundle, notStateRestorationTester's in-memory map". Whoever picks it up will otherwise write that test assuming a primitive goes through and be confused when the round trip produces a state holder. It is pinned inAdvancedPanelSavedStateTest, which drives a realSaveableStateRegistrywithcanBeSaved = { true }— deliberately, so a dropped entry cannot masquerade as therememberregression — plus a realBundle/Parcelround trip. Read that test before writing this one; the pattern is already there.Also relevant to the second mutation on this ticket, which I flagged earlier as asserted-not-verified: the same test class shows the shape of a saved-state assertion that genuinely bites (
expected:<1> but was:<0>whenrememberSaveableis downgraded toremember). If the ViewModel-scoping mutation turns out not to be constructible, a registry-level assertion of this kind may be the honest substitute — but only if it asserts something the JVM test cannot, which is the bar this ticket has to clear.