diff --git a/dev/a11y_assessments/lib/main.dart b/dev/a11y_assessments/lib/main.dart index 9bf39a96c8..5b7a14d767 100644 --- a/dev/a11y_assessments/lib/main.dart +++ b/dev/a11y_assessments/lib/main.dart @@ -2,12 +2,23 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'use_cases/use_cases.dart'; +// TODO(yjbanov): https://github.com/flutter/flutter/issues/83809 +// Currently this app (as most Flutter Web apps) relies on the +// `autofocus` property to guide the a11y focus when navigating +// across routes (screen transitions, dialogs, etc). We may want +// to revisit this after we figure out a long-term story for a11y +// focus. See also https://github.com/flutter/flutter/issues/97747 void main() { runApp(const App()); + if (kIsWeb) { + SemanticsBinding.instance.ensureSemantics(); + } } class App extends StatelessWidget { @@ -36,12 +47,13 @@ class App extends StatelessWidget { class HomePage extends StatelessWidget { const HomePage({super.key}); - Widget _buildUseCaseItem(UseCase useCase) { + Widget _buildUseCaseItem(int index, UseCase useCase) { return Padding( padding: const EdgeInsets.all(10), child: Builder( builder: (BuildContext context) { return TextButton( + autofocus: index == 0, key: Key(useCase.name), onPressed: () => Navigator.of(context).pushNamed(useCase.route), child: Text(useCase.name), @@ -57,7 +69,10 @@ class HomePage extends StatelessWidget { appBar: AppBar(title: const Text('Accessibility Assessments')), body: Center( child: ListView( - children: useCases.map(_buildUseCaseItem).toList(), + children: List.generate( + useCases.length, + (int index) => _buildUseCaseItem(index, useCases[index]), + ), ), ), ); diff --git a/dev/a11y_assessments/lib/use_cases/check_box_list_tile.dart b/dev/a11y_assessments/lib/use_cases/check_box_list_tile.dart index ecdf03427a..b21f5d0c95 100644 --- a/dev/a11y_assessments/lib/use_cases/check_box_list_tile.dart +++ b/dev/a11y_assessments/lib/use_cases/check_box_list_tile.dart @@ -30,16 +30,29 @@ class _MainWidgetState extends State<_MainWidget> { Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('CheckBoxListTile')), - body: Center( - child: CheckboxListTile( - value: _checked, - onChanged: (bool? value) { - setState(() { - _checked = value!; - }); - }, - title: const Text('a check box list title'), - ), + body: ListView( + children: [ + CheckboxListTile( + autofocus: true, + value: _checked, + onChanged: (bool? value) { + setState(() { + _checked = value!; + }); + }, + title: const Text('a check box list title'), + ), + CheckboxListTile( + value: _checked, + onChanged: (bool? value) { + setState(() { + _checked = value!; + }); + }, + title: const Text('a disabled check box list title'), + enabled: false, + ), + ], ), ); } diff --git a/dev/a11y_assessments/lib/use_cases/check_box_list_tile_disabled.dart b/dev/a11y_assessments/lib/use_cases/check_box_list_tile_disabled.dart deleted file mode 100644 index fe4d7e2be0..0000000000 --- a/dev/a11y_assessments/lib/use_cases/check_box_list_tile_disabled.dart +++ /dev/null @@ -1,47 +0,0 @@ -// 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 'package:flutter/material.dart'; - -import 'use_cases.dart'; - -class CheckBoxListTileDisabled extends UseCase { - - @override - String get name => 'CheckBoxListTile Disabled'; - - @override - String get route => '/check-box-list-tile-disabled'; - - @override - Widget build(BuildContext context) => _MainWidget(); -} - -class _MainWidget extends StatefulWidget { - @override - State<_MainWidget> createState() => _MainWidgetState(); -} - -class _MainWidgetState extends State<_MainWidget> { - bool _checked = false; - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('CheckBoxListTile Disabled')), - body: Center( - child: CheckboxListTile( - value: _checked, - onChanged: (bool? value) { - setState(() { - _checked = value!; - }); - }, - title: const Text('a disabled check box list title'), - enabled: false, - ), - ), - ); - } -} diff --git a/dev/a11y_assessments/lib/use_cases/date_picker.dart b/dev/a11y_assessments/lib/use_cases/date_picker.dart index 0a6b1e8556..a7192abc47 100644 --- a/dev/a11y_assessments/lib/use_cases/date_picker.dart +++ b/dev/a11y_assessments/lib/use_cases/date_picker.dart @@ -36,6 +36,7 @@ class _MainWidgetState extends State<_MainWidget> { ), body: Center( child: TextButton( + autofocus: true, onPressed: () => showDatePicker( context: context, initialEntryMode: DatePickerEntryMode.calendarOnly, diff --git a/dev/a11y_assessments/lib/use_cases/dialog.dart b/dev/a11y_assessments/lib/use_cases/dialog.dart index 73ebddc5a9..e110259f5c 100644 --- a/dev/a11y_assessments/lib/use_cases/dialog.dart +++ b/dev/a11y_assessments/lib/use_cases/dialog.dart @@ -29,6 +29,7 @@ class _MainWidget extends StatelessWidget { ), body: Center( child: TextButton( + autofocus: true, onPressed: () => showDialog( context: context, builder: (BuildContext context) => Dialog( @@ -41,6 +42,7 @@ class _MainWidget extends StatelessWidget { const Text('This is a typical dialog.'), const SizedBox(height: 15), TextButton( + autofocus: true, onPressed: () { Navigator.pop(context); }, diff --git a/dev/a11y_assessments/lib/use_cases/slider.dart b/dev/a11y_assessments/lib/use_cases/slider.dart index a83261dbea..8dbd398bf4 100644 --- a/dev/a11y_assessments/lib/use_cases/slider.dart +++ b/dev/a11y_assessments/lib/use_cases/slider.dart @@ -37,6 +37,7 @@ class MainWidgetState extends State { ), body: Center( child: Slider( + autofocus: true, value: currentSliderValue, max: 100, divisions: 5, diff --git a/dev/a11y_assessments/lib/use_cases/text_field.dart b/dev/a11y_assessments/lib/use_cases/text_field.dart index 3dd7e6eb16..0038cb52f2 100644 --- a/dev/a11y_assessments/lib/use_cases/text_field.dart +++ b/dev/a11y_assessments/lib/use_cases/text_field.dart @@ -28,14 +28,28 @@ class _MainWidget extends StatelessWidget { backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: const Text('TextField'), ), - body: const Center( - child: TextField( - decoration: InputDecoration( - labelText: 'Email', - suffixText: '@gmail.com', - hintText: 'Enter your email', + body: ListView( + children: [ + const TextField( + key: Key('enabled text field'), + autofocus: true, + decoration: InputDecoration( + labelText: 'Email', + suffixText: '@gmail.com', + hintText: 'Enter your email', + ), ), - ) + TextField( + key: const Key('disabled text field'), + decoration: const InputDecoration( + labelText: 'Email', + suffixText: '@gmail.com', + hintText: 'Enter your email', + ), + enabled: false, + controller: TextEditingController(text: 'xyz'), + ), + ], ), ); } diff --git a/dev/a11y_assessments/lib/use_cases/text_field_disabled.dart b/dev/a11y_assessments/lib/use_cases/text_field_disabled.dart deleted file mode 100644 index dcd2081800..0000000000 --- a/dev/a11y_assessments/lib/use_cases/text_field_disabled.dart +++ /dev/null @@ -1,44 +0,0 @@ -// 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 'package:flutter/material.dart'; - -import 'use_cases.dart'; - -class TextFieldDisabledUseCase extends UseCase { - - @override - String get name => 'TextField disabled'; - - @override - String get route => '/text-field-disabled'; - - @override - Widget build(BuildContext context) => const _MainWidget(); -} - -class _MainWidget extends StatelessWidget { - const _MainWidget(); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - title: const Text('TextField disabled'), - ), - body: Center( - child: TextField( - decoration: const InputDecoration( - labelText: 'Email', - suffixText: '@gmail.com', - hintText: 'Enter your email', - enabled: false, - ), - controller: TextEditingController(text: 'abc'), - ) - ), - ); - } -} diff --git a/dev/a11y_assessments/lib/use_cases/text_field_password.dart b/dev/a11y_assessments/lib/use_cases/text_field_password.dart index 5da9337cb6..3c6152dd34 100644 --- a/dev/a11y_assessments/lib/use_cases/text_field_password.dart +++ b/dev/a11y_assessments/lib/use_cases/text_field_password.dart @@ -28,14 +28,27 @@ class _MainWidget extends StatelessWidget { backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: const Text('TextField password'), ), - body: const Center( - child: TextField( - decoration: InputDecoration( - labelText: 'Password', - hintText: 'Enter your password', + body: ListView( + children: const [ + TextField( + key: Key('enabled password'), + autofocus: true, + decoration: InputDecoration( + labelText: 'Password', + hintText: 'Enter your password', + ), + obscureText: true, ), - obscureText: true, - ) + TextField( + key: Key('disabled password'), + decoration: InputDecoration( + labelText: 'Password', + hintText: 'Enter your password', + ), + enabled: false, + obscureText: true, + ), + ], ), ); } diff --git a/dev/a11y_assessments/lib/use_cases/use_cases.dart b/dev/a11y_assessments/lib/use_cases/use_cases.dart index 6099b87bc4..056f9e2b11 100644 --- a/dev/a11y_assessments/lib/use_cases/use_cases.dart +++ b/dev/a11y_assessments/lib/use_cases/use_cases.dart @@ -5,12 +5,10 @@ import 'package:flutter/widgets.dart'; import 'check_box_list_tile.dart'; -import 'check_box_list_tile_disabled.dart'; import 'date_picker.dart'; import 'dialog.dart'; import 'slider.dart'; import 'text_field.dart'; -import 'text_field_disabled.dart'; import 'text_field_password.dart'; abstract class UseCase { @@ -21,11 +19,9 @@ abstract class UseCase { final List useCases = [ CheckBoxListTile(), - CheckBoxListTileDisabled(), DialogUseCase(), SliderUseCase(), TextFieldUseCase(), - TextFieldDisabledUseCase(), TextFieldPasswordUseCase(), DatePickerUseCase(), ]; diff --git a/dev/a11y_assessments/pubspec.yaml b/dev/a11y_assessments/pubspec.yaml index f87fd7b36e..6d71cbeb20 100644 --- a/dev/a11y_assessments/pubspec.yaml +++ b/dev/a11y_assessments/pubspec.yaml @@ -1,5 +1,5 @@ name: a11y_assessments -description: "A new Flutter project." +description: A new Flutter project environment: sdk: '>=3.2.0-22.0.dev <4.0.0' diff --git a/dev/a11y_assessments/test/text_field_disabled_test.dart b/dev/a11y_assessments/test/text_field_disabled_test.dart deleted file mode 100644 index 0a304b9ef5..0000000000 --- a/dev/a11y_assessments/test/text_field_disabled_test.dart +++ /dev/null @@ -1,23 +0,0 @@ -// 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 'package:a11y_assessments/use_cases/text_field_disabled.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'test_utils.dart'; - -void main() { - testWidgets('text field disabled can run', (WidgetTester tester) async { - await pumpsUseCase(tester, TextFieldDisabledUseCase()); - expect(find.byType(TextField), findsOneWidget); - expect(find.text('abc'), findsOneWidget); - - await tester.tap(find.byType(TextField)); - await tester.pumpAndSettle(); - await tester.enterText(find.byType(TextField), 'bde'); - await tester.pumpAndSettle(); - expect(find.text('abc'), findsOneWidget); - }); -} diff --git a/dev/a11y_assessments/test/text_field_password_test.dart b/dev/a11y_assessments/test/text_field_password_test.dart index 44a65a4e00..90e4fd6a6a 100644 --- a/dev/a11y_assessments/test/text_field_password_test.dart +++ b/dev/a11y_assessments/test/text_field_password_test.dart @@ -11,12 +11,23 @@ import 'test_utils.dart'; void main() { testWidgets('text field password can run', (WidgetTester tester) async { await pumpsUseCase(tester, TextFieldPasswordUseCase()); - expect(find.byType(TextField), findsOneWidget); + expect(find.byType(TextField), findsExactly(2)); - await tester.tap(find.byType(TextField)); - await tester.pumpAndSettle(); - await tester.enterText(find.byType(TextField), 'abc'); - await tester.pumpAndSettle(); - expect(find.text('abc'), findsOneWidget); + // Test the enabled password + { + final Finder finder = find.byKey(const Key('enabled password')); + await tester.tap(finder); + await tester.pumpAndSettle(); + await tester.enterText(finder, 'abc'); + await tester.pumpAndSettle(); + expect(find.text('abc'), findsOneWidget); + } + + // Test the disabled password + { + final Finder finder = find.byKey(const Key('disabled password')); + final TextField passwordField = tester.widget(finder); + expect(passwordField.enabled, isFalse); + } }); } diff --git a/dev/a11y_assessments/test/text_field_test.dart b/dev/a11y_assessments/test/text_field_test.dart index 7623876edd..0b2ea4ad7a 100644 --- a/dev/a11y_assessments/test/text_field_test.dart +++ b/dev/a11y_assessments/test/text_field_test.dart @@ -11,12 +11,23 @@ import 'test_utils.dart'; void main() { testWidgets('text field can run', (WidgetTester tester) async { await pumpsUseCase(tester, TextFieldUseCase()); - expect(find.byType(TextField), findsOneWidget); + expect(find.byType(TextField), findsExactly(2)); - await tester.tap(find.byType(TextField)); - await tester.pumpAndSettle(); - await tester.enterText(find.byType(TextField), 'abc'); - await tester.pumpAndSettle(); - expect(find.text('abc'), findsOneWidget); + // Test the enabled text field + { + final Finder finder = find.byKey(const Key('enabled text field')); + await tester.tap(finder); + await tester.pumpAndSettle(); + await tester.enterText(finder, 'abc'); + await tester.pumpAndSettle(); + expect(find.text('abc'), findsOneWidget); + } + + // Test the disabled text field + { + final Finder finder = find.byKey(const Key('disabled text field')); + final TextField textField = tester.widget(finder); + expect(textField.enabled, isFalse); + } }); } diff --git a/dev/a11y_assessments/web/manifest.json b/dev/a11y_assessments/web/manifest.json index 9a2a11fa46..b2bfc8ba99 100644 --- a/dev/a11y_assessments/web/manifest.json +++ b/dev/a11y_assessments/web/manifest.json @@ -5,7 +5,7 @@ "display": "standalone", "background_color": "#0175C2", "theme_color": "#0175C2", - "description": ""A new Flutter project."", + "description": "A new Flutter project.", "orientation": "portrait-primary", "prefer_related_applications": false, "icons": [