Skip to content

Commit 0d8008c

Browse files
authored
Merge branch 'main' into main
2 parents 06e6818 + e2c2f72 commit 0d8008c

35 files changed

+817
-186
lines changed

ACHIEVEMENTS.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,16 @@ A curated list of major achievements by the Web Chat team. This document celebra
139139

140140
## 🚀 Performance & Developer Experience
141141

142+
143+
### 🧩 Introduction of PolyMiddleware
144+
145+
**Goal:** Enable composable, reusable, and unified way for managing middleware.
146+
**By:** [@compulim](https://github.com/compulim) in [PR #5515](https://github.com/microsoft/BotFramework-WebChat/pull/5515), [#5566](https://github.com/microsoft/BotFramework-WebChat/pull/5566)
147+
148+
- Allows multiple middleware to be composed and applied from a single place.
149+
- Simplifies extension, testing, and maintenance of middleware logic.
150+
- Lays groundwork for Web Chat becomoming an UI orchestration.
151+
142152
### 🧠 Memoization & Hook Optimizations
143153

144154
**Goal:** Reduce rerenders and memory footprint.

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ Notes: web developers are advised to use [`~` (tilde range)](https://github.com/
112112
- Deprecated `hideUploadButton` in favor of `disableFileUpload`.
113113
- Updated `BasicSendBoxToolbar` to rely solely on `disableFileUpload`.
114114
- Added support for livestreaming via `entities[type="streaminfo"]` in PR [#5517](https://github.com/microsoft/BotFramework-WebChat/pull/5517) by [@kylerohn](https://github.com/kylerohn) and [@compulim](https://github.com/compulim)
115-
- Added `polymiddleware`, a new [universal middleware for every UIs](./docs/MIDDLEWARE.md), by [@compulim](https://github.com/compulim) in PR [#5515](https://github.com/microsoft/BotFramework-WebChat/pull/5515)
115+
- Added `polymiddleware`, a new [universal middleware for every UIs](./docs/MIDDLEWARE.md), by [@compulim](https://github.com/compulim) in PR [#5515](https://github.com/microsoft/BotFramework-WebChat/pull/5515) and [#5566](https://github.com/microsoft/BotFramework-WebChat/pull/5566)
116116
- Added `polymiddleware` to `<ThemeProvider>`
117117
- Currently supports activity middleware and the new error box middleware
118118
- New internal packages, by [@compulim](https://github.com/compulim) in PR [#5515](https://github.com/microsoft/BotFramework-WebChat/pull/5515)

__tests__/html2/middleware/activity/hooks/useBuildRenderActivityCallback/activityPolymiddlewareReturnInvalidActivityComponent.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
testHelpers: { createDirectLineEmulator },
4141
WebChat: {
4242
DebugProvider,
43-
hooks: { useActivities, useBuildRenderActivityCallback },
4443
middleware: { activityComponent, createActivityPolymiddleware },
4544
ReactWebChat
4645
}

__tests__/html2/middleware/activity/hooks/useBuildRenderActivityCallback/activityPolymiddlewareThrowOnRender.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
testHelpers: { createDirectLineEmulator },
4141
WebChat: {
4242
DebugProvider,
43-
hooks: { useActivities, useBuildRenderActivityCallback },
4443
middleware: { activityComponent, createActivityPolymiddleware },
4544
ReactWebChat
4645
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<!doctype html>
2+
<html lang="en-US">
3+
<head>
4+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
5+
<script type="importmap">
6+
{
7+
"imports": {
8+
"@testduet/wait-for": "https://esm.sh/@testduet/wait-for",
9+
"jest-mock": "https://esm.sh/jest-mock",
10+
"react": "https://esm.sh/react@18.3.1",
11+
"react-dom": "https://esm.sh/react-dom@18.3.1",
12+
"react-dom/": "https://esm.sh/react-dom@18.3.1/"
13+
}
14+
}
15+
</script>
16+
<script type="module">
17+
import React from 'react';
18+
19+
window.React = React;
20+
</script>
21+
<script defer crossorigin="anonymous" src="/test-harness.js"></script>
22+
<script defer crossorigin="anonymous" src="/test-page-object.js"></script>
23+
<script defer crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
24+
</head>
25+
<body>
26+
<main id="webchat"></main>
27+
<script defer type="module">
28+
import { spyOn } from 'jest-mock';
29+
import { waitFor } from '@testduet/wait-for';
30+
import React, { createElement, memo } from 'react';
31+
import { createRoot } from 'react-dom/client';
32+
33+
run(async function () {
34+
const {
35+
testHelpers: { createDirectLineEmulator },
36+
WebChat: {
37+
Components: { Composer },
38+
hooks: { useActivities, useBuildRenderActivityCallback },
39+
middleware: { activityComponent, createActivityPolymiddleware, createActivityPolymiddlewareFromLegacy },
40+
ReactWebChat
41+
}
42+
} = window;
43+
44+
const { directLine, store } = createDirectLineEmulator();
45+
46+
const rootElement = document.getElementsByTagName('main')[0];
47+
48+
const polymiddleware = [
49+
createActivityPolymiddlewareFromLegacy(
50+
() => next => request => {
51+
const child = next({
52+
activity: {
53+
...request.activity,
54+
text: request.activity.text.toUpperCase()
55+
}
56+
});
57+
58+
return () =>
59+
createElement(
60+
'div',
61+
{},
62+
createElement('div', {}, `<Upstream data-activity-text="${request.activity.text}">`),
63+
child(),
64+
createElement('div', {}, '</Upstream>')
65+
);
66+
},
67+
() => next => request => {
68+
return () =>
69+
createElement(
70+
'div',
71+
{},
72+
createElement('div', {}, `<Downstream data-activity-text="${request.activity.text}" />`)
73+
);
74+
}
75+
)
76+
];
77+
78+
// WHEN: Web Chat is being rendered.
79+
createRoot(rootElement).render(
80+
createElement(ReactWebChat, {
81+
directLine,
82+
polymiddleware,
83+
store
84+
})
85+
);
86+
87+
await pageConditions.uiConnected();
88+
89+
spyOn(console, 'warn');
90+
91+
// WHEN: A message arrive: "Hello, World!"
92+
await directLine.emulateIncomingActivity({
93+
text: 'Hello, World!',
94+
timestamp: 0,
95+
type: 'message'
96+
});
97+
98+
// THEN: Should render both legacy middleware and polymiddleware.
99+
await waitFor(() =>
100+
expect(rootElement?.querySelector('.webchat__basic-transcript__transcript')?.children).toHaveLength(1)
101+
);
102+
103+
// THEN: Downstream should show original request.
104+
const activity = rootElement?.querySelector('.webchat__basic-transcript__activity-body');
105+
106+
expect(activity).toHaveProperty(
107+
'textContent',
108+
'<Upstream data-activity-text="Hello, World!"><Downstream data-activity-text="HELLO, WORLD!" /></Upstream>'
109+
);
110+
111+
// THEN: Should not warn because request change is allowed in legacy activity middleware.
112+
await expect(console.warn).toHaveBeenCalledTimes(0);
113+
114+
// THEN: Should match snapshot.
115+
await host.snapshot('local');
116+
});
117+
</script>
118+
</body>
119+
</html>
17.1 KB
Loading
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<!doctype html>
2+
<html lang="en-US">
3+
<head>
4+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
5+
<script type="importmap">
6+
{
7+
"imports": {
8+
"@testduet/wait-for": "https://esm.sh/@testduet/wait-for",
9+
"jest-mock": "https://esm.sh/jest-mock",
10+
"react": "https://esm.sh/react@18.3.1",
11+
"react-dom": "https://esm.sh/react-dom@18.3.1",
12+
"react-dom/": "https://esm.sh/react-dom@18.3.1/"
13+
}
14+
}
15+
</script>
16+
<script type="module">
17+
import React from 'react';
18+
19+
window.React = React;
20+
</script>
21+
<script defer crossorigin="anonymous" src="/test-harness.js"></script>
22+
<script defer crossorigin="anonymous" src="/test-page-object.js"></script>
23+
<script defer crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
24+
</head>
25+
<body>
26+
<main id="webchat"></main>
27+
<script defer type="module">
28+
import { spyOn } from 'jest-mock';
29+
import { waitFor } from '@testduet/wait-for';
30+
import React, { createElement, memo } from 'react';
31+
import { createRoot } from 'react-dom/client';
32+
33+
run(async function () {
34+
const {
35+
testHelpers: { createDirectLineEmulator },
36+
WebChat: {
37+
Components: { Composer },
38+
hooks: { useActivities, useBuildRenderActivityCallback },
39+
middleware: { activityComponent, createActivityPolymiddleware, createActivityPolymiddlewareFromLegacy },
40+
ReactWebChat
41+
}
42+
} = window;
43+
44+
const { directLine, store } = createDirectLineEmulator();
45+
46+
const rootElement = document.getElementsByTagName('main')[0];
47+
48+
const Downstream = function Downstream({ request }) {
49+
return createElement(
50+
'div',
51+
{},
52+
createElement('div', {}, `<Downstream data-activity-text="${request.activity.text}" />`)
53+
);
54+
};
55+
56+
const polymiddleware = [
57+
createActivityPolymiddlewareFromLegacy(() => next => request => {
58+
// Modify request, it should warn and revert.
59+
const child = next({
60+
activity: {
61+
...request.activity,
62+
text: request.activity.text.toUpperCase()
63+
}
64+
});
65+
66+
return () =>
67+
createElement(
68+
'div',
69+
{},
70+
createElement(
71+
'div',
72+
{},
73+
`<Upstream data-activity-text="${request.activity.text}">`,
74+
child?.(),
75+
`</Upstream>`
76+
)
77+
);
78+
}),
79+
createActivityPolymiddleware(next => request => activityComponent(Downstream, { request }))
80+
];
81+
82+
// WHEN: Web Chat is being rendered.
83+
createRoot(rootElement).render(
84+
createElement(ReactWebChat, {
85+
directLine,
86+
polymiddleware,
87+
store
88+
})
89+
);
90+
91+
await pageConditions.uiConnected();
92+
93+
spyOn(console, 'warn');
94+
95+
// WHEN: A message arrive: "Hello, World!"
96+
await directLine.emulateIncomingActivity({
97+
text: 'Hello, World!',
98+
timestamp: 0,
99+
type: 'message'
100+
});
101+
102+
// THEN: Should render both legacy middleware and polymiddleware.
103+
await waitFor(() =>
104+
expect(rootElement?.querySelector('.webchat__basic-transcript__transcript')?.children).toHaveLength(1)
105+
);
106+
107+
// THEN: Downstream should show original request.
108+
const activity = rootElement?.querySelector('.webchat__basic-transcript__activity-body');
109+
110+
expect(activity).toHaveProperty(
111+
'textContent',
112+
'<Upstream data-activity-text="Hello, World!"><Downstream data-activity-text="Hello, World!" /></Upstream>'
113+
);
114+
115+
// THEN: Should not warn because request change is allowed in legacy activity middleware.
116+
await expect(console.warn).toHaveBeenCalledTimes(1);
117+
118+
// THEN: Should match snapshot.
119+
await host.snapshot('local');
120+
});
121+
</script>
122+
</body>
123+
</html>
15.6 KB
Loading

0 commit comments

Comments
 (0)