fix(flutter/a11y assessments): h1 missing a11y from each page on the web app (#152198)

Adds Semantic Widget to wrap the contents of each page's AppBar title attribute and adds header: true and headingLevel: 1 as Semantic attributes so the content of the title is compiled as an h1 tag to meet accessibility guidelines that each page must have a h1 tag present. Also updates the test cases for the home page and each use case page to check for its respective h1.

[Before Screenshot - Accessibility Assessments Home Page](https://screenshot.googleplex.com/4i9LuiGwvLnEcZ8)
[Before Screenshot - Checkbox List Title -- note: each use case page is missing an h1](https://screenshot.googleplex.com/3qQjfqvAMTehRsm)
[After Screenshot - Accessibility Assessments Home Page](https://screenshot.googleplex.com/APSJJXBmwNBP35m)
[After Screenshot - Checkbox List Title-- note: change is similar across each Accessibility use case page](https://screenshot.googleplex.com/6EGgZnTusEgeN5L)

Fixes b/338035526
This commit is contained in:
Joy Serquiña 2024-08-13 15:22:28 -07:00 committed by GitHub
parent 87abed2b23
commit 85b3d97673
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 132 additions and 20 deletions

View File

@ -77,7 +77,9 @@ class HomePageState extends State<HomePage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('Accessibility Assessments')), appBar: AppBar(
title: Semantics(headingLevel: 1, child: const Text('Accessibility Assessments')),
),
body: Center( body: Center(
child: ListView( child: ListView(
controller: scrollController, controller: scrollController,

View File

@ -31,7 +31,7 @@ class MainWidgetState extends State<MainWidget> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary, backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('ActionChip'), title: Semantics(headingLevel:1, child: const Text('ActionChip')),
), ),
body: Center( body: Center(
child: Column( child: Column(

View File

@ -50,7 +50,7 @@ class _MainWidgetState extends State<_MainWidget> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary, backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('AutoComplete'), title: Semantics(headingLevel: 1, child: const Text('AutoComplete Demo')),
), ),
body: Center( body: Center(
child: Column( child: Column(

View File

@ -25,12 +25,13 @@ class MainWidget extends StatefulWidget {
} }
class MainWidgetState extends State<MainWidget> { class MainWidgetState extends State<MainWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary, backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('Badge'), title: Semantics(headingLevel: 1, child: const Text('Badge Demo')),
), ),
body: const Center( body: const Center(
child: Badge( child: Badge(

View File

@ -31,7 +31,7 @@ class MainWidgetState extends State<MainWidget> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary, backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('Card'), title: Semantics(headingLevel: 1, child: const Text('Card')),
), ),
body: const Center( body: const Center(
child: Column( child: Column(

View File

@ -28,7 +28,9 @@ class _MainWidgetState extends State<_MainWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('CheckBoxListTile')), appBar: AppBar(
title: Semantics(headingLevel: 1, child: const Text('CheckBoxListTile Demo')),
),
body: ListView( body: ListView(
children: <Widget>[ children: <Widget>[
CheckboxListTile( CheckboxListTile(

View File

@ -30,7 +30,7 @@ class _MainWidgetState extends State<_MainWidget> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary, backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('DatePicker'), title: Semantics(headingLevel: 1, child: const Text('DatePicker Demo')),
), ),
body: Center( body: Center(
child: TextButton( child: TextButton(

View File

@ -24,7 +24,7 @@ class _MainWidget extends StatelessWidget {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary, backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('Dialog'), title: Semantics(headingLevel: 1, child: const Text('Dialog Demo')),
), ),
body: Center( body: Center(
child: TextButton( child: TextButton(

View File

@ -31,8 +31,8 @@ class _DrawerExampleState extends State<DrawerExample> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Drawer Example'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary, backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Semantics(headingLevel: 1, child: const Text('Drawer Demo')),
), ),
endDrawer: Drawer( endDrawer: Drawer(
child: ListView( child: ListView(

View File

@ -32,7 +32,7 @@ class _ExpansionTileExampleState extends State<ExpansionTileExample> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary, backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('ExpansionTile'), title: Semantics(headingLevel: 1, child: const Text('ExpansionTile')),
), ),
body: Column( body: Column(
children: <Widget>[ children: <Widget>[

View File

@ -60,7 +60,7 @@ class MainWidgetState extends State<MainWidget> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary, backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('MaterialBanner'), title: Semantics(headingLevel: 1, child: const Text('MaterialBanner Demo')),
), ),
body: Center( body: Center(
child: ElevatedButton( child: ElevatedButton(

View File

@ -32,7 +32,7 @@ class MainWidgetState extends State<MainWidget> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary, backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('NavigationBar'), title: Semantics(headingLevel: 1, child: const Text('NavigationBar Demo')),
), ),
bottomNavigationBar: NavigationBar( bottomNavigationBar: NavigationBar(
onDestinationSelected: (int index) { onDestinationSelected: (int index) {

View File

@ -62,8 +62,8 @@ class _NavigationDrawerExampleState extends State<NavigationDrawerExample> {
return Scaffold( return Scaffold(
key: scaffoldKey, key: scaffoldKey,
appBar: AppBar( appBar: AppBar(
title: const Text('Navigation Drawer Example'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary, backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Semantics(headingLevel: 1, child: const Text('Navigation Drawer Demo')),
), ),
body: SafeArea( body: SafeArea(
bottom: false, bottom: false,

View File

@ -36,7 +36,9 @@ class _MainWidgetState extends State<_MainWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('Radio button')), appBar: AppBar(
title: Semantics(headingLevel: 1, child: const Text('Radio button demo'))
),
body: ListView( body: ListView(
children: <Widget>[ children: <Widget>[
RadioListTile<SingingCharacter>( RadioListTile<SingingCharacter>(

View File

@ -33,7 +33,7 @@ class MainWidgetState extends State<MainWidget> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary, backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('Slider'), title: Semantics(headingLevel: 1, child: const Text('Slider demo')),
), ),
body: Center( body: Center(
child: Semantics( child: Semantics(

View File

@ -30,7 +30,7 @@ class MainWidgetState extends State<MainWidget> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary, backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('SnackBar'), title: Semantics(headingLevel: 1, child: const Text('SnackBar')),
), ),
body: Center( body: Center(
child: Column( child: Column(

View File

@ -32,7 +32,7 @@ class _SwitchListTileExampleState extends State<SwitchListTileExample> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary, backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('SwitchListTile'), title: Semantics(headingLevel: 1, child: const Text('SwitchListTile')),
), ),
body: Center( body: Center(
child: Column( child: Column(

View File

@ -32,7 +32,7 @@ class MainWidgetState extends State<MainWidget> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary, backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('TextButton'), title: Semantics(headingLevel: 1, child: const Text('TextButton Demo')),
), ),
body: Center( body: Center(
child: Column( child: Column(

View File

@ -25,7 +25,7 @@ class _MainWidget extends StatelessWidget {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary, backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('TextField'), title: Semantics(headingLevel: 1, child: const Text('TextField demo')),
), ),
body: ListView( body: ListView(
children: <Widget>[ children: <Widget>[

View File

@ -25,7 +25,7 @@ class _MainWidget extends StatelessWidget {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary, backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('TextField password'), title: Semantics(headingLevel: 1, child: const Text('TextField password demo')),
), ),
body: ListView( body: ListView(
children: const <Widget>[ children: const <Widget>[

View File

@ -16,4 +16,11 @@ void main() {
expect(find.text('apple'), findsOneWidget); expect(find.text('apple'), findsOneWidget);
}); });
testWidgets('auto complete has one h1 tag', (WidgetTester tester) async {
await pumpsUseCase(tester, AutoCompleteUseCase());
final Finder findHeadingLevelOnes = find.bySemanticsLabel(RegExp('AutoComplete Demo'));
await tester.pumpAndSettle();
expect(findHeadingLevelOnes, findsOne);
});
} }

View File

@ -13,4 +13,11 @@ void main() {
expect(find.semantics.byLabel('5 new messages'), findsOne); expect(find.semantics.byLabel('5 new messages'), findsOne);
expect(find.semantics.byLabel('Messages'), findsOne); expect(find.semantics.byLabel('Messages'), findsOne);
}); });
testWidgets('badge has one h1 tag', (WidgetTester tester) async {
await pumpsUseCase(tester, BadgeUseCase());
final Finder findHeadingLevelOnes = find.bySemanticsLabel(RegExp('Badge Demo'));
await tester.pumpAndSettle();
expect(findHeadingLevelOnes, findsOne);
});
} }

View File

@ -14,4 +14,11 @@ void main() {
expect(find.text('a check box list title'), findsOneWidget); expect(find.text('a check box list title'), findsOneWidget);
expect(find.text('a disabled check box list title'), findsOneWidget); expect(find.text('a disabled check box list title'), findsOneWidget);
}); });
testWidgets('check box list has one h1 tag', (WidgetTester tester) async {
await pumpsUseCase(tester, CheckBoxListTile());
final Finder findHeadingLevelOnes = find.bySemanticsLabel(RegExp('CheckBoxListTile Demo'));
await tester.pumpAndSettle();
expect(findHeadingLevelOnes, findsOne);
});
} }

View File

@ -17,4 +17,11 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(find.byType(DatePickerDialog), findsOneWidget); expect(find.byType(DatePickerDialog), findsOneWidget);
}); });
testWidgets('datepicker has one h1 tag', (WidgetTester tester) async {
await pumpsUseCase(tester, DatePickerUseCase());
final Finder findHeadingLevelOnes = find.bySemanticsLabel('DatePicker Demo');
await tester.pumpAndSettle();
expect(findHeadingLevelOnes, findsOne);
});
} }

View File

@ -28,4 +28,11 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(find.text('This is a typical dialog.'), findsNothing); expect(find.text('This is a typical dialog.'), findsNothing);
}); });
testWidgets('dialog has one h1 tag', (WidgetTester tester) async {
await pumpsUseCase(tester, DialogUseCase());
final Finder findHeadingLevelOnes = find.bySemanticsLabel('Dialog Demo');
await tester.pumpAndSettle();
expect(findHeadingLevelOnes, findsOne);
});
} }

View File

@ -20,4 +20,11 @@ void main() {
expect(find.byType(Drawer), findsExactly(1)); expect(find.byType(Drawer), findsExactly(1));
}); });
testWidgets('drawer has one h1 tag', (WidgetTester tester) async {
await pumpsUseCase(tester, DrawerUseCase());
final Finder findHeadingLevelOnes = find.bySemanticsLabel('Drawer Demo');
await tester.pumpAndSettle();
expect(findHeadingLevelOnes, findsOne);
});
} }

View File

@ -140,4 +140,11 @@ void main() {
expect(appScheme.inversePrimary.value, expect(appScheme.inversePrimary.value,
MaterialDynamicColors.inversePrimary.getArgb(highContrastScheme)); MaterialDynamicColors.inversePrimary.getArgb(highContrastScheme));
}); });
testWidgets('a11y assessments home page has one h1 tag', (WidgetTester tester) async {
await tester.pumpWidget(const App());
final Finder findHeadingLevelOnes = find.bySemanticsLabel('Accessibility Assessments');
await tester.pumpAndSettle();
expect(findHeadingLevelOnes, findsOne);
});
} }

View File

@ -20,4 +20,11 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(find.text('Hello, I am a Material Banner'), findsNothing); expect(find.text('Hello, I am a Material Banner'), findsNothing);
}); });
testWidgets('material banner has one h1 tag', (WidgetTester tester) async {
await pumpsUseCase(tester, MaterialBannerUseCase());
final Finder findHeadingLevelOnes = find.bySemanticsLabel('MaterialBanner Demo');
await tester.pumpAndSettle();
expect(findHeadingLevelOnes, findsOne);
});
} }

View File

@ -20,4 +20,11 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(find.text('Page 3'), findsOneWidget); expect(find.text('Page 3'), findsOneWidget);
}); });
testWidgets('navigation bar has one h1 tag', (WidgetTester tester) async {
await pumpsUseCase(tester, NavigationBarUseCase());
final Finder findHeadingLevelOnes = find.bySemanticsLabel('NavigationBar Demo');
await tester.pumpAndSettle();
expect(findHeadingLevelOnes, findsOne);
});
} }

View File

@ -20,4 +20,11 @@ void main() {
expect(find.byType(NavigationDrawer), findsExactly(1)); expect(find.byType(NavigationDrawer), findsExactly(1));
}); });
testWidgets('navigation drawer has one h1 tag', (WidgetTester tester) async {
await pumpsUseCase(tester, NavigationDrawerUseCase());
final Finder findHeadingLevelOnes = find.bySemanticsLabel('Navigation Drawer Demo');
await tester.pumpAndSettle();
expect(findHeadingLevelOnes, findsOne);
});
} }

View File

@ -14,4 +14,11 @@ void main() {
expect(find.text('Lafayette'), findsOneWidget); expect(find.text('Lafayette'), findsOneWidget);
expect(find.text('Jefferson'), findsOneWidget); expect(find.text('Jefferson'), findsOneWidget);
}); });
testWidgets('radio button demo page has one h1 tag', (WidgetTester tester) async {
await pumpsUseCase(tester, RadioListTileUseCase());
final Finder findHeadingLevelOnes = find.bySemanticsLabel('Radio button demo');
await tester.pumpAndSettle();
expect(findHeadingLevelOnes, findsOne);
});
} }

