Fluent: suggested actions roving focus by OEvgeny · Pull Request #5154 · microsoft/BotFramework-WebChat · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
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.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
156 changes: 156 additions & 0 deletions __tests__/html/fluentTheme/suggestedActions.focus.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
<!doctype html>
<html lang="en-US">

<head>
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
<script crossorigin="anonymous" src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script crossorigin="anonymous" src="https://unpkg.com/react@16.8.6/umd/react.production.min.js"></script>
<script crossorigin="anonymous" src="https://unpkg.com/react-dom@16.8.6/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="/__dist__/botframework-webchat-fluent-theme.production.min.js"></script>
</head>

<body>
<main id="webchat"></main>
<script type="text/babel">
run(async function () {
const {
React,
ReactDOM: { render },
WebChat: { FluentThemeProvider, ReactWebChat }
} = window; // Imports in UMD fashion.

const { directLine, store } = testHelpers.createDirectLineEmulator();

const App = () => <ReactWebChat directLine={directLine} store={store} />;

render(
<FluentThemeProvider>
<App />
</FluentThemeProvider>,
document.getElementById('webchat')
);

await pageConditions.uiConnected();

await directLine.emulateIncomingActivity({
type: 'message',
textFormat: 'plain',
text: 'Please select one of the actions below',
suggestedActions: {
actions: [
{
image: `https://raw.githubusercontent.com/compulim/BotFramework-MockBot/master/public/assets/square-icon.png`,
imageAltText: 'a blue square',
title: 'IM back as string',
type: 'imBack',
value: 'postback imback-string'
},
{
image: `https://raw.githubusercontent.com/compulim/BotFramework-MockBot/master/public/assets/square-icon-red.png`,
imageAltText: 'a red square',
title: 'Post back as string',
type: 'postBack',
value: 'postback postback-string'
},
{
image: `https://raw.githubusercontent.com/compulim/BotFramework-MockBot/master/public/assets/square-icon-green.png`,
imageAltText: 'a green square',
title: 'Post back as JSON',
text: 'Some text',
type: 'postBack',
value: {
hello: 'World!'
}
},
{
image: `https://raw.githubusercontent.com/compulim/BotFramework-MockBot/master/public/assets/square-icon-purple.png`,
imageAltText: 'a purple square',
displayText: 'say Hello World!',
title: 'Message back as JSON with display text',
text: 'Some text',
type: 'messageBack',
value: {
hello: 'World!'
}
},
{
image: `https://raw.githubusercontent.com/compulim/BotFramework-MockBot/master/public/assets/square-icon-purple.png`,
imageAltText: 'a purple square',
title: 'Message back as JSON without display text',
type: 'messageBack',
value: {
hello: 'World!'
}
},
{
displayText: 'Aloha',
image: `https://raw.githubusercontent.com/compulim/BotFramework-MockBot/master/public/assets/square-icon-purple.png`,
imageAltText: 'a purple square',
text: 'echo Hello',
title: 'Aloha',
type: 'messageBack'
}
],
to: []
}
});

document.querySelector(`[data-testid="${WebChat.testIds.sendBoxTextBox}"]`).focus();

// WHEN: Focus suggested actions
await host.sendShiftTab();

// THEN: Should focus first suggested action
await host.snapshot();
const firstAction = document.activeElement;

// WHEN: Press arrow right key four times:
await host.sendKeys('ARROW_RIGHT'); // 2nd
await host.sendKeys('ARROW_RIGHT'); // 3rd
await host.sendKeys('ARROW_RIGHT'); // 4th
await host.sendKeys('ARROW_RIGHT'); // 5th
await host.sendKeys('ARROW_RIGHT'); // 6th

// THEN: Should focus the last suggested action
expect(document.activeElement?.innerText).toContain('Aloha');
const lastAction = document.activeElement;
await host.snapshot();

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

// THEN: Should focus sendbox
expect(document.activeElement).toBe(
document.querySelector(`[data-testid="${WebChat.testIds.sendBoxTextBox}"]`)
);
await host.snapshot();

// WHEN: Focus suggested actions
await host.sendShiftTab();

// THEN: Should focus the last suggested action
expect(document.activeElement).toBe(lastAction);

// WHEN: Press arrow right again
await host.sendKeys('ARROW_RIGHT');

// THEN: Should wrap around to the first action
expect(document.activeElement).toBe(firstAction);

// WHEN: Press arrow left and space keys
await host.sendKeys('ARROW_LEFT');
await (await directLine.actPostActivity(() => host.sendKeys(' '))).resolveAll();

// THEN: Should wrap around, send last action and focus sendbox
expect(document.activeElement).toBe(
document.querySelector(`[data-testid="${WebChat.testIds.sendBoxTextBox}"]`)
);
await host.snapshot();
});
</script>
</body>

