Skip to content

test: add automated E2E coverage for spring-boot-ai-classroom Expo/React Native client #2605

Description

@jfarcand

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:

    1. Add accessibilityLabel props to ~5 key elements in App.tsx (room buttons, text input, send button, streaming message view, offline banner).
    2. 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.
    3. 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

  • samples/spring-boot-ai-classroom/expo-client/App.tsx exposes accessibilityLabel on room buttons, chat input, send button, streamed message, offline banner
  • samples/spring-boot-ai-classroom/expo-client/.maestro/classroom.yaml drives a full happy-path flow and asserts streamed text arrives
  • .github/workflows/e2e.yml (or a new rn-e2e.yml) runs the flow on macos-latest behind a label, green
  • README updated with manual maestro test invocation for developers
  • At least one run against real backend proves the Metro symlink dedup still holds with current atmosphere.js HEAD

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions