Unverified Commit b815f762 authored by Darren Austin's avatar Darren Austin Committed by GitHub

Added tests for the new Android heading semantic flag to android_semantics_testing (#44031)

Added tests for the new Android heading semantic flag to android_semantics_testing.
parent bbb2a0f8
...@@ -11,6 +11,7 @@ import java.util.ArrayList; ...@@ -11,6 +11,7 @@ import java.util.ArrayList;
import java.lang.StringBuilder; import java.lang.StringBuilder;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.view.WindowManager; import android.view.WindowManager;
...@@ -82,6 +83,10 @@ public class MainActivity extends FlutterActivity { ...@@ -82,6 +83,10 @@ public class MainActivity extends FlutterActivity {
flags.put("isEnabled", node.isEnabled()); flags.put("isEnabled", node.isEnabled());
flags.put("isFocusable", node.isFocusable()); flags.put("isFocusable", node.isFocusable());
flags.put("isFocused", node.isFocused()); flags.put("isFocused", node.isFocused());
// heading flag is only available on Android Pie or newer
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
flags.put("isHeading", node.isHeading());
}
flags.put("isPassword", node.isPassword()); flags.put("isPassword", node.isPassword());
flags.put("isLongClickable", node.isLongClickable()); flags.put("isLongClickable", node.isLongClickable());
result.put("flags", flags); result.put("flags", flags);
......
...@@ -11,6 +11,7 @@ import 'package:flutter/services.dart'; ...@@ -11,6 +11,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_driver/driver_extension.dart'; import 'package:flutter_driver/driver_extension.dart';
import 'src/tests/controls_page.dart'; import 'src/tests/controls_page.dart';
import 'src/tests/headings_page.dart';
import 'src/tests/popup_constants.dart'; import 'src/tests/popup_constants.dart';
import 'src/tests/popup_page.dart'; import 'src/tests/popup_page.dart';
import 'src/tests/text_field_page.dart'; import 'src/tests/text_field_page.dart';
...@@ -46,6 +47,7 @@ Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{ ...@@ -46,6 +47,7 @@ Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
selectionControlsRoute : (BuildContext context) => SelectionControlsPage(), selectionControlsRoute : (BuildContext context) => SelectionControlsPage(),
popupControlsRoute : (BuildContext context) => PopupControlsPage(), popupControlsRoute : (BuildContext context) => PopupControlsPage(),
textFieldRoute : (BuildContext context) => TextFieldPage(), textFieldRoute : (BuildContext context) => TextFieldPage(),
headingsRoute: (BuildContext context) => HeadingsPage(),
}; };
class TestApp extends StatelessWidget { class TestApp extends StatelessWidget {
......
...@@ -33,6 +33,7 @@ class AndroidSemanticsNode { ...@@ -33,6 +33,7 @@ class AndroidSemanticsNode {
/// "isEnabled": bool, /// "isEnabled": bool,
/// "isFocusable": bool, /// "isFocusable": bool,
/// "isFocused": bool, /// "isFocused": bool,
/// "isHeading": bool,
/// "isPassword": bool, /// "isPassword": bool,
/// "isLongClickable": bool, /// "isLongClickable": bool,
/// }, /// },
...@@ -115,6 +116,9 @@ class AndroidSemanticsNode { ...@@ -115,6 +116,9 @@ class AndroidSemanticsNode {
/// Whether the node is focused. /// Whether the node is focused.
bool get isFocused => _flags['isFocused']; bool get isFocused => _flags['isFocused'];
/// Whether the node is considered a heading.
bool get isHeading => _flags['isHeading'];
/// Whether the node represents a password field. /// Whether the node represents a password field.
/// ///
/// Equivalent to [SemanticsFlag.isObscured]. /// Equivalent to [SemanticsFlag.isObscured].
......
...@@ -8,7 +8,10 @@ import 'flutter_test_alternative.dart'; ...@@ -8,7 +8,10 @@ import 'flutter_test_alternative.dart';
/// Matches an [AndroidSemanticsNode]. /// Matches an [AndroidSemanticsNode].
/// ///
/// Any properties which aren't supplied are ignored during the comparison. /// Any properties which aren't supplied are ignored during the comparison,
/// with the exception of `isHeading`. The heading property is not available
/// on all versions of Android. If it is not available on the tested version,
/// it will be match whatever it is compared against.
/// ///
/// This matcher is intended to compare the accessibility values generated by /// This matcher is intended to compare the accessibility values generated by
/// the Android accessibility bridge, and not the semantics object created by /// the Android accessibility bridge, and not the semantics object created by
...@@ -28,6 +31,7 @@ Matcher hasAndroidSemantics({ ...@@ -28,6 +31,7 @@ Matcher hasAndroidSemantics({
bool isEnabled, bool isEnabled,
bool isFocusable, bool isFocusable,
bool isFocused, bool isFocused,
bool isHeading,
bool isPassword, bool isPassword,
bool isLongClickable, bool isLongClickable,
}) { }) {
...@@ -45,6 +49,7 @@ Matcher hasAndroidSemantics({ ...@@ -45,6 +49,7 @@ Matcher hasAndroidSemantics({
isEnabled: isEnabled, isEnabled: isEnabled,
isFocusable: isFocusable, isFocusable: isFocusable,
isFocused: isFocused, isFocused: isFocused,
isHeading: isHeading,
isPassword: isPassword, isPassword: isPassword,
isLongClickable: isLongClickable, isLongClickable: isLongClickable,
); );
...@@ -65,6 +70,7 @@ class _AndroidSemanticsMatcher extends Matcher { ...@@ -65,6 +70,7 @@ class _AndroidSemanticsMatcher extends Matcher {
this.isEditable, this.isEditable,
this.isFocusable, this.isFocusable,
this.isFocused, this.isFocused,
this.isHeading,
this.isPassword, this.isPassword,
this.isLongClickable, this.isLongClickable,
}); });
...@@ -82,6 +88,7 @@ class _AndroidSemanticsMatcher extends Matcher { ...@@ -82,6 +88,7 @@ class _AndroidSemanticsMatcher extends Matcher {
final bool isEnabled; final bool isEnabled;
final bool isFocusable; final bool isFocusable;
final bool isFocused; final bool isFocused;
final bool isHeading;
final bool isPassword; final bool isPassword;
final bool isLongClickable; final bool isLongClickable;
...@@ -112,6 +119,8 @@ class _AndroidSemanticsMatcher extends Matcher { ...@@ -112,6 +119,8 @@ class _AndroidSemanticsMatcher extends Matcher {
description.add(' with flag isFocusable: $isFocusable'); description.add(' with flag isFocusable: $isFocusable');
if (isFocused != null) if (isFocused != null)
description.add(' with flag isFocused: $isFocused'); description.add(' with flag isFocused: $isFocused');
if (isHeading != null)
description.add(' with flag isHeading: $isHeading');
if (isPassword != null) if (isPassword != null)
description.add(' with flag isPassword: $isPassword'); description.add(' with flag isPassword: $isPassword');
if (isLongClickable != null) if (isLongClickable != null)
...@@ -155,6 +164,9 @@ class _AndroidSemanticsMatcher extends Matcher { ...@@ -155,6 +164,9 @@ class _AndroidSemanticsMatcher extends Matcher {
return _failWithMessage('Expected isFocusable: $isFocusable', matchState); return _failWithMessage('Expected isFocusable: $isFocusable', matchState);
if (isFocused != null && isFocused != item.isFocused) if (isFocused != null && isFocused != item.isFocused)
return _failWithMessage('Expected isFocused: $isFocused', matchState); return _failWithMessage('Expected isFocused: $isFocused', matchState);
// Heading is not available in all Android versions, so match anything if it is not set by the platform
if (isHeading != null && isHeading != item.isHeading && item.isHeading != null)
return _failWithMessage('Expected isHeading: $isHeading', matchState);
if (isPassword != null && isPassword != item.isPassword) if (isPassword != null && isPassword != item.isPassword)
return _failWithMessage('Expected isPassword: $isPassword', matchState); return _failWithMessage('Expected isPassword: $isPassword', matchState);
if (isLongClickable != null && isLongClickable != item.isLongClickable) if (isLongClickable != null && isLongClickable != item.isLongClickable)
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/// The name of the route containing the test suite.
const String headingsRoute = 'headings';
/// The string supplied to the [ValueKey] for the app bar title widget.
const String appBarTitleKeyValue = 'Headings#AppBarTitle';
/// The string supplied to the [ValueKey] for the body text widget.
const String bodyTextKeyValue = 'Headings#BodyText';
// Copyright 2019 The Chromium 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 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'headings_constants.dart';
export 'headings_constants.dart';
/// A test page with an app bar and some body text for testing heading flags.
class HeadingsPage extends StatelessWidget {
static const ValueKey<String> _appBarTitleKey = ValueKey<String>(appBarTitleKeyValue);
static const ValueKey<String> _bodyTextKey = ValueKey<String>(bodyTextKeyValue);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: const BackButton(key: ValueKey<String>('back')),
title: const Text('Heading', key: _appBarTitleKey),
),
body: const Center(
child: Text('Body text', key: _bodyTextKey),
),
);
}
}
...@@ -3,5 +3,6 @@ ...@@ -3,5 +3,6 @@
// found in the LICENSE file. // found in the LICENSE file.
export 'src/tests/controls_constants.dart'; export 'src/tests/controls_constants.dart';
export 'src/tests/headings_constants.dart';
export 'src/tests/popup_constants.dart'; export 'src/tests/popup_constants.dart';
export 'src/tests/text_field_constants.dart'; export 'src/tests/text_field_constants.dart';
...@@ -61,6 +61,8 @@ void main() { ...@@ -61,6 +61,8 @@ void main() {
group('TextField', () { group('TextField', () {
setUpAll(() async { setUpAll(() async {
await driver.tap(find.text(textFieldRoute)); await driver.tap(find.text(textFieldRoute));
// Delay for TalkBack to update focus as of November 2019 with Pixel 3 and Android API 28
await Future<void>.delayed(const Duration(milliseconds: 500));
}); });
test('TextField has correct Android semantics', () async { test('TextField has correct Android semantics', () async {
...@@ -85,6 +87,7 @@ void main() { ...@@ -85,6 +87,7 @@ void main() {
); );
await driver.tap(normalTextField); await driver.tap(normalTextField);
// Delay for TalkBack to update focus as of November 2019 with Pixel 3 and Android API 28
await Future<void>.delayed(const Duration(milliseconds: 500)); await Future<void>.delayed(const Duration(milliseconds: 500));
expect( expect(
...@@ -105,6 +108,7 @@ void main() { ...@@ -105,6 +108,7 @@ void main() {
); );
await driver.enterText('hello world'); await driver.enterText('hello world');
// Delay for TalkBack to update focus as of November 2019 with Pixel 3 and Android API 28
await Future<void>.delayed(const Duration(milliseconds: 500)); await Future<void>.delayed(const Duration(milliseconds: 500));
expect( expect(
...@@ -148,6 +152,8 @@ void main() { ...@@ -148,6 +152,8 @@ void main() {
); );
await driver.tap(passwordTextField); await driver.tap(passwordTextField);
// Delay for TalkBack to update focus as of November 2019 with Pixel 3 and Android API 28
await Future<void>.delayed(const Duration(milliseconds: 500));
expect( expect(
await getSemantics(passwordTextField), await getSemantics(passwordTextField),
...@@ -167,6 +173,8 @@ void main() { ...@@ -167,6 +173,8 @@ void main() {
); );
await driver.enterText('hello world'); await driver.enterText('hello world');
// Delay for TalkBack to update focus as of November 2019 with Pixel 3 and Android API 28
await Future<void>.delayed(const Duration(milliseconds: 500));
expect( expect(
await getSemantics(passwordTextField), await getSemantics(passwordTextField),
...@@ -630,5 +638,30 @@ void main() { ...@@ -630,5 +638,30 @@ void main() {
await driver.tap(find.byValueKey('back')); await driver.tap(find.byValueKey('back'));
}); });
}); });
group('Headings', () {
setUpAll(() async {
await driver.tap(find.text(headingsRoute));
});
test('AppBar title has correct Android heading semantics', () async {
expect(
await getSemantics(find.byValueKey(appBarTitleKeyValue)),
hasAndroidSemantics(isHeading: true),
);
});
test('body text does not have Android heading semantics', () async {
expect(
await getSemantics(find.byValueKey(bodyTextKeyValue)),
hasAndroidSemantics(isHeading: false),
);
});
tearDownAll(() async {
await driver.tap(find.byValueKey('back'));
});
});
}); });
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment