Summary: This diff adds a line to the codegen'd view configs which will check that all of the properties in the native view config are also in the JS view config we generate (note that the JS view config may have more properties than one native platform because it includes a union of both platforms)

Reviewed By: TheSavior

Differential Revision: D15278478

fbshipit-source-id: 0fef20c12265b952c69aca4e4c070a7d036db05a
This commit is contained in:
Rick Hanlon 2019-05-16 10:45:54 -07:00 коммит произвёл Facebook Github Bot
Родитель 1aca74586f
Коммит 382846aefd
4 изменённых файлов: 257 добавлений и 0 удалений

Просмотреть файл

@ -0,0 +1,99 @@
/**
* 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
* @emails oncall+react_native
*/
'use strict';
const getNativeComponentAttributes = require('getNativeComponentAttributes');
const verifyComponentAttributeEquivalence = require('verifyComponentAttributeEquivalence');
jest.mock('getNativeComponentAttributes', () => () => ({
NativeProps: {
value: 'BOOL',
},
bubblingEventTypes: {
topChange: {
phasedRegistrationNames: {
bubbled: 'onChange',
captured: 'onChangeCapture',
},
},
},
directEventTypes: {
topAccessibilityAction: {
registrationName: 'onAccessibilityAction',
},
},
validAttributes: {
borderColor: true,
style: {
borderColor: true,
transform: 'CATransform3D',
},
transform: 'CATransform3D',
},
}));
beforeEach(() => {
global.__DEV__ = true;
console.error = jest.fn();
jest.resetModules();
});
describe('verifyComponentAttributeEquivalence', () => {
test('should not verify in prod', () => {
global.__DEV__ = false;
verifyComponentAttributeEquivalence('TestComponent', {});
});
test('should not error with native config that is a subset of the given config', () => {
const configWithAdditionalProperties = getNativeComponentAttributes(
'TestComponent',
);
configWithAdditionalProperties.bubblingEventTypes.topFocus = {
phasedRegistrationNames: {
bubbled: 'onFocus',
captured: 'onFocusCapture',
},
};
configWithAdditionalProperties.directEventTypes.topSlidingComplete = {
registrationName: 'onSlidingComplete',
};
configWithAdditionalProperties.validAttributes.active = true;
verifyComponentAttributeEquivalence(
'TestComponent',
configWithAdditionalProperties,
);
verifyComponentAttributeEquivalence(
'TestComponent',
configWithAdditionalProperties,
);
expect(console.error).not.toBeCalled();
});
test('should error if given config is missing native config properties', () => {
verifyComponentAttributeEquivalence('TestComponent', {});
expect(console.error).toBeCalledTimes(3);
expect(console.error).toBeCalledWith(
'TestComponent generated view config for directEventTypes does not match native, missing: topAccessibilityAction',
);
expect(console.error).toBeCalledWith(
'TestComponent generated view config for bubblingEventTypes does not match native, missing: topChange',
);
expect(console.error).toBeCalledWith(
'TestComponent generated view config for validAttributes does not match native, missing: borderColor style',
);
});
});

Просмотреть файл

@ -0,0 +1,104 @@
/**
* 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
* @flow
*/
'use strict';
const getNativeComponentAttributes = require('getNativeComponentAttributes');
import type {ReactNativeBaseComponentViewConfig} from 'ReactNativeTypes';
const IGNORED_KEYS = ['transform'];
/**
* The purpose of this function is to validate that the view config that
* native exposes for a given view manager is the same as the view config
* that is specified for that view manager in JS.
*
* In order to improve perf, we want to avoid calling into native to get
* the view config when each view manager is used. To do this, we are moving
* the configs to JS. In the future we will use these JS based view configs
* to codegen the view manager on native to ensure they stay in sync without
* this runtime check.
*
* If this function fails, that likely means a change was made to the native
* view manager without updating the JS config as well. Ideally you can make
* that direct change to the JS config. If you don't know what the differences
* are, the best approach I've found is to create a view that prints
* the return value of getNativeComponentAttributes, and then copying that
* text and pasting it back into JS:
* <Text selectable={true}>{JSON.stringify(getNativeComponentAttributes('RCTView'))}</Text>
*
* This is meant to be a stopgap until the time comes when we only have a
* single source of truth. I wonder if this message will still be here two
* years from now...
*/
function verifyComponentAttributeEquivalence(
componentName: string,
config: ReactNativeBaseComponentViewConfig<>,
) {
if (__DEV__) {
const nativeAttributes = getNativeComponentAttributes(componentName);
['validAttributes', 'bubblingEventTypes', 'directEventTypes'].forEach(
prop => {
const diffKeys = Object.keys(
lefthandObjectDiff(nativeAttributes[prop], config[prop]),
);
if (diffKeys.length) {
console.error(
`${componentName} generated view config for ${prop} does not match native, missing: ${diffKeys.join(
' ',
)}`,
);
}
},
);
}
}
function lefthandObjectDiff(leftObj: Object, rightObj: Object) {
const differentKeys = {};
function compare(leftItem, rightItem, key) {
if (typeof leftItem !== typeof rightItem && leftItem != null) {
differentKeys[key] = rightItem;
return;
}
if (typeof leftItem === 'object') {
const objDiff = lefthandObjectDiff(leftItem, rightItem);
if (Object.keys(objDiff).length > 1) {
differentKeys[key] = objDiff;
}
return;
}
if (leftItem !== rightItem) {
differentKeys[key] = rightItem;
return;
}
}
for (const key in leftObj) {
if (IGNORED_KEYS.includes(key)) {
continue;
}
if (!rightObj) {
differentKeys[key] = {};
} else if (leftObj.hasOwnProperty(key)) {
compare(leftObj[key], rightObj[key], key);
}
}
return differentKeys;
}
module.exports = verifyComponentAttributeEquivalence;

