-
Notifications
You must be signed in to change notification settings - Fork 6k
[web] Update a11y announcements to append divs instead of setting content. #42258
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -2,6 +2,9 @@ | |||
| // Use of this source code is governed by a BSD-style license that can be | ||||
| // found in the LICENSE file. | ||||
|
|
||||
| import 'dart:async'; | ||||
| import 'dart:typed_data'; | ||||
|
|
||||
| import 'package:test/bootstrap/browser.dart'; | ||||
| import 'package:test/test.dart'; | ||||
| import 'package:ui/src/engine/dom.dart'; | ||||
|
|
@@ -18,20 +21,29 @@ void main() { | |||
| void testMain() { | ||||
| setUpAll(() async { | ||||
| await initializeEngine(); | ||||
| setLiveMessageDurationForTest(const Duration(milliseconds: 10)); | ||||
| }); | ||||
|
|
||||
| group('$AccessibilityAnnouncements', () { | ||||
| void expectAnnouncementElements({required bool present}) { | ||||
| expect( | ||||
| domDocument.getElementById('ftl-announcement-polite'), | ||||
| present ? isNotNull : isNull, | ||||
| ); | ||||
| expect( | ||||
| domDocument.getElementById('ftl-announcement-assertive'), | ||||
| present ? isNotNull : isNull, | ||||
| ); | ||||
| } | ||||
| void expectAnnouncementElements({required bool present}) { | ||||
| expect( | ||||
| domDocument.getElementById('ftl-announcement-polite'), | ||||
| present ? isNotNull : isNull, | ||||
| ); | ||||
| expect( | ||||
| domDocument.getElementById('ftl-announcement-assertive'), | ||||
| present ? isNotNull : isNull, | ||||
| ); | ||||
| } | ||||
|
|
||||
| tearDown(() async { | ||||
| // Completely reset accessibility announcements for subsequent tests. | ||||
| accessibilityAnnouncements.dispose(); | ||||
| await Future<void>.delayed(liveMessageDuration * 2); | ||||
| initializeAccessibilityAnnouncements(); | ||||
| expectAnnouncementElements(present: true); | ||||
| }); | ||||
|
|
||||
| group('$AccessibilityAnnouncements', () { | ||||
| test('Initialization and disposal', () { | ||||
| // Elements should be there right after engine initialization. | ||||
| expectAnnouncementElements(present: true); | ||||
|
|
@@ -43,76 +55,89 @@ void testMain() { | |||
| expectAnnouncementElements(present: true); | ||||
| }); | ||||
|
|
||||
| void resetAccessibilityAnnouncements() { | ||||
| accessibilityAnnouncements.dispose(); | ||||
| initializeAccessibilityAnnouncements(); | ||||
| expectAnnouncementElements(present: true); | ||||
| ByteData? encodeMessageOnly({required String message}) { | ||||
| return codec.encodeMessage(<dynamic, dynamic>{ | ||||
| 'data': <dynamic, dynamic>{'message': message}, | ||||
| }); | ||||
| } | ||||
|
|
||||
| void sendAnnouncementMessage({required String message, int? assertiveness}) { | ||||
| accessibilityAnnouncements.handleMessage(codec, codec.encodeMessage(<dynamic, dynamic>{ | ||||
| 'data': <dynamic, dynamic>{ | ||||
| 'message': message, | ||||
| 'assertiveness': assertiveness, | ||||
| }, | ||||
| })); | ||||
| } | ||||
|
|
||||
| void expectMessages({String polite = '', String assertive = ''}) { | ||||
| expect(accessibilityAnnouncements.ariaLiveElementFor(Assertiveness.polite).text, polite); | ||||
| expect(accessibilityAnnouncements.ariaLiveElementFor(Assertiveness.assertive).text, assertive); | ||||
| } | ||||
|
|
||||
| test('Default value of aria-live is polite when assertiveness is not specified', () { | ||||
| resetAccessibilityAnnouncements(); | ||||
| const Map<dynamic, dynamic> testInput = <dynamic, dynamic>{'data': <dynamic, dynamic>{'message': 'polite message'}}; | ||||
| accessibilityAnnouncements.handleMessage(codec, codec.encodeMessage(testInput)); | ||||
| expect(accessibilityAnnouncements.ariaLiveElementFor(Assertiveness.polite).text, 'polite message'); | ||||
| expect(accessibilityAnnouncements.ariaLiveElementFor(Assertiveness.assertive).text, ''); | ||||
| void expectNoMessages() => expectMessages(); | ||||
|
|
||||
| test('Default value of aria-live is polite when assertiveness is not specified', () async { | ||||
| accessibilityAnnouncements.handleMessage(codec, encodeMessageOnly(message: 'polite message')); | ||||
| expectMessages(polite: 'polite message'); | ||||
|
|
||||
| await Future<void>.delayed(liveMessageDuration); | ||||
| expectNoMessages(); | ||||
| }); | ||||
|
|
||||
| test('aria-live is assertive when assertiveness is set to 1', () { | ||||
| resetAccessibilityAnnouncements(); | ||||
| const Map<dynamic, dynamic> testInput = <dynamic, dynamic>{'data': <dynamic, dynamic>{'message': 'assertive message', 'assertiveness': 1}}; | ||||
| accessibilityAnnouncements.handleMessage(codec, codec.encodeMessage(testInput)); | ||||
| expect(accessibilityAnnouncements.ariaLiveElementFor(Assertiveness.polite).text, ''); | ||||
| expect(accessibilityAnnouncements.ariaLiveElementFor(Assertiveness.assertive).text, 'assertive message'); | ||||
| test('aria-live is assertive when assertiveness is set to 1', () async { | ||||
| sendAnnouncementMessage(message: 'assertive message', assertiveness: 1); | ||||
| expectMessages(assertive: 'assertive message'); | ||||
|
|
||||
| await Future<void>.delayed(liveMessageDuration); | ||||
| expectNoMessages(); | ||||
| }); | ||||
|
|
||||
| test('aria-live is polite when assertiveness is null', () { | ||||
| resetAccessibilityAnnouncements(); | ||||
| const Map<dynamic, dynamic> testInput = <dynamic, dynamic>{'data': <dynamic, dynamic>{'message': 'polite message', 'assertiveness': null}}; | ||||
| accessibilityAnnouncements.handleMessage(codec, codec.encodeMessage(testInput)); | ||||
| expect(accessibilityAnnouncements.ariaLiveElementFor(Assertiveness.polite).text, 'polite message'); | ||||
| expect(accessibilityAnnouncements.ariaLiveElementFor(Assertiveness.assertive).text, ''); | ||||
| test('aria-live is polite when assertiveness is null', () async { | ||||
| sendAnnouncementMessage(message: 'polite message'); | ||||
| expectMessages(polite: 'polite message'); | ||||
|
|
||||
| await Future<void>.delayed(liveMessageDuration); | ||||
| expectNoMessages(); | ||||
| }); | ||||
|
|
||||
| test('aria-live is polite when assertiveness is set to 0', () { | ||||
| resetAccessibilityAnnouncements(); | ||||
| const Map<dynamic, dynamic> testInput = <dynamic, dynamic>{'data': <dynamic, dynamic>{'message': 'polite message', 'assertiveness': 0}}; | ||||
| accessibilityAnnouncements.handleMessage(codec, codec.encodeMessage(testInput)); | ||||
| expect(accessibilityAnnouncements.ariaLiveElementFor(Assertiveness.polite).text, 'polite message'); | ||||
| expect(accessibilityAnnouncements.ariaLiveElementFor(Assertiveness.assertive).text, ''); | ||||
| test('aria-live is polite when assertiveness is set to 0', () async { | ||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we know/care what happens if assertiveness is a negative int? Or is this not a scenario we'll run into
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not a scenario we should run into - it would give an index out of bounds. This is a serialization of the
|
||||
| sendAnnouncementMessage(message: 'polite message', assertiveness: 0); | ||||
| expectMessages(polite: 'polite message'); | ||||
|
|
||||
| await Future<void>.delayed(liveMessageDuration); | ||||
| expectNoMessages(); | ||||
| }); | ||||
|
|
||||
| test('The same message announced twice is altered to convince the screen reader to read it again.', () { | ||||
| resetAccessibilityAnnouncements(); | ||||
| const Map<dynamic, dynamic> testInput = <dynamic, dynamic>{'data': <dynamic, dynamic>{'message': 'Hello'}}; | ||||
| accessibilityAnnouncements.handleMessage(codec, codec.encodeMessage(testInput)); | ||||
| expect(accessibilityAnnouncements.ariaLiveElementFor(Assertiveness.polite).text, 'Hello'); | ||||
| expect(accessibilityAnnouncements.ariaLiveElementFor(Assertiveness.assertive).text, ''); | ||||
|
|
||||
| // The DOM value gains a "." to make the message look updated. | ||||
| const Map<dynamic, dynamic> testInput2 = <dynamic, dynamic>{'data': <dynamic, dynamic>{'message': 'Hello'}}; | ||||
| accessibilityAnnouncements.handleMessage(codec, codec.encodeMessage(testInput2)); | ||||
| expect(accessibilityAnnouncements.ariaLiveElementFor(Assertiveness.polite).text, 'Hello.'); | ||||
| expect(accessibilityAnnouncements.ariaLiveElementFor(Assertiveness.assertive).text, ''); | ||||
|
|
||||
| // Now the "." is removed because the message without it will also look updated. | ||||
| const Map<dynamic, dynamic> testInput3 = <dynamic, dynamic>{'data': <dynamic, dynamic>{'message': 'Hello'}}; | ||||
| accessibilityAnnouncements.handleMessage(codec, codec.encodeMessage(testInput3)); | ||||
| expect(accessibilityAnnouncements.ariaLiveElementFor(Assertiveness.polite).text, 'Hello'); | ||||
| expect(accessibilityAnnouncements.ariaLiveElementFor(Assertiveness.assertive).text, ''); | ||||
| test('Rapid-fire messages are each announced.', () async { | ||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this behavior change if mixed assertive/polite rapid-fire messages are sent, rather than just polite?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nope. Nothing fundamentally changes. They go into different live divs, each of which is handled independently. These tests ensure that multiple messages of the same type don't clobber each other in any way. |
||||
| sendAnnouncementMessage(message: 'Hello'); | ||||
| expectMessages(polite: 'Hello'); | ||||
|
|
||||
| await Future<void>.delayed(liveMessageDuration * 0.5); | ||||
| sendAnnouncementMessage(message: 'There'); | ||||
| expectMessages(polite: 'HelloThere'); | ||||
|
|
||||
| await Future<void>.delayed(liveMessageDuration * 0.6); | ||||
| expectMessages(polite: 'There'); | ||||
|
|
||||
| await Future<void>.delayed(liveMessageDuration * 0.5); | ||||
| expectNoMessages(); | ||||
| }); | ||||
|
|
||||
| test('announce() polite', () { | ||||
| resetAccessibilityAnnouncements(); | ||||
| test('announce() polite', () async { | ||||
| accessibilityAnnouncements.announce('polite message', Assertiveness.polite); | ||||
| expect(accessibilityAnnouncements.ariaLiveElementFor(Assertiveness.polite).text, 'polite message'); | ||||
| expect(accessibilityAnnouncements.ariaLiveElementFor(Assertiveness.assertive).text, ''); | ||||
| expectMessages(polite: 'polite message'); | ||||
|
|
||||
| await Future<void>.delayed(liveMessageDuration); | ||||
| expectNoMessages(); | ||||
| }); | ||||
|
|
||||
| test('announce() assertive', () { | ||||
| resetAccessibilityAnnouncements(); | ||||
| test('announce() assertive', () async { | ||||
| accessibilityAnnouncements.announce('assertive message', Assertiveness.assertive); | ||||
| expect(accessibilityAnnouncements.ariaLiveElementFor(Assertiveness.polite).text, ''); | ||||
| expect(accessibilityAnnouncements.ariaLiveElementFor(Assertiveness.assertive).text, 'assertive message'); | ||||
| expectMessages(assertive: 'assertive message'); | ||||
|
|
||||
| await Future<void>.delayed(liveMessageDuration); | ||||
| expectNoMessages(); | ||||
| }); | ||||
| }); | ||||
| } | ||||
Uh oh!
There was an error while loading. Please reload this page.