UNPKG

@testing-library/react-native

Version:

Simple and complete React Native testing utilities that encourage good testing practices.

294 lines (277 loc) 10 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.accessibilityValueKeys = exports.accessibilityStateKeys = void 0; exports.computeAccessibleName = computeAccessibleName; exports.computeAriaBusy = computeAriaBusy; exports.computeAriaChecked = computeAriaChecked; exports.computeAriaDisabled = computeAriaDisabled; exports.computeAriaExpanded = computeAriaExpanded; exports.computeAriaLabel = computeAriaLabel; exports.computeAriaModal = computeAriaModal; exports.computeAriaSelected = computeAriaSelected; exports.computeAriaValue = computeAriaValue; exports.getRole = getRole; exports.isAccessibilityElement = isAccessibilityElement; exports.isHiddenFromAccessibility = isHiddenFromAccessibility; exports.isInaccessible = void 0; exports.normalizeRole = normalizeRole; exports.rolesSupportingCheckedState = void 0; var _reactNative = require("react-native"); var _componentTree = require("./component-tree"); var _findAll = require("./find-all"); var _hostComponentNames = require("./host-component-names"); var _textContent = require("./text-content"); var _textInput = require("./text-input"); const accessibilityStateKeys = exports.accessibilityStateKeys = ['disabled', 'selected', 'checked', 'busy', 'expanded']; const accessibilityValueKeys = exports.accessibilityValueKeys = ['min', 'max', 'now', 'text']; function isHiddenFromAccessibility(instance, { cache } = {}) { if (instance == null) { return true; } let current = instance; while (current) { let isCurrentSubtreeInaccessible = cache?.get(current); if (isCurrentSubtreeInaccessible === undefined) { isCurrentSubtreeInaccessible = isSubtreeInaccessible(current); cache?.set(current, isCurrentSubtreeInaccessible); } if (isCurrentSubtreeInaccessible) { return true; } current = current.parent; } return false; } /** RTL-compatibility alias for `isHiddenFromAccessibility` */ const isInaccessible = exports.isInaccessible = isHiddenFromAccessibility; function isSubtreeInaccessible(instance) { // See: https://reactnative.dev/docs/accessibility#aria-hidden if (instance.props['aria-hidden']) { return true; } // iOS: accessibilityElementsHidden // See: https://reactnative.dev/docs/accessibility#accessibilityelementshidden-ios if (instance.props.accessibilityElementsHidden) { return true; } // Android: importantForAccessibility // See: https://reactnative.dev/docs/accessibility#importantforaccessibility-android if (instance.props.importantForAccessibility === 'no-hide-descendants') { return true; } // Note that `opacity: 0` is not treated as inaccessible on iOS const flatStyle = _reactNative.StyleSheet.flatten(instance.props.style) ?? {}; if (flatStyle.display === 'none') return true; // iOS: accessibilityViewIsModal or aria-modal // See: https://reactnative.dev/docs/accessibility#accessibilityviewismodal-ios const hostSiblings = (0, _componentTree.getInstanceSiblings)(instance); if (hostSiblings.some(sibling => computeAriaModal(sibling))) { return true; } return false; } function isAccessibilityElement(instance) { if (instance == null) { return false; } // https://github.com/facebook/react-native/blob/8dabed60f456e76a9e53273b601446f34de41fb5/packages/react-native/Libraries/Image/Image.ios.js#L172 if ((0, _hostComponentNames.isHostImage)(instance) && instance.props.alt !== undefined) { return true; } if (instance.props.accessible !== undefined) { return instance.props.accessible; } return (0, _hostComponentNames.isHostText)(instance) || (0, _hostComponentNames.isHostTextInput)(instance) || (0, _hostComponentNames.isHostSwitch)(instance); } /** * Returns the accessibility role for given element. It will return explicit * role from either `role` or `accessibilityRole` props if set. * * If explicit role is not available, it would try to return default element * role: * - `text` for `Text` elements * * In all other cases this functions returns `none`. * * @param instance * @returns */ function getRole(instance) { const explicitRole = instance.props.role ?? instance.props.accessibilityRole; if (explicitRole) { return normalizeRole(explicitRole); } if ((0, _hostComponentNames.isHostText)(instance)) { return 'text'; } // Note: host Image elements report "image" role in screen reader only on Android, but not on iOS. // It's better to require explicit role for Image elements. return 'none'; } /** * There are some duplications between (ARIA) `Role` and `AccessibilityRole` types. * Resolve them by using ARIA `Role` type where possible. * * @param role Role to normalize * @returns Normalized role */ function normalizeRole(role) { if (role === 'image') { return 'img'; } return role; } function computeAriaModal(instance) { return instance.props['aria-modal'] ?? instance.props.accessibilityViewIsModal; } function computeAriaLabel(instance) { const labelElementIds = getAriaLabelledByIds(instance); if (labelElementIds.length > 0) { const container = (0, _componentTree.getContainerInstance)(instance); const labelTexts = labelElementIds.map(labelElementId => { const labelInstance = (0, _findAll.findAll)(container, node => (0, _componentTree.isTestInstance)(node) && node.props.nativeID === labelElementId, { includeHiddenElements: true }); return labelInstance.length > 0 ? (0, _textContent.getTextContent)(labelInstance[0]) : undefined; }).filter(labelText => labelText !== undefined); if (labelTexts.length > 0) { return labelTexts.join(' ').trim().replace(/\s+/g, ' '); } } const explicitLabel = instance.props['aria-label'] ?? instance.props.accessibilityLabel; if (explicitLabel) { return explicitLabel; } //https://github.com/facebook/react-native/blob/8dabed60f456e76a9e53273b601446f34de41fb5/packages/react-native/Libraries/Image/Image.ios.js#L173 if ((0, _hostComponentNames.isHostImage)(instance) && instance.props.alt) { return instance.props.alt; } return undefined; } function getAriaLabelledByIds(instance) { const ariaLabelledBy = instance.props['aria-labelledby']; if (typeof ariaLabelledBy === 'string') { return [ariaLabelledBy]; } const accessibilityLabelledBy = instance.props.accessibilityLabelledBy; if (Array.isArray(accessibilityLabelledBy)) { return accessibilityLabelledBy; } if (typeof accessibilityLabelledBy === 'string') { return [accessibilityLabelledBy]; } return []; } // See: https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State#busy-state function computeAriaBusy({ props }) { return props['aria-busy'] ?? props.accessibilityState?.busy ?? false; } // See: https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State#checked-state function computeAriaChecked(instance) { const { props } = instance; if ((0, _hostComponentNames.isHostSwitch)(instance)) { return props.value; } const role = getRole(instance); if (!rolesSupportingCheckedState[role]) { return undefined; } return props['aria-checked'] ?? props.accessibilityState?.checked; } // See: https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State#disabled-state function computeAriaDisabled(instance) { if ((0, _hostComponentNames.isHostTextInput)(instance) && !(0, _textInput.isEditableTextInput)(instance)) { return true; } const { props } = instance; if ((0, _hostComponentNames.isHostText)(instance) && props.disabled) { return true; } return props['aria-disabled'] ?? props.accessibilityState?.disabled ?? false; } // See: https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State#expanded-state function computeAriaExpanded({ props }) { return props['aria-expanded'] ?? props.accessibilityState?.expanded; } // See: https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State#selected-state function computeAriaSelected({ props }) { return props['aria-selected'] ?? props.accessibilityState?.selected ?? false; } function computeAriaValue(instance) { const { accessibilityValue, 'aria-valuemax': ariaValueMax, 'aria-valuemin': ariaValueMin, 'aria-valuenow': ariaValueNow, 'aria-valuetext': ariaValueText } = instance.props; return { max: ariaValueMax ?? accessibilityValue?.max, min: ariaValueMin ?? accessibilityValue?.min, now: ariaValueNow ?? accessibilityValue?.now, text: ariaValueText ?? accessibilityValue?.text }; } function computeAccessibleName(instance, options) { const label = computeAriaLabel(instance); if (label) { return label; } if ((0, _hostComponentNames.isHostTextInput)(instance) && instance.props.placeholder && options?.root !== false) { return instance.props.placeholder; } const parts = []; for (const child of instance.children) { if (typeof child === 'string') { if (child) { parts.push({ text: child, isInlineText: true }); } } else { const childLabel = computeAccessibleName(child, { root: false }); if (childLabel) { parts.push({ text: childLabel, isInlineText: (0, _hostComponentNames.isHostText)(child) }); } } } return joinAccessibleNameParts(parts, { inline: (0, _hostComponentNames.isHostText)(instance) }); } function joinAccessibleNameParts(parts, options) { return parts.reduce((accessibleName, part, index) => { if (index === 0) { return part.text; } const previousPart = parts[index - 1]; const separator = options.inline && previousPart.isInlineText && part.isInlineText ? '' : ' '; return `${accessibleName}${separator}${part.text}`; }, ''); } const rolesSupportingCheckedState = exports.rolesSupportingCheckedState = { checkbox: true, radio: true, switch: true }; //# sourceMappingURL=accessibility.js.map