Skip to content

Commit

Permalink
Screen reader: support "do primary action" (#4041)
Browse files Browse the repository at this point in the history
* Screen reader: support "do primary action"

* Don't render suggested actions container initially

* Add entry

* Add tests

* Refactor

* Add snapshots

* Add test

* Update entry

* Clean up

* Update for NVDA

* Clean up

* Clean up

* Add tests

* Fix tests

* Remove workaround for NVDA 2020.2

* Temporary disable internal HTTP tests

* Prepare to re-lit test
  • Loading branch information
compulim authored Sep 21, 2021
1 parent f581c7b commit 02985d2
Show file tree
Hide file tree
Showing 25 changed files with 455 additions and 75 deletions.
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'));
});
59 changes: 59 additions & 0 deletions __tests__/html/transcript.navigation.focusAttachment.enterKey.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

0 comments on commit 02985d2

Please sign in to comment.