diff --git a/ReactAndroid/src/androidTest/AndroidManifest.xml b/ReactAndroid/src/androidTest/AndroidManifest.xml index 62c744e8f895bf..0ebc7b4768ecba 100644 --- a/ReactAndroid/src/androidTest/AndroidManifest.xml +++ b/ReactAndroid/src/androidTest/AndroidManifest.xml @@ -6,6 +6,8 @@ + + diff --git a/ReactAndroid/src/androidTest/buck-runner/AndroidManifest.xml b/ReactAndroid/src/androidTest/buck-runner/AndroidManifest.xml index 8fd01fc76607a3..7672ea05ee6767 100644 --- a/ReactAndroid/src/androidTest/buck-runner/AndroidManifest.xml +++ b/ReactAndroid/src/androidTest/buck-runner/AndroidManifest.xml @@ -7,6 +7,8 @@ + + diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/ImageErrorTestCase.java b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/ImageErrorTestCase.java new file mode 100644 index 00000000000000..3676a9cd202cf1 --- /dev/null +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/ImageErrorTestCase.java @@ -0,0 +1,46 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.tests; + +import android.view.View; +import com.facebook.react.testing.ReactAppInstrumentationTestCase; +import com.facebook.react.testing.ReactInstanceSpecForTest; +import com.facebook.react.testing.StringRecordingModule; + +/** + * Simple test case to check that onError does not get called with undefined + */ +public class ImageErrorTestCase extends ReactAppInstrumentationTestCase { + + private StringRecordingModule mStringRecordingModule; + + @Override + protected String getReactApplicationKeyUnderTest() { + return "ImageErrorTestApp"; + } + + public void testErrorHasCause() throws Exception { + assertNotNull(getViewByTestId("image-1")); + assertNotNull(getViewByTestId("image-2")); + assertNotNull(getViewByTestId("image-3")); + + Thread.sleep(3000); + + assertEquals(3, mStringRecordingModule.getCalls().size()); + assertEquals("Got error: Unsupported uri scheme! Uri is: ", mStringRecordingModule.getCalls().get(0)); + assertEquals("Got error: /does/not/exist: open failed: ENOENT (No such file or directory)", mStringRecordingModule.getCalls().get(1)); + assertEquals("Got error: Unexpected HTTP code Response{protocol=http/1.1, code=404, message=Not Found, url=https://typo_error_facebook.github.io/react/logo-og.png}", mStringRecordingModule.getCalls().get(2)); + } + + @Override + protected ReactInstanceSpecForTest createReactInstanceSpecForTest() { + mStringRecordingModule = new StringRecordingModule(); + return super.createReactInstanceSpecForTest() + .addNativeModule(mStringRecordingModule); + } +} diff --git a/ReactAndroid/src/androidTest/js/ImageErrorTestApp.js b/ReactAndroid/src/androidTest/js/ImageErrorTestApp.js new file mode 100644 index 00000000000000..0afc6c925e1225 --- /dev/null +++ b/ReactAndroid/src/androidTest/js/ImageErrorTestApp.js @@ -0,0 +1,58 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +'use strict'; + +const React = require('React'); +const Image = require('Image'); +const StyleSheet = require('StyleSheet'); +const View = require('View'); + +const RecordingModule = require('NativeModules').Recording; + +class ImageErrorTestApp extends React.Component { + onError = e => { + RecordingModule.record('Got error: ' + e.nativeEvent.error); + }; + + render() { + // For some reason image-2 needs explicit height. Without it onError is not triggered. + return ( + + + + + + ); + } +} + +const styles = StyleSheet.create({ + image: { + height: 50, + width: 50, + }, +}); + +module.exports = ImageErrorTestApp; diff --git a/ReactAndroid/src/androidTest/js/TestBundle.js b/ReactAndroid/src/androidTest/js/TestBundle.js index 906a3601331bda..6fb617821b6754 100644 --- a/ReactAndroid/src/androidTest/js/TestBundle.js +++ b/ReactAndroid/src/androidTest/js/TestBundle.js @@ -58,6 +58,10 @@ const apps = [ appKey: 'ImageOverlayColorTestApp', component: () => require('ImageOverlayColorTestApp'), }, + { + appKey: 'ImageErrorTestApp', + component: () => require('ImageErrorTestApp'), + }, { appKey: 'InitialPropsTestApp', component: () => require('InitialPropsTestApp'), diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageLoadEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageLoadEvent.java index b897aac09b23de..43d4c152c48531 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageLoadEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageLoadEvent.java @@ -33,13 +33,18 @@ public class ImageLoadEvent extends Event { private final @Nullable String mImageUri; private final int mWidth; private final int mHeight; + private final @Nullable String mImageError; public ImageLoadEvent(int viewId, @ImageEventType int eventType) { this(viewId, eventType, null); } + public ImageLoadEvent(int viewId, @ImageEventType int eventType, boolean error, String message) { + this(viewId, eventType, null, 0, 0, message); + } + public ImageLoadEvent(int viewId, @ImageEventType int eventType, String imageUri) { - this(viewId, eventType, imageUri, 0, 0); + this(viewId, eventType, imageUri, 0, 0, null); } public ImageLoadEvent( @@ -48,11 +53,22 @@ public ImageLoadEvent( @Nullable String imageUri, int width, int height) { + this(viewId, eventType, imageUri, width, height, null); + } + + public ImageLoadEvent( + int viewId, + @ImageEventType int eventType, + @Nullable String imageUri, + int width, + int height, + @Nullable String message) { super(viewId); mEventType = eventType; mImageUri = imageUri; mWidth = width; mHeight = height; + mImageError = message; } public static String eventNameForType(@ImageEventType int eventType) { @@ -88,7 +104,7 @@ public short getCoalescingKey() { public void dispatch(RCTEventEmitter rctEventEmitter) { WritableMap eventData = null; - if (mImageUri != null || mEventType == ON_LOAD) { + if (mImageUri != null || (mEventType == ON_LOAD || mEventType == ON_ERROR)) { eventData = Arguments.createMap(); if (mImageUri != null) { @@ -103,6 +119,8 @@ public void dispatch(RCTEventEmitter rctEventEmitter) { source.putString("url", mImageUri); } eventData.putMap("source", source); + } else if (mEventType == ON_ERROR) { + eventData.putString("error", mImageError); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java index 722dd1225095f8..305f69526a3b17 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java @@ -262,9 +262,8 @@ public void onFinalImageSet( @Override public void onFailure(String id, Throwable throwable) { mEventDispatcher.dispatchEvent( - new ImageLoadEvent(getId(), ImageLoadEvent.ON_ERROR)); - mEventDispatcher.dispatchEvent( - new ImageLoadEvent(getId(), ImageLoadEvent.ON_LOAD_END)); + new ImageLoadEvent(getId(), ImageLoadEvent.ON_ERROR, + true, throwable.getMessage())); } }; }