diff --git a/dev/a11y_assessments/lib/main.dart b/dev/a11y_assessments/lib/main.dart index 5b7a14d767a..f9ba00a0551 100644 --- a/dev/a11y_assessments/lib/main.dart +++ b/dev/a11y_assessments/lib/main.dart @@ -44,9 +44,22 @@ class App extends StatelessWidget { } } -class HomePage extends StatelessWidget { +class HomePage extends StatefulWidget { const HomePage({super.key}); + @override + State createState() => HomePageState(); +} + +class HomePageState extends State { + final ScrollController scrollController = ScrollController(); + + @override + void dispose() { + scrollController.dispose(); + super.dispose(); + } + Widget _buildUseCaseItem(int index, UseCase useCase) { return Padding( padding: const EdgeInsets.all(10), @@ -69,6 +82,7 @@ class HomePage extends StatelessWidget { appBar: AppBar(title: const Text('Accessibility Assessments')), body: Center( child: ListView( + controller: scrollController, children: List.generate( useCases.length, (int index) => _buildUseCaseItem(index, useCases[index]), diff --git a/dev/a11y_assessments/lib/use_cases/auto_complete.dart b/dev/a11y_assessments/lib/use_cases/auto_complete.dart new file mode 100644 index 00000000000..7f9aa76fc45 --- /dev/null +++ b/dev/a11y_assessments/lib/use_cases/auto_complete.dart @@ -0,0 +1,75 @@ +// 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 AutoCompleteUseCase extends UseCase { + + @override + String get name => 'AutoComplete'; + + @override + String get route => '/auto-complete'; + + @override + Widget build(BuildContext context) => const _MainWidget(); +} + +class _MainWidget extends StatefulWidget { + const _MainWidget(); + + @override + State<_MainWidget> createState() => _MainWidgetState(); +} + +class _MainWidgetState extends State<_MainWidget> { + static const List _kOptions = [ + 'apple', + 'banana', + 'lemon', + ]; + + static Widget _fieldViewBuilder(BuildContext context, TextEditingController textEditingController, FocusNode focusNode, VoidCallback onFieldSubmitted) { + return TextFormField( + focusNode: focusNode, + autofocus: true, + controller: textEditingController, + onFieldSubmitted: (String value) { + onFieldSubmitted(); + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: const Text('AutoComplete'), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Type below to autocomplete the following possible results: $_kOptions.'), + Autocomplete( + optionsBuilder: (TextEditingValue textEditingValue) { + if (textEditingValue.text == '') { + return const Iterable.empty(); + } + return _kOptions.where((String option) { + return option.contains(textEditingValue.text.toLowerCase()); + }); + }, + fieldViewBuilder: _fieldViewBuilder, + ), + ], + ), + ), + ); + } +} diff --git a/dev/a11y_assessments/lib/use_cases/badge.dart b/dev/a11y_assessments/lib/use_cases/badge.dart new file mode 100644 index 00000000000..c39f1dccdab --- /dev/null +++ b/dev/a11y_assessments/lib/use_cases/badge.dart @@ -0,0 +1,49 @@ +// 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 BadgeUseCase extends UseCase { + + @override + String get name => 'Badge'; + + @override + String get route => '/badge'; + + @override + Widget build(BuildContext context) => const MainWidget(); +} + +class MainWidget extends StatefulWidget { + const MainWidget({super.key}); + + @override + State createState() => MainWidgetState(); +} + +class MainWidgetState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: const Text('Badge'), + ), + body: const Center( + child: Badge( + label: Text( + '5', + semanticsLabel: '5 new messages', + style: TextStyle(color: Colors.white), + ), + backgroundColor: Colors.green, + child: Icon(Icons.mail, semanticLabel: 'Messages'), + ), + ), + ); + } +} diff --git a/dev/a11y_assessments/lib/use_cases/material_banner.dart b/dev/a11y_assessments/lib/use_cases/material_banner.dart new file mode 100644 index 00000000000..219902cd9e3 --- /dev/null +++ b/dev/a11y_assessments/lib/use_cases/material_banner.dart @@ -0,0 +1,74 @@ +// 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 MaterialBannerUseCase extends UseCase { + + @override + String get name => 'MaterialBanner'; + + @override + String get route => '/material_banner'; + + @override + Widget build(BuildContext context) => const MainWidget(); +} + +class MainWidget extends StatefulWidget { + const MainWidget({super.key}); + + @override + State createState() => MainWidgetState(); +} + +class MainWidgetState extends State { + double currentSliderValue = 20; + ScaffoldFeatureController? controller; + + @override + Widget build(BuildContext context) { + VoidCallback? onPress; + if (controller == null) { + onPress = () { + setState(() { + controller = ScaffoldMessenger.of(context).showMaterialBanner( + MaterialBanner( + padding: const EdgeInsets.all(20), + content: const Text('Hello, I am a Material Banner'), + leading: const Icon(Icons.agriculture_outlined), + backgroundColor: Colors.green, + actions: [ + TextButton( + onPressed: () { + controller!.close(); + setState(() { + controller = null; + }); + }, + child: const Text('DISMISS'), + ), + ], + ), + ); + }); + }; + } + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: const Text('MaterialBanner'), + ), + body: Center( + child: ElevatedButton( + autofocus: true, + onPressed: onPress, + child: const Text('Show a MaterialBanner'), + ), + ), + ); + } +} diff --git a/dev/a11y_assessments/lib/use_cases/navigation_bar.dart b/dev/a11y_assessments/lib/use_cases/navigation_bar.dart new file mode 100644 index 00000000000..6dc4fad2e2b --- /dev/null +++ b/dev/a11y_assessments/lib/use_cases/navigation_bar.dart @@ -0,0 +1,79 @@ +// 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 NavigationBarUseCase extends UseCase { + + @override + String get name => 'NavigationBar'; + + @override + String get route => '/navigation-bar'; + + @override + Widget build(BuildContext context) => const MainWidget(); +} + +class MainWidget extends StatefulWidget { + const MainWidget({super.key}); + + @override + State createState() => MainWidgetState(); +} + +class MainWidgetState extends State { + int currentPageIndex = 0; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: const Text('NavigationBar'), + ), + bottomNavigationBar: NavigationBar( + onDestinationSelected: (int index) { + setState(() { + currentPageIndex = index; + }); + }, + indicatorColor: Colors.amber[800], + selectedIndex: currentPageIndex, + destinations: const [ + NavigationDestination( + selectedIcon: Icon(Icons.home), + icon: Icon(Icons.home_outlined), + label: 'Home', + ), + NavigationDestination( + icon: Icon(Icons.business), + label: 'Business', + ), + NavigationDestination( + selectedIcon: Icon(Icons.school), + icon: Icon(Icons.school_outlined), + label: 'School', + ), + ], + ), + body: [ + Container( + alignment: Alignment.center, + child: const Text('Page 1'), + ), + Container( + alignment: Alignment.center, + child: const Text('Page 2'), + ), + Container( + alignment: Alignment.center, + child: const Text('Page 3'), + ), + ][currentPageIndex], + ); + } +} diff --git a/dev/a11y_assessments/lib/use_cases/text_button.dart b/dev/a11y_assessments/lib/use_cases/text_button.dart new file mode 100644 index 00000000000..e9f240fa74f --- /dev/null +++ b/dev/a11y_assessments/lib/use_cases/text_button.dart @@ -0,0 +1,55 @@ +// 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 TextButtonUseCase extends UseCase { + + @override + String get name => 'TextButton'; + + @override + String get route => '/text-button'; + + @override + Widget build(BuildContext context) => const MainWidget(); +} + +class MainWidget extends StatefulWidget { + const MainWidget({super.key}); + + @override + State createState() => MainWidgetState(); +} + +class MainWidgetState extends State { + double currentSliderValue = 20; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: const Text('TextButton'), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextButton( + onPressed: () { }, + child: const Text('Text button'), + ), + const TextButton( + onPressed: null, + child: Text('Text button disabled'), + ), + ], + ), + ), + ); + } +} diff --git a/dev/a11y_assessments/lib/use_cases/use_cases.dart b/dev/a11y_assessments/lib/use_cases/use_cases.dart index 056f9e2b11a..ab10c33c1bf 100644 --- a/dev/a11y_assessments/lib/use_cases/use_cases.dart +++ b/dev/a11y_assessments/lib/use_cases/use_cases.dart @@ -4,10 +4,15 @@ import 'package:flutter/widgets.dart'; +import 'auto_complete.dart'; +import 'badge.dart'; import 'check_box_list_tile.dart'; import 'date_picker.dart'; import 'dialog.dart'; +import 'material_banner.dart'; +import 'navigation_bar.dart'; import 'slider.dart'; +import 'text_button.dart'; import 'text_field.dart'; import 'text_field_password.dart'; @@ -24,4 +29,9 @@ final List useCases = [ TextFieldUseCase(), TextFieldPasswordUseCase(), DatePickerUseCase(), + AutoCompleteUseCase(), + BadgeUseCase(), + MaterialBannerUseCase(), + NavigationBarUseCase(), + TextButtonUseCase(), ]; diff --git a/dev/a11y_assessments/test/accessibility_guideline_test.dart b/dev/a11y_assessments/test/accessibility_guideline_test.dart index 7bb1c56fcc3..08f73effed5 100644 --- a/dev/a11y_assessments/test/accessibility_guideline_test.dart +++ b/dev/a11y_assessments/test/accessibility_guideline_test.dart @@ -11,6 +11,11 @@ void main() { for (final UseCase useCase in useCases) { testWidgets('testing accessibility guideline for ${useCase.name}', (WidgetTester tester) async { await tester.pumpWidget(const App()); + final ScrollController controller = tester.state(find.byType(HomePage)).scrollController; + while (find.byKey(Key(useCase.name)).evaluate().isEmpty) { + controller.jumpTo(controller.offset + 600); + await tester.pumpAndSettle(); + } await tester.tap(find.byKey(Key(useCase.name))); await tester.pumpAndSettle(); diff --git a/dev/a11y_assessments/test/auto_complete_test.dart b/dev/a11y_assessments/test/auto_complete_test.dart new file mode 100644 index 00000000000..cc3bbb1635f --- /dev/null +++ b/dev/a11y_assessments/test/auto_complete_test.dart @@ -0,0 +1,19 @@ +// 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/auto_complete.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'test_utils.dart'; + +void main() { + testWidgets('auto complete can run', (WidgetTester tester) async { + await pumpsUseCase(tester, AutoCompleteUseCase()); + await tester.enterText(find.byType(TextFormField), 'a'); + await tester.pumpAndSettle(); + + expect(find.text('apple'), findsOneWidget); + }); +} diff --git a/dev/a11y_assessments/test/badge_test.dart b/dev/a11y_assessments/test/badge_test.dart new file mode 100644 index 00000000000..303ae8e5513 --- /dev/null +++ b/dev/a11y_assessments/test/badge_test.dart @@ -0,0 +1,16 @@ +// 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/badge.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'test_utils.dart'; + +void main() { + testWidgets('badge can run', (WidgetTester tester) async { + await pumpsUseCase(tester, BadgeUseCase()); + expect(find.semantics.byLabel('5 new messages'), findsOne); + expect(find.semantics.byLabel('Messages'), findsOne); + }); +} diff --git a/dev/a11y_assessments/test/material_banner_test.dart b/dev/a11y_assessments/test/material_banner_test.dart new file mode 100644 index 00000000000..7fab94fcd82 --- /dev/null +++ b/dev/a11y_assessments/test/material_banner_test.dart @@ -0,0 +1,23 @@ +// 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/material_banner.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'test_utils.dart'; + +void main() { + testWidgets('material banner can run', (WidgetTester tester) async { + await pumpsUseCase(tester, MaterialBannerUseCase()); + expect(find.text('Show a MaterialBanner'), findsOneWidget); + + await tester.tap(find.text('Show a MaterialBanner')); + await tester.pumpAndSettle(); + expect(find.text('Hello, I am a Material Banner'), findsOneWidget); + + await tester.tap(find.text('DISMISS')); + await tester.pumpAndSettle(); + expect(find.text('Hello, I am a Material Banner'), findsNothing); + }); +} diff --git a/dev/a11y_assessments/test/navigation_bar_test.dart b/dev/a11y_assessments/test/navigation_bar_test.dart new file mode 100644 index 00000000000..c90f593e3d7 --- /dev/null +++ b/dev/a11y_assessments/test/navigation_bar_test.dart @@ -0,0 +1,23 @@ +// 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/navigation_bar.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'test_utils.dart'; + +void main() { + testWidgets('navigation bar can run', (WidgetTester tester) async { + await pumpsUseCase(tester, NavigationBarUseCase()); + expect(find.text('Page 1'), findsOneWidget); + + await tester.tap(find.text('Business')); + await tester.pumpAndSettle(); + expect(find.text('Page 2'), findsOneWidget); + + await tester.tap(find.text('School')); + await tester.pumpAndSettle(); + expect(find.text('Page 3'), findsOneWidget); + }); +} diff --git a/dev/a11y_assessments/test/text_button_test.dart b/dev/a11y_assessments/test/text_button_test.dart new file mode 100644 index 00000000000..31309d5981c --- /dev/null +++ b/dev/a11y_assessments/test/text_button_test.dart @@ -0,0 +1,16 @@ +// 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_button.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'test_utils.dart'; + +void main() { + testWidgets('text button can run', (WidgetTester tester) async { + await pumpsUseCase(tester, TextButtonUseCase()); + expect(find.text('Text button'), findsOneWidget); + expect(find.text('Text button disabled'), findsOneWidget); + }); +}