Add web compat unit tests and fixes (#35316)

Summary:
Adds compat with W3C logical CSS properties. See https://github.com/facebook/react-native/issues/34425

This is a replacement for reverted https://github.com/facebook/react-native/issues/34590, which can no longer be imported internally.

## Changelog

[General][Added] - Added CSS logical properties.

Pull Request resolved: https://github.com/facebook/react-native/pull/35316

Test Plan: Unit test snapshots.

Reviewed By: NickGerleman

Differential Revision: D41230978

Pulled By: necolas

fbshipit-source-id: 40e93d0d697f0cb28390480ce2b4bcbce18da70a
This commit is contained in:
Nicolas Gallagher
2022-11-17 10:58:38 -08:00
committed by Facebook GitHub Bot
parent 04c286c3c2
commit 3681df2878
10 changed files with 804 additions and 101 deletions
@@ -8,6 +8,7 @@
* @format
*/
import type {AccessibilityRole} from '../../Components/View/ViewAccessibility';
import type {
MeasureInWindowOnSuccessCallback,
MeasureLayoutOnSuccessCallback,
@@ -21,7 +22,6 @@ import dismissKeyboard from '../../Utilities/dismissKeyboard';
import Platform from '../../Utilities/Platform';
import StatusBar from '../StatusBar/StatusBar';
import View from '../View/View';
import type {AccessibilityRole} from '../../Components/View/ViewAccessibility';
import AndroidDrawerLayoutNativeComponent, {
Commands,
} from './AndroidDrawerLayoutNativeComponent';
+44 -19
View File
@@ -1068,6 +1068,18 @@ const emptyFunctionThatReturnsTrue = () => true;
*
*/
function InternalTextInput(props: Props): React.Node {
const {
'aria-busy': ariaBusy,
'aria-checked': ariaChecked,
'aria-disabled': ariaDisabled,
'aria-expanded': ariaExpanded,
'aria-selected': ariaSelected,
accessibilityState,
id,
tabIndex,
...otherProps
} = props;
const inputRef = useRef<null | React.ElementRef<HostComponent<mixed>>>(null);
// Android sends a "onTextChanged" event followed by a "onSelectionChanged" event, for
@@ -1388,13 +1400,25 @@ function InternalTextInput(props: Props): React.Node {
// so omitting onBlur and onFocus pressability handlers here.
const {onBlur, onFocus, ...eventHandlers} = usePressability(config) || {};
const _accessibilityState = {
busy: props['aria-busy'] ?? props.accessibilityState?.busy,
checked: props['aria-checked'] ?? props.accessibilityState?.checked,
disabled: props['aria-disabled'] ?? props.accessibilityState?.disabled,
expanded: props['aria-expanded'] ?? props.accessibilityState?.expanded,
selected: props['aria-selected'] ?? props.accessibilityState?.selected,
};
let _accessibilityState;
if (
accessibilityState != null ||
ariaBusy != null ||
ariaChecked != null ||
ariaDisabled != null ||
ariaExpanded != null ||
ariaSelected != null
) {
_accessibilityState = {
busy: ariaBusy ?? accessibilityState?.busy,
checked: ariaChecked ?? accessibilityState?.checked,
disabled: ariaDisabled ?? accessibilityState?.disabled,
expanded: ariaExpanded ?? accessibilityState?.expanded,
selected: ariaSelected ?? accessibilityState?.selected,
};
}
let style = flattenStyle(props.style);
if (Platform.OS === 'ios') {
const RCTTextInputView =
@@ -1402,10 +1426,7 @@ function InternalTextInput(props: Props): React.Node {
? RCTMultilineTextInputView
: RCTSinglelineTextInputView;
const style =
props.multiline === true
? StyleSheet.flatten([styles.multilineInput, props.style])
: props.style;
style = props.multiline === true ? [styles.multilineInput, style] : style;
const useOnChangeSync =
(props.unstable_onChangeSync || props.unstable_onChangeTextSync) &&
@@ -1415,15 +1436,16 @@ function InternalTextInput(props: Props): React.Node {
<RCTTextInputView
// $FlowFixMe[incompatible-type] - Figure out imperative + forward refs.
ref={ref}
{...props}
{...otherProps}
{...eventHandlers}
accessible={accessible}
accessibilityState={_accessibilityState}
accessible={accessible}
submitBehavior={submitBehavior}
caretHidden={caretHidden}
dataDetectorTypes={props.dataDetectorTypes}
focusable={focusable}
focusable={tabIndex !== undefined ? !tabIndex : focusable}
mostRecentEventCount={mostRecentEventCount}
nativeID={id ?? props.nativeID}
onBlur={_onBlur}
onKeyPressSync={props.unstable_onKeyPressSync}
onChange={_onChange}
@@ -1439,7 +1461,6 @@ function InternalTextInput(props: Props): React.Node {
/>
);
} else if (Platform.OS === 'android') {
const style = [props.style];
const autoCapitalize = props.autoCapitalize || 'sentences';
const _accessibilityLabelledBy =
props?.['aria-labelledby'] ?? props?.accessibilityLabelledBy;
@@ -1466,18 +1487,19 @@ function InternalTextInput(props: Props): React.Node {
<AndroidTextInput
// $FlowFixMe[incompatible-type] - Figure out imperative + forward refs.
ref={ref}
{...props}
{...otherProps}
{...eventHandlers}
accessible={accessible}
accessibilityState={_accessibilityState}
accessibilityLabelledBy={_accessibilityLabelledBy}
accessible={accessible}
autoCapitalize={autoCapitalize}
submitBehavior={submitBehavior}
caretHidden={caretHidden}
children={children}
disableFullscreenUI={props.disableFullscreenUI}
focusable={focusable}
focusable={tabIndex !== undefined ? !tabIndex : focusable}
mostRecentEventCount={mostRecentEventCount}
nativeID={id ?? props.nativeID}
numberOfLines={props.rows ?? props.numberOfLines}
onBlur={_onBlur}
onChange={_onChange}
@@ -1606,11 +1628,12 @@ const ExportedForwardRef: React.AbstractComponent<
},
forwardedRef: ReactRefSetter<TextInputInstance>,
) {
const style = flattenStyle(restProps.style);
let style = flattenStyle(restProps.style);
if (style?.verticalAlign != null) {
style.textAlignVertical =
verticalAlignToTextAlignVerticalMap[style.verticalAlign];
delete style.verticalAlign;
}
return (
@@ -1650,6 +1673,8 @@ const ExportedForwardRef: React.AbstractComponent<
);
});
ExportedForwardRef.displayName = 'TextInput';
/**
* Switch to `deprecated-react-native-prop-types` for compatibility with future
* releases. This is deprecated and will be removed in the future.
@@ -179,3 +179,269 @@ describe('TextInput tests', () => {
);
});
});
describe('TextInput', () => {
it('default render', () => {
const instance = ReactTestRenderer.create(<TextInput />);
expect(instance.toJSON()).toMatchInlineSnapshot(`
<RCTSinglelineTextInputView
accessible={true}
allowFontScaling={true}
focusable={true}
forwardedRef={null}
mostRecentEventCount={0}
onBlur={[Function]}
onChange={[Function]}
onChangeSync={null}
onClick={[Function]}
onFocus={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onScroll={[Function]}
onSelectionChange={[Function]}
onSelectionChangeShouldSetResponder={[Function]}
onStartShouldSetResponder={[Function]}
rejectResponderTermination={true}
selection={null}
submitBehavior="blurAndSubmit"
text=""
underlineColorAndroid="transparent"
/>
`);
});
it('has displayName', () => {
expect(TextInput.displayName).toEqual('TextInput');
});
});
describe('TextInput compat with web', () => {
it('renders core props', () => {
const props = {
id: 'id',
tabIndex: 0,
testID: 'testID',
};
const instance = ReactTestRenderer.create(<TextInput {...props} />);
expect(instance.toJSON()).toMatchInlineSnapshot(`
<RCTSinglelineTextInputView
accessible={true}
allowFontScaling={true}
focusable={true}
forwardedRef={null}
mostRecentEventCount={0}
nativeID="id"
onBlur={[Function]}
onChange={[Function]}
onChangeSync={null}
onClick={[Function]}
onFocus={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onScroll={[Function]}
onSelectionChange={[Function]}
onSelectionChangeShouldSetResponder={[Function]}
onStartShouldSetResponder={[Function]}
rejectResponderTermination={true}
selection={null}
submitBehavior="blurAndSubmit"
testID="testID"
text=""
underlineColorAndroid="transparent"
/>
`);
});
it('renders "aria-*" props', () => {
const props = {
'aria-activedescendant': 'activedescendant',
'aria-atomic': true,
'aria-autocomplete': 'list',
'aria-busy': true,
'aria-checked': true,
'aria-columncount': 5,
'aria-columnindex': 3,
'aria-columnspan': 2,
'aria-controls': 'controls',
'aria-current': 'current',
'aria-describedby': 'describedby',
'aria-details': 'details',
'aria-disabled': true,
'aria-errormessage': 'errormessage',
'aria-expanded': true,
'aria-flowto': 'flowto',
'aria-haspopup': true,
'aria-hidden': true,
'aria-invalid': true,
'aria-keyshortcuts': 'Cmd+S',
'aria-label': 'label',
'aria-labelledby': 'labelledby',
'aria-level': 3,
'aria-live': 'polite',
'aria-modal': true,
'aria-multiline': true,
'aria-multiselectable': true,
'aria-orientation': 'portrait',
'aria-owns': 'owns',
'aria-placeholder': 'placeholder',
'aria-posinset': 5,
'aria-pressed': true,
'aria-readonly': true,
'aria-required': true,
role: 'main',
'aria-roledescription': 'roledescription',
'aria-rowcount': 5,
'aria-rowindex': 3,
'aria-rowspan': 3,
'aria-selected': true,
'aria-setsize': 5,
'aria-sort': 'ascending',
'aria-valuemax': 5,
'aria-valuemin': 0,
'aria-valuenow': 3,
'aria-valuetext': '3',
};
const instance = ReactTestRenderer.create(<TextInput {...props} />);
expect(instance.toJSON()).toMatchInlineSnapshot(`
<RCTSinglelineTextInputView
accessibilityState={
Object {
"busy": true,
"checked": true,
"disabled": true,
"expanded": true,
"selected": true,
}
}
accessible={true}
allowFontScaling={true}
aria-activedescendant="activedescendant"
aria-atomic={true}
aria-autocomplete="list"
aria-columncount={5}
aria-columnindex={3}
aria-columnspan={2}
aria-controls="controls"
aria-current="current"
aria-describedby="describedby"
aria-details="details"
aria-errormessage="errormessage"
aria-flowto="flowto"
aria-haspopup={true}
aria-hidden={true}
aria-invalid={true}
aria-keyshortcuts="Cmd+S"
aria-label="label"
aria-labelledby="labelledby"
aria-level={3}
aria-live="polite"
aria-modal={true}
aria-multiline={true}
aria-multiselectable={true}
aria-orientation="portrait"
aria-owns="owns"
aria-placeholder="placeholder"
aria-posinset={5}
aria-pressed={true}
aria-readonly={true}
aria-required={true}
aria-roledescription="roledescription"
aria-rowcount={5}
aria-rowindex={3}
aria-rowspan={3}
aria-setsize={5}
aria-sort="ascending"
aria-valuemax={5}
aria-valuemin={0}
aria-valuenow={3}
aria-valuetext="3"
focusable={true}
forwardedRef={null}
mostRecentEventCount={0}
onBlur={[Function]}
onChange={[Function]}
onChangeSync={null}
onClick={[Function]}
onFocus={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onScroll={[Function]}
onSelectionChange={[Function]}
onSelectionChangeShouldSetResponder={[Function]}
onStartShouldSetResponder={[Function]}
rejectResponderTermination={true}
role="main"
selection={null}
submitBehavior="blurAndSubmit"
text=""
underlineColorAndroid="transparent"
/>
`);
});
it('renders styles', () => {
const style = {
display: 'flex',
flex: 1,
backgroundColor: 'white',
marginInlineStart: 10,
userSelect: 'none',
verticalAlign: 'middle',
};
const instance = ReactTestRenderer.create(<TextInput style={style} />);
expect(instance.toJSON()).toMatchInlineSnapshot(`
<RCTSinglelineTextInputView
accessible={true}
allowFontScaling={true}
focusable={true}
forwardedRef={null}
mostRecentEventCount={0}
onBlur={[Function]}
onChange={[Function]}
onChangeSync={null}
onClick={[Function]}
onFocus={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onScroll={[Function]}
onSelectionChange={[Function]}
onSelectionChangeShouldSetResponder={[Function]}
onStartShouldSetResponder={[Function]}
rejectResponderTermination={true}
selection={null}
style={
Object {
"backgroundColor": "white",
"display": "flex",
"flex": 1,
"marginInlineStart": 10,
"textAlignVertical": "center",
"userSelect": "none",
}
}
submitBehavior="blurAndSubmit"
text=""
underlineColorAndroid="transparent"
/>
`);
});
});
@@ -2,15 +2,6 @@
exports[`TextInput tests should render as expected: should deep render when mocked (please verify output manually) 1`] = `
<RCTSinglelineTextInputView
accessibilityState={
Object {
"busy": undefined,
"checked": undefined,
"disabled": undefined,
"expanded": undefined,
"selected": undefined,
}
}
accessible={true}
allowFontScaling={true}
focusable={true}
@@ -40,15 +31,6 @@ exports[`TextInput tests should render as expected: should deep render when mock
exports[`TextInput tests should render as expected: should deep render when not mocked (please verify output manually) 1`] = `
<RCTSinglelineTextInputView
accessibilityState={
Object {
"busy": undefined,
"checked": undefined,
"disabled": undefined,
"expanded": undefined,
"selected": undefined,
}
}
accessible={true}
allowFontScaling={true}
focusable={true}
@@ -76,6 +58,6 @@ exports[`TextInput tests should render as expected: should deep render when not
/>
`;
exports[`TextInput tests should render as expected: should shallow render as <TextInput /> when mocked 1`] = `<ForwardRef(TextInput) />`;
exports[`TextInput tests should render as expected: should shallow render as <TextInput /> when mocked 1`] = `<TextInput />`;
exports[`TextInput tests should render as expected: should shallow render as <TextInput /> when not mocked 1`] = `<ForwardRef(TextInput) />`;
exports[`TextInput tests should render as expected: should shallow render as <TextInput /> when not mocked 1`] = `<TextInput />`;
+34 -16
View File
@@ -57,7 +57,6 @@ const View: React.AbstractComponent<
nativeID,
pointerEvents,
role,
style,
tabIndex,
...otherProps
}: ViewProps,
@@ -66,23 +65,42 @@ const View: React.AbstractComponent<
const _accessibilityLabelledBy =
ariaLabelledBy?.split(/\s*,\s*/g) ?? accessibilityLabelledBy;
const _accessibilityState = {
busy: ariaBusy ?? accessibilityState?.busy,
checked: ariaChecked ?? accessibilityState?.checked,
disabled: ariaDisabled ?? accessibilityState?.disabled,
expanded: ariaExpanded ?? accessibilityState?.expanded,
selected: ariaSelected ?? accessibilityState?.selected,
};
let _accessibilityState;
if (
accessibilityState != null ||
ariaBusy != null ||
ariaChecked != null ||
ariaDisabled != null ||
ariaExpanded != null ||
ariaSelected != null
) {
_accessibilityState = {
busy: ariaBusy ?? accessibilityState?.busy,
checked: ariaChecked ?? accessibilityState?.checked,
disabled: ariaDisabled ?? accessibilityState?.disabled,
expanded: ariaExpanded ?? accessibilityState?.expanded,
selected: ariaSelected ?? accessibilityState?.selected,
};
}
let _accessibilityValue;
if (
accessibilityValue != null ||
ariaValueMax != null ||
ariaValueMin != null ||
ariaValueNow != null ||
ariaValueText != null
) {
_accessibilityValue = {
max: ariaValueMax ?? accessibilityValue?.max,
min: ariaValueMin ?? accessibilityValue?.min,
now: ariaValueNow ?? accessibilityValue?.now,
text: ariaValueText ?? accessibilityValue?.text,
};
}
const _accessibilityValue = {
max: ariaValueMax ?? accessibilityValue?.max,
min: ariaValueMin ?? accessibilityValue?.min,
now: ariaValueNow ?? accessibilityValue?.now,
text: ariaValueText ?? accessibilityValue?.text,
};
let style = flattenStyle(otherProps.style);
const flattenedStyle = flattenStyle(style);
const newPointerEvents = flattenedStyle?.pointerEvents || pointerEvents;
const newPointerEvents = style?.pointerEvents || pointerEvents;
return (
<TextAncestor.Provider value={false}>
@@ -0,0 +1,193 @@
/**
* 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.
*
* @format
* @emails oncall+react_native
*/
'use strict';
const render = require('../../../../jest/renderer');
const React = require('../React');
const View = require('../View');
jest.unmock('../View');
jest.unmock('../ViewNativeComponent');
describe('View', () => {
it('default render', () => {
const instance = render.create(<View />);
expect(instance.toJSON()).toMatchInlineSnapshot(`<RCTView />`);
});
it('has displayName', () => {
expect(View.displayName).toEqual('View');
});
});
describe('View compat with web', () => {
it('renders core props', () => {
const props = {
id: 'id',
tabIndex: 0,
testID: 'testID',
};
const instance = render.create(<View {...props} />);
expect(instance.toJSON()).toMatchInlineSnapshot(`
<RCTView
focusable={true}
nativeID="id"
testID="testID"
/>
`);
});
it('renders "aria-*" props', () => {
const props = {
'aria-activedescendant': 'activedescendant',
'aria-atomic': true,
'aria-autocomplete': 'list',
'aria-busy': true,
'aria-checked': true,
'aria-columncount': 5,
'aria-columnindex': 3,
'aria-columnspan': 2,
'aria-controls': 'controls',
'aria-current': 'current',
'aria-describedby': 'describedby',
'aria-details': 'details',
'aria-disabled': true,
'aria-errormessage': 'errormessage',
'aria-expanded': true,
'aria-flowto': 'flowto',
'aria-haspopup': true,
'aria-hidden': true,
'aria-invalid': true,
'aria-keyshortcuts': 'Cmd+S',
'aria-label': 'label',
'aria-labelledby': 'labelledby',
'aria-level': 3,
'aria-live': 'polite',
'aria-modal': true,
'aria-multiline': true,
'aria-multiselectable': true,
'aria-orientation': 'portrait',
'aria-owns': 'owns',
'aria-placeholder': 'placeholder',
'aria-posinset': 5,
'aria-pressed': true,
'aria-readonly': true,
'aria-required': true,
role: 'main',
'aria-roledescription': 'roledescription',
'aria-rowcount': 5,
'aria-rowindex': 3,
'aria-rowspan': 3,
'aria-selected': true,
'aria-setsize': 5,
'aria-sort': 'ascending',
'aria-valuemax': 5,
'aria-valuemin': 0,
'aria-valuenow': 3,
'aria-valuetext': '3',
};
const instance = render.create(<View {...props} />);
expect(instance.toJSON()).toMatchInlineSnapshot(`
<RCTView
accessibilityElementsHidden={true}
accessibilityLabel="label"
accessibilityLabelledBy={
Array [
"labelledby",
]
}
accessibilityLiveRegion="polite"
accessibilityState={
Object {
"busy": true,
"checked": true,
"disabled": true,
"expanded": true,
"selected": true,
}
}
accessibilityValue={
Object {
"max": 5,
"min": 0,
"now": 3,
"text": "3",
}
}
aria-activedescendant="activedescendant"
aria-atomic={true}
aria-autocomplete="list"
aria-columncount={5}
aria-columnindex={3}
aria-columnspan={2}
aria-controls="controls"
aria-current="current"
aria-describedby="describedby"
aria-details="details"
aria-errormessage="errormessage"
aria-flowto="flowto"
aria-haspopup={true}
aria-invalid={true}
aria-keyshortcuts="Cmd+S"
aria-level={3}
aria-modal={true}
aria-multiline={true}
aria-multiselectable={true}
aria-orientation="portrait"
aria-owns="owns"
aria-placeholder="placeholder"
aria-posinset={5}
aria-pressed={true}
aria-readonly={true}
aria-required={true}
aria-roledescription="roledescription"
aria-rowcount={5}
aria-rowindex={3}
aria-rowspan={3}
aria-setsize={5}
aria-sort="ascending"
importantForAccessibility="no-hide-descendants"
/>
`);
});
it('renders styles', () => {
const style = {
display: 'flex',
flex: 1,
backgroundColor: 'white',
marginInlineStart: 10,
pointerEvents: 'none',
};
const instance = render.create(<View style={style} />);
expect(instance.toJSON()).toMatchInlineSnapshot(`
<RCTView
pointerEvents="none"
style={
Object {
"backgroundColor": "white",
"display": "flex",
"flex": 1,
"marginInlineStart": 10,
"pointerEvents": "none",
}
}
/>
`);
});
});
+1 -1
View File
@@ -158,13 +158,13 @@ const BaseImage = (props: ImagePropsType, forwardedRef) => {
const {width = props.width, height = props.height, uri} = source;
style = flattenStyle([{width, height}, styles.base, props.style]);
sources = [source];
if (uri === '') {
console.warn('source.uri should not be an empty string');
}
}
const {height, width, ...restProps} = props;
const {onLoadStart, onLoad, onLoadEnd, onError} = props;
const nativeProps = {
...restProps,
+5 -3
View File
@@ -199,7 +199,7 @@ type ____LayoutStyle_Internal = $ReadOnly<{
marginBlockStart?: DimensionValue,
/** `marginBottom` works like `margin-bottom` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/margin-bottom
* See https://developer.mozilla.org/en-US/docs/Web/CSS/margin-block-start
* for more details.
*/
marginBottom?: DimensionValue,
@@ -270,17 +270,19 @@ type ____LayoutStyle_Internal = $ReadOnly<{
/** Setting `paddingBlock` is like setting both of
* `paddingTop` and `paddingBottom`.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/padding-block
* for more details.
*/
paddingBlock?: DimensionValue,
/** `paddingBlockEnd` works like `padding-bottom` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/padding-bottom
* See https://developer.mozilla.org/en-US/docs/Web/CSS/padding-block-end
* for more details.
*/
paddingBlockEnd?: DimensionValue,
/** `paddingBlockStart` works like `padding-top` in CSS.
* See https://developer.mozilla.org/en-US/docs/Web/CSS/padding-top
* See https://developer.mozilla.org/en-US/docs/Web/CSS/padding-block-start
* for more details.
*/
paddingBlockStart?: DimensionValue,
+49 -41
View File
@@ -9,17 +9,16 @@
*/
import type {PressEvent} from '../Types/CoreEventTypes';
import type {TextProps} from './TextProps';
import * as PressabilityDebug from '../Pressability/PressabilityDebug';
import usePressability from '../Pressability/usePressability';
import flattenStyle from '../StyleSheet/flattenStyle';
import processColor from '../StyleSheet/processColor';
import StyleSheet from '../StyleSheet/StyleSheet';
import {getAccessibilityRoleFromRole} from '../Utilities/AcessibilityMapping';
import Platform from '../Utilities/Platform';
import TextAncestor from './TextAncestor';
import {NativeText, NativeVirtualText} from './TextNativeComponent';
import {type TextProps} from './TextProps';
import * as React from 'react';
import {useContext, useMemo, useState} from 'react';
@@ -36,6 +35,7 @@ const Text: React.AbstractComponent<
accessible,
accessibilityLabel,
accessibilityRole,
accessibilityState,
allowFontScaling,
'aria-busy': ariaBusy,
'aria-checked': ariaChecked,
@@ -64,13 +64,23 @@ const Text: React.AbstractComponent<
const [isHighlighted, setHighlighted] = useState(false);
const _accessibilityState = {
busy: ariaBusy ?? props.accessibilityState?.busy,
checked: ariaChecked ?? props.accessibilityState?.checked,
disabled: ariaDisabled ?? props.accessibilityState?.disabled,
expanded: ariaExpanded ?? props.accessibilityState?.expanded,
selected: ariaSelected ?? props.accessibilityState?.selected,
};
let _accessibilityState;
if (
accessibilityState != null ||
ariaBusy != null ||
ariaChecked != null ||
ariaDisabled != null ||
ariaExpanded != null ||
ariaSelected != null
) {
_accessibilityState = {
busy: ariaBusy ?? accessibilityState?.busy,
checked: ariaChecked ?? accessibilityState?.checked,
disabled: ariaDisabled ?? accessibilityState?.disabled,
expanded: ariaExpanded ?? accessibilityState?.expanded,
selected: ariaSelected ?? accessibilityState?.selected,
};
}
const _disabled =
restProps.disabled != null
@@ -174,25 +184,11 @@ const Text: React.AbstractComponent<
? null
: processColor(restProps.selectionColor);
let style = flattenStyle(restProps.style);
let _selectable = restProps.selectable;
if (style?.userSelect != null) {
_selectable = userSelectToSelectableMap[style.userSelect];
}
if (style?.verticalAlign != null) {
style = StyleSheet.compose(style, {
textAlignVertical:
verticalAlignToTextAlignVerticalMap[style.verticalAlign],
});
}
let style = restProps.style;
if (__DEV__) {
if (PressabilityDebug.isEnabled() && onPress != null) {
style = StyleSheet.compose(restProps.style, {
color: 'magenta',
});
style = [restProps.style, {color: 'magenta'}];
}
}
@@ -211,10 +207,22 @@ const Text: React.AbstractComponent<
default: accessible,
});
let flattenedStyle = flattenStyle(style);
style = flattenStyle(style);
if (typeof flattenedStyle?.fontWeight === 'number') {
flattenedStyle.fontWeight = flattenedStyle?.fontWeight.toString();
if (typeof style?.fontWeight === 'number') {
style.fontWeight = style?.fontWeight.toString();
}
let _selectable = restProps.selectable;
if (style?.userSelect != null) {
_selectable = userSelectToSelectableMap[style.userSelect];
delete style.userSelect;
}
if (style?.verticalAlign != null) {
style.textAlignVertical =
verticalAlignToTextAlignVerticalMap[style.verticalAlign];
delete style.verticalAlign;
}
const _hasOnPressOrOnLongPress =
@@ -223,46 +231,46 @@ const Text: React.AbstractComponent<
return hasTextAncestor ? (
<NativeVirtualText
{...restProps}
accessibilityState={_accessibilityState}
{...eventHandlersForText}
accessibilityLabel={ariaLabel ?? accessibilityLabel}
accessibilityRole={
role ? getAccessibilityRoleFromRole(role) : accessibilityRole
}
accessibilityState={_accessibilityState}
isHighlighted={isHighlighted}
isPressable={isPressable}
selectable={_selectable}
nativeID={id ?? nativeID}
numberOfLines={numberOfLines}
selectionColor={selectionColor}
style={flattenedStyle}
ref={forwardedRef}
selectable={_selectable}
selectionColor={selectionColor}
style={style}
/>
) : (
<TextAncestor.Provider value={true}>
<NativeText
{...restProps}
{...eventHandlersForText}
disabled={_disabled}
selectable={_selectable}
accessibilityLabel={ariaLabel ?? accessibilityLabel}
accessibilityRole={
role ? getAccessibilityRoleFromRole(role) : accessibilityRole
}
accessibilityState={nativeTextAccessibilityState}
accessible={
accessible == null && Platform.OS === 'android'
? _hasOnPressOrOnLongPress
: _accessible
}
accessibilityLabel={ariaLabel ?? accessibilityLabel}
accessibilityState={nativeTextAccessibilityState}
accessibilityRole={
role ? getAccessibilityRoleFromRole(role) : accessibilityRole
}
allowFontScaling={allowFontScaling !== false}
disabled={_disabled}
ellipsizeMode={ellipsizeMode ?? 'tail'}
isHighlighted={isHighlighted}
nativeID={id ?? nativeID}
numberOfLines={numberOfLines}
selectionColor={selectionColor}
style={flattenedStyle}
ref={forwardedRef}
selectable={_selectable}
selectionColor={selectionColor}
style={style}
/>
</TextAncestor.Provider>
);
+209
View File
@@ -0,0 +1,209 @@
/**
* 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.
*
* @format
* @emails oncall+react_native
*/
'use strict';
const render = require('../../../jest/renderer');
const React = require('../React');
const Text = require('../Text');
jest.unmock('../Text');
jest.unmock('../TextNativeComponent');
describe('Text', () => {
it('default render', () => {
const instance = render.create(<Text />);
expect(instance.toJSON()).toMatchInlineSnapshot(`
<RCTText
accessible={true}
allowFontScaling={true}
ellipsizeMode="tail"
isHighlighted={false}
selectionColor={null}
/>
`);
});
it('has displayName', () => {
expect(Text.displayName).toEqual('Text');
});
});
describe('Text compat with web', () => {
it('renders core props', () => {
const props = {
id: 'id',
tabIndex: 0,
testID: 'testID',
};
const instance = render.create(<Text {...props} />);
expect(instance.toJSON()).toMatchInlineSnapshot(`
<RCTText
accessible={true}
allowFontScaling={true}
ellipsizeMode="tail"
isHighlighted={false}
nativeID="id"
selectionColor={null}
tabIndex={0}
testID="testID"
/>
`);
});
it('renders "aria-*" props', () => {
const props = {
'aria-activedescendant': 'activedescendant',
'aria-atomic': true,
'aria-autocomplete': 'list',
'aria-busy': true,
'aria-checked': true,
'aria-columncount': 5,
'aria-columnindex': 3,
'aria-columnspan': 2,
'aria-controls': 'controls',
'aria-current': 'current',
'aria-describedby': 'describedby',
'aria-details': 'details',
'aria-disabled': true,
'aria-errormessage': 'errormessage',
'aria-expanded': true,
'aria-flowto': 'flowto',
'aria-haspopup': true,
'aria-hidden': true,
'aria-invalid': true,
'aria-keyshortcuts': 'Cmd+S',
'aria-label': 'label',
'aria-labelledby': 'labelledby',
'aria-level': 3,
'aria-live': 'polite',
'aria-modal': true,
'aria-multiline': true,
'aria-multiselectable': true,
'aria-orientation': 'portrait',
'aria-owns': 'owns',
'aria-placeholder': 'placeholder',
'aria-posinset': 5,
'aria-pressed': true,
'aria-readonly': true,
'aria-required': true,
role: 'main',
'aria-roledescription': 'roledescription',
'aria-rowcount': 5,
'aria-rowindex': 3,
'aria-rowspan': 3,
'aria-selected': true,
'aria-setsize': 5,
'aria-sort': 'ascending',
'aria-valuemax': 5,
'aria-valuemin': 0,
'aria-valuenow': 3,
'aria-valuetext': '3',
};
const instance = render.create(<Text {...props} />);
expect(instance.toJSON()).toMatchInlineSnapshot(`
<RCTText
accessibilityLabel="label"
accessibilityState={
Object {
"busy": true,
"checked": true,
"disabled": true,
"expanded": true,
"selected": true,
}
}
accessible={true}
allowFontScaling={true}
aria-activedescendant="activedescendant"
aria-atomic={true}
aria-autocomplete="list"
aria-columncount={5}
aria-columnindex={3}
aria-columnspan={2}
aria-controls="controls"
aria-current="current"
aria-describedby="describedby"
aria-details="details"
aria-errormessage="errormessage"
aria-flowto="flowto"
aria-haspopup={true}
aria-hidden={true}
aria-invalid={true}
aria-keyshortcuts="Cmd+S"
aria-labelledby="labelledby"
aria-level={3}
aria-live="polite"
aria-modal={true}
aria-multiline={true}
aria-multiselectable={true}
aria-orientation="portrait"
aria-owns="owns"
aria-placeholder="placeholder"
aria-posinset={5}
aria-pressed={true}
aria-readonly={true}
aria-required={true}
aria-roledescription="roledescription"
aria-rowcount={5}
aria-rowindex={3}
aria-rowspan={3}
aria-setsize={5}
aria-sort="ascending"
aria-valuemax={5}
aria-valuemin={0}
aria-valuenow={3}
aria-valuetext="3"
disabled={true}
ellipsizeMode="tail"
isHighlighted={false}
selectionColor={null}
/>
`);
});
it('renders styles', () => {
const style = {
display: 'flex',
flex: 1,
backgroundColor: 'white',
marginInlineStart: 10,
userSelect: 'none',
verticalAlign: 'middle',
};
const instance = render.create(<Text style={style} />);
expect(instance.toJSON()).toMatchInlineSnapshot(`
<RCTText
accessible={true}
allowFontScaling={true}
ellipsizeMode="tail"
isHighlighted={false}
selectable={false}
selectionColor={null}
style={
Object {
"backgroundColor": "white",
"display": "flex",
"flex": 1,
"marginInlineStart": 10,
"textAlignVertical": "center",
}
}
/>
`);
});
});