Gap
samples/spring-boot-ai-classroom/expo-client/ is the only sample with zero automated test coverage. It's a full Expo/React Native app that exercises the atmosphere.js/react-native subpath (setupReactNative, AtmosphereProvider, useStreamingRN) against a real Atmosphere backend — every other sample has at least one Playwright/JUnit/contract test.
What's covered today (and what isn't)
| Layer |
Coverage |
File |
| Java backend WebSocket broadcast (multi-client) |
✅ |
modules/integration-tests/e2e/ai-classroom.spec.ts |
| Java backend per-room routing |
✅ |
modules/integration-tests/e2e/spring-boot-ai-classroom.spec.ts |
atmosphere.js RN shim (Node-side unit tests, mocked) |
✅ |
atmosphere.js/tests/unit/react-native/{platform,hooks,event-source-polyfill,websocket-url}.test.ts |
| Real RN runtime end-to-end |
❌ |
none |
Metro symlink bundling of file:../../../atmosphere.js |
❌ |
none |
| AppState-aware suspend/resume on background |
❌ |
none |
NetInfo offline banner + reconnect |
❌ |
none |
| EventSource polyfill behavior on RN 0.83 Hermes |
❌ |
none |
Nothing today catches a regression in those paths. The app is verified manually via bunx expo start + tap-testing on a device.
Options considered
| # |
Approach |
Real RN runtime? |
CI-able? |
Setup cost |
| A |
iOS simulator via MCP ios-simulator tools (one-shot) |
✅ |
❌ (manual) |
~0 |
| B |
Detox |
✅ |
✅ |
High (~½ day: detox init, .detoxrc.js, dev build, Xcode scheme) |
| C |
Maestro (YAML flows) |
✅ |
✅ (needs macos-latest runner) |
Low (accessibilityLabel props + ~40-line YAML) |
| D |
@testing-library/react-native with mocked transport |
❌ (jest-dom) |
✅ |
Medium |
| E |
Expo Web export + Playwright |
❌ (web shim, not RN) |
✅ |
Low |
Recommendation
Adopt A + C together:
-
A (manual one-shot) now — validate the app exactly once via the same MCP-driven simulator pattern used for the other 20 samples today. Proves the whole bundle actually works with the current atmosphere.js HEAD before we spend effort on CI infra. Estimated 3–5 min with warm Xcode cache, 8–12 min cold.
-
C (Maestro) for CI — Maestro is lightweight, and the Atmosphere dev machines already have it on PATH (~/.maestro/bin per CLAUDE.md). Deliverables:
- Add
accessibilityLabel props to ~5 key elements in App.tsx (room buttons, text input, send button, streaming message view, offline banner).
- Create
samples/spring-boot-ai-classroom/expo-client/.maestro/classroom.yaml with a flow: launchApp → tapOn Math → inputText → assertVisible on streamed text → tapOn back → re-join Science.
- Add a conditional job to
.github/workflows/e2e.yml that runs on macos-latest behind a rn-e2e PR label (or nightly cron), building the app via bunx expo prebuild && bunx expo run:ios and invoking maestro test.
Rejected options and why
- B (Detox): too much setup weight for a single sample. Makes sense when you have 5+ RN apps sharing the harness.
- D (react-native-testing-library): mocks the transport, so it tests components not integration. The classroom demo exists to prove
atmosphere.js/react-native works end-to-end against a real backend — a mocked test does not prove that.
- E (Expo Web): the web build of RN polyfills
NetInfo, AppState, and fetch streams — exactly the things the demo is meant to exercise in their real RN forms. Testing the web bundle re-tests what Playwright already covers via the Vite frontend at samples/spring-boot-ai-classroom/frontend/.
Known risks / prior art
- Metro symlink gotcha (per project memory):
file:../../../atmosphere.js + tsup's require() wrapper inside try/catch broke Metro static resolution in a prior session. The workaround already in place (metro.config.js watchFolders + extraNodeModules Proxy + blockList for React dedup + unstable_enablePackageExports: true) must be verified still applied before any CI build.
- RN 0.83 / Expo SDK 55 / React 19: newest toolchain. CI job should pin the Xcode version and CocoaPods version to avoid drift.
bun preferred over npm for this sample (per project memory). CI job must use bun install not npm ci.
- Backend needs an LLM. CI job should start the backend with
llm.mode=fake or LLM_MODE=fake so the classroom runs the built-in FakeLlmClient (deterministic canned responses) rather than calling a real provider.
Acceptance criteria
Out of scope
- Android emulator coverage (follow-up — iOS first)
- Visual regression / screenshot diffing
- Performance benchmarks of the streaming pipeline on-device
Context
Surfaced during the sample-by-sample validation of 4.0.36-SNAPSHOT on 2026-04-10, where 20/20 Spring Boot and legacy samples were validated end-to-end via Chrome DevTools MCP but the Expo classroom client was skipped for lack of tooling parity.
Gap
samples/spring-boot-ai-classroom/expo-client/is the only sample with zero automated test coverage. It's a full Expo/React Native app that exercises theatmosphere.js/react-nativesubpath (setupReactNative,AtmosphereProvider,useStreamingRN) against a real Atmosphere backend — every other sample has at least one Playwright/JUnit/contract test.What's covered today (and what isn't)
modules/integration-tests/e2e/ai-classroom.spec.tsmodules/integration-tests/e2e/spring-boot-ai-classroom.spec.tsatmosphere.jsRN shim (Node-side unit tests, mocked)atmosphere.js/tests/unit/react-native/{platform,hooks,event-source-polyfill,websocket-url}.test.tsfile:../../../atmosphere.jsNetInfooffline banner + reconnectNothing today catches a regression in those paths. The app is verified manually via
bunx expo start+ tap-testing on a device.Options considered
ios-simulatortools (one-shot).detoxrc.js, dev build, Xcode scheme)macos-latestrunner)accessibilityLabelprops + ~40-line YAML)@testing-library/react-nativewith mocked transportRecommendation
Adopt A + C together:
A (manual one-shot) now — validate the app exactly once via the same MCP-driven simulator pattern used for the other 20 samples today. Proves the whole bundle actually works with the current
atmosphere.jsHEAD before we spend effort on CI infra. Estimated 3–5 min with warm Xcode cache, 8–12 min cold.C (Maestro) for CI — Maestro is lightweight, and the Atmosphere dev machines already have it on PATH (
~/.maestro/binperCLAUDE.md). Deliverables:accessibilityLabelprops to ~5 key elements inApp.tsx(room buttons, text input, send button, streaming message view, offline banner).samples/spring-boot-ai-classroom/expo-client/.maestro/classroom.yamlwith a flow: launchApp → tapOnMath→ inputText → assertVisible on streamed text → tapOn back → re-joinScience..github/workflows/e2e.ymlthat runs onmacos-latestbehind arn-e2ePR label (or nightly cron), building the app viabunx expo prebuild && bunx expo run:iosand invokingmaestro test.Rejected options and why
atmosphere.js/react-nativeworks end-to-end against a real backend — a mocked test does not prove that.NetInfo,AppState, and fetch streams — exactly the things the demo is meant to exercise in their real RN forms. Testing the web bundle re-tests what Playwright already covers via the Vite frontend atsamples/spring-boot-ai-classroom/frontend/.Known risks / prior art
file:../../../atmosphere.js+ tsup'srequire()wrapper insidetry/catchbroke Metro static resolution in a prior session. The workaround already in place (metro.config.jswatchFolders+extraNodeModulesProxy +blockListfor React dedup +unstable_enablePackageExports: true) must be verified still applied before any CI build.bunpreferred overnpmfor this sample (per project memory). CI job must usebun installnotnpm ci.llm.mode=fakeorLLM_MODE=fakeso the classroom runs the built-inFakeLlmClient(deterministic canned responses) rather than calling a real provider.Acceptance criteria
samples/spring-boot-ai-classroom/expo-client/App.tsxexposesaccessibilityLabelon room buttons, chat input, send button, streamed message, offline bannersamples/spring-boot-ai-classroom/expo-client/.maestro/classroom.yamldrives a full happy-path flow and asserts streamed text arrives.github/workflows/e2e.yml(or a newrn-e2e.yml) runs the flow onmacos-latestbehind a label, greenmaestro testinvocation for developersMetrosymlink dedup still holds with currentatmosphere.jsHEADOut of scope
Context
Surfaced during the sample-by-sample validation of 4.0.36-SNAPSHOT on 2026-04-10, where 20/20 Spring Boot and legacy samples were validated end-to-end via Chrome DevTools MCP but the Expo classroom client was skipped for lack of tooling parity.