Skip to content

Fix wasmJsBrowserTest and jsBrowserTest - #46

Open
LZRS wants to merge 12 commits into
ohs-foundation:mainfrom
LZRS:fix/wasm-js-browser-tests
Open

Fix wasmJsBrowserTest and jsBrowserTest#46
LZRS wants to merge 12 commits into
ohs-foundation:mainfrom
LZRS:fix/wasm-js-browser-tests

Conversation

@LZRS

@LZRS LZRS commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes wasmJsBrowserTest and jsBrowserTest, 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/)

  • Compose Resources: .cvr files fail to load under Karma in this multi-module setup (compose-multiplatform#4654), throwing MissingResourceException on any stringResource() call.
  • Skiko (js target only): nothing loaded skiko.wasm or wired its exports onto window, so tests threw ReferenceError on first canvas touch.
  • Mocha timeout: default 2000ms is too tight for a Compose/Skia render under full-suite load, causing intermittent failures. Raised to 10000ms.
  • V8 stack size: 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 a runCatching) instead of crashing. Raised via --stack-size=8192.
  • (Their generated setup files are now .gitignored.)

App bugs fixed

  • Dead Dispatchers.Main override: 15 view-factory/component files pinned rememberCoroutineScope() to Dispatchers.Main — a leftover from before the Kotlin Multiplatform migration. Harmless everywhere except wasmJs tests, where it hid the launched coroutine from runComposeUiTest's synchronization and timed out click-driven updates. Removed.
  • LazyColumn key 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 / .value instead of implicit toString().

Test-only changes

  • Made the text-input debounce duration test-injectable — runComposeUiTest's virtual clock never resumes a real delay() on non-Android targets (compose-multiplatform#4805).
  • Installed a test Dispatchers.Main for UIQuestionnaireTest (its pagination/repeated-group logic runs through viewModelScope, which resolves to the real platform Main dispatcher).
  • Switched one Slider assertion to tolerance-based (Material3's current isn't bit-reproducible across Kotlin targets).
  • Removed a stray _root_ide_package_ IDE artifact from QuantityViewFactoryTest.kt.

CI

Bumped JVM/Kotlin-daemon stack sizes to fix a StackOverflowError during IR lowering of kotlin-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's androidHostTest source set, a known gap in current Compose Multiplatform/AGP tooling. Tracked as follow-up; may switch CI to androidDeviceTest instead.

Test plan

  • ./gradlew :datacapture:wasmJsBrowserTest — full pass, multiple reruns
  • ./gradlew :datacapture:jsBrowserTest — full pass, multiple reruns
  • CI run on this PR (Wasm/JS jobs; Android job still expected red pending follow-up)

LZRS added 7 commits July 23, 2026 07:36
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.
@LZRS
LZRS requested a review from a team July 23, 2026 05:14
@LZRS
LZRS marked this pull request as draft July 23, 2026 05:14
@LZRS LZRS linked an issue Jul 23, 2026 that may be closed by this pull request
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
LZRS force-pushed the fix/wasm-js-browser-tests branch from 628a3a4 to c128930 Compare July 23, 2026 05:23
LZRS added 4 commits July 25, 2026 04:27
…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
LZRS marked this pull request as ready for review August 4, 2026 13:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix failing CI tests for js/wasm/android in the main branch

1 participant