Skip to content

fix(iOS): integration test flakiness#152

Merged
ddfreiling merged 2 commits into
mainfrom
agents/fix-ios-integration-test-flakiness
Jun 10, 2026
Merged

fix(iOS): integration test flakiness#152
ddfreiling merged 2 commits into
mainfrom
agents/fix-ios-integration-test-flakiness

Conversation

@ddfreiling

@ddfreiling ddfreiling commented Jun 10, 2026

Copy link
Copy Markdown
Member

Problem

The iOS integration tests were intermittently failing on CI (e.g. goToLocator round-trips back to a saved position — "No initial textLocator emitted", and the TTS test with a TimeoutException).

Root cause (iOS-specific): the FlutterEventChannel onListen handshake is asynchronous and can complete after the freshly-mounted EPUB platform view has already fired its first locationDidChange. With a nil eventSink at that moment, the first text-locator / reader-status event was silently dropped — so a test (or app) subscribing around mount time never saw the initial locator.

Android and Web are structurally immune: Android only emits the first locator after onPageLoaded (well after the Dart handshake) and dispatches via a coroutine, and Web uses in-process StreamControllers with no channel handshake.

Fix

  • Native buffering (opt-in)EventStreamHandler buffers the latest event when no Dart subscriber is attached and flushes it on onListen. Only text-locator and reader-status opt in; timebased-state and error must not buffer (they carry one-shot / sentinel values such as the .none state emitted on close).
  • Clear buffers on closeclosePublication() clears the buffered locator/status so a stale value from a closed publication is never replayed to the next one.
  • Reset Dart streams on close — the lazy stream fields are nulled in closePublication(), so the next getter access re-establishes the native onListen handshake before the next book's widget mounts.
  • onReaderStatusChanged is now a broadcast stream — supports multiple subscribers (previously single-subscription).
  • Documented the buffering contract on EventStreamHandler: buffer state-like streams, never event-like / sentinel streams; clear buffered streams on close.

Testing

All 22 integration tests pass on iOS, including the previously-flaky goToLocator round-trips, initialLocator restores, and opens EPUB and enables TTS.

No Android or Web changes.

ddfreiling and others added 2 commits June 10, 2026 02:43
The text-locator and reader-status channels buffer their latest event so
the first emission isn't dropped when the EPUB platform view fires before
the EventChannel onListen handshake completes. Clear those buffers in
closePublication() so a stale locator/status from a closed publication is
never replayed to the next one, and reset the lazy Dart stream fields so
the next getter access re-establishes the native onListen handshake before
the next book's widget mounts.

Also document the per-channel buffering contract on EventStreamHandler
(buffer state-like streams, never event-like/sentinel streams) and make
onReaderStatusChanged a broadcast stream so multiple subscribers are
supported. Android and Web are structurally immune to this race and need
no change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ddfreiling ddfreiling merged commit 7e5ea68 into main Jun 10, 2026
13 checks passed
ddfreiling added a commit that referenced this pull request Jun 10, 2026
Brings the Spotlight decoration feature (#142), iOS event buffering (#152),
and the page_width-120 reformat from main into the web-TS refactor branch.

Conflict resolutions:
- Spotlight ported into the refactored web modules: body-dimming + CSS Custom
  Highlight restore lives in decorations/decorationOverrides.ts; group
  activation lives in decorations/DecorationController.ts. The thin
  ReadiumReader facade delegates to DecorationController (no direct spotlight
  imports).
- FlutterTTSNavigator / FlutterEpubNavigator keep the shared
  navigators/locatorEnrich.ts (flattenToc / enrichWithTocHref) instead of
  main's per-module duplicates.
- Navigators drop the focus-stealing window.focus() in positionChanged;
  FlutterWebPubNavigator gains the same 200ms trailing-edge text-locator
  debounce as EPUB.
- Dart conflicts (reader_decoration isActive, method-channel formatting,
  example spotlight segment, integration-test list-stable waits) take main.
- Web bundle regenerated from web/src and copied into example/web.

Stale web/_scripts cleanup:
- bin/install: install web deps from the plugin root where package.json lives.
- Removed the orphaned readiumReader.js.LICENSE.txt (a Terser artifact the dev
  build no longer emits; all its module paths were stale).
- Updated stale path/name references in a few source comments.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant