| // Copyright 2014 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 'dart:ui'; |
| |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| import 'checkbox_tester.dart'; |
| import 'radio_group_tester.dart'; |
| import 'radio_tester.dart'; |
| |
| void main() { |
| group('tab', () { |
| testWidgets('failure case, empty', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics(role: SemanticsRole.tab, child: const Text('a tab')), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| final error = exception! as FlutterError; |
| expect(error.message, 'A tab needs selected states'); |
| }); |
| |
| testWidgets('failure case, no tap', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics(role: SemanticsRole.tab, selected: false, child: const Text('a tab')), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| final error = exception! as FlutterError; |
| expect(error.message, 'A tab must have a tap action'); |
| }); |
| |
| testWidgets('success case', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.tab, |
| selected: false, |
| onTap: () {}, |
| child: const Text('a tab'), |
| ), |
| ), |
| ); |
| expect(tester.takeException(), isNull); |
| }); |
| }); |
| |
| group('list', () { |
| testWidgets('failure case, list item without list parent', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics(role: SemanticsRole.listItem, child: const Text('some child')), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| final error = exception! as FlutterError; |
| expect( |
| error.message, |
| startsWith('Semantics node 1 has role ${SemanticsRole.listItem}, but its parent'), |
| ); |
| }); |
| |
| testWidgets('Success case', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.list, |
| explicitChildNodes: true, |
| child: Semantics(role: SemanticsRole.listItem, child: const Text('some child')), |
| ), |
| ), |
| ); |
| expect(tester.takeException(), isNull); |
| }); |
| }); |
| |
| group('tabBar', () { |
| testWidgets('failure case, empty child', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.tabBar, |
| child: const ExcludeSemantics(child: Text('something')), |
| ), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| final error = exception! as FlutterError; |
| expect(error.message, 'a TabBar cannot be empty'); |
| }); |
| |
| testWidgets('failure case, non tab child', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.tabBar, |
| explicitChildNodes: true, |
| child: Semantics(child: const Text('some child')), |
| ), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| final error = exception! as FlutterError; |
| expect(error.message, 'Children of TabBar must have the tab role'); |
| }); |
| |
| testWidgets('Success case', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.tabBar, |
| explicitChildNodes: true, |
| child: Semantics( |
| role: SemanticsRole.tab, |
| selected: false, |
| onTap: () {}, |
| child: const Text('some child'), |
| ), |
| ), |
| ), |
| ); |
| expect(tester.takeException(), isNull); |
| }); |
| }); |
| |
| group('radioGroup', () { |
| testWidgets('success case, child is not mutually exclusive', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.radioGroup, |
| explicitChildNodes: true, |
| child: Semantics( |
| checked: false, |
| inMutuallyExclusiveGroup: false, |
| child: const SizedBox.square(dimension: 1), |
| ), |
| ), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isNull); |
| }); |
| |
| testWidgets('failure case, multiple checked children', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.radioGroup, |
| explicitChildNodes: true, |
| child: Column( |
| children: <Widget>[ |
| Semantics( |
| checked: true, |
| inMutuallyExclusiveGroup: true, |
| child: const SizedBox.square(dimension: 1), |
| ), |
| Semantics( |
| checked: true, |
| inMutuallyExclusiveGroup: true, |
| child: const SizedBox.square(dimension: 1), |
| ), |
| ], |
| ), |
| ), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| final error = exception! as FlutterError; |
| expect(error.message, 'Radio groups must not have multiple checked children'); |
| }); |
| |
| testWidgets('success case', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.radioGroup, |
| explicitChildNodes: true, |
| child: Column( |
| children: <Widget>[ |
| Semantics( |
| checked: false, |
| inMutuallyExclusiveGroup: true, |
| child: const SizedBox.square(dimension: 1), |
| ), |
| Semantics( |
| checked: true, |
| inMutuallyExclusiveGroup: true, |
| child: const SizedBox.square(dimension: 1), |
| ), |
| ], |
| ), |
| ), |
| ), |
| ); |
| expect(tester.takeException(), isNull); |
| }); |
| |
| testWidgets('success case, radio buttons with labels', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.radioGroup, |
| explicitChildNodes: true, |
| child: Column( |
| children: <Widget>[ |
| Semantics( |
| label: 'Option A', |
| child: Semantics( |
| checked: false, |
| inMutuallyExclusiveGroup: true, |
| child: const SizedBox.square(dimension: 1), |
| ), |
| ), |
| Semantics( |
| label: 'Option B', |
| child: Semantics( |
| checked: true, |
| inMutuallyExclusiveGroup: true, |
| child: const SizedBox.square(dimension: 1), |
| ), |
| ), |
| ], |
| ), |
| ), |
| ), |
| ); |
| expect(tester.takeException(), isNull); |
| }); |
| |
| testWidgets('success case, radio group with no checkable children', ( |
| WidgetTester tester, |
| ) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.radioGroup, |
| explicitChildNodes: true, |
| child: Semantics(toggled: true, child: const SizedBox.square(dimension: 1)), |
| ), |
| ), |
| ); |
| expect(tester.takeException(), isNull); |
| }); |
| |
| testWidgets('success case, radio group can have checkbox children', ( |
| WidgetTester tester, |
| ) async { |
| final node0 = FocusNode(); |
| addTearDown(node0.dispose); |
| final node1 = FocusNode(); |
| addTearDown(node1.dispose); |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: TestRadioGroup<int>( |
| child: Column( |
| children: <Widget>[ |
| TestCheckbox(value: false, onChanged: (bool? value) {}), |
| const TestRadio<int>(value: 0), |
| const TestRadio<int>(value: 1), |
| ], |
| ), |
| ), |
| ), |
| ); |
| expect(tester.takeException(), isNull); |
| }); |
| |
| testWidgets('success case, radio group can nest', (WidgetTester tester) async { |
| final stringNode = FocusNode(); |
| addTearDown(stringNode.dispose); |
| final node0 = FocusNode(); |
| addTearDown(node0.dispose); |
| final node1 = FocusNode(); |
| addTearDown(node1.dispose); |
| await tester.pumpWidget( |
| const Directionality( |
| textDirection: TextDirection.ltr, |
| child: TestRadioGroup<int>( |
| child: Column( |
| children: <Widget>[ |
| TestRadioGroup<String>(child: TestRadio<String>(value: 'string')), |
| TestRadio<int>(value: 0), |
| TestRadio<int>(value: 1), |
| ], |
| ), |
| ), |
| ), |
| ); |
| expect(tester.takeException(), isNull); |
| }); |
| }); |
| |
| group('menu', () { |
| testWidgets('failure case, empty child', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.menu, |
| child: const ExcludeSemantics(child: Text('something')), |
| ), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| final error = exception! as FlutterError; |
| expect(error.message, 'a menu cannot be empty'); |
| }); |
| |
| testWidgets('Success case', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.menu, |
| explicitChildNodes: true, |
| child: Semantics( |
| role: SemanticsRole.menuItem, |
| selected: false, |
| onTap: () {}, |
| child: const Text('some child'), |
| ), |
| ), |
| ), |
| ); |
| expect(tester.takeException(), isNull); |
| }); |
| }); |
| |
| group('menuBar', () { |
| testWidgets('failure case, empty child', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.menuBar, |
| child: const ExcludeSemantics(child: Text('something')), |
| ), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| final error = exception! as FlutterError; |
| expect(error.message, 'a menu bar cannot be empty'); |
| }); |
| |
| testWidgets('Success case', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.menuBar, |
| explicitChildNodes: true, |
| child: Semantics( |
| role: SemanticsRole.menuItem, |
| selected: false, |
| onTap: () {}, |
| child: const Text('some child'), |
| ), |
| ), |
| ), |
| ); |
| expect(tester.takeException(), isNull); |
| }); |
| }); |
| |
| group('menuItem', () { |
| testWidgets('failure case, no menu or menuBar as ancester', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.menuItem, |
| child: const ExcludeSemantics(child: Text('something')), |
| ), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| final error = exception! as FlutterError; |
| expect(error.message, 'A menu item must be a child of a menu or a menu bar'); |
| }); |
| |
| testWidgets('Success case with menu as an ancester', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.menu, |
| explicitChildNodes: true, |
| child: Semantics( |
| role: SemanticsRole.menuItem, |
| onTap: () {}, |
| child: const Text('some child'), |
| ), |
| ), |
| ), |
| ); |
| expect(tester.takeException(), isNull); |
| }); |
| |
| testWidgets('Success case with menuBar as an ancester', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.menuBar, |
| explicitChildNodes: true, |
| child: Semantics( |
| role: SemanticsRole.menuItem, |
| onTap: () {}, |
| child: const Text('some child'), |
| ), |
| ), |
| ), |
| ); |
| expect(tester.takeException(), isNull); |
| }); |
| }); |
| |
| group('menuItemCheckbox', () { |
| testWidgets('failure case, no checked flag', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.menuItemCheckbox, |
| child: const ExcludeSemantics(child: Text('something')), |
| ), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| final error = exception! as FlutterError; |
| expect(error.message, 'a menu item checkbox must be checkable'); |
| }); |
| |
| testWidgets('failure case, no menu or menuBar as its ancestor', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.menuItemCheckbox, |
| checked: false, |
| child: const ExcludeSemantics(child: Text('something')), |
| ), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| final error = exception! as FlutterError; |
| expect(error.message, 'A menu item checkbox must be a child of a menu or a menu bar'); |
| }); |
| |
| testWidgets('Success case with menu as an ancester', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.menu, |
| explicitChildNodes: true, |
| child: Semantics( |
| role: SemanticsRole.menuItemCheckbox, |
| checked: true, |
| onTap: () {}, |
| child: const Text('some child'), |
| ), |
| ), |
| ), |
| ); |
| expect(tester.takeException(), isNull); |
| }); |
| |
| testWidgets('Success case with menuBar as an ancester', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.menuBar, |
| explicitChildNodes: true, |
| child: Semantics( |
| role: SemanticsRole.menuItemCheckbox, |
| checked: false, |
| onTap: () {}, |
| child: const Text('some child'), |
| ), |
| ), |
| ), |
| ); |
| expect(tester.takeException(), isNull); |
| }); |
| }); |
| |
| group('menuItemRadio', () { |
| testWidgets('failure case, no checked flag', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.menuItemRadio, |
| child: const ExcludeSemantics(child: Text('something')), |
| ), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| final error = exception! as FlutterError; |
| expect(error.message, 'a menu item radio must be checkable'); |
| }); |
| |
| testWidgets('failure case, no menu or menuBar as its ancestor', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.menuItemRadio, |
| checked: false, |
| child: const ExcludeSemantics(child: Text('something')), |
| ), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| final error = exception! as FlutterError; |
| expect(error.message, 'A menu item radio must be a child of a menu or a menu bar'); |
| }); |
| |
| testWidgets('Success case with menu as an ancester', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.menu, |
| explicitChildNodes: true, |
| child: Semantics( |
| role: SemanticsRole.menuItemRadio, |
| checked: true, |
| onTap: () {}, |
| child: const Text('some child'), |
| ), |
| ), |
| ), |
| ); |
| expect(tester.takeException(), isNull); |
| }); |
| |
| testWidgets('Success case with menuBar as an ancester', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.menuBar, |
| explicitChildNodes: true, |
| child: Semantics( |
| role: SemanticsRole.menuItemRadio, |
| checked: false, |
| onTap: () {}, |
| child: const Text('some child'), |
| ), |
| ), |
| ), |
| ); |
| expect(tester.takeException(), isNull); |
| }); |
| }); |
| |
| group('alert and status', () { |
| testWidgets('failure case, alert and live region', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.alert, |
| liveRegion: true, |
| child: const SizedBox.square(dimension: 1), |
| ), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| final error = exception! as FlutterError; |
| expect( |
| error.message, |
| startsWith('Node 1 has role SemanticsRole.alert but is also a live region.'), |
| ); |
| }); |
| |
| testWidgets('failure case, status and live region', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.status, |
| liveRegion: true, |
| child: const SizedBox.square(dimension: 1), |
| ), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| final error = exception! as FlutterError; |
| expect( |
| error.message, |
| startsWith('Node 1 has role SemanticsRole.status but is also a live region.'), |
| ); |
| }); |
| |
| testWidgets('success case', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| explicitChildNodes: true, |
| child: Column( |
| children: <Widget>[ |
| Semantics(role: SemanticsRole.status, child: const SizedBox.square(dimension: 1)), |
| Semantics(role: SemanticsRole.alert, child: const SizedBox.square(dimension: 1)), |
| ], |
| ), |
| ), |
| ), |
| ); |
| expect(tester.takeException(), isNull); |
| }); |
| |
| testWidgets('success case, radio buttons with labels', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.radioGroup, |
| explicitChildNodes: true, |
| child: Column( |
| children: <Widget>[ |
| Semantics( |
| label: 'Option A', |
| child: Semantics( |
| checked: false, |
| inMutuallyExclusiveGroup: true, |
| child: const SizedBox.square(dimension: 1), |
| ), |
| ), |
| Semantics( |
| label: 'Option B', |
| child: Semantics( |
| checked: true, |
| inMutuallyExclusiveGroup: true, |
| child: const SizedBox.square(dimension: 1), |
| ), |
| ), |
| ], |
| ), |
| ), |
| ), |
| ); |
| expect(tester.takeException(), isNull); |
| }); |
| |
| testWidgets('success case, radio group with no checkable children', ( |
| WidgetTester tester, |
| ) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.radioGroup, |
| explicitChildNodes: true, |
| child: Semantics(toggled: true, child: const SizedBox.square(dimension: 1)), |
| ), |
| ), |
| ); |
| expect(tester.takeException(), isNull); |
| }); |
| }); |
| |
| group('landmarks', () { |
| testWidgets('failure case, complementary role is contained by other landmark roles', ( |
| WidgetTester tester, |
| ) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.main, |
| child: SizedBox( |
| child: Semantics(role: SemanticsRole.complementary, child: const Text('some child')), |
| ), |
| ), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| final error = exception! as FlutterError; |
| expect( |
| error.message, |
| startsWith( |
| 'The complementary landmark role should not contained within any other landmark roles.', |
| ), |
| ); |
| }); |
| |
| testWidgets('failure case, multiple nodes have the same complementary role', ( |
| WidgetTester tester, |
| ) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Column( |
| children: <Widget>[ |
| Semantics( |
| role: SemanticsRole.complementary, |
| child: const SizedBox.square(dimension: 1), |
| ), |
| Semantics( |
| container: true, |
| child: SizedBox( |
| child: Semantics( |
| role: SemanticsRole.complementary, |
| child: const SizedBox.square(dimension: 1), |
| ), |
| ), |
| ), |
| ], |
| ), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| final error = exception! as FlutterError; |
| expect( |
| error.message, |
| startsWith( |
| 'The complementary landmark role should have a unique label as it is used more than once.', |
| ), |
| ); |
| }); |
| |
| testWidgets('Success case, complementary role', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics(role: SemanticsRole.complementary, child: const Text('some child')), |
| ), |
| ); |
| expect(tester.takeException(), isNull); |
| }); |
| |
| testWidgets('Success case, complementary role is used more than once', ( |
| WidgetTester tester, |
| ) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Column( |
| children: <Widget>[ |
| Semantics( |
| label: 'complementary 1', |
| role: SemanticsRole.complementary, |
| child: const SizedBox.square(dimension: 1), |
| ), |
| Semantics( |
| label: 'complementary 2', |
| role: SemanticsRole.complementary, |
| child: const SizedBox.square(dimension: 1), |
| ), |
| ], |
| ), |
| ), |
| ); |
| expect(tester.takeException(), isNull); |
| }); |
| |
| testWidgets('failure case, multiple nodes have the same contentInfo role', ( |
| WidgetTester tester, |
| ) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Column( |
| children: <Widget>[ |
| Semantics( |
| role: SemanticsRole.contentInfo, |
| child: const SizedBox.square(dimension: 1), |
| ), |
| Semantics( |
| container: true, |
| child: SizedBox( |
| child: Semantics( |
| role: SemanticsRole.contentInfo, |
| child: const SizedBox.square(dimension: 1), |
| ), |
| ), |
| ), |
| ], |
| ), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| final error = exception! as FlutterError; |
| expect( |
| error.message, |
| startsWith( |
| 'The contentInfo landmark role should have a unique label as it is used more than once.', |
| ), |
| ); |
| }); |
| |
| testWidgets('failure case, contentInfo role is contained by other landmark roles', ( |
| WidgetTester tester, |
| ) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.complementary, |
| child: SizedBox( |
| child: Semantics(role: SemanticsRole.contentInfo, child: const Text('some child')), |
| ), |
| ), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| final error = exception! as FlutterError; |
| expect( |
| error.message, |
| startsWith( |
| 'The contentInfo landmark role should not contained within any other landmark roles.', |
| ), |
| ); |
| }); |
| |
| testWidgets('Success case, contentInfo role', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics(role: SemanticsRole.contentInfo, child: const Text('some child')), |
| ), |
| ); |
| expect(tester.takeException(), isNull); |
| }); |
| |
| testWidgets('Success case, contentInfo role is used more than once', ( |
| WidgetTester tester, |
| ) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Column( |
| children: <Widget>[ |
| Semantics( |
| label: 'contentInfo 1', |
| role: SemanticsRole.contentInfo, |
| child: const SizedBox.square(dimension: 1), |
| ), |
| Semantics( |
| label: 'contentInfo 2', |
| role: SemanticsRole.contentInfo, |
| child: const SizedBox.square(dimension: 1), |
| ), |
| ], |
| ), |
| ), |
| ); |
| expect(tester.takeException(), isNull); |
| }); |
| |
| testWidgets('failure case, multiple nodes have the same main role', ( |
| WidgetTester tester, |
| ) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Column( |
| children: <Widget>[ |
| Semantics(role: SemanticsRole.main, child: const SizedBox.square(dimension: 1)), |
| Semantics( |
| container: true, |
| child: SizedBox( |
| child: Semantics( |
| role: SemanticsRole.main, |
| child: const SizedBox.square(dimension: 1), |
| ), |
| ), |
| ), |
| ], |
| ), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| final error = exception! as FlutterError; |
| expect( |
| error.message, |
| startsWith( |
| 'The main landmark role should have a unique label as it is used more than once.', |
| ), |
| ); |
| }); |
| |
| testWidgets('failure case, main role is contained by other landmark roles', ( |
| WidgetTester tester, |
| ) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.contentInfo, |
| child: SizedBox( |
| child: Semantics(role: SemanticsRole.main, child: const Text('some child')), |
| ), |
| ), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| final error = exception! as FlutterError; |
| expect( |
| error.message, |
| startsWith('The main landmark role should not contained within any other landmark roles.'), |
| ); |
| }); |
| |
| testWidgets('Success case, main role', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics(role: SemanticsRole.main, child: const Text('some child')), |
| ), |
| ); |
| expect(tester.takeException(), isNull); |
| }); |
| |
| testWidgets('Success case, main role is used more than once', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Column( |
| children: <Widget>[ |
| Semantics( |
| label: 'main 1', |
| role: SemanticsRole.main, |
| child: const SizedBox.square(dimension: 1), |
| ), |
| Semantics( |
| label: 'main 2', |
| role: SemanticsRole.main, |
| child: const SizedBox.square(dimension: 1), |
| ), |
| ], |
| ), |
| ), |
| ); |
| expect(tester.takeException(), isNull); |
| }); |
| |
| testWidgets('failure case, multiple nodes have the same navigation role', ( |
| WidgetTester tester, |
| ) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Column( |
| children: <Widget>[ |
| Semantics(role: SemanticsRole.navigation, child: const SizedBox.square(dimension: 1)), |
| Semantics( |
| container: true, |
| child: SizedBox( |
| child: Semantics( |
| role: SemanticsRole.navigation, |
| child: const SizedBox.square(dimension: 1), |
| ), |
| ), |
| ), |
| ], |
| ), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| final error = exception! as FlutterError; |
| expect( |
| error.message, |
| startsWith( |
| 'The navigation landmark role should have a unique label as it is used more than once.', |
| ), |
| ); |
| }); |
| |
| testWidgets('Success case, navigation role', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics(role: SemanticsRole.navigation, child: const Text('some child')), |
| ), |
| ); |
| expect(tester.takeException(), isNull); |
| }); |
| |
| testWidgets('Success case, navigation role is used more than once', ( |
| WidgetTester tester, |
| ) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Column( |
| children: <Widget>[ |
| Semantics( |
| label: 'navigation 1', |
| role: SemanticsRole.navigation, |
| child: const SizedBox.square(dimension: 1), |
| ), |
| Semantics( |
| label: 'navigation 2', |
| role: SemanticsRole.navigation, |
| child: const SizedBox.square(dimension: 1), |
| ), |
| ], |
| ), |
| ), |
| ); |
| expect(tester.takeException(), isNull); |
| }); |
| |
| testWidgets('failure case, region role without label', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics(role: SemanticsRole.region, child: const SizedBox()), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| final error = exception! as FlutterError; |
| expect( |
| error.message, |
| startsWith( |
| 'A region role should include a label that describes the purpose of the content.', |
| ), |
| ); |
| }); |
| |
| testWidgets('Success case, region role', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics(label: 'Header 1', role: SemanticsRole.region, child: const SizedBox()), |
| ), |
| ); |
| expect(tester.takeException(), isNull); |
| }); |
| }); |
| |
| group('progressBar', () { |
| testWidgets('failure case, missing value, min, and max', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics(role: SemanticsRole.progressBar, child: const SizedBox()), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| final error = exception! as FlutterError; |
| expect(error.message, 'A progress bar must have a value, a minValue, a maxValue.'); |
| }); |
| |
| testWidgets('failure case, has min and max but not value', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.progressBar, |
| minValue: '0', |
| maxValue: '10', |
| child: const SizedBox(), |
| ), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| final error = exception! as FlutterError; |
| expect(error.message, 'A progress bar must have a value, a minValue, a maxValue.'); |
| }); |
| |
| testWidgets('failure case, has value but not min and max', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics(role: SemanticsRole.progressBar, value: '5', child: const SizedBox()), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| final error = exception! as FlutterError; |
| expect(error.message, 'A progress bar must have a value, a minValue, a maxValue.'); |
| }); |
| |
| testWidgets('failure case, valid min and max but invalid value', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.progressBar, |
| value: 'invalid', |
| minValue: '0', |
| maxValue: '10', |
| child: const SizedBox(), |
| ), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| final error = exception! as FlutterError; |
| expect( |
| error.message, |
| 'Progress bar value, minValue, and maxValue must be valid numbers. ' |
| 'value: "invalid", minValue: "0", maxValue: "10"', |
| ); |
| }); |
| |
| testWidgets('failure case, min and max are percentages, invalid value', ( |
| WidgetTester tester, |
| ) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.progressBar, |
| value: 'invalid', |
| minValue: '0%', |
| maxValue: '100%', |
| child: const SizedBox(), |
| ), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| final error = exception! as FlutterError; |
| expect( |
| error.message, |
| 'Progress bar value, minValue, and maxValue must be valid numbers. ' |
| 'value: "invalid", minValue: "0%", maxValue: "100%"', |
| ); |
| }); |
| |
| testWidgets('failure case, invalid numbers', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.progressBar, |
| value: 'invalid', |
| minValue: 'invalid', |
| maxValue: 'invalid', |
| child: const SizedBox(), |
| ), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| final error = exception! as FlutterError; |
| expect( |
| error.message, |
| 'Progress bar value, minValue, and maxValue must be valid numbers. ' |
| 'value: "invalid", minValue: "invalid", maxValue: "invalid"', |
| ); |
| }); |
| |
| testWidgets('failure case, min >= max', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.progressBar, |
| value: '5', |
| minValue: '10', |
| maxValue: '0', |
| child: const SizedBox(), |
| ), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| final error = exception! as FlutterError; |
| expect(error.message, 'Progress bar minValue (10) must be less than maxValue (0)'); |
| }); |
| |
| testWidgets('failure case, value out of range (number)', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.progressBar, |
| value: '20', |
| minValue: '0', |
| maxValue: '10', |
| child: const SizedBox(), |
| ), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| final error = exception! as FlutterError; |
| expect( |
| error.message, |
| 'Progress bar value (20) must be between minValue (0) and maxValue (10)', |
| ); |
| }); |
| |
| testWidgets('failure case, value out of range (percentage)', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.progressBar, |
| value: '150%', |
| minValue: '0', |
| maxValue: '10', |
| child: const SizedBox(), |
| ), |
| ), |
| ); |
| final Object? exception = tester.takeException(); |
| expect(exception, isFlutterError); |
| final error = exception! as FlutterError; |
| expect(error.message, 'Progress bar percentage value (150%) must be between 0% and 100%'); |
| }); |
| |
| testWidgets('success case, value is a valid number', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.progressBar, |
| value: '5', |
| minValue: '0', |
| maxValue: '10', |
| child: const SizedBox(), |
| ), |
| ), |
| ); |
| expect(tester.takeException(), isNull); |
| }); |
| |
| testWidgets('success case, value is a valid percentage', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Semantics( |
| role: SemanticsRole.progressBar, |
| value: '50%', |
| minValue: '0', |
| maxValue: '10', |
| child: const SizedBox(), |
| ), |
| ), |
| ); |
| expect(tester.takeException(), isNull); |
| }); |
| }); |
| } |