❤Neurotic Pumpkin Murderer
Miss any of our Open RFC calls?Watch the recordings here! »

@formidable-webview/ersatz

2.0.3 • Public • Published

npm codecov CI

@formidable-webview/ersatz

🚀 Emulate and test WebView behaviors in node with jest and jsdom (written in Typescript 💙)

When Should I Use this Library?

The best use-case is when your application or library uses a WebView component with injected JavaScript. With Ersatz, you can mock the WebView, perform assertions on the DOM, and verify that your JS to native communication is behaving as expected. Fundamentally, you can now-on consider code injected in a WebView as part of the codebase and tested as deemed appropriate.

How to Use?

The easiest way to use Ersatz is in combination with jest, @formidable-webview/ersatz-testing and @testing-library/react-native. See examples here. Because there is no hard dependency on jest, You should be able to use Ersatz with any testing framework running on node, capable of mounting React components.

Compatibility Table

@formidable-webview/ersatz react-native-webview
1.0.0 ≥ 7.6.0 < 11

Emulated Features

Basic Props

All the props in common with ScrollView are supported.

  • Deprecated props are ignored.
  • Platform-specific props are ignored.

Other props which can influence the DOM or the WebView and their support are listed bellow. If a prop is not listed, it is probably irrelevant (related to visual rendering) and will be ignored:

Prop Support Comments
source ✔️ Both remote URI (including body, headers and method) and inline HTML are supported. Local files are not supported.
javaScriptEnabled ✔️
containerStyle ✔️ Mapped to ScrollView contentContainerStyle.
renderError ✔️
renderLoading ✔️
injectedJavaScript ✔️ This code is evaluated in the DOM by jsdom. The code shouldn't be able to access scopes outside of the DOM, thanks to sandboxing.
injectedJavaScriptBeforeContentLoaded ✔️ Ibidem.
userAgent ✔️
injectedJavaScriptForMainFrameOnly ⚠️ Consider the behavior of Ersatz as if this prop was forced to true.
injectedJavaScriptBeforeContentLoadedForMainFrameOnly ⚠️ Consider the behavior of Ersatz as if this prop was forced to true.
incognito ⚠️ Technically, that is true because nothing is persisted between to jsdom instantiations!
allowsFullscreenVideo ⚠️ There are no visual rendering in jsdom, so this prop cannot be emulated.
cacheEnabled ⚠️ There is no cache implemented in jsdom. You can consider value false.
javaScriptCanOpenWindowsAutomatically ❌ Navigation is not (yet) supported.
mediaPlaybackRequiresUserAction ❌
originWhitelist ❌
startInLoadingState ❌
applicationNameForUserAgent ❌

DOM Event Handlers Props

To reproduce the logic behind the different event handlers, we based our assumptions on the doc, the original source code and manual testing in Android, at version 10.3.3 of react-native-webview. If you find a divergence in behaviors, you are welcome to open a bug report. Bellow is a list of event handler props with a description of our understanding of when this handler will be invoked.

Event Handler Support Behavior
onLoad ✔️ Invoked when the WebView has finished the load operation with success.
onLoadEnd ✔️ Invoked when the WebView has finished the load operation, either with a success or failure
onError ✔️ Invoked when the WebView has finished the load operation with a failure. Failures cannot be reproduced though.
onLoadStart ✔️ Invoked when the WebView is starting to load from a source object.
onLoadProgress ✔️ Invoked multiple times when the WebView is loading. Although we support this, only one event will be fired at the end with progress: 1.
onHttpError ✔️ Invoked when a HTTP request fetching the resource in source.uri fails. We do provide the description and httpStatus attributes of nativeEvent.
onMessage ✔️ Invoked when a script in the backend has posted a message with window.ReactNativeWebView.postMessage. We also provide the legacy window.postMessage.
onNavigationStateChange ⚠️ Invoked when the WebView loading starts or ends. Special events such as formsubmit are not handled. Also, navigation to different URLs is not supported.
onShouldStartLoadWithRequest ❌ Unsupported. We don't have navigation right now.
onFileDownload ❌ Access to local filesystem is unsupported.

Instance Methods

Method Support Behavior
injectJavaScript ✔️ Full support.
reload ✔️ Full support.
stopLoading ⚠️ Method is present but does nothing.
goBack ⚠️ Method is present but does nothing. Navigation is not supported.
goForward ⚠️ Method is present but does nothing. Navigation is not supported.
requestFocus ⚠️ Method is present but does nothing.

Additional Instance Methods specifics to Ersatz

Method Behavior
getDocument Return the Document instance loaded from the DOM. Prerequisite: the DOM must be loaded.
getWindow Return the Window instance loaded from the DOM. Prerequisite: the DOM must be loaded.

Load Cycle

As per the manual tests we performed, a load cycle appears to start:

  • When the component is mounted with a source prop;
  • When an attribute of source changes (a deep-equal don't trigger);
  • When the reload method is invoked;
  • When the document.location.href is changed (internal navigation);
  • When any event handler reference is changed;
FETCHING (0)   LOAD START (1)              PROGRESS (2)      LOAD END (3)
onHttpError -> onLoadStart              -> onLoadProgress -> onLoadEnd
               onNavigationStateChange                       onNavigationStateChange
                                                             onLoad (success)
                                                             onError (failure)

According to the documentation, injectedJavaScriptBeforeContentLoaded and injectedScript are loaded between (1) and (3), but injectedJavaScriptBeforeContentLoaded is run right after document object creation, while injectedScript is run after DOMContentLoaded event. Our manual tests found out that both scripts have access to window.ReactNativeWebView.

Edge cases

  • When loaded with an undefined source prop, onLoadStart is triggered with loading: false.
  • When loaded with an undefined source prop, injectedJavaScript and other scripts are still run.
  • When loaded with a source.url prop, url in events is set to about:blank.

Caveats

  • NativeSyntheticEvents types are respected, but outside of nativeEvent attribute, their content is meaningless.
  • The mounting of the DOM is performed with jsdom. Thus, the assertions you can do on the DOM are constrained by this library capabilities. Notably:

Install

npm i @formidable-webview/ersatz

DownloadsWeekly Downloads

88

Version

2.0.3

License

MIT

Unpacked Size

82.6 kB

Total Files

35

Last publish

Collaborators

  • avatar