+ `); + }); + + it('renders styles', () => { + const style = { + display: 'flex', + flex: 1, + backgroundColor: 'white', + marginInlineStart: 10, + userSelect: 'none', + verticalAlign: 'middle', + }; + + const instance = ReactTestRenderer.create(); + + expect(instance.toJSON()).toMatchInlineSnapshot(` + + `); + }); +}); diff --git a/Libraries/Components/TextInput/__tests__/__snapshots__/TextInput-test.js.snap b/Libraries/Components/TextInput/__tests__/__snapshots__/TextInput-test.js.snap index 033943923caa09..4f5e8f32b2e48e 100644 --- a/Libraries/Components/TextInput/__tests__/__snapshots__/TextInput-test.js.snap +++ b/Libraries/Components/TextInput/__tests__/__snapshots__/TextInput-test.js.snap @@ -2,15 +2,6 @@ exports[`TextInput tests should render as expected: should deep render when mocked (please verify output manually) 1`] = ` `; -exports[`TextInput tests should render as expected: should shallow render as when mocked 1`] = ``; +exports[`TextInput tests should render as expected: should shallow render as when mocked 1`] = ``; -exports[`TextInput tests should render as expected: should shallow render as when not mocked 1`] = ``; +exports[`TextInput tests should render as expected: should shallow render as when not mocked 1`] = ``; diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js index 8ea48f9673f849..8095d2703db9af 100644 --- a/Libraries/Components/View/View.js +++ b/Libraries/Components/View/View.js @@ -66,20 +66,38 @@ 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, - }; - - const _accessibilityValue = { - max: ariaValueMax ?? accessibilityValue?.max, - min: ariaValueMin ?? accessibilityValue?.min, - now: ariaValueNow ?? accessibilityValue?.now, - text: ariaValueText ?? accessibilityValue?.text, - }; + 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, + }; + } let style = flattenStyle(otherProps.style); style = processLayoutProps(style); diff --git a/Libraries/Components/View/__tests__/View-test.js b/Libraries/Components/View/__tests__/View-test.js new file mode 100644 index 00000000000000..f0e24d1189401c --- /dev/null +++ b/Libraries/Components/View/__tests__/View-test.js @@ -0,0 +1,199 @@ +/** + * 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(); + + expect(instance.toJSON()).toMatchInlineSnapshot(` + + `); + }); + + 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(); + + expect(instance.toJSON()).toMatchInlineSnapshot(` + + `); + }); + + 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(); + + expect(instance.toJSON()).toMatchInlineSnapshot(` + + `); + }); + + it('renders styles', () => { + const style = { + display: 'flex', + flex: 1, + backgroundColor: 'white', + marginInlineStart: 10, + pointerEvents: 'none', + }; + + const instance = render.create(); + + expect(instance.toJSON()).toMatchInlineSnapshot(` + + `); + }); +}); diff --git a/Libraries/StyleSheet/__tests__/processStyles-test.js b/Libraries/StyleSheet/__tests__/processStyles-test.js index 0e4f45c2f7dfd1..e98cc55050da09 100644 --- a/Libraries/StyleSheet/__tests__/processStyles-test.js +++ b/Libraries/StyleSheet/__tests__/processStyles-test.js @@ -15,6 +15,7 @@ const processLayoutProps = require('../processStyles'); describe('processLayoutProps', () => { it('it should map layout style properties', () => { const style = { + backgroundColor: 'white', marginInlineStart: 10, marginInlineEnd: 20, marginBlockStart: 30, @@ -27,33 +28,27 @@ describe('processLayoutProps', () => { paddingBlockEnd: 100, paddingBlock: 110, paddingInline: 120, + verticalAlign: 'middle', }; const processedStyle = processLayoutProps(style); - expect(processedStyle.marginStart).toBe(10); - expect(processedStyle.marginEnd).toBe(20); - expect(processedStyle.marginTop).toBe(30); - expect(processedStyle.marginBottom).toBe(40); - expect(processedStyle.marginVertical).toBe(50); - expect(processedStyle.marginHorizontal).toBe(60); - expect(processedStyle.paddingStart).toBe(70); - expect(processedStyle.paddingEnd).toBe(80); - expect(processedStyle.paddingTop).toBe(90); - expect(processedStyle.paddingBottom).toBe(100); - expect(processedStyle.paddingVertical).toBe(110); - expect(processedStyle.paddingHorizontal).toBe(120); - - expect(processedStyle.marginInlineStart).toBe(undefined); - expect(processedStyle.marginInlineEnd).toBe(undefined); - expect(processedStyle.marginBlockStart).toBe(undefined); - expect(processedStyle.marginBlockEnd).toBe(undefined); - expect(processedStyle.marginBlock).toBe(undefined); - expect(processedStyle.marginInline).toBe(undefined); - expect(processedStyle.paddingInlineStart).toBe(undefined); - expect(processedStyle.paddingInlineEnd).toBe(undefined); - expect(processedStyle.paddingBlockStart).toBe(undefined); - expect(processedStyle.paddingBlockEnd).toBe(undefined); - expect(processedStyle.paddingBlock).toBe(undefined); - expect(processedStyle.paddingInline).toBe(undefined); + expect(processedStyle).toMatchInlineSnapshot(` + Object { + "backgroundColor": "white", + "marginBottom": 40, + "marginEnd": 20, + "marginHorizontal": 60, + "marginStart": 10, + "marginTop": 30, + "marginVertical": 50, + "paddingBottom": 100, + "paddingEnd": 80, + "paddingHorizontal": 120, + "paddingStart": 70, + "paddingTop": 90, + "paddingVertical": 110, + "textAlignVertical": "center", + } + `); }); it('should override style properties', () => { diff --git a/Libraries/StyleSheet/processStyles.js b/Libraries/StyleSheet/processStyles.js index 91c210a6e91da1..9bb01cf6a53bb0 100644 --- a/Libraries/StyleSheet/processStyles.js +++ b/Libraries/StyleSheet/processStyles.js @@ -12,29 +12,45 @@ import type {____FlattenStyleProp_Internal} from './StyleSheetTypes'; -function processLayoutProps( +const propMap = { + marginInlineStart: 'marginStart', + marginInlineEnd: 'marginEnd', + marginBlockStart: 'marginTop', + marginBlockEnd: 'marginBottom', + marginBlock: 'marginVertical', + marginInline: 'marginHorizontal', + paddingInlineStart: 'paddingStart', + paddingInlineEnd: 'paddingEnd', + paddingBlockStart: 'paddingTop', + paddingBlockEnd: 'paddingBottom', + paddingBlock: 'paddingVertical', + paddingInline: 'paddingHorizontal', + verticalAlign: 'textAlignVertical', +}; + +const verticalAlignValueMap = { + auto: 'auto', + top: 'top', + bottom: 'bottom', + middle: 'center', +}; + +function processStyles( flattenedStyle: ____FlattenStyleProp_Internal, ): ____FlattenStyleProp_Internal { const _flattenedStyle = {...flattenedStyle}; - const layoutPropMap = { - marginInlineStart: 'marginStart', - marginInlineEnd: 'marginEnd', - marginBlockStart: 'marginTop', - marginBlockEnd: 'marginBottom', - marginBlock: 'marginVertical', - marginInline: 'marginHorizontal', - paddingInlineStart: 'paddingStart', - paddingInlineEnd: 'paddingEnd', - paddingBlockStart: 'paddingTop', - paddingBlockEnd: 'paddingBottom', - paddingBlock: 'paddingVertical', - paddingInline: 'paddingHorizontal', - }; - if (_flattenedStyle) { - Object.keys(layoutPropMap).forEach(key => { - if (_flattenedStyle && _flattenedStyle[key] !== undefined) { - _flattenedStyle[layoutPropMap[key]] = _flattenedStyle[key]; + + if (_flattenedStyle != null) { + Object.keys(_flattenedStyle).forEach(key => { + const alt = propMap[key]; + const originalValue = _flattenedStyle[key]; + let _value = originalValue; + if (key === 'verticalAlign') { + _value = verticalAlignValueMap[originalValue]; + } + if (alt != null) { delete _flattenedStyle[key]; + _flattenedStyle[alt] = _value; } }); } @@ -42,4 +58,4 @@ function processLayoutProps( return _flattenedStyle; } -module.exports = processLayoutProps; +module.exports = processStyles; diff --git a/Libraries/Text/Text.js b/Libraries/Text/Text.js index 1fca19fdada83f..2c92762b32d8d1 100644 --- a/Libraries/Text/Text.js +++ b/Libraries/Text/Text.js @@ -15,8 +15,7 @@ import * as PressabilityDebug from '../Pressability/PressabilityDebug'; import usePressability from '../Pressability/usePressability'; import flattenStyle from '../StyleSheet/flattenStyle'; import processColor from '../StyleSheet/processColor'; -import processLayoutProps from '../StyleSheet/processStyles'; -import StyleSheet from '../StyleSheet/StyleSheet'; +import processStyles from '../StyleSheet/processStyles'; import {getAccessibilityRoleFromRole} from '../Utilities/AcessibilityMapping'; import Platform from '../Utilities/Platform'; import TextAncestor from './TextAncestor'; @@ -37,6 +36,7 @@ const Text: React.AbstractComponent< accessible, accessibilityLabel, accessibilityRole, + accessibilityState, allowFontScaling, 'aria-busy': ariaBusy, 'aria-checked': ariaChecked, @@ -65,13 +65,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 @@ -175,16 +185,12 @@ const Text: React.AbstractComponent< ? null : processColor(restProps.selectionColor); - let style; + let style = restProps.style; if (__DEV__) { if (PressabilityDebug.isEnabled() && onPress != null) { - style = StyleSheet.compose(restProps.style, { - color: 'magenta', - }); + style = [restProps.style, {color: 'magenta'}]; } - } else { - style = restProps.style; } let numberOfLines = restProps.numberOfLines; @@ -203,7 +209,7 @@ const Text: React.AbstractComponent< }); style = flattenStyle(style); - style = processLayoutProps(style); + style = processStyles(style); if (typeof style?.fontWeight === 'number') { style.fontWeight = style?.fontWeight.toString(); @@ -214,59 +220,52 @@ const Text: React.AbstractComponent< _selectable = userSelectToSelectableMap[style.userSelect]; } - if (style?.verticalAlign != null) { - style = StyleSheet.compose(style, { - textAlignVertical: - verticalAlignToTextAlignVerticalMap[style.verticalAlign], - }); - } - const _hasOnPressOrOnLongPress = props.onPress != null || props.onLongPress != null; return hasTextAncestor ? ( ) : ( ); @@ -301,11 +300,4 @@ const userSelectToSelectableMap = { all: true, }; -const verticalAlignToTextAlignVerticalMap = { - auto: 'auto', - top: 'top', - bottom: 'bottom', - middle: 'center', -}; - module.exports = Text; diff --git a/Libraries/Text/__tests__/Text-test.js b/Libraries/Text/__tests__/Text-test.js new file mode 100644 index 00000000000000..2aa7f1e8b92d60 --- /dev/null +++ b/Libraries/Text/__tests__/Text-test.js @@ -0,0 +1,213 @@ +/** + * 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(); + + expect(instance.toJSON()).toMatchInlineSnapshot(` + + `); + }); + + 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(); + + expect(instance.toJSON()).toMatchInlineSnapshot(` + + `); + }); + + 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(); + + expect(instance.toJSON()).toMatchInlineSnapshot(` + + `); + }); + + it('renders styles', () => { + const style = { + display: 'flex', + flex: 1, + backgroundColor: 'white', + marginInlineStart: 10, + userSelect: 'none', + verticalAlign: 'middle', + }; + + const instance = render.create(); + + expect(instance.toJSON()).toMatchInlineSnapshot(` + + `); + }); +});