Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Screen reader: support "do primary action" #4041

Merged
merged 17 commits into from
Sep 21, 2021
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Fixed

- Fixes [#4020](https://github.com/microsoft/BotFramework-WebChat/issues/4020). With or without scan mode turned on, screen reader users should be able to press <kbd>ENTER</kbd> to focus on interactive activity, by [@compulim](https://github.com/compulim), in PR [#4041](https://github.com/microsoft/BotFramework-WebChat/pull/4041)
- Fixes [#4021](https://github.com/microsoft/BotFramework-WebChat/issues/4021). For screen reader usability, suggested actions container should not render "Is empty" alt text initially, by [@compulim](https://github.com/compulim), in PR [#4041](https://github.com/microsoft/BotFramework-WebChat/pull/4041)

## [4.14.1] - 2021-09-07

### Fixed
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
<script crossorigin="anonymous" src="/test-harness.js"></script>
<script crossorigin="anonymous" src="/test-page-object.js"></script>
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
</head>
<body>
<div id="webchat"></div>
<script>
// (Related to #4021)
run(async function () {
const directLine = await testHelpers.createDirectLineWithTranscript();

WebChat.renderWebChat(
{
directLine,
store: testHelpers.createStore()
},
document.getElementById('webchat')
);

// GIVEN: Web Chat is loaded initially.
await pageConditions.uiConnected();

// WHEN: No suggested actions were shown before.
await pageConditions.numActivitiesShown(0);

// THEN: Suggested actions container should not be rendered, or not to render "empty" alt text.
expect(
~(document.querySelector('.webchat__suggested-actions')?.innerText || '').indexOf(
'Suggested Actions Container: Is empty'
)
).toBeFalsy();

// GIVEN: Suggested actions is shown.
directLine.activityDeferredObservable.next({
from: {
role: 'bot'
},
suggestedActions: {
actions: [
{
title: 'Coffee',
type: 'imBack',
value: 'I like coffee.'
},
{
title: 'Milk',
type: 'imBack',
value: 'I like milk.'
},
{
title: 'Orange juice',
type: 'imBack',
value: 'I like orange juice.'
},
{
title: 'Tea',
type: 'imBack',
value: 'I like tea.'
}
],
to: []
},
text: 'What drink is best?',
type: 'message'
});

await pageConditions.numActivitiesShown(1);
await pageConditions.suggestedActionsShown();

expect(
~document
.querySelector('.webchat__suggested-actions')
.innerText.indexOf('Suggested Actions Container: Has content.')
).toBeTruthy();

// WHEN: After a suggested action is selected and sent to the bot.
await host.click(pageElements.suggestedActions()[1]);

await pageConditions.numActivitiesShown(2);
await pageConditions.became(
'no suggested actions are shown',
() => !pageElements.suggestedActions().length,
1000
);

// THEN: Suggested actions container should be rendered with alt text "Suggested Actions Container: Is empty".
expect(
~document
.querySelector('.webchat__suggested-actions')
.innerText.indexOf('Suggested Actions Container: Is empty')
).toBeTruthy();

// We are not taking snapshot in this test because snapshot cannot show the alt text.
});
</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */

describe('accessibility usability', () => {
test('should not render suggested actions container at initial', () =>
runHTML('accessibility.usability.suggestedActions.hideOnInitial.html'));
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
<script crossorigin="anonymous" src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
<script crossorigin="anonymous" src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
<script crossorigin="anonymous" src="/test-harness.js"></script>
<script crossorigin="anonymous" src="/test-page-object.js"></script>
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
<script
crossorigin="anonymous"
src="https://unpkg.com/[email protected]/umd/react-dom-test-utils.production.min.js"
></script>
</head>
<body>
<div id="webchat"></div>
<script>
run(async function () {
WebChat.renderWebChat(
{
directLine: WebChat.createDirectLine({ token: await testHelpers.token.fetchDirectLineToken() }),
store: testHelpers.createStore()
},
document.getElementById('webchat')
);

// GIVEN: An "input" card is shown and the focus is on the transcript and the card input activity.
await pageConditions.uiConnected();
await pageObjects.sendMessageViaSendBox('card inputs');
await pageConditions.numActivitiesShown(2);
await pageConditions.scrollStabilized();
await pageConditions.became(
'focus is on the send box',
() => document.activeElement === pageElements.sendBoxTextBox(),
1000
);
await host.sendShiftTab(3);
await pageConditions.became(
'focus is on the input card',
() => pageElements.focusedActivity() === pageElements.activities()[1],
1000
);
await pageConditions.scrollStabilized();

// WHEN: ENTER key is pressed.
await host.sendKeys('ENTER');

// THEN: The first text box in the input card should be focused.
await pageConditions.became(
'focus is on the first text box in the input card',
() => document.activeElement === document.querySelector('.ac-input ,ac-textInput'),
1000
);

await host.snapshot();
});
</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */

describe('transcript navigation', () => {
test('should focus inside the attachment when ENTER key is pressed', () => runHTML('transcript.navigation.focusAttachment.enterKey'));
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
<script crossorigin="anonymous" src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
<script crossorigin="anonymous" src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
<script crossorigin="anonymous" src="/test-harness.js"></script>
<script crossorigin="anonymous" src="/test-page-object.js"></script>
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
<script
crossorigin="anonymous"
src="https://unpkg.com/[email protected]/umd/react-dom-test-utils.production.min.js"
></script>
</head>
<body>
<div id="webchat"></div>
<script>
run(async function () {
WebChat.renderWebChat(
{
directLine: WebChat.createDirectLine({ token: await testHelpers.token.fetchDirectLineToken() }),
store: testHelpers.createStore()
},
document.getElementById('webchat')
);

// GIVEN: An "input" card is shown and the focus is on the transcript and the card input activity.
await pageConditions.uiConnected();
await pageObjects.sendMessageViaSendBox('card inputs');
await pageConditions.numActivitiesShown(2);
await pageConditions.scrollStabilized();
await pageConditions.became(
'focus is on the send box',
() => document.activeElement === pageElements.sendBoxTextBox(),
1000
);
await host.sendShiftTab(3);
await pageConditions.became(
'focus is on the input card',
() => pageElements.focusedActivity() === pageElements.activities()[1],
1000
);
await pageConditions.scrollStabilized();

// WHEN: "click" event is fired, which mimic pressing ENTER key while NVDA is in browse mode.
// Per React requirement, `ReactTestUtils` is required to fire artificial "click" event, instead of `createEvent`/`dispatchEvent`.
ReactTestUtils.Simulate.click(pageElements.focusedActivity().querySelector('.webchat__screen-reader-activity :first-child'));

// THEN: The first text box in the input card should be focused.
await pageConditions.became(
'focus is on the first text box in the input card',
() => document.activeElement === document.querySelector('.ac-input ,ac-textInput'),
1000
);

await host.snapshot();
});
</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */

describe('transcript navigation with NVDA in browse mode', () => {
test('should focus inside the attachment when ENTER is pressed', () => runHTML('transcript.navigation.focusAttachment.screenReaderPrimaryAction.nvda'));
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
<script crossorigin="anonymous" src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
<script crossorigin="anonymous" src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
<script crossorigin="anonymous" src="/test-harness.js"></script>
<script crossorigin="anonymous" src="/test-page-object.js"></script>
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
<script
crossorigin="anonymous"
src="https://unpkg.com/[email protected]/umd/react-dom-test-utils.production.min.js"
></script>
</head>
<body>
<div id="webchat"></div>
<script>
run(async function () {
WebChat.renderWebChat(
{
directLine: WebChat.createDirectLine({ token: await testHelpers.token.fetchDirectLineToken() }),
store: testHelpers.createStore()
},
document.getElementById('webchat')
);

// GIVEN: An "input" card is shown and the focus is on the transcript and the card input activity.
await pageConditions.uiConnected();
await pageObjects.sendMessageViaSendBox('card inputs');
await pageConditions.numActivitiesShown(2);
await pageConditions.scrollStabilized();
await pageConditions.became(
'focus is on the send box',
() => document.activeElement === pageElements.sendBoxTextBox(),
1000
);
await host.sendShiftTab(3);
await pageConditions.became(
'focus is on the input card',
() => pageElements.focusedActivity() === pageElements.activities()[1],
1000
);
await pageConditions.scrollStabilized();

// WHEN: "click" event is fired, which mimic Windows Narrator "do primary action", or CAPSLOCK + ENTER.
// Per React requirement, `ReactTestUtils` is required to fire artificial "click" event, instead of `createEvent`/`dispatchEvent`.
ReactTestUtils.Simulate.click(pageElements.focusedActivity());

// THEN: The first text box in the input card should be focused.
await pageConditions.became(
'focus is on the first text box in the input card',
() => document.activeElement === document.querySelector('.ac-input ,ac-textInput'),
1000
);

await host.snapshot();
});
</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */

describe('transcript navigation with Windows Narrator', () => {
test('should focus inside the attachment when "do primary action" is performed', () => runHTML('transcript.navigation.focusAttachment.screenReaderPrimaryAction.windowsNarrator'));
});
2 changes: 1 addition & 1 deletion packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"prestart": "npm run build:babel",
"start": "concurrently --kill-others --names \"babel,globalize,tsc\" \"npm run start:babel\" \"npm run start:globalize\" \"npm run start:typescript\"",
"start:babel": "npm run build:babel -- --skip-initial-build --watch",
"start:globalize": "node-dev --respawn scripts/createPrecompiledGlobalize.js",
"start:globalize": "node-dev --respawn scripts/createPrecompiledGlobalize.mjs",
"start:typescript": "npm run build:typescript -- --watch"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/localization/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"ACTIVITY_BOT_ATTACHED_ALT": "Bot attached:",
"_ACTIVITY_BOT_ATTACHED_ALT.comment": "This is for screen reader and is narrated before each attachments sent by the bot.",
"ACTIVITY_ERROR_BOX_TITLE": "Error message",
"ACTIVITY_INTERACTIVE_LABEL_ALT": "Press ENTER to interact.",
"ACTIVITY_INTERACTIVE_LABEL_ALT": "Click to interact.",
"_ACTIVITY_INTERACTIVE_LABEL_ALT.comment": "This is for screen reader. When the user is navigating on the transcript, it give hints if the current activity have interactive contents.",
"ACTIVITY_YOU_ATTACHED_ALT": "You attached:",
"_ACTIVITY_YOU_ATTACHED_ALT.comment": "This is for screen reader and is narrated before each attachments sent by the user.",
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/localization/yue.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"ACTIVITY_BOT_ATTACHED_ALT": "Bot 嘅附件:",
"ACTIVITY_BOT_SAID_ALT": "Bot $1 話:",
"ACTIVITY_ERROR_BOX_TITLE": "錯嘅信息",
"ACTIVITY_INTERACTIVE_LABEL_ALT": "襟 ENTER 嚟進行互動。",
"ACTIVITY_INTERACTIVE_LABEL_ALT": "襟呢度嚟進行互動。",
"ACTIVITY_NUM_ATTACHMENTS_FEW_ALT": "$1 件附件。",
"ACTIVITY_NUM_ATTACHMENTS_MANY_ALT": "$1 件附件。",
"ACTIVITY_NUM_ATTACHMENTS_ONE_ALT": "一件附件。",
Expand Down
Loading