Skip to content

Commit 647b269

Browse files
authored
[P0] Add Fluent theme pack (#5120)
* Add themeability * Add sendBoxMiddleware * Add immutability test * Fix tests * Build template * Move upload button to send box toolbar middleware * Export BasicSendBoxToolbar * Add tests * Add tests * Add tests * Add Fluent package * Fix package * Add entries * Pack more artifacts * Fix precommit * Add typings * Sort * Add comments
1 parent d0aa541 commit 647b269

File tree

97 files changed

+3986
-277
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

97 files changed

+3986
-277
lines changed

.github/workflows/pull-request-validation.yml

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ jobs:
6262
./package.json
6363
./package-lock.json
6464
./packages/bundle/dist/
65+
./packages/fluent-theme/dist/
6566
./packages/test/harness/
6667
./packages/test/page-object/dist/
6768
./serve-test.json

CHANGELOG.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
3232
- Resolves [#5083](https://github.com/microsoft/BotFramework-WebChat/issues/5083). Added `sendAttachmentOn` style option to send attachments and text in a single activity, by [@ms-jb](https://github.com/ms-jb) and [@compulim](https://github.com/compulim)
3333
- `useSendMessage` hook is updated to support sending attachments with a message
3434
- `useSendBoxAttachments` hook is added to get/set attachments in the send box
35-
- Resolves [#5081](https://github.com/microsoft/BotFramework-WebChat/issues/5081). Added `uploadAccept` and `uploadMultiple` style options, by [@ms-jb](https://github.com/ms-jb)
35+
- Resolves [#5081](https://github.com/microsoft/BotFramework-WebChat/issues/5081). Added `uploadAccept` and `uploadMultiple` style options, by [@ms-jb](https://github.com/ms-jb), in PR [#5048](https://github.com/microsoft/BotFramework-WebChat/pull/5048)
36+
- Added `sendBoxMiddleware` and `sendBoxToolbarMiddleware`, by [@compulim](https://github.com/compulim), in PR [#5120](https://github.com/microsoft/BotFramework-WebChat/pull/5120)
37+
- Added `botframework-webchat-fluent-theme` package for applying Fluent UI theme to Web Chat, by [@compulim](https://github.com/compulim), in PR [#5120](https://github.com/microsoft/BotFramework-WebChat/pull/5120)
38+
- Added `<ThemeProvider>` component to apply theme pack to Web Chat, by [@compulim](https://github.com/compulim), in PR [#5120](https://github.com/microsoft/BotFramework-WebChat/pull/5120)
3639

3740
### Fixed
3841

Loading

__tests__/hooks/useGroupTimestamp.js

-12
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,6 @@ test('getter should return group timestamp set in styleOptions', async () => {
1919
expect(groupTimestamp).toMatchInlineSnapshot(`1000`);
2020
});
2121

22-
test('getter should return group timestamp set in props (deprecated)', async () => {
23-
const { pageObjects } = await setupWebDriver({
24-
props: {
25-
groupTimestamp: 1000
26-
}
27-
});
28-
29-
const [groupTimestamp] = await pageObjects.runHook('useGroupTimestamp');
30-
31-
expect(groupTimestamp).toMatchInlineSnapshot(`1000`);
32-
});
33-
3422
test('getter should return default group timestamp if not set in styleOptions', async () => {
3523
const { pageObjects } = await setupWebDriver();
3624

__tests__/hooks/useTimeoutForSend.js

+1-14
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,8 @@ test('getter should return timeout for sending activity', async () => {
1919
expect(timeoutForSend).toMatchInlineSnapshot(`1000`);
2020
});
2121

22-
test('getter should return timeout for sending activity if set in props', async () => {
23-
const { pageObjects } = await setupWebDriver({
24-
props: {
25-
sendTimeout: 1000
26-
}
27-
});
28-
29-
const [timeoutForSend] = await pageObjects.runHook('useTimeoutForSend');
30-
31-
expect(timeoutForSend).toMatchInlineSnapshot(`1000`);
32-
});
33-
34-
test('getter should return default timeout for sending activity if not set in props', async () => {
22+
test('getter should return default timeout for sending activity', async () => {
3523
const { pageObjects } = await setupWebDriver();
36-
3724
const [timeoutForSend] = await pageObjects.runHook('useTimeoutForSend');
3825

3926
expect(timeoutForSend).toMatchInlineSnapshot(`20000`);
+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<!doctype html>
2+
<html lang="en-US">
3+
<head>
4+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
5+
<script crossorigin="anonymous" src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
6+
<script crossorigin="anonymous" src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
7+
<script crossorigin="anonymous" src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
8+
<script crossorigin="anonymous" src="/test-harness.js"></script>
9+
<script crossorigin="anonymous" src="/test-page-object.js"></script>
10+
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
11+
<script crossorigin="anonymous" src="/__dist__/botframework-webchat-fluent-theme.production.min.js"></script>
12+
</head>
13+
<body>
14+
<main id="webchat"></main>
15+
<script type="text/babel">
16+
run(async function () {
17+
const {
18+
React,
19+
ReactDOM: { render },
20+
WebChat: { FluentThemeProvider, ReactWebChat }
21+
} = window; // Imports in UMD fashion.
22+
23+
const { directLine, store } = testHelpers.createDirectLineEmulator();
24+
25+
const App = () => <ReactWebChat directLine={directLine} store={store} />;
26+
27+
render(
28+
<FluentThemeProvider>
29+
<App />
30+
</FluentThemeProvider>,
31+
document.getElementById('webchat')
32+
);
33+
34+
await pageConditions.uiConnected();
35+
36+
await directLine.emulateIncomingActivity(
37+
'Eiusmod anim adipisicing cupidatat adipisicing officia sint qui consequat veniam id aute.'
38+
);
39+
40+
await pageConditions.numActivitiesShown(1);
41+
42+
// THEN: Should render the activity.
43+
await host.snapshot();
44+
45+
// THEN: Fluent theme should be loaded.
46+
expect(
47+
document.querySelector('meta[name="botframework-webchat-fluent-theme:version"]').getAttribute('name')
48+
).toBeTruthy();
49+
50+
expect(window.WebChat.FluentThemeProvider).toBeTruthy();
51+
});
52+
</script>
53+
</body>
54+
</html>

__tests__/html/fluentTheme/simple.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */
2+
3+
describe('Fluent theme applied', () => {
4+
test('should render', () => runHTML('fluentTheme/simple'));
5+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
<!doctype html>
2+
<html lang="en-US">
3+
<head>
4+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
5+
<script crossorigin="anonymous" src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
6+
<script crossorigin="anonymous" src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
7+
<script crossorigin="anonymous" src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
8+
<script crossorigin="anonymous" src="/test-harness.js"></script>
9+
<script crossorigin="anonymous" src="/test-page-object.js"></script>
10+
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
11+
</head>
12+
<body>
13+
<main id="webchat"></main>
14+
<script type="text/babel">
15+
run(async function () {
16+
const {
17+
React,
18+
React: { createContext, useContext, useMemo, useState },
19+
ReactDOM: { render },
20+
WebChat: {
21+
Components: { BasicWebChat, Composer },
22+
hooks: { useActivities }
23+
}
24+
} = window; // Imports in UMD fashion.
25+
26+
const AppContext = createContext({ showBorder: false });
27+
28+
const Border = ({ children }) => {
29+
const { showBorder } = useContext(AppContext);
30+
31+
return (
32+
<div
33+
style={{
34+
outlineColor: 'Red',
35+
outlineOffset: -2,
36+
outlineStyle: 'dashed',
37+
outlineWidth: showBorder ? 2 : 0
38+
}}
39+
>
40+
{children}
41+
</div>
42+
);
43+
};
44+
45+
const { directLine, store } = testHelpers.createDirectLineEmulator();
46+
47+
const OnActivity = () => {
48+
const [activities] = useActivities();
49+
const { setEndOfConversation, setShowBorder } = useContext(AppContext);
50+
51+
const lastActivity = activities[activities.length - 1];
52+
53+
useMemo(
54+
() => setEndOfConversation(lastActivity?.inputHint === 'ignoring'),
55+
[lastActivity, setEndOfConversation]
56+
);
57+
58+
useMemo(() => setShowBorder(lastActivity?.from.role === 'bot'), [lastActivity, setShowBorder]);
59+
60+
return false;
61+
};
62+
63+
const App = () => {
64+
const [showBorder, setShowBorder] = useState(false);
65+
const [endOfConversation, setEndOfConversation] = useState(false);
66+
const context = useMemo(
67+
() => ({ endOfConversation, setEndOfConversation, setShowBorder, showBorder }),
68+
[endOfConversation, setEndOfConversation, setShowBorder, showBorder]
69+
);
70+
71+
const attentionMiddleware = useMemo(
72+
() =>
73+
Object.freeze([
74+
() => next => request => {
75+
const NextComponent = next(request);
76+
77+
return props => (
78+
<Border>
79+
<NextComponent {...props} />
80+
</Border>
81+
);
82+
}
83+
]),
84+
[]
85+
);
86+
87+
const endOfConversationMiddleware = useMemo(
88+
() => Object.freeze([() => () => () => () => <div>Thank you. You can close the chat now.</div>]),
89+
[]
90+
);
91+
92+
const sendBoxMiddleware = useMemo(
93+
() => (endOfConversation ? endOfConversationMiddleware : attentionMiddleware),
94+
[attentionMiddleware, endOfConversation, endOfConversationMiddleware]
95+
);
96+
97+
return (
98+
<AppContext.Provider value={context}>
99+
<Composer directLine={directLine} store={store} sendBoxMiddleware={sendBoxMiddleware}>
100+
<OnActivity />
101+
<BasicWebChat />
102+
</Composer>
103+
</AppContext.Provider>
104+
);
105+
};
106+
107+
render(<App />, document.getElementById('webchat'));
108+
109+
await pageConditions.uiConnected();
110+
111+
// SCENARIO: If the last message is from bot, show a border around the send box.
112+
113+
// WHEN: Received a message from bot.
114+
await directLine.emulateIncomingActivity(
115+
'Culpa qui aliqua officia pariatur sit commodo in occaecat deserunt excepteur ad irure.'
116+
);
117+
118+
await pageConditions.numActivitiesShown(1);
119+
120+
// THEN: Should render a border around the default send box.
121+
await host.snapshot();
122+
123+
// WHEN: Send a message.
124+
await (await directLine.emulateOutgoingActivity('Hello, World!')).resolveAll();
125+
126+
// THEN: Should not render a border around the default send box because the last message is not from bot.
127+
await host.snapshot();
128+
129+
// WHEN: Received a message from bot with inputHint of "ignoring".
130+
await directLine.emulateIncomingActivity({
131+
inputHint: 'ignoring',
132+
text: 'Irure incididunt excepteur incididunt sunt occaecat excepteur.',
133+
type: 'message'
134+
});
135+
136+
// THEN: Should hide the send box because it changed to a different set of middleware.
137+
await host.snapshot();
138+
});
139+
</script>
140+
</body>
141+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */
2+
3+
describe('sendBoxMiddleware', () => {
4+
test('should change modality based on last activity received', () => runHTML('sendBoxMiddleware/changeModality'));
5+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<!doctype html>
2+
<html lang="en-US">
3+
<head>
4+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
5+
<script crossorigin="anonymous" src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
6+
<script crossorigin="anonymous" src="/test-harness.js"></script>
7+
<script crossorigin="anonymous" src="/test-page-object.js"></script>
8+
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
9+
</head>
10+
<body>
11+
<main id="webchat"></main>
12+
<script>
13+
run(async function () {
14+
const { directLine, store } = testHelpers.createDirectLineEmulator();
15+
const Border = ({ children }) =>
16+
React.createElement('div', { style: { outline: 'solid 2px Red', outlineOffset: -2 } }, children);
17+
18+
WebChat.renderWebChat(
19+
{
20+
directLine,
21+
store,
22+
sendBoxMiddleware: [
23+
() => next => request => {
24+
const NextComponent = next(request);
25+
26+
return props =>
27+
React.createElement(Border, {}, NextComponent ? React.createElement(NextComponent, props) : false);
28+
}
29+
]
30+
},
31+
document.getElementById('webchat')
32+
);
33+
34+
await pageConditions.uiConnected();
35+
36+
await directLine.emulateIncomingActivity(
37+
'Culpa qui aliqua officia pariatur sit commodo in occaecat deserunt excepteur ad irure.'
38+
);
39+
40+
await pageConditions.numActivitiesShown(1);
41+
42+
// THEN: Should render a border around the default send box.
43+
await host.snapshot();
44+
});
45+
</script>
46+
</body>
47+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */
2+
3+
describe('sendBoxMiddleware', () => {
4+
test('should decorate default send box', () => runHTML('sendBoxMiddleware/decorate'));
5+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<!doctype html>
2+
<html lang="en-US">
3+
<head>
4+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
5+
<script crossorigin="anonymous" src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
6+
<script crossorigin="anonymous" src="/test-harness.js"></script>
7+
<script crossorigin="anonymous" src="/test-page-object.js"></script>
8+
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
9+
</head>
10+
<body>
11+
<main id="webchat"></main>
12+
<script>
13+
run(async function () {
14+
const { directLine, store } = testHelpers.createDirectLineEmulator();
15+
const sendBoxMiddleware = [
16+
() => next => request => () => React.createElement('div', {}, 'Anim sint in ut id.')
17+
];
18+
19+
const render = bubbleBackground =>
20+
WebChat.renderWebChat(
21+
{ directLine, store, sendBoxMiddleware, styleOptions: { bubbleBackground } },
22+
document.getElementById('webchat')
23+
);
24+
25+
render('#fee');
26+
27+
await pageConditions.uiConnected();
28+
29+
await directLine.emulateIncomingActivity(
30+
'Culpa qui aliqua officia pariatur sit commodo in occaecat deserunt excepteur ad irure.'
31+
);
32+
33+
await pageConditions.numActivitiesShown(1);
34+
35+
// THEN: Should render the custom send box.
36+
await host.snapshot();
37+
38+
// WHEN: "sendBoxMiddleware" if mutated.
39+
sendBoxMiddleware[0] = () => next => request => () =>
40+
React.createElement('div', {}, 'Sunt amet veniam id minim.');
41+
42+
render('#efe');
43+
44+
await directLine.emulateIncomingActivity(
45+
'Eiusmod irure laboris amet velit eiusmod eiusmod nulla in exercitation exercitation.'
46+
);
47+
48+
await pageConditions.numActivitiesShown(2);
49+
50+
// THEN: Should not render the new sendBoxMiddleware.
51+
await host.snapshot();
52+
});
53+
</script>
54+
</body>
55+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */
2+
3+
describe('sendBoxMiddleware', () => {
4+
test('when mutated should be treated as immutable', () => runHTML('sendBoxMiddleware/immutable'));
5+
});

0 commit comments

Comments
 (0)