Use HostContext to warn about invalid View/Text nesting (#12766)

This commit is contained in:
Brian Vaughn 2018-05-14 15:34:01 -07:00 committed by GitHub
parent c5d3104fc0
commit c802d29bd1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 352 additions and 141 deletions

View File

@ -22,7 +22,7 @@ import * as ReactNativeViewConfigRegistry from 'ReactNativeViewConfigRegistry';
import ReactFiberReconciler from 'react-reconciler';
import deepFreezeAndThrowOnMutationInDev from 'deepFreezeAndThrowOnMutationInDev';
import emptyObject from 'fbjs/lib/emptyObject';
import invariant from 'fbjs/lib/invariant';
// Modules provided by RN:
import TextInputState from 'TextInputState';
@ -35,6 +35,10 @@ import UIManager from 'UIManager';
// This means that they never overlap.
let nextReactTag = 2;
type HostContext = $ReadOnly<{|
isInAParentText: boolean,
|}>;
/**
* This is used for refs on host components.
*/
@ -135,7 +139,7 @@ const ReactFabricRenderer = ReactFiberReconciler({
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: {},
hostContext: HostContext,
internalInstanceHandle: Object,
): Instance {
const tag = nextReactTag;
@ -151,6 +155,11 @@ const ReactFabricRenderer = ReactFiberReconciler({
}
}
invariant(
type !== 'RCTView' || !hostContext.isInAParentText,
'Nesting of <View> within <Text> is not currently supported.',
);
const updatePayload = ReactNativeAttributePayload.create(
props,
viewConfig.validAttributes,
@ -175,9 +184,14 @@ const ReactFabricRenderer = ReactFiberReconciler({
createTextInstance(
text: string,
rootContainerInstance: Container,
hostContext: {},
hostContext: HostContext,
internalInstanceHandle: Object,
): TextInstance {
invariant(
hostContext.isInAParentText,
'Text strings must be rendered within a <Text> component.',
);
const tag = nextReactTag;
nextReactTag += 2;
@ -203,12 +217,27 @@ const ReactFabricRenderer = ReactFiberReconciler({
return false;
},
getRootHostContext(): {} {
return emptyObject;
getRootHostContext(rootContainerInstance: Container): HostContext {
return {isInAParentText: false};
},
getChildHostContext(): {} {
return emptyObject;
getChildHostContext(
parentHostContext: HostContext,
type: string,
): HostContext {
const prevIsInAParentText = parentHostContext.isInAParentText;
const isInAParentText =
type === 'AndroidTextInput' || // Android
type === 'RCTMultilineTextInputView' || // iOS
type === 'RCTSinglelineTextInputView' || // iOS
type === 'RCTText' ||
type === 'RCTVirtualText';
if (prevIsInAParentText !== isInAParentText) {
return {isInAParentText};
} else {
return parentHostContext;
}
},
getPublicInstance(instance) {
@ -230,7 +259,7 @@ const ReactFabricRenderer = ReactFiberReconciler({
oldProps: Props,
newProps: Props,
rootContainerInstance: Container,
hostContext: {},
hostContext: HostContext,
): null | Object {
const viewConfig = instance.canonical.viewConfig;
const updatePayload = ReactNativeAttributePayload.diff(

View File

@ -12,6 +12,7 @@ import type {ReactNativeBaseComponentViewConfig} from './ReactNativeTypes';
import ReactFiberReconciler from 'react-reconciler';
import emptyObject from 'fbjs/lib/emptyObject';
import invariant from 'fbjs/lib/invariant';
// Modules provided by RN:
import UIManager from 'UIManager';
import deepFreezeAndThrowOnMutationInDev from 'deepFreezeAndThrowOnMutationInDev';
@ -35,6 +36,10 @@ export type Instance = {
type Props = Object;
type TextInstance = number;
type HostContext = $ReadOnly<{|
isInAParentText: boolean,
|}>;
// Counter for uniquely identifying views.
// % 10 === 1 means it is a rootTag.
// % 2 === 0 means it is a Fabric tag.
@ -71,7 +76,7 @@ const NativeRenderer = ReactFiberReconciler({
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: {},
hostContext: HostContext,
internalInstanceHandle: Object,
): Instance {
const tag = allocateTag();
@ -85,6 +90,11 @@ const NativeRenderer = ReactFiberReconciler({
}
}
invariant(
type !== 'RCTView' || !hostContext.isInAParentText,
'Nesting of <View> within <Text> is not currently supported.',
);
const updatePayload = ReactNativeAttributePayload.create(
props,
viewConfig.validAttributes,
@ -110,9 +120,14 @@ const NativeRenderer = ReactFiberReconciler({
createTextInstance(
text: string,
rootContainerInstance: Container,
hostContext: {},
hostContext: HostContext,
internalInstanceHandle: Object,
): TextInstance {
invariant(
hostContext.isInAParentText,
'Text strings must be rendered within a <Text> component.',
);
const tag = allocateTag();
UIManager.createView(
@ -155,12 +170,27 @@ const NativeRenderer = ReactFiberReconciler({
return false;
},
getRootHostContext(): {} {
return emptyObject;
getRootHostContext(rootContainerInstance: Container): HostContext {
return {isInAParentText: false};
},
getChildHostContext(): {} {
return emptyObject;
getChildHostContext(
parentHostContext: HostContext,
type: string,
): HostContext {
const prevIsInAParentText = parentHostContext.isInAParentText;
const isInAParentText =
type === 'AndroidTextInput' || // Android
type === 'RCTMultilineTextInputView' || // iOS
type === 'RCTSinglelineTextInputView' || // iOS
type === 'RCTText' ||
type === 'RCTVirtualText';
if (prevIsInAParentText !== isInAParentText) {
return {isInAParentText};
} else {
return parentHostContext;
}
},
getPublicInstance(instance) {
@ -181,7 +211,7 @@ const NativeRenderer = ReactFiberReconciler({
oldProps: Props,
newProps: Props,
rootContainerInstance: Container,
hostContext: {},
hostContext: HostContext,
): null | Object {
return emptyObject;
},

View File

@ -33,9 +33,9 @@ describe('ReactFabric', () => {
});
it('should be able to create and render a native component', () => {
const View = createReactNativeComponentClass('View', () => ({
const View = createReactNativeComponentClass('RCTView', () => ({
validAttributes: {foo: true},
uiViewClassName: 'View',
uiViewClassName: 'RCTView',
}));
ReactFabric.render(<View foo="test" />, 1);
@ -45,9 +45,9 @@ describe('ReactFabric', () => {
});
it('should be able to create and update a native component', () => {
const View = createReactNativeComponentClass('View', () => ({
const View = createReactNativeComponentClass('RCTView', () => ({
validAttributes: {foo: true},
uiViewClassName: 'View',
uiViewClassName: 'RCTView',
}));
const firstNode = {};
@ -67,9 +67,9 @@ describe('ReactFabric', () => {
});
it('should not call FabricUIManager.cloneNode after render for properties that have not changed', () => {
const Text = createReactNativeComponentClass('Text', () => ({
const Text = createReactNativeComponentClass('RCTText', () => ({
validAttributes: {foo: true},
uiViewClassName: 'Text',
uiViewClassName: 'RCTText',
}));
ReactFabric.render(<Text foo="a">1</Text>, 11);
@ -110,15 +110,15 @@ describe('ReactFabric', () => {
});
it('should only pass props diffs to FabricUIManager.cloneNode', () => {
const View = createReactNativeComponentClass('View', () => ({
const Text = createReactNativeComponentClass('RCTText', () => ({
validAttributes: {foo: true, bar: true},
uiViewClassName: 'View',
uiViewClassName: 'RCTText',
}));
ReactFabric.render(
<View foo="a" bar="a">
<Text foo="a" bar="a">
1
</View>,
</Text>,
11,
);
expect(FabricUIManager.cloneNode).not.toBeCalled();
@ -127,9 +127,9 @@ describe('ReactFabric', () => {
expect(FabricUIManager.cloneNodeWithNewChildrenAndProps).not.toBeCalled();
ReactFabric.render(
<View foo="a" bar="b">
<Text foo="a" bar="b">
1
</View>,
</Text>,
11,
);
expect(FabricUIManager.cloneNodeWithNewProps.mock.calls[0][1]).toEqual({
@ -138,9 +138,9 @@ describe('ReactFabric', () => {
expect(FabricUIManager.__dumpHierarchyForJestTestsOnly()).toMatchSnapshot();
ReactFabric.render(
<View foo="b" bar="b">
<Text foo="b" bar="b">
2
</View>,
</Text>,
11,
);
expect(
@ -152,9 +152,9 @@ describe('ReactFabric', () => {
});
it('should not call UIManager.updateView from setNativeProps for properties that have not changed', () => {
const View = createReactNativeComponentClass('View', () => ({
const View = createReactNativeComponentClass('RCTView', () => ({
validAttributes: {foo: true},
uiViewClassName: 'View',
uiViewClassName: 'RCTView',
}));
class Subclass extends ReactFabric.NativeComponent {
@ -187,9 +187,9 @@ describe('ReactFabric', () => {
});
it('returns the correct instance and calls it in the callback', () => {
const View = createReactNativeComponentClass('View', () => ({
const View = createReactNativeComponentClass('RCTView', () => ({
validAttributes: {foo: true},
uiViewClassName: 'View',
uiViewClassName: 'RCTView',
}));
let a;
@ -208,9 +208,9 @@ describe('ReactFabric', () => {
});
it('renders and reorders children', () => {
const View = createReactNativeComponentClass('View', () => ({
const View = createReactNativeComponentClass('RCTView', () => ({
validAttributes: {title: true},
uiViewClassName: 'View',
uiViewClassName: 'RCTView',
}));
class Component extends React.Component {
@ -249,9 +249,9 @@ describe('ReactFabric', () => {
});
it('should call complete after inserting children', () => {
const View = createReactNativeComponentClass('View', () => ({
const View = createReactNativeComponentClass('RCTView', () => ({
validAttributes: {foo: true},
uiViewClassName: 'View',
uiViewClassName: 'RCTView',
}));
const snapshots = [];
@ -272,4 +272,80 @@ describe('ReactFabric', () => {
);
expect(snapshots).toMatchSnapshot();
});
it('should throw when <View> is used inside of a <Text> ancestor', () => {
const Image = createReactNativeComponentClass('RCTImage', () => ({
validAttributes: {},
uiViewClassName: 'RCTImage',
}));
const Text = createReactNativeComponentClass('RCTText', () => ({
validAttributes: {},
uiViewClassName: 'RCTText',
}));
const View = createReactNativeComponentClass('RCTView', () => ({
validAttributes: {},
uiViewClassName: 'RCTView',
}));
expect(() =>
ReactFabric.render(
<Text>
<View />
</Text>,
11,
),
).toThrow('Nesting of <View> within <Text> is not currently supported.');
// Non-View things (e.g. Image) are fine
ReactFabric.render(
<Text>
<Image />
</Text>,
11,
);
});
it('should throw for text not inside of a <Text> ancestor', () => {
const ScrollView = createReactNativeComponentClass('RCTScrollView', () => ({
validAttributes: {},
uiViewClassName: 'RCTScrollView',
}));
const Text = createReactNativeComponentClass('RCTText', () => ({
validAttributes: {},
uiViewClassName: 'RCTText',
}));
const View = createReactNativeComponentClass('RCTView', () => ({
validAttributes: {},
uiViewClassName: 'RCTView',
}));
expect(() => ReactFabric.render(<View>this should warn</View>, 11)).toThrow(
'Text strings must be rendered within a <Text> component.',
);
expect(() =>
ReactFabric.render(
<Text>
<ScrollView>hi hello hi</ScrollView>
</Text>,
11,
),
).toThrow('Text strings must be rendered within a <Text> component.');
});
it('should not throw for text inside of an indirect <Text> ancestor', () => {
const Text = createReactNativeComponentClass('RCTText', () => ({
validAttributes: {},
uiViewClassName: 'RCTText',
}));
const Indirection = () => 'Hi';
ReactFabric.render(
<Text>
<Indirection />
</Text>,
11,
);
});
});

View File

@ -31,9 +31,9 @@ describe('ReactFabric', () => {
});
it('find Fabric nodes with the RN renderer', () => {
const View = createReactNativeComponentClass('View', () => ({
const View = createReactNativeComponentClass('RCTView', () => ({
validAttributes: {title: true},
uiViewClassName: 'View',
uiViewClassName: 'RCTView',
}));
let ref = React.createRef();

View File

@ -153,7 +153,7 @@ it('handles events', () => {
it('handles events on text nodes', () => {
expect(RCTEventEmitter.register.mock.calls.length).toBe(1);
const EventEmitter = RCTEventEmitter.register.mock.calls[0][0];
const Text = fakeRequireNativeComponent('Text', {});
const Text = fakeRequireNativeComponent('RCTText', {});
class ContextHack extends React.Component {
static childContextTypes = {isInAParentText: PropTypes.bool};

View File

@ -27,9 +27,9 @@ describe('ReactNative', () => {
});
it('should be able to create and render a native component', () => {
const View = createReactNativeComponentClass('View', () => ({
const View = createReactNativeComponentClass('RCTView', () => ({
validAttributes: {foo: true},
uiViewClassName: 'View',
uiViewClassName: 'RCTView',
}));
ReactNative.render(<View foo="test" />, 1);
@ -40,9 +40,9 @@ describe('ReactNative', () => {
});
it('should be able to create and update a native component', () => {
const View = createReactNativeComponentClass('View', () => ({
const View = createReactNativeComponentClass('RCTView', () => ({
validAttributes: {foo: true},
uiViewClassName: 'View',
uiViewClassName: 'RCTView',
}));
ReactNative.render(<View foo="foo" />, 11);
@ -57,13 +57,13 @@ describe('ReactNative', () => {
expect(UIManager.createView.mock.calls.length).toBe(1);
expect(UIManager.setChildren.mock.calls.length).toBe(1);
expect(UIManager.manageChildren).not.toBeCalled();
expect(UIManager.updateView).toBeCalledWith(3, 'View', {foo: 'bar'});
expect(UIManager.updateView).toBeCalledWith(3, 'RCTView', {foo: 'bar'});
});
it('should not call UIManager.updateView after render for properties that have not changed', () => {
const Text = createReactNativeComponentClass('Text', () => ({
const Text = createReactNativeComponentClass('RCTText', () => ({
validAttributes: {foo: true},
uiViewClassName: 'Text',
uiViewClassName: 'RCTText',
}));
ReactNative.render(<Text foo="a">1</Text>, 11);
@ -87,9 +87,9 @@ describe('ReactNative', () => {
});
it('should not call UIManager.updateView from setNativeProps for properties that have not changed', () => {
const View = createReactNativeComponentClass('View', () => ({
const View = createReactNativeComponentClass('RCTView', () => ({
validAttributes: {foo: true},
uiViewClassName: 'View',
uiViewClassName: 'RCTView',
}));
class Subclass extends ReactNative.NativeComponent {
@ -122,9 +122,9 @@ describe('ReactNative', () => {
});
it('returns the correct instance and calls it in the callback', () => {
const View = createReactNativeComponentClass('View', () => ({
const View = createReactNativeComponentClass('RCTView', () => ({
validAttributes: {foo: true},
uiViewClassName: 'View',
uiViewClassName: 'RCTView',
}));
let a;
@ -143,9 +143,9 @@ describe('ReactNative', () => {
});
it('renders and reorders children', () => {
const View = createReactNativeComponentClass('View', () => ({
const View = createReactNativeComponentClass('RCTView', () => ({
validAttributes: {title: true},
uiViewClassName: 'View',
uiViewClassName: 'RCTView',
}));
class Component extends React.Component {
@ -182,4 +182,80 @@ describe('ReactNative', () => {
ReactNative.render(<Component />, 11);
expect(mockArgs.length).toEqual(0);
});
it('should throw when <View> is used inside of a <Text> ancestor', () => {
const Image = createReactNativeComponentClass('RCTImage', () => ({
validAttributes: {},
uiViewClassName: 'RCTImage',
}));
const Text = createReactNativeComponentClass('RCTText', () => ({
validAttributes: {},
uiViewClassName: 'RCTText',
}));
const View = createReactNativeComponentClass('RCTView', () => ({
validAttributes: {},
uiViewClassName: 'RCTView',
}));
expect(() =>
ReactNative.render(
<Text>
<View />
</Text>,
11,
),
).toThrow('Nesting of <View> within <Text> is not currently supported.');
// Non-View things (e.g. Image) are fine
ReactNative.render(
<Text>
<Image />
</Text>,
11,
);
});
it('should throw for text not inside of a <Text> ancestor', () => {
const ScrollView = createReactNativeComponentClass('RCTScrollView', () => ({
validAttributes: {},
uiViewClassName: 'RCTScrollView',
}));
const Text = createReactNativeComponentClass('RCTText', () => ({
validAttributes: {},
uiViewClassName: 'RCTText',
}));
const View = createReactNativeComponentClass('RCTView', () => ({
validAttributes: {},
uiViewClassName: 'RCTView',
}));
expect(() => ReactNative.render(<View>this should warn</View>, 11)).toThrow(
'Text strings must be rendered within a <Text> component.',
);
expect(() =>
ReactNative.render(
<Text>
<ScrollView>hi hello hi</ScrollView>
</Text>,
11,
),
).toThrow('Text strings must be rendered within a <Text> component.');
});
it('should not throw for text inside of an indirect <Text> ancestor', () => {
const Text = createReactNativeComponentClass('RCTText', () => ({
validAttributes: {},
uiViewClassName: 'RCTText',
}));
const Indirection = () => 'Hi';
ReactNative.render(
<Text>
<Indirection />
</Text>,
11,
);
});
});

View File

@ -2,69 +2,69 @@
exports[`ReactFabric renders and reorders children 1`] = `
"11
View null
View {\\"title\\":\\"a\\"}
View {\\"title\\":\\"b\\"}
View {\\"title\\":\\"c\\"}
View {\\"title\\":\\"d\\"}
View {\\"title\\":\\"e\\"}
View {\\"title\\":\\"f\\"}
View {\\"title\\":\\"g\\"}
View {\\"title\\":\\"h\\"}
View {\\"title\\":\\"i\\"}
View {\\"title\\":\\"j\\"}
View {\\"title\\":\\"k\\"}
View {\\"title\\":\\"l\\"}
View {\\"title\\":\\"m\\"}
View {\\"title\\":\\"n\\"}
View {\\"title\\":\\"o\\"}
View {\\"title\\":\\"p\\"}
View {\\"title\\":\\"q\\"}
View {\\"title\\":\\"r\\"}
View {\\"title\\":\\"s\\"}
View {\\"title\\":\\"t\\"}"
RCTView null
RCTView {\\"title\\":\\"a\\"}
RCTView {\\"title\\":\\"b\\"}
RCTView {\\"title\\":\\"c\\"}
RCTView {\\"title\\":\\"d\\"}
RCTView {\\"title\\":\\"e\\"}
RCTView {\\"title\\":\\"f\\"}
RCTView {\\"title\\":\\"g\\"}
RCTView {\\"title\\":\\"h\\"}
RCTView {\\"title\\":\\"i\\"}
RCTView {\\"title\\":\\"j\\"}
RCTView {\\"title\\":\\"k\\"}
RCTView {\\"title\\":\\"l\\"}
RCTView {\\"title\\":\\"m\\"}
RCTView {\\"title\\":\\"n\\"}
RCTView {\\"title\\":\\"o\\"}
RCTView {\\"title\\":\\"p\\"}
RCTView {\\"title\\":\\"q\\"}
RCTView {\\"title\\":\\"r\\"}
RCTView {\\"title\\":\\"s\\"}
RCTView {\\"title\\":\\"t\\"}"
`;
exports[`ReactFabric renders and reorders children 2`] = `
"11
View null
View {\\"title\\":\\"m\\"}
View {\\"title\\":\\"x\\"}
View {\\"title\\":\\"h\\"}
View {\\"title\\":\\"p\\"}
View {\\"title\\":\\"g\\"}
View {\\"title\\":\\"w\\"}
View {\\"title\\":\\"f\\"}
View {\\"title\\":\\"r\\"}
View {\\"title\\":\\"a\\"}
View {\\"title\\":\\"l\\"}
View {\\"title\\":\\"k\\"}
View {\\"title\\":\\"e\\"}
View {\\"title\\":\\"o\\"}
View {\\"title\\":\\"i\\"}
View {\\"title\\":\\"v\\"}
View {\\"title\\":\\"c\\"}
View {\\"title\\":\\"s\\"}
View {\\"title\\":\\"t\\"}
View {\\"title\\":\\"z\\"}
View {\\"title\\":\\"y\\"}"
RCTView null
RCTView {\\"title\\":\\"m\\"}
RCTView {\\"title\\":\\"x\\"}
RCTView {\\"title\\":\\"h\\"}
RCTView {\\"title\\":\\"p\\"}
RCTView {\\"title\\":\\"g\\"}
RCTView {\\"title\\":\\"w\\"}
RCTView {\\"title\\":\\"f\\"}
RCTView {\\"title\\":\\"r\\"}
RCTView {\\"title\\":\\"a\\"}
RCTView {\\"title\\":\\"l\\"}
RCTView {\\"title\\":\\"k\\"}
RCTView {\\"title\\":\\"e\\"}
RCTView {\\"title\\":\\"o\\"}
RCTView {\\"title\\":\\"i\\"}
RCTView {\\"title\\":\\"v\\"}
RCTView {\\"title\\":\\"c\\"}
RCTView {\\"title\\":\\"s\\"}
RCTView {\\"title\\":\\"t\\"}
RCTView {\\"title\\":\\"z\\"}
RCTView {\\"title\\":\\"y\\"}"
`;
exports[`ReactFabric should call complete after inserting children 1`] = `
Array [
"View {\\"foo\\":\\"a\\"}
View {\\"foo\\":\\"b\\"}",
"RCTView {\\"foo\\":\\"a\\"}
RCTView {\\"foo\\":\\"b\\"}",
]
`;
exports[`ReactFabric should only pass props diffs to FabricUIManager.cloneNode 1`] = `
"11
View {\\"foo\\":\\"a\\",\\"bar\\":\\"b\\"}
RCTText {\\"foo\\":\\"a\\",\\"bar\\":\\"b\\"}
RCTRawText {\\"text\\":\\"1\\"}"
`;
exports[`ReactFabric should only pass props diffs to FabricUIManager.cloneNode 2`] = `
"11
View {\\"foo\\":\\"b\\",\\"bar\\":\\"b\\"}
RCTText {\\"foo\\":\\"b\\",\\"bar\\":\\"b\\"}
RCTRawText {\\"text\\":\\"2\\"}"
`;

View File

@ -2,50 +2,50 @@
exports[`ReactNative renders and reorders children 1`] = `
"<native root> {}
View null
View {\\"title\\":\\"a\\"}
View {\\"title\\":\\"b\\"}
View {\\"title\\":\\"c\\"}
View {\\"title\\":\\"d\\"}
View {\\"title\\":\\"e\\"}
View {\\"title\\":\\"f\\"}
View {\\"title\\":\\"g\\"}
View {\\"title\\":\\"h\\"}
View {\\"title\\":\\"i\\"}
View {\\"title\\":\\"j\\"}
View {\\"title\\":\\"k\\"}
View {\\"title\\":\\"l\\"}
View {\\"title\\":\\"m\\"}
View {\\"title\\":\\"n\\"}
View {\\"title\\":\\"o\\"}
View {\\"title\\":\\"p\\"}
View {\\"title\\":\\"q\\"}
View {\\"title\\":\\"r\\"}
View {\\"title\\":\\"s\\"}
View {\\"title\\":\\"t\\"}"
RCTView null
RCTView {\\"title\\":\\"a\\"}
RCTView {\\"title\\":\\"b\\"}
RCTView {\\"title\\":\\"c\\"}
RCTView {\\"title\\":\\"d\\"}
RCTView {\\"title\\":\\"e\\"}
RCTView {\\"title\\":\\"f\\"}
RCTView {\\"title\\":\\"g\\"}
RCTView {\\"title\\":\\"h\\"}
RCTView {\\"title\\":\\"i\\"}
RCTView {\\"title\\":\\"j\\"}
RCTView {\\"title\\":\\"k\\"}
RCTView {\\"title\\":\\"l\\"}
RCTView {\\"title\\":\\"m\\"}
RCTView {\\"title\\":\\"n\\"}
RCTView {\\"title\\":\\"o\\"}
RCTView {\\"title\\":\\"p\\"}
RCTView {\\"title\\":\\"q\\"}
RCTView {\\"title\\":\\"r\\"}
RCTView {\\"title\\":\\"s\\"}
RCTView {\\"title\\":\\"t\\"}"
`;
exports[`ReactNative renders and reorders children 2`] = `
"<native root> {}
View null
View {\\"title\\":\\"m\\"}
View {\\"title\\":\\"x\\"}
View {\\"title\\":\\"h\\"}
View {\\"title\\":\\"p\\"}
View {\\"title\\":\\"g\\"}
View {\\"title\\":\\"w\\"}
View {\\"title\\":\\"f\\"}
View {\\"title\\":\\"r\\"}
View {\\"title\\":\\"a\\"}
View {\\"title\\":\\"l\\"}
View {\\"title\\":\\"k\\"}
View {\\"title\\":\\"e\\"}
View {\\"title\\":\\"o\\"}
View {\\"title\\":\\"i\\"}
View {\\"title\\":\\"v\\"}
View {\\"title\\":\\"c\\"}
View {\\"title\\":\\"s\\"}
View {\\"title\\":\\"t\\"}
View {\\"title\\":\\"z\\"}
View {\\"title\\":\\"y\\"}"
RCTView null
RCTView {\\"title\\":\\"m\\"}
RCTView {\\"title\\":\\"x\\"}
RCTView {\\"title\\":\\"h\\"}
RCTView {\\"title\\":\\"p\\"}
RCTView {\\"title\\":\\"g\\"}
RCTView {\\"title\\":\\"w\\"}
RCTView {\\"title\\":\\"f\\"}
RCTView {\\"title\\":\\"r\\"}
RCTView {\\"title\\":\\"a\\"}
RCTView {\\"title\\":\\"l\\"}
RCTView {\\"title\\":\\"k\\"}
RCTView {\\"title\\":\\"e\\"}
RCTView {\\"title\\":\\"o\\"}
RCTView {\\"title\\":\\"i\\"}
RCTView {\\"title\\":\\"v\\"}
RCTView {\\"title\\":\\"c\\"}
RCTView {\\"title\\":\\"s\\"}
RCTView {\\"title\\":\\"t\\"}
RCTView {\\"title\\":\\"z\\"}
RCTView {\\"title\\":\\"y\\"}"
`;