Просмотреть файл

@ -68,6 +68,8 @@ function getReactDiffProcessValue(prop) {
const componentTemplate = `
const ::_COMPONENT_NAME_::ViewConfig = VIEW_CONFIG;
verifyComponentAttributeEquivalence('::_COMPONENT_NAME_::', ::_COMPONENT_NAME_::ViewConfig);
ReactNativeViewConfigRegistry.register(
'::_COMPONENT_NAME_::',
() => ::_COMPONENT_NAME_::ViewConfig,
@ -247,6 +249,9 @@ module.exports = {
imports.add(
"const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry');",
);
imports.add(
"const verifyComponentAttributeEquivalence = require('verifyComponentAttributeEquivalence');",
);
const moduleResults = Object.keys(schema.modules)
.map(moduleName => {

Просмотреть файл

@ -17,6 +17,7 @@ Map {
const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry');
const ReactNativeViewViewConfig = require('ReactNativeViewViewConfig');
const verifyComponentAttributeEquivalence = require('verifyComponentAttributeEquivalence');
const ArrayPropsNativeComponentViewConfig = {
uiViewClassName: 'ArrayPropsNativeComponent',
@ -42,6 +43,8 @@ const ArrayPropsNativeComponentViewConfig = {
}
};
verifyComponentAttributeEquivalence('ArrayPropsNativeComponent', ArrayPropsNativeComponentViewConfig);
ReactNativeViewConfigRegistry.register(
'ArrayPropsNativeComponent',
() => ArrayPropsNativeComponentViewConfig,
@ -69,6 +72,7 @@ Map {
const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry');
const ReactNativeViewViewConfig = require('ReactNativeViewViewConfig');
const verifyComponentAttributeEquivalence = require('verifyComponentAttributeEquivalence');
const BooleanPropNativeComponentViewConfig = {
uiViewClassName: 'BooleanPropNativeComponent',
@ -88,6 +92,8 @@ const BooleanPropNativeComponentViewConfig = {
}
};
verifyComponentAttributeEquivalence('BooleanPropNativeComponent', BooleanPropNativeComponentViewConfig);
ReactNativeViewConfigRegistry.register(
'BooleanPropNativeComponent',
() => BooleanPropNativeComponentViewConfig,
@ -115,6 +121,7 @@ Map {
const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry');
const ReactNativeViewViewConfig = require('ReactNativeViewViewConfig');
const verifyComponentAttributeEquivalence = require('verifyComponentAttributeEquivalence');
const ColorPropNativeComponentViewConfig = {
uiViewClassName: 'ColorPropNativeComponent',
@ -134,6 +141,8 @@ const ColorPropNativeComponentViewConfig = {
}
};
verifyComponentAttributeEquivalence('ColorPropNativeComponent', ColorPropNativeComponentViewConfig);
ReactNativeViewConfigRegistry.register(
'ColorPropNativeComponent',
() => ColorPropNativeComponentViewConfig,
@ -161,6 +170,7 @@ Map {
const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry');
const ReactNativeViewViewConfig = require('ReactNativeViewViewConfig');
const verifyComponentAttributeEquivalence = require('verifyComponentAttributeEquivalence');
const EnumPropsNativeComponentViewConfig = {
uiViewClassName: 'EnumPropsNativeComponent',
@ -180,6 +190,8 @@ const EnumPropsNativeComponentViewConfig = {
}
};
verifyComponentAttributeEquivalence('EnumPropsNativeComponent', EnumPropsNativeComponentViewConfig);
ReactNativeViewConfigRegistry.register(
'EnumPropsNativeComponent',
() => EnumPropsNativeComponentViewConfig,
@ -207,6 +219,7 @@ Map {
const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry');
const ReactNativeViewViewConfig = require('ReactNativeViewViewConfig');
const verifyComponentAttributeEquivalence = require('verifyComponentAttributeEquivalence');
const EventsNestedObjectNativeComponentViewConfig = {
uiViewClassName: 'EventsNestedObjectNativeComponent',
@ -234,6 +247,8 @@ const EventsNestedObjectNativeComponentViewConfig = {
}
};
verifyComponentAttributeEquivalence('EventsNestedObjectNativeComponent', EventsNestedObjectNativeComponentViewConfig);
ReactNativeViewConfigRegistry.register(
'EventsNestedObjectNativeComponent',
() => EventsNestedObjectNativeComponentViewConfig,
@ -261,6 +276,7 @@ Map {
const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry');
const ReactNativeViewViewConfig = require('ReactNativeViewViewConfig');
const verifyComponentAttributeEquivalence = require('verifyComponentAttributeEquivalence');
const EventsNativeComponentViewConfig = {
uiViewClassName: 'EventsNativeComponent',
@ -293,6 +309,8 @@ const EventsNativeComponentViewConfig = {
}
};
verifyComponentAttributeEquivalence('EventsNativeComponent', EventsNativeComponentViewConfig);
ReactNativeViewConfigRegistry.register(
'EventsNativeComponent',
() => EventsNativeComponentViewConfig,
@ -320,6 +338,7 @@ Map {
const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry');
const ReactNativeViewViewConfig = require('ReactNativeViewViewConfig');
const verifyComponentAttributeEquivalence = require('verifyComponentAttributeEquivalence');
const FloatPropNativeComponentViewConfig = {
uiViewClassName: 'FloatPropNativeComponent',
@ -344,6 +363,8 @@ const FloatPropNativeComponentViewConfig = {
}
};
verifyComponentAttributeEquivalence('FloatPropNativeComponent', FloatPropNativeComponentViewConfig);
ReactNativeViewConfigRegistry.register(
'FloatPropNativeComponent',
() => FloatPropNativeComponentViewConfig,
@ -371,6 +392,7 @@ Map {
const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry');
const ReactNativeViewViewConfig = require('ReactNativeViewViewConfig');
const verifyComponentAttributeEquivalence = require('verifyComponentAttributeEquivalence');
const ImagePropNativeComponentViewConfig = {
uiViewClassName: 'ImagePropNativeComponent',
@ -390,6 +412,8 @@ const ImagePropNativeComponentViewConfig = {
}
};
verifyComponentAttributeEquivalence('ImagePropNativeComponent', ImagePropNativeComponentViewConfig);
ReactNativeViewConfigRegistry.register(
'ImagePropNativeComponent',
() => ImagePropNativeComponentViewConfig,
@ -417,6 +441,7 @@ Map {
const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry');
const ReactNativeViewViewConfig = require('ReactNativeViewViewConfig');
const verifyComponentAttributeEquivalence = require('verifyComponentAttributeEquivalence');
const IntegerPropNativeComponentViewConfig = {
uiViewClassName: 'IntegerPropNativeComponent',
@ -438,6 +463,8 @@ const IntegerPropNativeComponentViewConfig = {
}
};
verifyComponentAttributeEquivalence('IntegerPropNativeComponent', IntegerPropNativeComponentViewConfig);
ReactNativeViewConfigRegistry.register(
'IntegerPropNativeComponent',
() => IntegerPropNativeComponentViewConfig,
@ -465,6 +492,7 @@ Map {
const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry');
const ReactNativeViewViewConfig = require('ReactNativeViewViewConfig');
const verifyComponentAttributeEquivalence = require('verifyComponentAttributeEquivalence');
const InterfaceOnlyComponentViewConfig = {
uiViewClassName: 'InterfaceOnlyComponent',
@ -492,6 +520,8 @@ const InterfaceOnlyComponentViewConfig = {
}
};
verifyComponentAttributeEquivalence('InterfaceOnlyComponent', InterfaceOnlyComponentViewConfig);
ReactNativeViewConfigRegistry.register(
'InterfaceOnlyComponent',
() => InterfaceOnlyComponentViewConfig,
@ -519,6 +549,7 @@ Map {
const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry');
const ReactNativeViewViewConfig = require('ReactNativeViewViewConfig');
const verifyComponentAttributeEquivalence = require('verifyComponentAttributeEquivalence');
const ImageColorPropNativeComponentViewConfig = {
uiViewClassName: 'ImageColorPropNativeComponent',
@ -541,6 +572,8 @@ const ImageColorPropNativeComponentViewConfig = {
}
};
verifyComponentAttributeEquivalence('ImageColorPropNativeComponent', ImageColorPropNativeComponentViewConfig);
ReactNativeViewConfigRegistry.register(
'ImageColorPropNativeComponent',
() => ImageColorPropNativeComponentViewConfig,
@ -568,6 +601,7 @@ Map {
const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry');
const ReactNativeViewViewConfig = require('ReactNativeViewViewConfig');
const verifyComponentAttributeEquivalence = require('verifyComponentAttributeEquivalence');
const PointPropNativeComponentViewConfig = {
uiViewClassName: 'PointPropNativeComponent',
@ -587,6 +621,8 @@ const PointPropNativeComponentViewConfig = {
}
};
verifyComponentAttributeEquivalence('PointPropNativeComponent', PointPropNativeComponentViewConfig);
ReactNativeViewConfigRegistry.register(
'PointPropNativeComponent',
() => PointPropNativeComponentViewConfig,
@ -614,6 +650,7 @@ Map {
const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry');
const ReactNativeViewViewConfig = require('ReactNativeViewViewConfig');
const verifyComponentAttributeEquivalence = require('verifyComponentAttributeEquivalence');
const StringPropComponentViewConfig = {
uiViewClassName: 'StringPropComponent',
@ -633,6 +670,8 @@ const StringPropComponentViewConfig = {
}
};
verifyComponentAttributeEquivalence('StringPropComponent', StringPropComponentViewConfig);
ReactNativeViewConfigRegistry.register(
'StringPropComponent',
() => StringPropComponentViewConfig,
@ -660,6 +699,7 @@ Map {
const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry');
const ReactNativeViewViewConfig = require('ReactNativeViewViewConfig');
const verifyComponentAttributeEquivalence = require('verifyComponentAttributeEquivalence');
const MultiFile1NativeComponentViewConfig = {
uiViewClassName: 'MultiFile1NativeComponent',
@ -679,6 +719,8 @@ const MultiFile1NativeComponentViewConfig = {
}
};
verifyComponentAttributeEquivalence('MultiFile1NativeComponent', MultiFile1NativeComponentViewConfig);
ReactNativeViewConfigRegistry.register(
'MultiFile1NativeComponent',
() => MultiFile1NativeComponentViewConfig,
@ -704,6 +746,8 @@ const MultiFile2NativeComponentViewConfig = {
}
};
verifyComponentAttributeEquivalence('MultiFile2NativeComponent', MultiFile2NativeComponentViewConfig);
ReactNativeViewConfigRegistry.register(
'MultiFile2NativeComponent',
() => MultiFile2NativeComponentViewConfig,
@ -731,6 +775,7 @@ Map {
const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry');
const ReactNativeViewViewConfig = require('ReactNativeViewViewConfig');
const verifyComponentAttributeEquivalence = require('verifyComponentAttributeEquivalence');
const MultiComponent1NativeComponentViewConfig = {
uiViewClassName: 'MultiComponent1NativeComponent',
@ -750,6 +795,8 @@ const MultiComponent1NativeComponentViewConfig = {
}
};
verifyComponentAttributeEquivalence('MultiComponent1NativeComponent', MultiComponent1NativeComponentViewConfig);
ReactNativeViewConfigRegistry.register(
'MultiComponent1NativeComponent',
() => MultiComponent1NativeComponentViewConfig,
@ -775,6 +822,8 @@ const MultiComponent2NativeComponentViewConfig = {
}
};
verifyComponentAttributeEquivalence('MultiComponent2NativeComponent', MultiComponent2NativeComponentViewConfig);
ReactNativeViewConfigRegistry.register(
'MultiComponent2NativeComponent',
() => MultiComponent2NativeComponentViewConfig,