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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@ Breaking changes in this release:
- Fixed Fluent/Copilot typing indicator animation background color, in PR [#5770](https://github.com/microsoft/BotFramework-WebChat/pull/5770), by [@OEvgeny](https://github.com/OEvgeny)
- Fixed `<AddFullBundle>` should not re-render when `attachment[ForScreenReader]Middleware` is updated without noticeable different (`iterateEquals`), by [@compulim](https://github.com/compulim), in PR [#5779](https://github.com/microsoft/BotFramework-WebChat/pull/5779)
- Fixed send box should narrate `aria-label` prop, by [@compulim](https://github.com/compulim), in PR [#5805](https://github.com/microsoft/BotFramework-WebChat/pull/5805)
- Fixed polymiddleware error should not propagate to React runtime if `<DebugProvider>` is not mounted, by [@compulim](https://github.com/compulim), in PR [#5833](https://github.com/microsoft/BotFramework-WebChat/pull/5833)

## [4.18.0] - 2024-07-10

Expand Down
96 changes: 96 additions & 0 deletions __tests__/html2/adaptiveCard/renderError.withoutDebug.html
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>
const warn = console.warn.bind(console);

// We need to fake the mock ourselves without using jest-mock.
// jest-mock is ESM and cannot mock before Web Chat.
console.warn = (...args) => {
console.warn.mock.calls.push(args);

warn(...args);
};

console.warn._isMockFunction = true;
console.warn.getMockName = () => 'warn';
console.warn.mock = { calls: [] };
</script>
<script type="importmap">
{
"imports": {
"react": "https://esm.sh/react@18.3.1",
"react-dom": "https://esm.sh/react-dom@18.3.1",
"react-dom/": "https://esm.sh/react-dom@18.3.1/"
}
}
</script>
<script crossorigin="anonymous" src="/test-harness.js"></script>
<script crossorigin="anonymous" src="/test-page-object.js"></script>
<script type="module">
import React from 'react';

window.React = React;
</script>
<script crossorigin="anonymous" defer src="/__dist__/webchat-es5.js"></script>
<script crossorigin="anonymous" defer src="/__dist__/botframework-webchat-debug-theme.development.js"></script>
</head>
<body>
<main id="webchat"></main>
<script type="module">
import React, { createElement } from 'react';
import { createRoot } from 'react-dom/client';

run(async function () {
const {
testHelpers: { createStore },
WebChat: { ReactWebChat }
} = window;

const { directLine, store } = testHelpers.createDirectLineEmulator();

// GIVEN: Web Chat is being rendered without <DebugProvider>.
createRoot(document.getElementById('webchat')).render(createElement(ReactWebChat, { directLine, store }));

await pageConditions.uiConnected();

await directLine.emulateIncomingActivity({
attachments: [
{
contentType: 'application/vnd.microsoft.card.adaptive',
content: {
// WHEN: We want to render a failing Adaptive Cards, adding "*" here to fail the renderer.
type: '*AdaptiveCard*',
$schema: 'http://adaptivecards.io/schemas/adaptive-card.json',
version: '1.5',
body: [
{
text: 'Hello, World!',
type: 'TextBlock'
}
]
}
}
],
type: 'message'
});

// THEN: Should have "render Adaptive Cards" error message.
expect(console.warn).toHaveBeenCalledWith(
expect.stringContaining('Failed to render Adaptive Cards.'),
expect.anything()
);

// THEN: Should not show RCoR error as we have injected catchall.
expect(console.warn).not.toHaveBeenCalledWith(
expect.stringContaining('the request has fall through all middleware, set "fallbackComponent" as a catchall'),
expect.anything()
);

// THEN: Should not show red box.
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.
109 changes: 109 additions & 0 deletions __tests__/html2/middleware/errorBox/proxy.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<!doctype html>
<html lang="en-US">
<head>
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
<script>
const warn = console.warn.bind(console);

// We need to fake the mock ourselves without using jest-mock.
// jest-mock is ESM and cannot mock before Web Chat.
console.warn = (...args) => {
console.warn.mock.calls.push(args);

warn(...args);
};

console.warn._isMockFunction = true;
console.warn.getMockName = () => 'warn';
console.warn.mock = { calls: [] };
</script>
<script type="importmap">
{
"imports": {
"jest-mock": "https://esm.sh/jest-mock",
"react": "https://esm.sh/react@18.3.1",
"react-dom": "https://esm.sh/react-dom@18.3.1",
"react-dom/": "https://esm.sh/react-dom@18.3.1/"
}
}
</script>
<script crossorigin="anonymous" src="/test-harness.js"></script>
<script crossorigin="anonymous" src="/test-page-object.js"></script>
<script type="module">
import React from 'react';
window.React = React;
</script>
<script crossorigin="anonymous" defer src="/__dist__/webchat-es5.js"></script>
<script crossorigin="anonymous" defer src="/__dist__/botframework-webchat-debug-theme.development.js"></script>
<style type="text/css">
.webchat > .error-box {
border: solid 1px red;
}
</style>
</head>
<body>
<main id="webchat"></main>
<script type="module">
import { fn, spyOn } from 'jest-mock';
import { createElement, Fragment, memo } from 'react';
import { createRoot } from 'react-dom/client';

run(async function () {
const {
testHelpers: { createDirectLineEmulator },
WebChat: {
Components: { Composer, ThemeProvider },
DebugProvider,
middleware: { ErrorBoxPolymiddlewareProxy },
ReactWebChat
}
} = window;

const { directLine, store } = createDirectLineEmulator();

const SystemUnderTest = () =>
createElement(
'div',
{ className: 'error-box' },
createElement(ErrorBoxPolymiddlewareProxy, { error: new Error('Hello, World!'), where: 'Artificial' })
);

const hasDebugProvider = !new URL(location.href).searchParams.has('no-debug-provider');
const onTelemetry = fn();

createRoot(document.getElementById('webchat')).render(
createElement(
hasDebugProvider ? DebugProvider : Fragment,
{},
createElement(
Composer,
{
directLine,
onTelemetry,
store
},
createElement(SystemUnderTest)
)
)
);

await pageConditions.uiConnected();

// THEN: Should emit as exception event.
const exceptionEvent = onTelemetry.mock.calls.find(call => call[0]?.type === 'exception');

expect(exceptionEvent).toBeTruthy();
expect(exceptionEvent[0].error.message).toBe('Hello, World!');

// THEN: Should render a red box with DebugProvider.
await host.snapshot('local');

// THEN: Should not warn with RCoR fallthrough.
expect(console.warn).not.toHaveBeenCalledWith(
expect.stringContaining('the request has fall through all middleware, set "fallbackComponent" as a catchall'),
expect.anything()
);
});
</script>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<html lang="en-US">
<head>
<script>
location.href = './simple?no-debug-provider';
location.href = './proxy?no-debug-provider';
</script>
</head>
<body></body>
Expand Down
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
Expand Up @@ -2,6 +2,21 @@
<html lang="en-US">
<head>
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
<script>
const warn = console.warn.bind(console);

// We need to fake the mock ourselves without using jest-mock.
// jest-mock is ESM and cannot mock before Web Chat.
console.warn = (...args) => {
console.warn.mock.calls.push(args);

warn(...args);
};

console.warn._isMockFunction = true;
console.warn.getMockName = () => 'warn';
console.warn.mock = { calls: [] };
</script>
<script type="importmap">
{
"imports": {
Expand Down Expand Up @@ -39,7 +54,7 @@
WebChat: {
Components: { Composer, ThemeProvider },
DebugProvider,
middleware: { createErrorBoxPolymiddleware, errorBoxComponent, useBuildRenderErrorBoxCallback },
middleware: { useBuildRenderErrorBoxCallback },
ReactWebChat
}
} = window;
Expand Down Expand Up @@ -83,6 +98,12 @@

// THEN: Should render a red box with DebugProvider.
await host.snapshot('local');

// THEN: Should not warn with RCoR fallthrough.
expect(console.warn).not.toHaveBeenCalledWith(
expect.stringContaining('the request has fall through all middleware, set "fallbackComponent" as a catchall'),
expect.anything()
);
});
</script>
</body>
Expand Down
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,9 @@
<!doctype html>
<html lang="en-US">
<head>
<script>
location.href = './useBuildRenderCallback?no-debug-provider';
</script>
</head>
<body></body>
</html>
1 change: 1 addition & 0 deletions __tests__/html2/part-grouping/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,7 @@

// Then: show the closing message after the list
await pageConditions.numActivitiesShown(6);
await pageConditions.scrollToBottomCompleted();
await host.snapshot('local');
});
</script>
Expand Down
18 changes: 15 additions & 3 deletions packages/api-middleware/src/errorBoxPolymiddleware.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { validateProps } from '@msinternal/botframework-webchat-react-valibot';
import React, { memo, useMemo } from 'react';
import React, { memo, useCallback, useMemo } from 'react';
import { object, pipe, readonly, string, unknown, type InferInput } from 'valibot';

import templatePolymiddleware, {
Expand All @@ -18,7 +18,7 @@ const {
Provider: ErrorBoxPolymiddlewareProvider,
Proxy,
reactComponent: errorBoxComponent,
useBuildRenderCallback: useBuildRenderErrorBoxCallback
useBuildRenderCallback
} = templatePolymiddleware<{ readonly error: unknown; readonly where: string }, { readonly children?: never }>(
'ErrorBox'
);
Expand All @@ -41,15 +41,27 @@ const ErrorBoxPolymiddlewareProxyPropsSchema = pipe(

type ErrorBoxPolymiddlewareProxyProps = Readonly<InferInput<typeof ErrorBoxPolymiddlewareProxyPropsSchema>>;

// If no error box is defined, do not fallthrough into RCoR and it would error out. Render nothing instead.
const NullComponent = () => null;

// A friendlier version than the organic <Proxy>.
const ErrorBoxPolymiddlewareProxy = memo(function ErrorBoxPolymiddlewareProxy(props: ErrorBoxPolymiddlewareProxyProps) {
const { error, where } = validateProps(ErrorBoxPolymiddlewareProxyPropsSchema, props);

const request = useMemo(() => ({ error, where }), [error, where]);

return <Proxy request={request} />;
return <Proxy fallbackComponent={NullComponent} request={request} />;
});

const useBuildRenderErrorBoxCallback: typeof useBuildRenderCallback = () => {
const buildRenderCallback = useBuildRenderCallback();

return useCallback(
(request, options) => buildRenderCallback(request, Object.freeze({ fallbackComponent: NullComponent, ...options })),
[buildRenderCallback]
);
Comment thread
compulim marked this conversation as resolved.
};

export {
createErrorBoxPolymiddleware,
errorBoxComponent,
Expand Down
Loading