Skip to content
Draft
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
3 changes: 3 additions & 0 deletions .changelog/5982-fix-react-16-shadow-dom.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Fixed

- Fixed `renderWebChat()` ignoring clicks and typing inside a shadow root on React 16, related to [#5315](https://github.com/microsoft/BotFramework-WebChat/issues/5315), in PR [#5982](https://github.com/microsoft/BotFramework-WebChat/pull/5982), by [@OEvgeny](https://github.com/OEvgeny)
99 changes: 99 additions & 0 deletions __tests__/html2/customElement/shadowRoot.interact.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<!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>
<!-- No React on the page: Web Chat uses the React DOM 16.8.6 it bundles. -->
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
</head>
<body>
<webchat-element>
<template shadowrootmode="open">
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
<link href="/assets/focusManagement.css" rel="stylesheet" type="text/css" />
<style>
/* Same as "body.jest input" in index.css, which does not reach into the shadow root: no blinking caret in snapshots. */
input {
caret-color: transparent;
}
</style>
<main id="webchat"></main>
</template>
</webchat-element>
<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 { directLine, store } = testHelpers.createDirectLineEmulator();
const { shadowRoot } = document.querySelector('webchat-element');

// GIVEN: Web Chat with React DOM 16.8.6 renders into an element inside a shadow root.
WebChat.renderWebChat(
{ directLine, store, styleOptions: { disableFileUpload: true, stylesRoot: shadowRoot } },
shadowRoot.getElementById('webchat')
);

pageElements.root(shadowRoot);

await pageConditions.uiConnected();

// WHEN: The user types a message and presses ENTER.
const { activity: typedActivity, resolveAll: resolveTypedActivity } = await directLine.actPostActivity(
async () => {
await host.click(pageElements.sendBoxTextBox());
await host.sendKeys('Hello, World!', 'ENTER');

// Without the fix, React 16 misses the keystrokes and sends nothing.
await pageConditions.numActivitiesShown(1);
}
);

await resolveTypedActivity();

expect(typedActivity).toHaveProperty('text', 'Hello, World!');
expect(shadowRoot.activeElement).toBe(pageElements.sendBoxTextBox());

// WHEN: The bot replies with a suggested action.
await directLine.emulateIncomingActivity({
suggestedActions: { actions: [{ title: 'USA', type: 'imBack', value: 'USA' }] },
text: 'Where are you from?'
});

await pageConditions.suggestedActionsShown();

// THEN: "Hello, World!" is sent, the bot asks "Where are you from?", the send box is empty and has the focus.
await host.snapshot('local');

// WHEN: The user clicks the suggested action.
const { activity: clickedActivity, resolveAll: resolveClickedActivity } = await directLine.actPostActivity(
async () => {
await pageObjects.clickNthSuggestedAction(1);

// Without the fix, React 16 misses the click and sends nothing.
await pageConditions.numActivitiesShown(3);
}
);

await resolveClickedActivity();

expect(clickedActivity).toHaveProperty('text', 'USA');

await waitFor(() => expect(pageElements.suggestedActions()).toHaveLength(0));

// Clicking a suggested action moves the focus to the send box.
await waitFor(() => expect(shadowRoot.activeElement).toBe(pageElements.sendBoxTextBox()));

// THEN: "USA" is sent, the suggested action is gone and the send box has the focus.
await host.snapshot('local');
});
</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.
97 changes: 97 additions & 0 deletions __tests__/html2/customElement/shadowRoot.interact.react18.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<!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="https://unpkg.com/react@18.3.1/umd/react.production.min.js"></script>
<script crossorigin="anonymous" src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js"></script>
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
</head>
<body>
<webchat-element>
<template shadowrootmode="open">
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
<link href="/assets/focusManagement.css" rel="stylesheet" type="text/css" />
<style>
/* Same as "body.jest input" in index.css, which does not reach into the shadow root: no blinking caret in snapshots. */
input {
caret-color: transparent;
}
</style>
<main id="webchat"></main>
</template>
</webchat-element>
<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 { directLine, store } = testHelpers.createDirectLineEmulator();
const { shadowRoot } = document.querySelector('webchat-element');

// GIVEN: Web Chat with React DOM 18.3.1 renders into an element inside a shadow root.
WebChat.renderWebChat(
{ directLine, store, styleOptions: { disableFileUpload: true, stylesRoot: shadowRoot } },
shadowRoot.getElementById('webchat')
);

pageElements.root(shadowRoot);

