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
2 changes: 2 additions & 0 deletions .eslintrc.react.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
plugins:
- react
- react-hooks
- local-rules

extends:
- plugin:react/recommended
Expand All @@ -15,6 +16,7 @@ settings:
version: detect

rules:
local-rules/forbid-use-hook-producer: error
react/button-has-type: error
react/default-props-match-prop-types: error
react/destructuring-assignment: error
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Notes: web developers are advised to use [`~` (tilde range)](https://github.com/
- HTML sanitizer is moved from `renderMarkdown` to HTML content transformer middleware, please refer to PR [#5338](https://github.com/microsoft/BotFramework-WebChat/pull/5338)
- If you customized `renderMarkdown` with a custom HTML sanitizer, please move the HTML sanitizer to the new HTML content transformer middleware
- `useGroupActivities` hook is being deprecated in favor of the `useGroupActivitiesByName` hook. The hook will be removed on or after 2027-05-04
- `useSuggestedActions()` hook is being deprecated in favor of the `useSuggestedActionsHooks().useSuggestedActions()` hook. The hook will be removed on or after 2027-05-30

### Added

Expand Down Expand Up @@ -195,6 +196,7 @@ Notes: web developers are advised to use [`~` (tilde range)](https://github.com/
- [`webpack@5.98.0`](https://npmjs.com/package/webpack/)
- Fixed [#5446](https://github.com/microsoft/BotFramework-WebChat/issues/5446). Embedded `uuid` so `microsoft-cognitiveservices-speech-sdk` do not need to use dynamic loading, as this could fail in Webpack 4 environment, in PR [#5445](https://github.com/microsoft/BotFramework-WebChat/pull/5445), by [@compulim](https://github.com/compulim)
- Fixed [#5476](https://github.com/microsoft/BotFramework-WebChat/issues/5476). Modernizing components through memoization and use [`valibot`](https://npmjs.com/package/valibot) for props validation, by [@compulim](https://github.com/compulim)
- Ported `useSuggestedActions` to use React hooks as backend instead of Redux store, in PR [#5489](https://github.com/microsoft/BotFramework-WebChat/pull/5489), by [@compulim](https://github.com/compulim)

### Fixed

Expand Down Expand Up @@ -230,6 +232,7 @@ Notes: web developers are advised to use [`~` (tilde range)](https://github.com/
# Removed

- Deprecating `disabled` props and `useDisabled` hook in favor of new `uiState` props and `useUIState` hook, in PR [#5276](https://github.com/microsoft/BotFramework-WebChat/pull/5276), by [@compulim](https://github.com/compulim)
- `useSuggestedActions()` hook is being deprecated in favor of the `useSuggestedActionsHooks().useSuggestedActions()` hook, in PR [#5489](https://github.com/microsoft/BotFramework-WebChat/pull/5489), by [@compulim](https://github.com/compulim)

## [4.18.0] - 2024-07-10

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
);

await pageConditions.webChatRendered();
await pageConditions.uiConnected();

await directLine.emulateIncomingActivity({
// Setting sequence ID to simplify the order of these test cases.
Expand Down
5 changes: 1 addition & 4 deletions __tests__/html2/hooks/private/renderHook.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,7 @@ export default function renderHook(
return { rerender: render, unmount: () => ReactDOM.unmountComponentAtNode(element) };
};

const { rerender: baseRerender, unmount } = render(
React.createElement(TestComponent, { renderCallbackProps: initialProps }),
renderOptions
);
const { rerender: baseRerender, unmount } = render({ renderCallbackProps: initialProps });

function rerender(rerenderCallbackProps) {
return baseRerender({ renderCallbackProps: rerenderCallbackProps });
Expand Down
25 changes: 25 additions & 0 deletions __tests__/html2/simple.emulator.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<!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>
<main id="webchat"></main>
<script>
run(async function () {
const {
testHelpers: { createDirectLineEmulator }
} = window;

const { directLine, store } = createDirectLineEmulator();

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

await pageConditions.uiConnected();
});
</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<!doctype html>
<html lang="en-US">
<head>
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
<script crossorigin="anonymous" src="https://unpkg.com/react@16.8.6/umd/react.development.js"></script>
<script crossorigin="anonymous" src="https://unpkg.com/react-dom@16.8.6/umd/react-dom.development.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://unpkg.com/@testduet/wait-for@main/dist/wait-for.mjs"
}
}
</script>
<script type="module">
import { waitFor } from '@testduet/wait-for';
import renderHook from '../../hooks/private/renderHook.js';

const {
React: { createElement },
testHelpers: { createDirectLineEmulator },
WebChat: {
Components: { BasicWebChat, Composer },
hooks: { useSuggestedActions },
testIds
}
} = window;

run(async function () {
const { directLine, store } = createDirectLineEmulator();
const WebChatWrapper = ({ children }) =>
createElement(Composer, { directLine, store }, createElement(BasicWebChat), children);

// WHEN: Render initially.
const renderResult = renderHook(
props => {
const state = useSuggestedActions();

if (props) {
state[1](props.suggestedActions);
} else {
return state;
}
},
{
legacyRoot: true,
wrapper: WebChatWrapper
}
);

await pageConditions.uiConnected();

// THEN: useSuggestedActions() getter should return empty array.
await waitFor(() =>
expect(renderResult).toHaveProperty('result.current', [[], expect.anything(), { activity: undefined }])
);

// WHEN: An activity with 2 suggested actions is received.
await directLine.emulateIncomingActivity({
from: { role: 'bot' },
suggestedActions: {
actions: [
{ title: 'Hello, World!', type: 'imBack' },
{ title: 'Aloha!', type: 'imBack' }
],
to: ''
},
text: 'Hello, World!',
type: 'message'
});

// THEN: useSuggestedActions() getter should return 2 suggested actions and origin activity.
renderResult.rerender();

await waitFor(() =>
expect(renderResult).toHaveProperty('result.current', [
[
{ title: 'Hello, World!', type: 'imBack' },
{ title: 'Aloha!', type: 'imBack' }
],
expect.any(Function),
{
activity: expect.objectContaining({
from: { role: 'bot' },
suggestedActions: {
actions: [
{ title: 'Hello, World!', type: 'imBack' },
{ title: 'Aloha!', type: 'imBack' }
],
to: ''
},
text: 'Hello, World!',
type: 'message'
})
}
])
);

// WHEN: useSuggestedActions() setter is called with 1 suggested action.
renderResult.rerender({ suggestedActions: [{ title: 'Good morning!', type: 'imBack' }] });

// THEN: Should show 1 suggested action.
await waitFor(() => expect(pageElements.allByTestId(testIds.suggestedActionButton)).toHaveLength(1));
expect(pageElements.allByTestId(testIds.suggestedActionButton)[0]).toHaveProperty(
'textContent',
'Good morning!'
);

// THEN: Should return 1 suggested action.
renderResult.rerender();
await waitFor(() =>
expect(renderResult).toHaveProperty('result.current', [
[{ title: 'Good morning!', type: 'imBack' }],
expect.any(Function),
{ activity: undefined }
])
);

// THEN: getState() should have 1 suggested action.
expect(store.getState().suggestedActions).toHaveLength(1);
expect(store.getState().suggestedActions[0]).toEqual({ title: 'Good morning!', type: 'imBack' });

// WHEN: useSuggestedActions() is called with no suggested actions.
renderResult.rerender({ suggestedActions: [] });

// THEN: Should hide suggested actions.
await waitFor(() => expect(pageElements.allByTestId(testIds.suggestedActionButton)).toHaveLength(0));

// THEN: Should return 0 suggested actions.
renderResult.rerender();
await waitFor(() =>
expect(renderResult).toHaveProperty('result.current', [[], expect.any(Function), { activity: undefined }])
);

// THEN: getState() should have 1 suggested action.
expect(store.getState().suggestedActions).toHaveLength(0);
});
</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<!doctype html>
<html lang="en-US">
<head>
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
<script crossorigin="anonymous" src="https://unpkg.com/react@16.8.6/umd/react.development.js"></script>
<script crossorigin="anonymous" src="https://unpkg.com/react-dom@16.8.6/umd/react-dom.development.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';
import renderHook from '../../hooks/private/renderHook.js';

run(async function () {
const {
React: { createElement },
testHelpers: { createDirectLineEmulator },
WebChat: {
Components: { BasicWebChat, Composer },
hooks: { useSuggestedActions },
testIds
}
} = window;

const { directLine, store } = createDirectLineEmulator();

// WHEN: Render initially.
const renderResult = renderHook(() => useSuggestedActions(), {
legacyRoot: true,
wrapper: ({ children }) =>
createElement(Composer, { directLine, store }, createElement(BasicWebChat), children)
});

await pageConditions.uiConnected();

// WHEN: Receive an activity with a suggested action.
await directLine.emulateIncomingActivity({
from: { role: 'bot' },
suggestedActions: {
actions: [{ title: 'Aloha!', type: 'imBack' }],
to: ''
},
text: 'Hello, World!',
type: 'message'
});

await pageConditions.numActivitiesShown(1);

// THEN: Should have one suggested action button shown.
expect(pageElements.allByTestId(testIds.suggestedActionButton)).toHaveLength(1);
expect(pageElements.allByTestId(testIds.suggestedActionButton)[0]).toHaveProperty('textContent', 'Aloha!');

// THEN: getState() should have 1 suggested actions and origin activity.
expect(store.getState().suggestedActions).toHaveLength(1);
expect(store.getState().suggestedActions[0]).toEqual({ title: 'Aloha!', type: 'imBack' });
expect(store.getState().suggestedActionsOriginActivity).toEqual({
activity: expect.objectContaining({
from: { role: 'bot' },
suggestedActions: {
actions: [{ title: 'Aloha!', type: 'imBack' }],
to: ''
},
text: 'Hello, World!',
type: 'message'
})
});

// WHEN: Dispatching "WEB_CHAT/CLEAR_SUGGESTED_ACTIONS" action.
store.dispatch({ type: 'WEB_CHAT/CLEAR_SUGGESTED_ACTIONS' });

// THEN: Should not remove activity.
expect(pageElements.activities()).toHaveLength(1);

// THEN: Should have cleared suggested action.
await waitFor(() => expect(pageElements.allByTestId(testIds.suggestedActionButton)).toHaveLength(0));

// THEN: useSuggestedActions() should have emptied.
renderResult.rerender();
expect(renderResult).toHaveProperty('result.current', [[], expect.any(Function), { activity: undefined }]);
});
</script>
</body>
</html>
Loading
Loading