Skip to content

Refactor BaseStoryScreenshotTest to use single activity#76

Draft
EmilioBejasa wants to merge 21 commits into
mainfrom
refactor-base-story-screenshot-test
Draft

Refactor BaseStoryScreenshotTest to use single activity#76
EmilioBejasa wants to merge 21 commits into
mainfrom
refactor-base-story-screenshot-test

Conversation

@EmilioBejasa
Copy link
Copy Markdown
Collaborator

Summary

  • Moves loadStory to an instance method on StorybookRegistry, removing the companion object hack
  • Adds loadStory event emitter and wires it through BaseStoryRendererActivity
  • Refactors BaseStoryScreenshotTest to use a single activity instead of launching a new one per story

Test plan

  • CI passes (screenshot tests + unit tests)

🤖 Generated with Claude Code

EmilioBejasa and others added 21 commits February 25, 2026 12:43
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
loadStory doesn't need to be static — it's called from within the
module instance where reactApplicationContext is already available.
Removes the reactCtx field and init block that were only needed to
bridge the instance context into the companion object.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a native-to-JS event emitter so the test runner can signal
which story to render without relaunching the activity.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a NativeEventEmitter subscription to the loadStory event emitted
by StorybookRegistry. Currently just logs the event — wiring up
re-rendering is the next step.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Instead of storing ReactApplicationContext statically in StorybookRegistry
companion object, expose loadStory() on BaseStoryRendererActivity which
retrieves the module via the React instance manager. This removes the
init block and reactCtx static field entirely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Required by BaseStoryRendererActivity.loadStory() which retrieves
the module via ReactContext.getNativeModule().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Instead of launching a new ActivityScenario per story, launch once
and loop through stories using StorybookRegistry.loadStory() events.
ActivityScenario is now opened and closed once per test run.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Uses scenario.onActivity { it.loadStory(storyName) } to trigger story
switching, removing the need for a static loadStory on StorybookRegistry.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…Manager

reactInstanceManager throws IllegalStateException on new arch because
there is no ReactNativeHost. Use ReactApplication.reactHost to get the
current ReactContext instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…events

Two bugs:
- storyNameToId crashed on '__bootstrap__' (no '/') with TypeError on
  name.toLowerCase(). Bootstrap story only needs to register stories,
  not render anything, so bail out early when storyName has no '/'.
- loadStory event listener only logged; it never updated the displayed
  story. Add currentStoryName state, update it on the event, and reset
  loading so renderStory re-runs and notifyStoryReady fires.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
notifyStoryReady() was called synchronously in a useEffect, which runs
after React commits on the JS thread but before native view mutations
are flushed to the main thread. Wrapping in requestAnimationFrame
ensures the frame is painted before the screenshot is taken.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After awaitStoryReady(), React Native's native view mutations may still
be pending on the UI thread. waitForIdleSync() ensures the UI thread
has flushed all pending operations before the screenshot is taken.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
waitForIdleSync() drains the message queue but view drawing is driven
by VSYNC via Choreographer, not the queue. Screenshots were capturing
a blank frame because drawing hadn't happened yet. waitForNextDraw()
uses OnDrawListener to block until the next frame is actually painted.

Co-Authored-By: Claude Sonnet 4.6 <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