View File

@ -26,4 +26,11 @@ void main() {
find.bySemanticsLabel('Accessibility Test Slider'); find.bySemanticsLabel('Accessibility Test Slider');
expect(semanticsWidget, findsOneWidget); expect(semanticsWidget, findsOneWidget);
}); });
testWidgets('slider demo page has one h1 tag', (WidgetTester tester) async {
await pumpsUseCase(tester, SliderUseCase());
final Finder findHeadingLevelOnes = find.bySemanticsLabel('Slider demo');
await tester.pumpAndSettle();
expect(findHeadingLevelOnes, findsOne);
});
} }

View File

@ -27,4 +27,11 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(find.text('Clicked 2 time(s).'), findsOneWidget); expect(find.text('Clicked 2 time(s).'), findsOneWidget);
}); });
testWidgets('text button demo page has one h1 tag', (WidgetTester tester) async {
await pumpsUseCase(tester, TextButtonUseCase());
final Finder findHeadingLevelOnes = find.bySemanticsLabel('TextButton Demo');
await tester.pumpAndSettle();
expect(findHeadingLevelOnes, findsOne);
});
} }

View File

@ -50,4 +50,11 @@ void main() {
expect(textField.decoration?.hintText, isNull); expect(textField.decoration?.hintText, isNull);
} }
}); });
testWidgets('text field password demo page has one h1 tag', (WidgetTester tester) async {
await pumpsUseCase(tester, TextFieldPasswordUseCase());
final Finder findHeadingLevelOnes = find.bySemanticsLabel('TextField password demo');
await tester.pumpAndSettle();
expect(findHeadingLevelOnes, findsOne);
});
} }

View File

@ -64,4 +64,11 @@ void main() {
find.bySemanticsLabel(RegExp(textFieldLabel)); find.bySemanticsLabel(RegExp(textFieldLabel));
expect(semanticsWidgets, findsExactly(2)); expect(semanticsWidgets, findsExactly(2));
}); });
testWidgets('text field demo page has one h1 tag', (WidgetTester tester) async {
await pumpsUseCase(tester, TextFieldUseCase());
final Finder findHeadingLevelOnes = find.bySemanticsLabel('TextField demo');
await tester.pumpAndSettle();
expect(findHeadingLevelOnes, findsOne);
});
} }