Fix wasmJsBrowserTest and jsBrowserTest - #46
Open
LZRS wants to merge 12 commits into
Open
Conversation
wasmJsBrowserTest and jsBrowserTest were both badly broken: - Compose Resources' .cvr files fail to fetch under Karma in this multi-module setup, throwing MissingResourceException for any getString()/stringResource() call (JetBrains/compose-multiplatform#4654). Serve and proxy the resource directory via karma.config.d. - On the js (non-Wasm) target specifically, nothing ever loads skiko.wasm or wires its exports onto window, so every Compose UI test threw ReferenceError the moment it touched the canvas. Load js-reexport-symbols.mjs and gate Mocha's run on its readiness. - Karma/Mocha's default 2000ms per-test timeout is too tight for a Compose/Skia render under full-suite load; bump it to 10s.
rememberCoroutineScope() already dispatches compatibly with the
composition on every target; the explicit { Dispatchers.Main }
override (leftover from before the Kotlin Multiplatform migration)
was inert everywhere except wasmJs tests, where it made the launched
coroutine invisible to runComposeUiTest's synchronization: work
scheduled onto it never ran, so click-driven state updates timed out
or never resolved.
On Kotlin/JS, string-templating a Coding (or its nested FHIR String fields) can collapse distinct values to the same "[object Object]" text, so every option/repeated-group ended up sharing one key and LazyColumn threw IllegalArgumentException on any answer options dialog or repeated group with more than one item. - OptionDialogSelect: derive the key from the stable list index instead of Coding.toString(). - QuestionnaireViewModel: build RepeatedGroupHeader/AddButton ids from linkId.value (the raw String) instead of the linkId wrapper object.
runComposeUiTest's virtual clock never resumes a real delay() on non-Android targets (JetBrains/compose-multiplatform#4805), so the 500ms Flow.debounce() driving text input handling never fired and every affected test timed out waiting on it. Turn the debounce constant into a var and zero it out for the duration of each affected test, restoring it afterward, instead of changing the production default.
QuestionnaireViewModel drives pagination and repeated-group mutation through the AndroidX viewModelScope, which resolves to the real platform Dispatchers.Main. On wasmJs that dispatcher isn't hooked into runComposeUiTest's synchronization, so page navigation and repeated-group clicks appeared to leave the UI one step stale and assertions failed against pre-click state. Install Dispatchers.Unconfined as Main for the duration of these tests so viewModelScope work runs eagerly and is observed by the test.
Compose Material3 Slider's fraction/position round-trip for `current` isn't bit-reproducible across Kotlin targets (e.g. an input of 10 renders as 10.000000149011612 on Kotlin/JS but exactly 10 on JVM/Wasm). Range and step count are still asserted exactly.
compileTestDevelopmentExecutableKotlinJs has been crashing with a StackOverflowError since CI was added, while lowering IR generated by the kotlin-fhirpath dependency's large FHIR R5 model file. It doesn't reproduce locally even with matching heap/worker settings, pointing at a thread stack size difference between this CI runner and local machines. Add -Xss4m; the exact value is unverified against CI itself since the crash never reproduced locally to confirm against.
Renamed HANDLE_INPUT_DEBOUNCE_TIME to handleInputDebounceTime: ktlint's standard:property-naming rule requires camelCase once it's a mutable var rather than a const val. Also removes the stray _root_ide_package_ IDE artifact and applies spotlessApply's formatting/unused-import cleanup across files touched in this branch.
LZRS
force-pushed
the
fix/wasm-js-browser-tests
branch
from
July 23, 2026 05:23
628a3a4 to
c128930
Compare
…s on the no-daemon JVM Two prior attempts (-Xss4m, then -Xss32m on the Gradle client JVM via GRADLE_OPTS) both failed against the same StackOverflowError in compileTestDevelopmentExecutableKotlinJs. That crash happens entirely inside the compiler's own IR-to-text dumper while it tries to report an internal error in kotlin-fhirpath's generated FHIR R5 MoreSealedInterfaces.kt, so it's plausible the compiling thread simply isn't the one whose stack -Xss was resizing under Gradle's Worker API in --no-daemon mode. Re-enabling the Kotlin daemon runs the actual compilation in its own forked JVM, whose main thread reliably inherits kotlin.daemon.jvmargs at startup - a more direct lever than hoping -Xss on the Gradle client JVM propagates to whichever worker thread Kotlin's Gradle plugin uses internally. Unverified against the real CI StackOverflowError (doesn't reproduce locally), but confirmed to compile cleanly and not regress anything else.
…al failures Now that compileTestDevelopmentExecutableKotlinJs succeeds (previous commit), 10 jsBrowserTest failures surfaced in EnablementEvaluatorTest, TemplateExtractionEngineTest, and UIQuestionnaireTest - all assertion failures that looked like wrong/empty/default values. The captured system-out reveals the real cause: FhirPathService.evaluate() silently swallows exceptions and returns emptyList(), and the swallowed exception is "RangeError: Maximum call stack size exceeded" - a genuine runtime JS stack overflow while executing kotlin-fhirpath's generated sealed-interface dispatch (MoreSealedInterfaces.kt and friends), not a Kotlin/compiler issue this time. This doesn't reproduce locally because V8's default call-stack size varies by Chrome/Chromium build; CI's installed Chrome apparently has less headroom than this machine's Chromium. Add a custom Karma launcher passing --js-flags=--stack-size=8192 to raise it explicitly instead of depending on whatever's installed. Confirmed not to regress jsBrowserTest or wasmJsBrowserTest locally; unverified against the actual CI RangeError since it doesn't reproduce here.
wasmJsBrowserTest/jsBrowserTest write compose-resources-testing-setup.js and skiko-await-setup.mjs into this tracked source directory at Karma startup; ignore them so local test runs don't leave stray untracked files.
LZRS
marked this pull request as ready for review
August 4, 2026 13:05
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes
wasmJsBrowserTestandjsBrowserTest, both broken in CI and unable to even launch locally. Also fixes two real app bugs that surfaced along the way.Karma harness (new scripts under
datacapture/karma.config.d/).cvrfiles fail to load under Karma in this multi-module setup (compose-multiplatform#4654), throwingMissingResourceExceptionon anystringResource()call.jstarget only): nothing loadedskiko.wasmor wired its exports ontowindow, so tests threwReferenceErroron first canvas touch.kotlin-fhirpath's generated sealed-interface dispatch compiles to deeply nested JS that can exceed V8's default call-stack limit, silently producing wrong/empty FHIRPath results (swallowed by arunCatching) instead of crashing. Raised via--stack-size=8192..gitignored.)App bugs fixed
Dispatchers.Mainoverride: 15 view-factory/component files pinnedrememberCoroutineScope()toDispatchers.Main— a leftover from before the Kotlin Multiplatform migration. Harmless everywhere except wasmJs tests, where it hid the launched coroutine fromrunComposeUiTest's synchronization and timed out click-driven updates. Removed.LazyColumnkey collisions on Kotlin/JS: string-templating a FHIR model object (e.g.Coding) collapses to"[object Object]", so every row in the answer-options dialog / repeated groups got the same key. Fixed by keying on list index /.valueinstead of implicittoString().Test-only changes
runComposeUiTest's virtual clock never resumes a realdelay()on non-Android targets (compose-multiplatform#4805).Dispatchers.MainforUIQuestionnaireTest(its pagination/repeated-group logic runs throughviewModelScope, which resolves to the real platform Main dispatcher).currentisn't bit-reproducible across Kotlin targets)._root_ide_package_IDE artifact fromQuantityViewFactoryTest.kt.CI
Bumped JVM/Kotlin-daemon stack sizes to fix a
StackOverflowErrorduring IR lowering ofkotlin-fhirpath's generated FHIR R5 model. Doesn't reproduce locally, so exact values are unverified against CI itself.Not included:
testAndroidHostTest(349/425 failing) is a separate issue — Robolectric's shadow environment never activates for this project'sandroidHostTestsource set, a known gap in current Compose Multiplatform/AGP tooling. Tracked as follow-up; may switch CI toandroidDeviceTestinstead.Test plan
./gradlew :datacapture:wasmJsBrowserTest— full pass, multiple reruns./gradlew :datacapture:jsBrowserTest— full pass, multiple reruns