</html>
5 changes: 5 additions & 0 deletions __tests__/html/fluentTheme/suggestedActions.focus.js
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('Fluent theme applied', () => {
test('suggested actions roving focus', () => runHTML('fluentTheme/suggestedActions.focus'));
});
100 changes: 100 additions & 0 deletions __tests__/html/fluentTheme/suggestedActions.layout.flow.focus.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<!doctype html>
<html lang="en-US">

<head>
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
<script crossorigin="anonymous" src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script crossorigin="anonymous" src="https://unpkg.com/react@16.8.6/umd/react.production.min.js"></script>
<script crossorigin="anonymous" src="https://unpkg.com/react-dom@16.8.6/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="/__dist__/botframework-webchat-fluent-theme.production.min.js"></script>
</head>

<body>
<main id="webchat"></main>
<script type="text/babel">
run(async function () {
const {
React,
ReactDOM: { render },
WebChat: { FluentThemeProvider, ReactWebChat }
} = window; // Imports in UMD fashion.

const { directLine, store } = testHelpers.createDirectLineEmulator();

const App = () => (
<ReactWebChat directLine={directLine} store={store} styleOptions={{ suggestedActionLayout: 'flow' }} />
);

render(
<FluentThemeProvider>
<App />
</FluentThemeProvider>,
document.getElementById('webchat')
);

await pageConditions.uiConnected();

await directLine.emulateIncomingActivity({
type: 'message',
textFormat: 'plain',
text: 'Please select one of the actions below',
suggestedActions: {
actions: [
{ title: 'One', value: 'One', type: 'imBack' },
{ title: 'Two', value: 'Two', type: 'imBack' },
{ title: 'Three', value: 'Three', type: 'imBack' },
{ title: 'Four', value: 'Four', type: 'imBack' },
{ title: 'Five', value: 'Five', type: 'imBack' }
],
to: []
}
});

document.querySelector(`[data-testid="${WebChat.testIds.sendBoxTextBox}"]`).focus();

// WHEN: Focus suggested actions
await host.sendShiftTab();

// THEN: Should focus first suggested action
await host.snapshot();
const firstAction = document.activeElement;

// WHEN: Press arrow right key four times:
await host.sendKeys('ARROW_RIGHT'); // 2nd
await host.sendKeys('ARROW_RIGHT'); // 3rd
await host.sendKeys('ARROW_RIGHT'); // 4th
await host.sendKeys('ARROW_RIGHT'); // 5th

// THEN: Should focus the last suggested action
expect(document.activeElement?.innerText).toContain('Five');
await host.snapshot();

// WHEN: Press arrow right again
await host.sendKeys('ARROW_RIGHT');

// THEN: Should wrap around to the first action
expect(document.activeElement).toBe(firstAction);
await host.snapshot();

// WHEN: Press arrow left key
await host.sendKeys('ARROW_LEFT');

// THEN: Should wrap around to the last action
expect(document.activeElement?.innerText).toContain('Five');

// WHEN: Press the space key
await (await directLine.actPostActivity(() => host.sendKeys(' '))).resolveAll();

// THEN: Should send last action and focus sendbox
expect(document.activeElement).toBe(
document.querySelector(`[data-testid="${WebChat.testIds.sendBoxTextBox}"]`)
);
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('Fluent theme applied', () => {
test('suggested actions roving focus in flow layout', () => runHTML('fluentTheme/suggestedActions.layout.flow.focus'));
});
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { hooks } from 'botframework-webchat-component';
import { type DirectLineCardAction } from 'botframework-webchat-core';
import cx from 'classnames';
import React, { MouseEventHandler, memo, useCallback, useRef } from 'react';
import React, { MouseEventHandler, memo, useCallback } from 'react';
Comment thread
OEvgeny marked this conversation as resolved.
import styles from './SuggestedAction.module.css';
import { useStyles } from '../../styles';
import AccessibleButton from './AccessibleButton';
import { useRovingFocusItemRef } from './private/rovingFocus';

const { useDisabled, useFocus, usePerformCardAction, useScrollToEnd, useStyleSet, useSuggestedActions } = hooks;

Expand Down Expand Up @@ -36,6 +37,7 @@ function SuggestedAction({
displayText,
image,
imageAlt,
itemIndex,
text,
type,
value
Expand All @@ -44,7 +46,7 @@ function SuggestedAction({
const [{ suggestedAction: suggestedActionStyleSet }] = useStyleSet();
const [disabled] = useDisabled();
const focus = useFocus();
const focusRef = useRef<HTMLButtonElement>(null);
const focusRef = useRovingFocusItemRef<HTMLButtonElement>(itemIndex);
const performCardAction = usePerformCardAction();
const classNames = useStyles(styles);
const scrollToEnd = useScrollToEnd();
Expand Down
Loading