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
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<!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="outer-shadow-root">
<style>
#inner-host {
height: 100%;
}
</style>
<div id="inner-host"></div>
</template>
<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 outerShadowRoot = document.getElementById('webchat').attachShadow({ mode: 'open' });

outerShadowRoot.append(document.getElementById('outer-shadow-root').content.cloneNode(true));

const shadowRoot = outerShadowRoot.getElementById('inner-host').attachShadow({ mode: 'open' });

// `ShadowRoot.getRootNode()` returns the shadow root itself, however deep its host is nested.
expect(shadowRoot.getRootNode()).toBe(shadowRoot);

// GIVEN: Web Chat with React DOM 16.8.6 renders into a shadow root whose host is inside another shadow root.
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.
35 changes: 35 additions & 0 deletions packages/bundle/src/renderWebChat.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,41 @@ describe('Element in a shadow root', () => {
});
});

describe('Shadow root nested in another shadow root', () => {
let innerShadowRoot: ShadowRoot;

beforeEach(() => {
const innerHost = document.createElement('div');
const outerHost = document.createElement('div');

outerHost.attachShadow({ mode: 'open' }).append(innerHost);
document.body.append(outerHost);

innerShadowRoot = innerHost.attachShadow({ mode: 'open' });
});

afterEach(() => document.body.replaceChildren());

test('passed as the inner shadow root itself on React 16 should be rendered by ReactDOM.render()', () => {
// `ShadowRoot.getRootNode()` returns the shadow root itself, however deep its host is nested.
expect(innerShadowRoot.getRootNode()).toBe(innerShadowRoot);

renderWebChat(ReactWebChat, {}, innerShadowRoot);

expect(render).toHaveBeenCalledTimes(1);
expect(render.mock.calls[0][1]).toBe(innerShadowRoot);
});

test('with an element inside the inner shadow root on React 16 should throw', () => {
const element = document.createElement('div');

innerShadowRoot.append(element);

expect(() => renderWebChat(ReactWebChat, {}, element)).toThrow('on React 16 does not support');
expect(render).not.toHaveBeenCalled();
});
});

test('Element outside of a shadow root on React 16 should be rendered by ReactDOM.render()', () => {
const element = document.createElement('div');

Expand Down
Loading