await pageConditions.uiConnected();

// WHEN: The user types a message and presses ENTER.
const { activity: typedActivity, resolveAll: resolveTypedActivity } = await directLine.actPostActivity(
async () => {
await host.click(pageElements.sendBoxTextBox());
await host.sendKeys('Hello, World!', 'ENTER');

await pageConditions.numActivitiesShown(1);
}
);

await resolveTypedActivity();

expect(typedActivity).toHaveProperty('text', 'Hello, World!');
expect(shadowRoot.activeElement).toBe(pageElements.sendBoxTextBox());

// WHEN: The bot replies with a suggested action.
await directLine.emulateIncomingActivity({
suggestedActions: { actions: [{ title: 'USA', type: 'imBack', value: 'USA' }] },
text: 'Where are you from?'
});

await pageConditions.suggestedActionsShown();

// THEN: "Hello, World!" is sent, the bot asks "Where are you from?", the send box is empty and has the focus.
await host.snapshot('local');

// WHEN: The user clicks the suggested action.
const { activity: clickedActivity, resolveAll: resolveClickedActivity } = await directLine.actPostActivity(
async () => {
await pageObjects.clickNthSuggestedAction(1);
await pageConditions.numActivitiesShown(3);
}
);

await resolveClickedActivity();

expect(clickedActivity).toHaveProperty('text', 'USA');

await waitFor(() => expect(pageElements.suggestedActions()).toHaveLength(0));

// Clicking a suggested action moves the focus to the send box.
await waitFor(() => expect(shadowRoot.activeElement).toBe(pageElements.sendBoxTextBox()));

// THEN: "USA" is sent, the suggested action is gone and the send box has the focus.
await host.snapshot('local');
});
</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.
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<!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>
<!-- No React on the page: Web Chat uses the React DOM 16.8.6 it bundles. -->
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
</head>
<body>
<main id="webchat"></main>
<template id="shadow-root-styles">
<link href="/assets/focusManagement.css" rel="stylesheet" type="text/css" />
<style>
/* Same as "body.jest input" in index.css, which does not reach into the shadow root: no blinking caret in snapshots. */
input {
caret-color: transparent;
}
</style>
</template>
<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 { directLine, store } = testHelpers.createDirectLineEmulator();
const shadowRoot = document.getElementById('webchat').attachShadow({ mode: 'open' });

// GIVEN: Web Chat with React DOM 16.8.6 renders into the shadow root itself.
WebChat.renderWebChat(
{ directLine, store, styleOptions: { disableFileUpload: true, stylesRoot: shadowRoot } },
shadowRoot
);

// React empties the container when it first renders: add the styles after.
shadowRoot.append(document.getElementById('shadow-root-styles').content.cloneNode(true));

pageElements.root(shadowRoot);

await pageConditions.uiConnected();

// WHEN: The user types a message and presses ENTER.
const { activity: typedActivity, resolveAll: resolveTypedActivity } = await directLine.actPostActivity(
async () => {
await host.click(pageElements.sendBoxTextBox());
await host.sendKeys('Hello, World!', 'ENTER');

await pageConditions.numActivitiesShown(1);
}
);

await resolveTypedActivity();

expect(typedActivity).toHaveProperty('text', 'Hello, World!');
expect(shadowRoot.activeElement).toBe(pageElements.sendBoxTextBox());

// WHEN: The bot replies with a suggested action.
await directLine.emulateIncomingActivity({
suggestedActions: { actions: [{ title: 'USA', type: 'imBack', value: 'USA' }] },
text: 'Where are you from?'
});

await pageConditions.suggestedActionsShown();

// THEN: "Hello, World!" is sent, the bot asks "Where are you from?", the send box is empty and has the focus.
await host.snapshot('local');

// WHEN: The user clicks the suggested action.
const { activity: clickedActivity, resolveAll: resolveClickedActivity } = await directLine.actPostActivity(
async () => {
await pageObjects.clickNthSuggestedAction(1);
await pageConditions.numActivitiesShown(3);
}
);

await resolveClickedActivity();

expect(clickedActivity).toHaveProperty('text', 'USA');

await waitFor(() => expect(pageElements.suggestedActions()).toHaveLength(0));

// Clicking a suggested action moves the focus to the send box.
await waitFor(() => expect(shadowRoot.activeElement).toBe(pageElements.sendBoxTextBox()));

// THEN: "USA" is sent, the suggested action is gone and the send box has the focus.
await host.snapshot('local');
});
</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
Loading