Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
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
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ Notes: web developers are advised to use [`~` (tilde range)](https://github.com/
- Fixed [#5472](https://github.com/microsoft/BotFramework-WebChat/issues/5472), ensure proper inheritance from the outside of Web Chat for visibility CSS property when hidden, in PR [#5473](https://github.com/microsoft/BotFramework-WebChat/pull/5473), by [@OEvgeny](https://github.com/OEvgeny)
- Fixed [#5474](https://github.com/microsoft/BotFramework-WebChat/issues/5474). Disable AMD glue code in bundle, in PR [#5478](https://github.com/microsoft/BotFramework-WebChat/pull/5478), by [@compulim](https://github.com/compulim)
- Downstreamers who use our CommonJS and ES Modules output with esbuild will need to disable AMD themselves to prevent conflict with RequireJS
- Fixed [#5479](https://github.com/microsoft/BotFramework-WebChat/issues/5479). Fixed feedback form buttons should not squash other buttons, in PR [#5480](https://github.com/microsoft/BotFramework-WebChat/pull/5480), by [@compulim](https://github.com/compulim)

# Removed

Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
149 changes: 149 additions & 0 deletions __tests__/html2/feedbackForm/behavior.resetByEscapeKey.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
<!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@7.8.7/babel.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>
</head>
<body>
<main id="webchat"></main>
<script type="importmap">
{
"imports": {
"@testduet/wait-for": "https://esm.sh/@testduet/wait-for"
}
}
</script>
<script type="module">
import { waitFor } from '@testduet/wait-for';

run(async function () {
const {
WebChat: { renderWebChat, testIds }
} = window; // Imports in UMD fashion.

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

const styleOptions = {
feedbackActionsPlacement: 'activity-actions'
};

renderWebChat({ directLine, store, styleOptions }, document.getElementById('webchat'));

await pageConditions.uiConnected();

await directLine.emulateIncomingActivity({
text: 'Pariatur dolore culpa cupidatat proident reprehenderit id fugiat exercitation est.',
type: 'message'
});

await directLine.emulateIncomingActivity({
channelData: {
feedbackLoop: {
type: 'default'
}
},
entities: [
{
'@context': 'https://schema.org',
'@id': '',
'@type': 'Message',
type: 'https://schema.org/Message',
potentialAction: [
{
'@type': 'LikeAction',
actionStatus: 'PotentialActionStatus',
target: {
'@type': 'EntryPoint',
urlTemplate: 'ms-directline://postback?interaction=like'
}
},
{
'@type': 'DislikeAction',
actionStatus: 'PotentialActionStatus',
result: {
'@type': 'Review',
reviewBody: "I don't like it.",
'reviewBody-input': {
'@type': 'PropertyValueSpecification',
valueMinLength: 3,
valueName: 'reason'
}
},
target: {
'@type': 'EntryPoint',
urlTemplate: 'ms-directline://postback?interaction=dislike{&reason}'
}
}
]
}
],
text: `Occaecat cillum amet ipsum amet pariatur proident commodo eiusmod cupidatat voluptate.`,
type: 'message'
});

await directLine.emulateIncomingActivity({
text: 'Mollit est aliqua magna cillum ullamco officia cupidatat voluptate enim.',
type: 'message'
});

await pageConditions.numActivitiesShown(3);

// WHEN: Feedback form is opened.
document.querySelector(`[data-testid="${testIds.sendBoxTextBox}"]`).focus();
await host.sendShiftTab(3);
await host.sendKeys('UP', 'ENTER');
await host.sendTab();
await host.sendKeys('ENTER');

console.log(document.querySelectorAll(`[data-testid="${testIds.feedbackButton}"]`));

// THEN: The dislike button should be pressed.
expect(
Array.from(document.querySelectorAll(`[data-testid="${testIds.feedbackButton}"]`)).map(element =>
element.getAttribute('aria-pressed')
)
).toEqual(['false', 'true']);

// THEN: It should focus on the feedback box.
await waitFor(() =>
expect(document.activeElement).toBe(document.querySelector(`[data-testid="${testIds.feedbackSendBox}"]`))
);

// THEN: Should match the snapshot.
await host.snapshot('local');

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

// THEN: It should hide the feedback form.
() => expect(document.querySelector(`[data-testid="${testIds.feedbackSendBox}"]`)).toBeFalsy();

// THEN: It should unselect all feedback buttons.
expect(
Array.from(document.querySelectorAll(`[data-testid="${testIds.feedbackButton}"]`)).map(element =>
element.getAttribute('aria-pressed')
)
).toEqual(['false', 'false']);

// THEN: Should focus on the dislike button.
await waitFor(() =>
expect(document.activeElement).toBe(
document.querySelectorAll(`[data-testid="${testIds.feedbackButton}"]`).item(1)
)
);

// THEN: Should match the snapshot.
await host.snapshot('local');

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

// THEN: Should focus on the activity.
await expect(pageElements.activeActivity()).toBe(pageElements.activities()[1]);
});
</script>
</body>
</html>
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.
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
Expand Up @@ -63,7 +63,7 @@
await host.sendKeys('ENTER');
await host.sendKeys('ENTER');

await host.snapshot('local');
return await host.snapshot('local');

// Dismiss like button
await host.sendShiftTab(2);
Expand Down
101 changes: 101 additions & 0 deletions __tests__/html2/feedbackForm/layout.withCopyButton.html
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="https://unpkg.com/@babel/standalone@7.8.7/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" style="position: relative"></main>
<script type="text/babel">
run(async function () {
const {
React,
ReactDOM: { render },
WebChat: { FluentThemeProvider, ReactWebChat, testIds }
} = window; // Imports in UMD fashion.

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

const styleOptions = {
feedbackActionsPlacement: 'activity-actions'
};

render(
<FluentThemeProvider>
<ReactWebChat directLine={directLine} store={store} styleOptions={styleOptions} />
</FluentThemeProvider>,
document.getElementById('webchat')
);

await pageConditions.uiConnected();

await directLine.emulateIncomingActivity({
channelData: {
feedbackLoop: {
type: 'default'
}
},
entities: [
{
'@context': 'https://schema.org',
'@id': '',
'@type': 'Message',
type: 'https://schema.org/Message',
keywords: ['AllowCopy'],
potentialAction: [
{
'@type': 'LikeAction',
actionStatus: 'PotentialActionStatus',
target: {
'@type': 'EntryPoint',
urlTemplate: 'ms-directline://postback?interaction=like'
}
},
{
'@type': 'DislikeAction',
actionStatus: 'PotentialActionStatus',
result: {
'@type': 'Review',
reviewBody: "I don't like it.",
'reviewBody-input': {
'@type': 'PropertyValueSpecification',
valueMinLength: 3,
valueName: 'reason'
}
},
target: {
'@type': 'EntryPoint',
urlTemplate: 'ms-directline://postback?interaction=dislike{&reason}'
}
}
]
}
],
text: `Occaecat cillum amet ipsum amet pariatur proident commodo eiusmod cupidatat voluptate.`,
type: 'message'
});

await pageConditions.numActivitiesShown(1);

// THEN: It should match the snapshot.
await host.snapshot('local');

// WHEN: Feedback form is opened.
document.querySelector(`[data-testid="${testIds.sendBoxTextBox}"]`).focus();
await host.sendShiftTab(2);
await host.sendKeys('ENTER');
await host.sendTab();
await host.sendKeys('ENTER');

// THEN: It should match the snapshot.
await host.snapshot('local');
});
</script>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,4 @@
</script>
</body>

</html>
</html>
70 changes: 56 additions & 14 deletions packages/component/src/ActivityFeedback/ActivityFeedback.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,69 @@
import { WebChatActivity } from 'botframework-webchat-core';
import React, { memo } from 'react';
import classNames from 'classnames';
import React, { memo, useCallback, type FormEventHandler, type KeyboardEventHandler } from 'react';
import { Extract, wrapWith } from 'react-wrap-with';
import { useRefFrom } from 'use-ref-from';

import useStyleSet from '../hooks/useStyleSet';
import FeedbackLoopWithMessage from './private/FeedbackLoopWithMessage';
import FeedbackLoopWithoutMessage from './private/FeedbackLoopWithoutMessage';
import ActivityFeedbackComposer from './providers/ActivityFeedbackComposer';
import useFeedbackText from './providers/useFeedbackText';
import useFocusAction from './providers/useFocusAction';
import useSelectedAction from './providers/useSelectedAction';
import useShouldShowFeedbackForm from './providers/useShouldShowFeedbackForm';
import useSubmitCallback from './providers/useSubmitCallback';

function InternalActivityFeedback() {
const [{ feedbackForm }] = useStyleSet();
const [feedbackText, setFeedbackText] = useFeedbackText();
const [selectedAction, setSelectedAction] = useSelectedAction();
const [shouldShowFeedbackForm] = useShouldShowFeedbackForm();
const focusAction = useFocusAction();
const submit = useSubmitCallback();

const feedbackTextRef = useRefFrom(feedbackText);
const selectedActionRef = useRefFrom(selectedAction);

const InternalActivityFeedback = memo(() =>
useShouldShowFeedbackForm()[0] ? <FeedbackLoopWithMessage /> : <FeedbackLoopWithoutMessage />
);
const handleReset = useCallback<FormEventHandler<HTMLFormElement>>(() => {
focusAction(selectedActionRef.current);

InternalActivityFeedback.displayName = 'InternalActivityFeedback';
setFeedbackText(undefined);
setSelectedAction(undefined);
}, [focusAction, selectedActionRef, setFeedbackText, setSelectedAction]);

type ActivityFeedbackProps = Readonly<{
activity: WebChatActivity;
}>;
const handleSubmit = useCallback<FormEventHandler<HTMLFormElement>>(
event => {
event.preventDefault();

submit(selectedActionRef.current, feedbackTextRef.current);
},
[feedbackTextRef, selectedActionRef, submit]
);

const handleKeyDown = useCallback<KeyboardEventHandler<HTMLFormElement>>(
event => {
if (event.key === 'Escape' && selectedActionRef.current) {
event.stopPropagation();
event.currentTarget.reset();
}
},
[selectedActionRef]
);

function ActivityFeedback({ activity }: ActivityFeedbackProps) {
return (
<ActivityFeedbackComposer activity={activity}>
<InternalActivityFeedback />
</ActivityFeedbackComposer>
<form
className={classNames('webchat__feedback-form-real', feedbackForm + '')}
Comment thread
compulim marked this conversation as resolved.
Outdated
onKeyDown={handleKeyDown}
onReset={handleReset}
onSubmit={handleSubmit}
>
{shouldShowFeedbackForm ? <FeedbackLoopWithMessage /> : <FeedbackLoopWithoutMessage />}
</form>
);
}

const ActivityFeedback = wrapWith(ActivityFeedbackComposer, { activity: Extract })(InternalActivityFeedback);

ActivityFeedback.displayName = 'ActivityFeedback';

export default memo(ActivityFeedback);
export { type ActivityFeedbackProps };
Loading
Loading