From 83e5cdd369de637bbf9ea20d93cebc9984efaabb Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Tue, 11 May 2021 01:21:47 -0700 Subject: [PATCH] Fix accessibility when entire text node is a link Summary: Changelog: [internal] Fix accessibility when entire text node is a link Reviewed By: JoshuaGross Differential Revision: D28325749 fbshipit-source-id: 9ac68b802f13d028b5cdb6cae7bdae5f4924fc07 --- ...ParagraphComponentAccessibilityProvider.mm | 12 ++-- .../Text/RCTParagraphComponentViewTests.mm | 63 +++++++++++++++++++ 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentAccessibilityProvider.mm b/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentAccessibilityProvider.mm index 6d456280cba..1048679265d 100644 --- a/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentAccessibilityProvider.mm +++ b/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentAccessibilityProvider.mm @@ -57,15 +57,15 @@ using namespace facebook::react; // build an array of the accessibleElements NSMutableArray *elements = [NSMutableArray new]; - NSString *accessibilityLabel = [_view valueForKey:@"accessibilityLabel"]; - if (!accessibilityLabel.length) { + NSString *accessibilityLabel = _view.accessibilityLabel; + if (accessibilityLabel.length == 0) { accessibilityLabel = RCTNSStringFromString(_attributedString.getString()); } // add first element has the text for the whole textview in order to read out the whole text RCTAccessibilityElement *firstElement = [[RCTAccessibilityElement alloc] initWithAccessibilityContainer:_view.superview]; firstElement.isAccessibilityElement = YES; - firstElement.accessibilityTraits = UIAccessibilityTraitStaticText; + firstElement.accessibilityTraits = _view.accessibilityTraits; firstElement.accessibilityLabel = accessibilityLabel; firstElement.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(_view.bounds, _view); [firstElement setAccessibilityActivationPoint:CGPointMake( @@ -80,7 +80,11 @@ using namespace facebook::react; enumerateAttribute:RCTTextAttributesAccessibilityRoleAttributeName frame:_frame usingBlock:^(CGRect fragmentRect, NSString *_Nonnull fragmentText, NSString *value) { - if (![value isEqualToString:@"button"] && ![value isEqualToString:@"link"]) { + if ([fragmentText isEqualToString:firstElement.accessibilityLabel]) { + // The fragment is the entire paragraph. This is handled as `firstElement`. + return; + } + if ((![value isEqualToString:@"button"] && ![value isEqualToString:@"link"])) { return; } if ([value isEqualToString:@"button"] && diff --git a/React/Tests/Text/RCTParagraphComponentViewTests.mm b/React/Tests/Text/RCTParagraphComponentViewTests.mm index 4bc04029269..e847ef76f2f 100644 --- a/React/Tests/Text/RCTParagraphComponentViewTests.mm +++ b/React/Tests/Text/RCTParagraphComponentViewTests.mm @@ -404,4 +404,67 @@ static ParagraphShadowNode::ConcreteState::Shared stateWithShadowNode( @"Expected the second accessibilityElement has link trait"); } +- (void)testEntireParagraphLink +{ + std::shared_ptr rootShadowNode; + std::shared_ptr paragrahShadowNode; + + auto element = Element() + .reference(rootShadowNode) + .tag(1) + .props([] { + auto sharedProps = std::make_shared(); + auto &props = *sharedProps; + props.layoutConstraints = LayoutConstraints{{0, 0}, {500, 500}}; + auto &yogaStyle = props.yogaStyle; + yogaStyle.dimensions()[YGDimensionWidth] = YGValue{200, YGUnitPoint}; + yogaStyle.dimensions()[YGDimensionHeight] = YGValue{200, YGUnitPoint}; + return sharedProps; + }) + .children({ + Element() + .reference(paragrahShadowNode) + .props([] { + auto sharedProps = std::make_shared(); + auto &props = *sharedProps; + props.accessible = true; + props.accessibilityTraits = AccessibilityTraits::Link; + auto &yogaStyle = props.yogaStyle; + yogaStyle.positionType() = YGPositionTypeAbsolute; + yogaStyle.position()[YGEdgeLeft] = YGValue{0, YGUnitPoint}; + yogaStyle.position()[YGEdgeTop] = YGValue{0, YGUnitPoint}; + yogaStyle.dimensions()[YGDimensionWidth] = YGValue{200, YGUnitPoint}; + yogaStyle.dimensions()[YGDimensionHeight] = YGValue{20, YGUnitPoint}; + return sharedProps; + }) + .children({ + Element() + .props([] { + auto sharedProps = std::make_shared(); + auto &props = *sharedProps; + props.textAttributes.accessibilityRole = AccessibilityRole::Link; + return sharedProps; + }) + .children({Element().reference(RawTextShadowNodeABA_).props([] { + auto sharedProps = std::make_shared(); + auto &props = *sharedProps; + props.text = "A long text that happens to be a link"; + return sharedProps; + })}), + }), + }); + + builder_->build(element); + rootShadowNode->layoutIfNeeded(); + + ParagraphShadowNode::ConcreteState::Shared _state = stateWithShadowNode(paragrahShadowNode); + RCTParagraphComponentView *paragraphComponentView = [[RCTParagraphComponentView alloc] init]; + [paragraphComponentView updateProps:paragrahShadowNode->getProps() oldProps:nullptr]; + [paragraphComponentView updateState:_state oldState:nil]; + + NSArray *elements = paragraphComponentView.accessibilityElements; + XCTAssertEqual(elements.count, 1); + XCTAssertTrue(elements[0].accessibilityTraits & UIAccessibilityTraitLink); +} + @end