diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java index 438ec3fd0b57c6..c97c53e32848c1 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java @@ -393,6 +393,10 @@ public BoxedColorPropSetter(ReactProp prop, Method setter) { */ /*package*/ static Map getNativePropSettersForShadowNodeClass( Class cls) { + if(cls == null) { + return EMPTY_PROPS_MAP; + }; + for (Class iface : cls.getInterfaces()) { if (iface == ReactShadowNode.class) { return EMPTY_PROPS_MAP; diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropConstantsTest.java b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropConstantsTest.java deleted file mode 100644 index 85f4919df54ae7..00000000000000 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropConstantsTest.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and 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.uimanager; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - -import android.view.View; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.common.MapBuilder; -import com.facebook.react.uimanager.annotations.ReactProp; -import com.facebook.react.uimanager.annotations.ReactPropGroup; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; - -/** Verifies that prop constants are generated properly based on {@code ReactProp} annotation. */ -@RunWith(RobolectricTestRunner.class) -@Ignore // TODO T14964130 -public class ReactPropConstantsTest { - - private class ViewManagerUnderTest extends ViewManager { - - @Override - public String getName() { - return "SomeView"; - } - - @Override - public ReactShadowNode createShadowNodeInstance() { - fail("This method should not be executed as a part of this test"); - return null; - } - - @Override - protected View createViewInstance(ThemedReactContext reactContext) { - fail("This method should not be executed as a part of this test"); - return null; - } - - @Override - public Class getShadowNodeClass() { - return ReactShadowNode.class; - } - - @Override - public void updateExtraData(View root, Object extraData) { - fail("This method should not be executed as a part of this test"); - } - - @ReactProp(name = "boolProp") - public void setBoolProp(View v, boolean value) {} - - @ReactProp(name = "intProp") - public void setIntProp(View v, int value) {} - - @ReactProp(name = "floatProp") - public void setFloatProp(View v, float value) {} - - @ReactProp(name = "doubleProp") - public void setDoubleProp(View v, double value) {} - - @ReactProp(name = "stringProp") - public void setStringProp(View v, String value) {} - - @ReactProp(name = "boxedBoolProp") - public void setBoxedBoolProp(View v, Boolean value) {} - - @ReactProp(name = "boxedIntProp") - public void setBoxedIntProp(View v, Integer value) {} - - @ReactProp(name = "arrayProp") - public void setArrayProp(View v, ReadableArray value) {} - - @ReactProp(name = "mapProp") - public void setMapProp(View v, ReadableMap value) {} - - @ReactPropGroup( - names = { - "floatGroupPropFirst", - "floatGroupPropSecond", - }) - public void setFloatGroupProp(View v, int index, float value) {} - - @ReactPropGroup(names = {"intGroupPropFirst", "intGroupPropSecond"}) - public void setIntGroupProp(View v, int index, int value) {} - - @ReactPropGroup( - names = { - "boxedIntGroupPropFirst", - "boxedIntGroupPropSecond", - }) - public void setBoxedIntGroupProp(View v, int index, Integer value) {} - - @ReactProp(name = "customIntProp", customType = "date") - public void customIntProp(View v, int value) {} - - @ReactPropGroup( - names = {"customBoxedIntGroupPropFirst", "customBoxedIntGroupPropSecond"}, - customType = "color") - public void customIntGroupProp(View v, int index, Integer value) {} - } - - @Test - public void testNativePropsIncludeCorrectTypes() { - List viewManagers = Arrays.asList(new ViewManagerUnderTest()); - ReactApplicationContext reactContext = - new ReactApplicationContext(RuntimeEnvironment.getApplication()); - UIManagerModule uiManagerModule = new UIManagerModule(reactContext, viewManagers, 0); - Map constants = - (Map) valueAtPath(uiManagerModule.getConstants(), "SomeView", "NativeProps"); - assertThat(constants) - .isEqualTo( - MapBuilder.builder() - .put("boolProp", "boolean") - .put("intProp", "number") - .put("doubleProp", "number") - .put("floatProp", "number") - .put("stringProp", "String") - .put("boxedBoolProp", "boolean") - .put("boxedIntProp", "number") - .put("arrayProp", "Array") - .put("mapProp", "Map") - .put("floatGroupPropFirst", "number") - .put("floatGroupPropSecond", "number") - .put("intGroupPropFirst", "number") - .put("intGroupPropSecond", "number") - .put("boxedIntGroupPropFirst", "number") - .put("boxedIntGroupPropSecond", "number") - .put("customIntProp", "date") - .put("customBoxedIntGroupPropFirst", "color") - .put("customBoxedIntGroupPropSecond", "color") - .build()); - } - - private static Object valueAtPath(Map nestedMap, String... keyPath) { - assertThat(keyPath).isNotEmpty(); - Object value = nestedMap; - for (String key : keyPath) { - assertThat(value).isInstanceOf(Map.class); - nestedMap = (Map) value; - assertThat(nestedMap).containsKey(key); - value = nestedMap.get(key); - } - return value; - } -} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropConstantsTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropConstantsTest.kt new file mode 100644 index 00000000000000..aaa11bb50f3534 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropConstantsTest.kt @@ -0,0 +1,129 @@ +/* + * Copyright (c) Meta Platforms, Inc. and 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.uimanager + +import android.view.View +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReadableArray +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.common.MapBuilder +import com.facebook.react.uimanager.annotations.ReactProp +import com.facebook.react.uimanager.annotations.ReactPropGroup +import org.assertj.core.api.Assertions +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment + +/** Verifies that prop constants are generated properly based on `ReactProp` annotation. */ +@RunWith(RobolectricTestRunner::class) +class ReactPropConstantsTest { + @Suppress("UNUSED_PARAMETER") + private inner class ViewManagerUnderTest : ViewManager?>() { + override fun getName(): String { + return "SomeView" + } + + override fun createShadowNodeInstance(): ReactShadowNode<*>? { + Assertions.fail("This method should not be executed as a part of this test") + return null + } + + override fun createViewInstance(reactContext: ThemedReactContext): View { + Assertions.fail("This method should not be executed as a part of this test") + return View(reactContext) + } + + override fun getShadowNodeClass(): Class> { + return ReactShadowNode::class.java + } + + override fun updateExtraData(root: View, extraData: Any) { + Assertions.fail("This method should not be executed as a part of this test") + } + + @ReactProp(name = "boolProp") fun setBoolProp(v: View?, value: Boolean) {} + + @ReactProp(name = "intProp") fun setIntProp(v: View?, value: Int) {} + + @ReactProp(name = "floatProp") fun setFloatProp(v: View?, value: Float) {} + + @ReactProp(name = "doubleProp") fun setDoubleProp(v: View?, value: Double) {} + + @ReactProp(name = "stringProp") fun setStringProp(v: View?, value: String?) {} + + @ReactProp(name = "boxedBoolProp") fun setBoxedBoolProp(v: View?, value: Boolean?) {} + + @ReactProp(name = "boxedIntProp") fun setBoxedIntProp(v: View?, value: Int?) {} + + @ReactProp(name = "arrayProp") fun setArrayProp(v: View?, value: ReadableArray?) {} + + @ReactProp(name = "mapProp") fun setMapProp(v: View?, value: ReadableMap?) {} + + @ReactPropGroup(names = ["floatGroupPropFirst", "floatGroupPropSecond"]) + fun setFloatGroupProp(v: View?, index: Int, value: Float) {} + + @ReactPropGroup(names = ["intGroupPropFirst", "intGroupPropSecond"]) + fun setIntGroupProp(v: View?, index: Int, value: Int) {} + + @ReactPropGroup(names = ["boxedIntGroupPropFirst", "boxedIntGroupPropSecond"]) + fun setBoxedIntGroupProp(v: View?, index: Int, value: Int?) {} + + @ReactProp(name = "customIntProp", customType = "date") + fun customIntProp(v: View?, value: Int) {} + + @ReactPropGroup( + names = ["customBoxedIntGroupPropFirst", "customBoxedIntGroupPropSecond"], + customType = "color") + fun customIntGroupProp(v: View?, index: Int, value: Int?) {} + } + + @Test + fun testNativePropsIncludeCorrectTypes() { + val viewManagers = listOf>(ViewManagerUnderTest()) + val reactContext = ReactApplicationContext(RuntimeEnvironment.getApplication()) + val uiManagerModule = UIManagerModule(reactContext, viewManagers, 0) + val constants: Map<*, *> = + valueAtPath(uiManagerModule.constants as Map<*, *>, "SomeView", "NativeProps") + + Assertions.assertThat(constants) + .isEqualTo( + MapBuilder.builder() + .put("boolProp", "boolean") + .put("intProp", "number") + .put("doubleProp", "number") + .put("floatProp", "number") + .put("stringProp", "String") + .put("boxedBoolProp", "boolean") + .put("boxedIntProp", "number") + .put("arrayProp", "Array") + .put("mapProp", "Map") + .put("floatGroupPropFirst", "number") + .put("floatGroupPropSecond", "number") + .put("intGroupPropFirst", "number") + .put("intGroupPropSecond", "number") + .put("boxedIntGroupPropFirst", "number") + .put("boxedIntGroupPropSecond", "number") + .put("customIntProp", "date") + .put("customBoxedIntGroupPropFirst", "color") + .put("customBoxedIntGroupPropSecond", "color") + .build()) + } + + companion object { + private fun valueAtPath(nestedMap: Map<*, *>, vararg keyPath: String): Map<*, *> { + require(keyPath.isNotEmpty()) { "keyPath must not be empty" } + var value: Map<*, *> = nestedMap + for (key in keyPath) { + require(key in value) { "Key '$key' not found in the map" } + require(value[key] is Map<*, *>) { "Key '$key' must be a map itself" } + value = value[key] as Map<*, *> + } + return value + } + } +}