| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import <OCMock/OCMock.h> |
| #import <XCTest/XCTest.h> |
| |
| #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" |
| #import "flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.h" |
| |
| FLUTTER_ASSERT_ARC |
| |
| namespace flutter { |
| namespace { |
| |
| class SemanticsActionObservation { |
| public: |
| SemanticsActionObservation(int32_t observed_id, SemanticsAction observed_action) |
| : id(observed_id), action(observed_action) {} |
| |
| int32_t id; |
| SemanticsAction action; |
| }; |
| |
| class MockAccessibilityBridge : public AccessibilityBridgeIos { |
| public: |
| MockAccessibilityBridge() : observations({}) { view_ = [[UIView alloc] init]; } |
| UIView* view() const override { return view_; } |
| UIView<UITextInput>* textInputView() override { return nil; } |
| void DispatchSemanticsAction(int32_t id, SemanticsAction action) override { |
| SemanticsActionObservation observation(id, action); |
| observations.push_back(observation); |
| } |
| void DispatchSemanticsAction(int32_t id, |
| SemanticsAction action, |
| fml::MallocMapping args) override { |
| SemanticsActionObservation observation(id, action); |
| observations.push_back(observation); |
| } |
| void AccessibilityObjectDidBecomeFocused(int32_t id) override {} |
| void AccessibilityObjectDidLoseFocus(int32_t id) override {} |
| std::shared_ptr<FlutterPlatformViewsController> GetPlatformViewsController() const override { |
| return nil; |
| } |
| std::vector<SemanticsActionObservation> observations; |
| |
| private: |
| UIView* view_; |
| }; |
| } // namespace |
| } // namespace flutter |
| |
| @interface SemanticsObjectTest : XCTestCase |
| @end |
| |
| @implementation SemanticsObjectTest |
| |
| - (void)testCreate { |
| fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory( |
| new flutter::MockAccessibilityBridge()); |
| fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr(); |
| SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:0]; |
| XCTAssertNotNil(object); |
| } |
| |
| - (void)testSetChildren { |
| fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory( |
| new flutter::MockAccessibilityBridge()); |
| fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr(); |
| SemanticsObject* parent = [[SemanticsObject alloc] initWithBridge:bridge uid:0]; |
| SemanticsObject* child = [[SemanticsObject alloc] initWithBridge:bridge uid:1]; |
| parent.children = @[ child ]; |
| XCTAssertEqual(parent, child.parent); |
| parent.children = @[]; |
| XCTAssertNil(child.parent); |
| } |
| |
| - (void)testReplaceChildAtIndex { |
| fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory( |
| new flutter::MockAccessibilityBridge()); |
| fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr(); |
| SemanticsObject* parent = [[SemanticsObject alloc] initWithBridge:bridge uid:0]; |
| SemanticsObject* child1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1]; |
| SemanticsObject* child2 = [[SemanticsObject alloc] initWithBridge:bridge uid:2]; |
| parent.children = @[ child1 ]; |
| [parent replaceChildAtIndex:0 withChild:child2]; |
| XCTAssertNil(child1.parent); |
| XCTAssertEqual(parent, child2.parent); |
| XCTAssertEqualObjects(parent.children, @[ child2 ]); |
| } |
| |
| - (void)testPlainSemanticsObjectWithLabelHasStaticTextTrait { |
| fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory( |
| new flutter::MockAccessibilityBridge()); |
| fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr(); |
| flutter::SemanticsNode node; |
| node.label = "foo"; |
| FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0]; |
| [object setSemanticsNode:&node]; |
| XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitStaticText); |
| } |
| |
| - (void)testNodeWithImplicitScrollIsAnAccessibilityElementWhenItisHidden { |
| fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory( |
| new flutter::MockAccessibilityBridge()); |
| fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr(); |
| flutter::SemanticsNode node; |
| node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling) | |
| static_cast<int32_t>(flutter::SemanticsFlags::kIsHidden); |
| FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0]; |
| [object setSemanticsNode:&node]; |
| XCTAssertEqual(object.isAccessibilityElement, YES); |
| } |
| |
| - (void)testNodeWithImplicitScrollIsNotAnAccessibilityElementWhenItisNotHidden { |
| fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory( |
| new flutter::MockAccessibilityBridge()); |
| fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr(); |
| flutter::SemanticsNode node; |
| node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling); |
| FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0]; |
| [object setSemanticsNode:&node]; |
| XCTAssertEqual(object.isAccessibilityElement, NO); |
| } |
| |
| - (void)testIntresetingSemanticsObjectWithLabelHasStaticTextTrait { |
| fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory( |
| new flutter::MockAccessibilityBridge()); |
| fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr(); |
| flutter::SemanticsNode node; |
| node.label = "foo"; |
| FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0]; |
| SemanticsObject* child1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1]; |
| object.children = @[ child1 ]; |
| [object setSemanticsNode:&node]; |
| XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitNone); |
| } |
| |
| - (void)testIntresetingSemanticsObjectWithLabelHasStaticTextTrait1 { |
| fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory( |
| new flutter::MockAccessibilityBridge()); |
| fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr(); |
| flutter::SemanticsNode node; |
| node.label = "foo"; |
| node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsTextField); |
| FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0]; |
| [object setSemanticsNode:&node]; |
| XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitNone); |
| } |
| |
| - (void)testIntresetingSemanticsObjectWithLabelHasStaticTextTrait2 { |
| fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory( |
| new flutter::MockAccessibilityBridge()); |
| fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr(); |
| flutter::SemanticsNode node; |
| node.label = "foo"; |
| node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsButton); |
| FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0]; |
| [object setSemanticsNode:&node]; |
| XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitButton); |
| } |
| |
| - (void)testShouldTriggerAnnouncement { |
| fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory( |
| new flutter::MockAccessibilityBridge()); |
| fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr(); |
| SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:0]; |
| |
| // Handle nil with no node set. |
| XCTAssertFalse([object nodeShouldTriggerAnnouncement:nil]); |
| |
| // Handle initial setting of node with liveRegion |
| flutter::SemanticsNode node; |
| node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsLiveRegion); |
| node.label = "foo"; |
| XCTAssertTrue([object nodeShouldTriggerAnnouncement:&node]); |
| |
| // Handle nil with node set. |
| [object setSemanticsNode:&node]; |
| XCTAssertFalse([object nodeShouldTriggerAnnouncement:nil]); |
| |
| // Handle new node, still has live region, same label. |
| XCTAssertFalse([object nodeShouldTriggerAnnouncement:&node]); |
| |
| // Handle update node with new label, still has live region. |
| flutter::SemanticsNode updatedNode; |
| updatedNode.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsLiveRegion); |
| updatedNode.label = "bar"; |
| XCTAssertTrue([object nodeShouldTriggerAnnouncement:&updatedNode]); |
| |
| // Handle dropping the live region flag. |
| updatedNode.flags = 0; |
| XCTAssertFalse([object nodeShouldTriggerAnnouncement:&updatedNode]); |
| |
| // Handle adding the flag when the label has not changed. |
| updatedNode.label = "foo"; |
| [object setSemanticsNode:&updatedNode]; |
| XCTAssertTrue([object nodeShouldTriggerAnnouncement:&node]); |
| } |
| |
| - (void)testShouldDispatchShowOnScreenActionForHeader { |
| fml::WeakPtrFactory<flutter::MockAccessibilityBridge> factory( |
| new flutter::MockAccessibilityBridge()); |
| fml::WeakPtr<flutter::MockAccessibilityBridge> bridge = factory.GetWeakPtr(); |
| SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:1]; |
| |
| // Handle initial setting of node with header. |
| flutter::SemanticsNode node; |
| node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsHeader); |
| node.label = "foo"; |
| |
| [object setSemanticsNode:&node]; |
| |
| // Simulate accessibility focus. |
| [object accessibilityElementDidBecomeFocused]; |
| |
| XCTAssertTrue(bridge->observations.size() == 1); |
| XCTAssertTrue(bridge->observations[0].id == 1); |
| XCTAssertTrue(bridge->observations[0].action == flutter::SemanticsAction::kShowOnScreen); |
| } |
| |
| - (void)testShouldDispatchShowOnScreenActionForHidden { |
| fml::WeakPtrFactory<flutter::MockAccessibilityBridge> factory( |
| new flutter::MockAccessibilityBridge()); |
| fml::WeakPtr<flutter::MockAccessibilityBridge> bridge = factory.GetWeakPtr(); |
| SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:1]; |
| |
| // Handle initial setting of node with hidden. |
| flutter::SemanticsNode node; |
| node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsHidden); |
| node.label = "foo"; |
| |
| [object setSemanticsNode:&node]; |
| |
| // Simulate accessibility focus. |
| [object accessibilityElementDidBecomeFocused]; |
| |
| XCTAssertTrue(bridge->observations.size() == 1); |
| XCTAssertTrue(bridge->observations[0].id == 1); |
| XCTAssertTrue(bridge->observations[0].action == flutter::SemanticsAction::kShowOnScreen); |
| } |
| |
| - (void)testSemanticsObjectAndPlatformViewSemanticsContainerDontHaveRetainCycle { |
| fml::WeakPtrFactory<flutter::MockAccessibilityBridge> factory( |
| new flutter::MockAccessibilityBridge()); |
| fml::WeakPtr<flutter::MockAccessibilityBridge> bridge = factory.GetWeakPtr(); |
| SemanticsObject* object = [[SemanticsObject alloc] initWithBridge:bridge uid:1]; |
| object.platformViewSemanticsContainer = |
| [[FlutterPlatformViewSemanticsContainer alloc] initWithSemanticsObject:object]; |
| __weak SemanticsObject* weakObject = object; |
| object = nil; |
| XCTAssertNil(weakObject); |
| } |
| |
| - (void)testFlutterSwitchSemanticsObjectForwardsCalls { |
| SemanticsObject* mockSemanticsObject = OCMClassMock([SemanticsObject class]); |
| FlutterSwitchSemanticsObject* switchObj = |
| [[FlutterSwitchSemanticsObject alloc] initWithSemanticsObject:mockSemanticsObject]; |
| OCMStub([mockSemanticsObject accessibilityActivate]).andReturn(YES); |
| OCMStub([mockSemanticsObject accessibilityScroll:UIAccessibilityScrollDirectionRight]) |
| .andReturn(NO); |
| OCMStub([mockSemanticsObject accessibilityPerformEscape]).andReturn(YES); |
| |
| XCTAssertTrue([switchObj accessibilityActivate]); |
| OCMVerify([mockSemanticsObject accessibilityActivate]); |
| |
| [switchObj accessibilityIncrement]; |
| OCMVerify([mockSemanticsObject accessibilityIncrement]); |
| |
| [switchObj accessibilityDecrement]; |
| OCMVerify([mockSemanticsObject accessibilityDecrement]); |
| |
| XCTAssertFalse([switchObj accessibilityScroll:UIAccessibilityScrollDirectionRight]); |
| OCMVerify([mockSemanticsObject accessibilityScroll:UIAccessibilityScrollDirectionRight]); |
| |
| XCTAssertTrue([switchObj accessibilityPerformEscape]); |
| OCMVerify([mockSemanticsObject accessibilityPerformEscape]); |
| |
| [switchObj accessibilityElementDidBecomeFocused]; |
| OCMVerify([mockSemanticsObject accessibilityElementDidBecomeFocused]); |
| |
| [switchObj accessibilityElementDidLoseFocus]; |
| OCMVerify([mockSemanticsObject accessibilityElementDidLoseFocus]); |
| } |
| |
| @end |