Skip to content

Commit 0f4abdb

Browse files
OEvgenyEugene Olonov
andauthored
Do not set role="figure" on unlabelled Adaptive Cards, and show a pointer on clickable elements (#5915)
* Do not set role="figure" on Adaptive Cards without a label A card without "speak" has no name, and an unnamed "figure" or "form" is announced without its content. Set the role only when the card has a label, fixes #5049. * Show a pointer cursor on clickable Adaptive Card elements Containers and images with "selectAction" get cursor: pointer; push buttons keep the default cursor, fixes #5130. * Add PR number to changelog * Show the live region copy in role mod snapshots * Give the second card in hack.roleMod.html a distinct speak Both cards had speak 'Hello, World!', so the live region snapshot read the same line twice. * Move the changelog entry to .changelog/ * Fix unlabelled role mod test for card action and live region changes on main #5900 removed the capture-phase click listener from the card root, so Chrome now leaves the role-less card out of the accessibility tree (role "none") instead of exposing it as "generic". Assert that neither card is a "figure" or a "form" instead. The live region now reads the text of cards without "speak" (#5922), update the snapshot. --------- Co-authored-by: Eugene Olonov <eugeneolonov@microsoft.com>
1 parent 8280ac6 commit 0f4abdb

7 files changed

Lines changed: 122 additions & 3 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
### Fixed
2+
3+
- Fixed [#5049](https://github.com/microsoft/BotFramework-WebChat/issues/5049), Adaptive Cards without a label being announced as an unnamed figure by screen readers, in PR [#5915](https://github.com/microsoft/BotFramework-WebChat/pull/5915), by [@OEvgeny](https://github.com/OEvgeny)
4+
- Fixed [#5130](https://github.com/microsoft/BotFramework-WebChat/issues/5130), clickable containers and images in Adaptive Cards not showing a pointer cursor, in PR [#5915](https://github.com/microsoft/BotFramework-WebChat/pull/5915), by [@OEvgeny](https://github.com/OEvgeny)

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
<html lang="en-US">
33
<head>
44
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
5+
<!-- Shows the live region, so the snapshot shows the copy sent to screen readers. -->
6+
<link href="/assets/accessibility.liveRegionAttachment.css" rel="stylesheet" type="text/css" />
57
<script crossorigin="anonymous" src="/test-harness.js"></script>
68
<script crossorigin="anonymous" src="/test-page-object.js"></script>
79
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
@@ -12,7 +14,11 @@
1214
run(async function () {
1315
const { directLine, store } = testHelpers.createDirectLineEmulator();
1416

15-
WebChat.renderWebChat({ directLine, store }, document.getElementById('webchat'));
17+
// GIVEN: Web Chat keeps the live region copy of new messages, so it can be seen in the snapshot.
18+
WebChat.renderWebChat(
19+
{ directLine, store, styleOptions: { hideSendBox: true, internalLiveRegionFadeAfter: 60000 } },
20+
document.getElementById('webchat')
21+
);
1622

1723
await pageConditions.uiConnected();
1824

@@ -38,7 +44,7 @@
3844
type: 'AdaptiveCard',
3945
$schema: 'http://adaptivecards.io/schemas/adaptive-card.json',
4046
version: '1.5',
41-
speak: 'Hello, World!',
47+
speak: 'Please enter your full name',
4248
body: [
4349
{
4450
type: 'Input.Text',
@@ -66,6 +72,11 @@
6672

6773
expect(secondAdaptiveCard.getAttribute('aria-label')).toBeTruthy();
6874
expect(secondAdaptiveCard.getAttribute('role')).toBe('form');
75+
76+
await pageConditions.liveRegionStabilized();
77+
78+
// THEN: The live region shows what screen readers announce: "Bot said:", one line per card from its "speak" property ("A card: Hello, World!", "A card: Please enter your full name"), then the input of the second card.
79+
await host.snapshot('local');
6980
});
7081
</script>
7182
</body>
21.1 KB
Loading
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<!doctype html>
2+
<html lang="en-US">
3+
<head>
4+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
5+
<!-- Shows the live region, so the snapshot shows the copy sent to screen readers. -->
6+
<link href="/assets/accessibility.liveRegionAttachment.css" rel="stylesheet" type="text/css" />
7+
<script crossorigin="anonymous" src="/test-harness.js"></script>
8+
<script crossorigin="anonymous" src="/test-page-object.js"></script>
9+
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
10+
</head>
11+
<body>
12+
<main id="webchat"></main>
13+
<script type="importmap">
14+
{
15+
"imports": {
16+
"@testduet/wait-for": "https://esm.sh/@testduet/wait-for"
17+
}
18+
}
19+
</script>
20+
<script type="module">
21+
import { waitFor } from '@testduet/wait-for';
22+
23+
run(async function () {
24+
const { directLine, store } = testHelpers.createDirectLineEmulator();
25+
26+
// GIVEN: Web Chat keeps the live region copy of new messages, so it can be seen in the snapshot.
27+
WebChat.renderWebChat(
28+
{ directLine, store, styleOptions: { hideSendBox: true, internalLiveRegionFadeAfter: 60000 } },
29+
document.getElementById('webchat')
30+
);
31+
32+
await pageConditions.uiConnected();
33+
34+
// WHEN: Bot sends 2 Adaptive Cards without "speak", so the card has no label: one without inputs and one with inputs.
35+
await directLine.emulateIncomingActivity({
36+
attachments: [
37+
{
38+
content: {
39+
$schema: 'http://adaptivecards.io/schemas/adaptive-card.json',
40+
body: [{ text: 'Sunny, 21°C', type: 'TextBlock' }],
41+
type: 'AdaptiveCard',
42+
version: '1.5'
43+
},
44+
contentType: 'application/vnd.microsoft.card.adaptive'
45+
},
46+
{
47+
content: {
48+
$schema: 'http://adaptivecards.io/schemas/adaptive-card.json',
49+
actions: [{ title: 'Submit', type: 'Action.Submit' }],
50+
body: [
51+
{
52+
id: 'name',
53+
label: 'What is your full name',
54+
placeholder: 'For example, John Doe', // The live region copy names inputs by their placeholder until #5913 (by their label).
55+
type: 'Input.Text'
56+
}
57+
],
58+
type: 'AdaptiveCard',
59+
version: '1.5'
60+
},
61+
contentType: 'application/vnd.microsoft.card.adaptive'
62+
}
63+
],
64+
type: 'message'
65+
});
66+
67+
await pageConditions.numActivitiesShown(1);
68+
await waitFor(() => expect(document.querySelectorAll('.ac-adaptiveCard')).toHaveLength(2));
69+
70+
const cardElements = Array.from(document.querySelectorAll('.ac-adaptiveCard'));
71+
72+
// THEN: Both cards should have no label.
73+
expect(cardElements.map(cardElement => cardElement.getAttribute('aria-label'))).toEqual([null, null]);
74+
75+
// THEN: Both cards should have no role, as "figure" and "form" without a name are announced without their content.
76+
expect(cardElements.map(cardElement => cardElement.getAttribute('role'))).toEqual([null, null]);
77+
78+
const cardRoles = (
79+
await Promise.all(cardElements.map(cardElement => testHelpers.getAccessibilityNode(cardElement)))
80+
).map(({ role }) => role?.value);
81+
82+
// THEN: Neither card should be a "figure" or a "form" in the accessibility tree.
83+
// Without a role, Chrome may leave the card out of the accessibility tree (role "none") and expose its content directly.
84+
expect(cardRoles).not.toContain('figure');
85+
expect(cardRoles).not.toContain('form');
86+
87+
await pageConditions.liveRegionStabilized();
88+
89+
// THEN: The live region shows what screen readers announce: "Bot said:", "A card:" with the text of each card, then the input and the "Submit" button of the second card.
90+
await host.snapshot('local');
91+
});
92+
</script>
93+
</body>
94+
</html>
20.5 KB
Loading

‎packages/bundle/src/adaptiveCards/Attachment/AdaptiveCardHacks/useRoleModEffect.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ import type { AdaptiveCard } from 'adaptivecards';
2929
* This violates WAI-ARIA because "aria-label" must not be set on an element without a "role".
3030
*
3131
* We need to set "role" attribute to "form" if the card has any input fields and is valid as a "form" role, otherwise, "figure".
32+
*
33+
* Without a label, the card gets no "role": "figure" and "form" take their names from the author only,
34+
* so a nameless "figure" or "form" would be announced without its content.
3235
*/
3336
export default function useRoleModEffect(
3437
adaptiveCard: AdaptiveCard
@@ -43,7 +46,9 @@ export default function useRoleModEffect(
4346
cardElement.getAttribute('aria-labelledby') ||
4447
cardElement.getAttribute('title')
4548
? 'form'
46-
: 'figure'
49+
: cardElement.getAttribute('aria-label')
50+
? 'figure'
51+
: false
4752
),
4853
[]
4954
);

‎packages/bundle/src/adaptiveCards/Attachment/AdaptiveCardRenderer.module.css‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717
padding: var(--webchat__padding--regular);
1818
}
1919

20+
/* Elements with "selectAction", such as containers and images, are clickable. Push buttons keep the default cursor of buttons. */
21+
:global(.ac-selectable:not(.ac-pushButton)) {
22+
cursor: pointer;
23+
}
24+
2025
:global(.ac-pushButton) {
2126
appearance: none;
2227
background-color: white;

0 commit comments

Comments
 (0)