diff --git a/packages/react-native-renderer/src/__mocks__/react-native/Libraries/ReactPrivate/ReactNativeViewConfigRegistry.js b/packages/react-native-renderer/src/__mocks__/react-native/Libraries/ReactPrivate/ReactNativeViewConfigRegistry.js index 77424ad5105..05c8b93c9ee 100644 --- a/packages/react-native-renderer/src/__mocks__/react-native/Libraries/ReactPrivate/ReactNativeViewConfigRegistry.js +++ b/packages/react-native-renderer/src/__mocks__/react-native/Libraries/ReactPrivate/ReactNativeViewConfigRegistry.js @@ -101,10 +101,13 @@ exports.get = function(name: string): ReactNativeBaseComponentViewConfig<> { : '', ); } - viewConfigCallbacks.set(name, null); viewConfig = callback(); processEventTypes(viewConfig); viewConfigs.set(name, viewConfig); + + // Clear the callback after the config is set so that + // we don't mask any errors during registration. + viewConfigCallbacks.set(name, null); } else { viewConfig = viewConfigs.get(name); } diff --git a/packages/react-native-renderer/src/__tests__/ReactNativeEvents-test.internal.js b/packages/react-native-renderer/src/__tests__/ReactNativeEvents-test.internal.js index 6f7ddb09ec6..31b913129dd 100644 --- a/packages/react-native-renderer/src/__tests__/ReactNativeEvents-test.internal.js +++ b/packages/react-native-renderer/src/__tests__/ReactNativeEvents-test.internal.js @@ -75,6 +75,49 @@ beforeEach(() => { .ReactNativeViewConfigRegistry.register; }); +it('fails to register the same event name with different types', () => { + const InvalidEvents = createReactNativeComponentClass('InvalidEvents', () => { + if (!__DEV__) { + // Simulate a registration error in prod. + throw new Error('Event cannot be both direct and bubbling: topChange'); + } + + // This view config has the same bubbling and direct event name + // which will fail to register in developement. + return { + uiViewClassName: 'InvalidEvents', + validAttributes: { + onChange: true, + }, + bubblingEventTypes: { + topChange: { + phasedRegistrationNames: { + bubbled: 'onChange', + captured: 'onChangeCapture', + }, + }, + }, + directEventTypes: { + topChange: { + registrationName: 'onChange', + }, + }, + }; + }); + + // The first time this renders, + // we attempt to register the view config and fail. + expect(() => ReactNative.render(, 1)).toThrow( + 'Event cannot be both direct and bubbling: topChange', + ); + + // Continue to re-register the config and + // fail so that we don't mask the above failure. + expect(() => ReactNative.render(, 1)).toThrow( + 'Event cannot be both direct and bubbling: topChange', + ); +}); + it('fails if unknown/unsupported event types are dispatched', () => { expect(RCTEventEmitter.register).toHaveBeenCalledTimes(1); const EventEmitter = RCTEventEmitter.register.mock.calls[0][0]; diff --git a/scripts/rollup/shims/react-native/ReactNativeViewConfigRegistry.js b/scripts/rollup/shims/react-native/ReactNativeViewConfigRegistry.js index 5ebc0d36d39..7290c84546c 100644 --- a/scripts/rollup/shims/react-native/ReactNativeViewConfigRegistry.js +++ b/scripts/rollup/shims/react-native/ReactNativeViewConfigRegistry.js @@ -98,10 +98,13 @@ exports.get = function(name: string): ReactNativeBaseComponentViewConfig<> { : '', ); } - viewConfigCallbacks.set(name, null); viewConfig = callback(); processEventTypes(viewConfig); viewConfigs.set(name, viewConfig); + + // Clear the callback after the config is set so that + // we don't mask any errors during registration. + viewConfigCallbacks.set(name, null); } else { viewConfig = viewConfigs.get(name); }