Skip to content

Commit 2c28a27

Browse files
OEvgenyEugene Olonov
andauthored
Announce card action buttons as buttons, describe pressed actions as Selected (#5900)
* Name Action.ShowCard as the exception in Adaptive Card hack comments The comments named Action.ShowUrl, but the code exempts buttons with aria-expanded, which is Action.ShowCard. * Announce card action buttons as buttons, describe pressed actions as Selected Card action buttons had aria-pressed, so screen readers announced them as toggle buttons that never turn off (#5716, #5719, #5745, #5750). They are now plain buttons; pressed actions are described as "Selected" via aria-describedby, which keeps the choice announced (#3622), and is styled via the adaptive-card-renderer__action--selected class instead of [aria-pressed='true'] in the default and Fluent themes. * Add PR number to changelog * Select card actions only after Adaptive Cards performed them An Action.Submit whose required inputs fail validation is not executed by Adaptive Cards, but the capture-phase click handler still marked it as selected. Mark the action from onExecuteAction instead. * Shorten the changelog entry * Snapshot every key step in ariaPushed.html * Wait for the live region to clear before the accessibility check in selectedAction.validation.html * Move the changelog entry to .changelog/ --------- Co-authored-by: Eugene Olonov <eugeneolonov@microsoft.com>
1 parent 96c1416 commit 2c28a27

30 files changed

Lines changed: 566 additions & 190 deletions
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
### Changed
2+
3+
- 💥 Card action buttons the user pressed are styled with the `adaptive-card-renderer__action--selected` class instead of `[aria-pressed='true']`: host CSS that targets `.ac-pushButton[aria-pressed='true']` should target `.ac-pushButton.adaptive-card-renderer__action--selected` instead, in PR [#5900](https://github.com/microsoft/BotFramework-WebChat/pull/5900), by [@OEvgeny](https://github.com/OEvgeny)
4+
5+
### Fixed
6+
7+
- Fixed [#4583](https://github.com/microsoft/BotFramework-WebChat/issues/4583), [#5716](https://github.com/microsoft/BotFramework-WebChat/issues/5716), [#5719](https://github.com/microsoft/BotFramework-WebChat/issues/5719), [#5745](https://github.com/microsoft/BotFramework-WebChat/issues/5745), and [#5750](https://github.com/microsoft/BotFramework-WebChat/issues/5750), card action buttons being announced as toggle buttons, and Submit being marked as selected when the card's inputs fail validation, in PR [#5900](https://github.com/microsoft/BotFramework-WebChat/pull/5900), by [@OEvgeny](https://github.com/OEvgeny)

‎__tests__/html2/accessibility/adaptiveCard/ariaPushed.html‎

Lines changed: 76 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!DOCTYPE html>
1+
<!doctype html>
22
<html lang="en-US">
33
<head>
44
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
@@ -8,30 +8,29 @@
88
</head>
99
<body>
1010
<main id="webchat"></main>
11-
<script>
12-
function createActivity(text) {
13-
return {
14-
from: { role: 'bot' },
15-
id: Math.random().toString(36).substr(2, 5),
16-
text,
17-
timestamp: new Date().toISOString(),
18-
type: 'message'
19-
};
11+
<script type="importmap">
12+
{
13+
"imports": {
14+
"@testduet/wait-for": "https://esm.sh/@testduet/wait-for"
15+
}
2016
}
17+
</script>
18+
<script type="module">
19+
import { waitFor } from '@testduet/wait-for';
2120

22-
/** Checks if the element has an ancestor that fulfill the predicate. */
23-
function hasAncestor(element, predicate) {
24-
let current = element.parentElement;
21+
async function getSelectedButtonTexts() {
22+
const selectedButtonTexts = [];
2523

26-
while (current) {
27-
if (predicate.call(element, current)) {
28-
return true;
29-
}
24+
for (const pushButton of document.querySelectorAll('.ac-pushButton')) {
25+
const { description, properties } = await testHelpers.getAccessibilityNode(pushButton);
3026

31-
current = current.parentElement;
27+
// Action buttons are plain buttons, not toggle buttons.
28+
expect(properties?.find(({ name }) => name === 'pressed')).toBeUndefined();
29+
30+
description?.value === 'Selected' && selectedButtonTexts.push(pushButton.innerText);
3231
}
3332

34-
return false;
33+
return selectedButtonTexts;
3534
}
3635

3736
run(async function () {
@@ -97,7 +96,8 @@
9796
directLine,
9897
store: testHelpers.createStore(),
9998
styleOptions: {
100-
autoScrollSnapOnActivity: 1
99+
autoScrollSnapOnActivity: 1,
100+
hideSendBox: true
101101
}
102102
},
103103
document.getElementById('webchat')
@@ -107,53 +107,68 @@
107107
await pageConditions.numActivitiesShown(1);
108108
await pageConditions.scrollToBottomCompleted();
109109

110-
// SETUP: Focus on the send box.
111-
await pageObjects.focusSendBoxTextBox();
112-
113110
// SETUP: There should be 2 set of `ac-actionSet` containers.
114111
expect(document.querySelectorAll('.ac-actionSet')).toHaveLength(2);
115112

116-
// SETUP: All 'ac-pushButton' should not have `aria-pressed="true"`.
117-
Array.from(document.querySelectorAll('.ac-pushButton')).every(pushButton =>
118-
expect(pushButton.hasAttribute('aria-pressed')).not.toBe('true')
119-
);
113+
// SETUP: No 'ac-pushButton' should be selected.
114+
expect(await getSelectedButtonTexts()).toEqual([]);
115+
116+
// SETUP: Focus on the transcript, the card is the last and only activity.
117+
pageObjects.focusTranscript();
118+
119+
// WHEN: Pressing ENTER to focus inside the card.
120+
await host.sendKeys('ENTER');
121+
122+
// THEN: The "Yes" button should have focus and nothing should be selected.
123+
await waitFor(() => expect(document.activeElement).toHaveProperty('innerText', 'Yes'));
124+
expect(await getSelectedButtonTexts()).toEqual([]);
125+
await host.snapshot('local');
126+
127+
// WHEN: Pressing TAB.
128+
await host.sendTab();
129+
130+
// THEN: The "No" button should have focus.
131+
await waitFor(() => expect(document.activeElement).toHaveProperty('innerText', 'No'));
132+
await host.snapshot('local');
133+
134+
// WHEN: Pressing TAB.
135+
await host.sendTab();
136+
137+
// THEN: The "Submit card" button should have focus.
138+
await waitFor(() => expect(document.activeElement).toHaveProperty('innerText', 'Submit card'));
139+
await host.snapshot('local');
140+
141+
// WHEN: Pressing ENTER on the "Submit card" button.
142+
await host.sendKeys('ENTER');
143+
144+
// THEN: Only the "Submit card" button should be selected, and it should keep focus.
145+
expect(document.activeElement).toHaveProperty('innerText', 'Submit card');
146+
await waitFor(() => expect(document.activeElement.hasAttribute('aria-describedby')).toBe(true)); // "Selected" description attached
147+
expect(await getSelectedButtonTexts()).toEqual(['Submit card']);
148+
await host.snapshot('local');
149+
150+
// WHEN: Pressing SHIFT-TAB.
151+
await host.sendShiftTab();
152+
153+
// THEN: The "No" button should have focus, the "Submit card" button should stay selected.
154+
await waitFor(() => expect(document.activeElement).toHaveProperty('innerText', 'No'));
155+
await host.snapshot('local');
156+
157+
// WHEN: Pressing SHIFT-TAB.
158+
await host.sendShiftTab();
159+
160+
// THEN: The "Yes" button should have focus, the "Submit card" button should stay selected.
161+
await waitFor(() => expect(document.activeElement).toHaveProperty('innerText', 'Yes'));
162+
await host.snapshot('local');
120163

121-
// WHEN: Clicking on the card action button ("Submit card").
122-
await host.sendShiftTab(3);
123-
await host.sendKeys('ENTER', 'TAB', 'TAB', 'ENTER');
124-
125-
// THEN: Selected `ac-pushButton` should have `aria-pressed` set to `true`.
126-
Array.from(document.querySelectorAll('.ac-pushButton'))
127-
.filter(pushButton => pushButton.innerText === 'Submit card')
128-
.forEach(pushButton => expect(pushButton.getAttribute('aria-pressed')).toBe('true'));
129-
130-
// THEN: Only one button is selected.
131-
expect(
132-
Array.from(document.querySelectorAll('.ac-pushButton')).filter(
133-
pushButton => pushButton.getAttribute('aria-pressed') === 'true'
134-
)
135-
).toHaveLength(1);
136-
137-
// THEN: Non-selection should not have `aria-pressed` set.
138-
Array.from(document.querySelectorAll('.ac-pushButton'))
139-
.filter(pushButton => pushButton.innerText !== 'Submit card')
140-
.forEach(pushButton => expect(pushButton.getAttribute('aria-pressed')).not.toBe('true'));
141-
142-
// WHEN: Click on the first action set button ("Yes")
143-
await host.sendShiftTab(2);
164+
// WHEN: Pressing ENTER on the "Yes" button.
144165
await host.sendKeys('ENTER');
145166

146-
// THEN: Selected `ac-pushButton` should have `aria-pressed` set to `true`.
147-
Array.from(document.querySelectorAll('.ac-pushButton'))
148-
.filter(pushButton => pushButton.innerText === 'Yes')
149-
.forEach(pushButton => expect(pushButton.getAttribute('aria-pressed')).toBe('true'));
150-
151-
// THEN: Two buttons are selected.
152-
expect(
153-
Array.from(document.querySelectorAll('.ac-pushButton')).filter(
154-
pushButton => pushButton.getAttribute('aria-pressed') === 'true'
155-
)
156-
).toHaveLength(2);
167+
// THEN: Both the "Yes" and "Submit card" buttons should be selected, the "Yes" button should keep focus.
168+
expect(document.activeElement).toHaveProperty('innerText', 'Yes');
169+
await waitFor(() => expect(document.activeElement.hasAttribute('aria-describedby')).toBe(true)); // "Selected" description attached
170+
expect(await getSelectedButtonTexts()).toEqual(['Yes', 'Submit card']);
171+
await host.snapshot('local');
157172
});
158173
</script>
159174
</body>
11.7 KB
Loading
11.7 KB
Loading
11.7 KB
Loading
11.7 KB
Loading
11.7 KB
Loading
11.7 KB
Loading
11.7 KB
Loading

‎__tests__/html2/accessibility/adaptiveCard/hack.actionShouldBePushButtonMod.html‎

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
<body>
1010
<main id="webchat"></main>
1111
<script>
12+
async function getDescription(element) {
13+
return (await testHelpers.getAccessibilityNode(element)).description?.value;
14+
}
15+
1216
run(async function () {
1317
const { directLine, store } = testHelpers.createDirectLineEmulator();
1418

@@ -50,40 +54,50 @@
5054
const [firstPushButton, secondPushButton] = Array.from(document.querySelectorAll('.ac-pushButton'));
5155

5256
expect(firstPushButton).toHaveProperty('textContent', 'Button 1');
53-
expect(firstPushButton.getAttribute('aria-pressed')).toBe('false');
57+
expect(firstPushButton.hasAttribute('aria-pressed')).toBe(false);
58+
expect(await getDescription(firstPushButton)).toBeUndefined();
5459
expect(firstPushButton.hasAttribute('aria-posinset')).toBe(false);
5560
expect(firstPushButton.hasAttribute('aria-setsize')).toBe(false);
5661
expect(firstPushButton.hasAttribute('role')).toBe(false);
5762

5863
expect(secondPushButton).toHaveProperty('textContent', 'Show card');
5964
expect(secondPushButton.hasAttribute('aria-pressed')).toBe(false);
65+
expect(await getDescription(secondPushButton)).toBeUndefined();
6066
expect(secondPushButton.hasAttribute('aria-posinset')).toBe(false);
6167
expect(secondPushButton.hasAttribute('aria-setsize')).toBe(false);
6268
expect(secondPushButton.hasAttribute('role')).toBe(false);
6369

6470
await (await directLine.actPostActivity(() => host.click(firstPushButton))).resolveAll();
6571

66-
expect(firstPushButton.getAttribute('aria-pressed')).toBe('true');
72+
expect(firstPushButton.hasAttribute('aria-pressed')).toBe(false);
73+
expect(await getDescription(firstPushButton)).toBe('Selected');
6774

6875
await host.click(secondPushButton);
6976

77+
// `Action.ShowCard` is never selected, its state is indicated by `aria-expanded`.
78+
expect(secondPushButton.getAttribute('aria-expanded')).toBe('true');
79+
expect(await getDescription(secondPushButton)).toBeUndefined();
80+
7081
const [, , thirdPushButton, fourthPushButton] = Array.from(document.querySelectorAll('.ac-pushButton'));
7182

7283
expect(thirdPushButton).toHaveProperty('textContent', 'Button 2');
73-
expect(thirdPushButton.getAttribute('aria-pressed')).toBe('false');
84+
expect(thirdPushButton.hasAttribute('aria-pressed')).toBe(false);
85+
expect(await getDescription(thirdPushButton)).toBeUndefined();
7486
expect(thirdPushButton.hasAttribute('aria-posinset')).toBe(false);
7587
expect(thirdPushButton.hasAttribute('aria-setsize')).toBe(false);
7688
expect(thirdPushButton.hasAttribute('role')).toBe(false);
7789

7890
await (await directLine.actPostActivity(() => host.click(thirdPushButton))).resolveAll();
7991

80-
expect(thirdPushButton.getAttribute('aria-pressed')).toBe('true');
92+
expect(thirdPushButton.hasAttribute('aria-pressed')).toBe(false);
93+
expect(await getDescription(thirdPushButton)).toBe('Selected');
8194

8295
await WebChat.renderWebChat({ directLine, disabled: true, store }, document.getElementById('webchat'));
8396

8497
await host.click(fourthPushButton);
8598

86-
expect(fourthPushButton.getAttribute('aria-pressed')).toBe('false');
99+
expect(fourthPushButton.hasAttribute('aria-pressed')).toBe(false);
100+
expect(await getDescription(fourthPushButton)).toBeUndefined();
87101
});
88102
</script>
89103
</body>

0 commit comments

Comments
 (0)