Skip to content

Commit db2a1bb

Browse files
authored
Add React hooks for customization (part 4) (#2543)
* Typo * Update entry * Add useActivities, useReferenceGrammarID and useSendBoxDictationStarted * Add useStyleOptions and useStyleSet * Fix errors * Add useStyleOptions and useStyleSet * Add useLanguage and useLocalize * Fix ESLint * Cleanup * Clean up * Fix useLocalize * Revert dev build * Typo * Update entry * Fix ESLint * Add useStyleOptions and useStyleSet * Add useStyleOptions and useStyleSet * Add useLanguage and useLocalize * Use useLocalize * Add useStyleOptions and useStyleSet * Add useLanguage and useLocalize * Add useAdaptiveCards* and useRenderMarkdownAsHTML * Update PR number * Use HostConfig from package * Update entry * Fix tests * Improve test reliability * Remove useWebSpeechPonyfill test * Fix tests * Fix ESLint * Fix ESLint * Fix bad merge * Fix bad merge * Undo dev changes * Fix test * Add test * Add test * Update screenshot * Fix tests
1 parent 39f7c70 commit db2a1bb

32 files changed

Lines changed: 389 additions & 197 deletions

CHANGELOG.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
2222

2323
## [Unreleased]
2424

25-
### Changed
25+
### Breaking changes
2626

27-
- `bundle`: Webpack will now use `webpack-stats-plugin` instead of `webpack-visualizer-plugin`, by [@compulim](https://github.com/compulim) in PR [#2584](https://github.com/microsoft/BotFramework-WebChat/pull/2584)
28-
- This will fix [#2583](https://github.com/microsoft/BotFramework-WebChat/issues/2583) by not bringing in transient dependency of React
29-
- To view the bundle stats, browse to https://chrisbateman.github.io/webpack-visualizer/ and drop the file `/packages/bundle/dist/stats.json`
27+
- `adaptiveCardHostConfig` is being renamed to `adaptiveCardsHostConfig`
28+
- If you are using the deprecated `adaptiveCardHostConfig`, we will rename it automatically
3029

3130
### Fixed
3231

@@ -37,10 +36,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
3736

3837
### Added
3938

40-
- Resolves [#2539](https://github.com/Microsoft/BotFramework-WebChat/issues/2539), added React hooks for customziation, by [@compulim](https://github.com/compulim) and [@corinagum](https://github.com/corinagum), in the following PRs:
39+
- Resolves [#2539](https://github.com/Microsoft/BotFramework-WebChat/issues/2539), added React hooks for customization, by [@compulim](https://github.com/compulim), in the following PRs:
4140
- PR [#2540](https://github.com/microsoft/BotFramework-WebChat/pull/2540): `useActivities`, `useReferenceGrammarID`, `useSendBoxDictationStarted`
4241
- PR [#2541](https://github.com/microsoft/BotFramework-WebChat/pull/2541): `useStyleOptions`, `useStyleSet`
4342
- PR [#2542](https://github.com/microsoft/BotFramework-WebChat/pull/2542): `useLanguage`, `useLocalize`, `useLocalizeDate`
43+
- PR [#2543](https://github.com/microsoft/BotFramework-WebChat/pull/2543): `useAdaptiveCardsHostConfig`, `useAdaptiveCardsPackage`, `useRenderMarkdownAsHTML`
44+
- Bring your own Adaptive Cards package by specifying `adaptiveCardsPackage` prop, by [@compulim](https://github.com/compulim) in PR [#2543](https://github.com/microsoft/BotFramework-WebChat/pull/2543)
4445

4546
### Fixed
4647

@@ -120,6 +121,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
120121
121122
- `component`: Bumps [`[email protected]`](https://npmjs.com/package/adaptivecards), by [@corinagum](https://github.com/corinagum) in PR [#2523](https://github.com/microsoft/BotFramework-WebChat/pull/2532)
122123
- Bumps Chrome in Docker to 78.0.3904.70, by [@spyip](https://github.com/spyip) in PR [#2545](https://github.com/microsoft/BotFramework-WebChat/pull/2545)
124+
- `bundle`: Webpack will now use `webpack-stats-plugin` instead of `webpack-visualizer-plugin`, by [@compulim](https://github.com/compulim) in PR [#2584](https://github.com/microsoft/BotFramework-WebChat/pull/2584)
125+
- This will fix [#2583](https://github.com/microsoft/BotFramework-WebChat/issues/2583) by not bringing in transient dependency of React
126+
- To view the bundle stats, browse to https://chrisbateman.github.io/webpack-visualizer/ and drop the file `/packages/bundle/dist/stats.json`
123127

124128
### Samples
125129

19.9 KB
Loading

__tests__/adaptiveCards.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,26 @@ test('breakfast card with custom host config', async () => {
4747
expect(base64PNG).toMatchImageSnapshot(imageSnapshotOptions);
4848
});
4949

50+
test('breakfast card with custom style options', async () => {
51+
const { driver, pageObjects } = await setupWebDriver({
52+
props: {
53+
styleOptions: {
54+
bubbleTextColor: '#FF0000'
55+
}
56+
}
57+
});
58+
59+
await driver.wait(uiConnected(), timeouts.directLine);
60+
await pageObjects.sendMessageViaSendBox('card breakfast', { waitForSend: true });
61+
62+
await driver.wait(minNumActivitiesShown(2), timeouts.directLine);
63+
await driver.wait(allImagesLoaded(), 2000);
64+
65+
const base64PNG = await driver.takeScreenshot();
66+
67+
expect(base64PNG).toMatchImageSnapshot(imageSnapshotOptions);
68+
});
69+
5070
test('disable card inputs', async () => {
5171
const { driver, pageObjects } = await setupWebDriver();
5272

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { timeouts } from '../constants.json';
2+
3+
// selenium-webdriver API doc:
4+
// https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html
5+
6+
jest.setTimeout(timeouts.test);
7+
8+
test('getter should return Adaptive Cards host config set in props', async () => {
9+
const { pageObjects } = await setupWebDriver({
10+
props: {
11+
adaptiveCardsHostConfig: {
12+
supportsInteractivity: false
13+
}
14+
}
15+
});
16+
17+
const [adaptiveCardsHostConfig] = await pageObjects.runHook('useAdaptiveCardsHostConfig');
18+
19+
expect(adaptiveCardsHostConfig).toMatchInlineSnapshot(`
20+
Object {
21+
"supportsInteractivity": false,
22+
}
23+
`);
24+
});
25+
26+
test('getter should return default Adaptive Cards host config if not set in props', async () => {
27+
const { pageObjects } = await setupWebDriver();
28+
29+
const [adaptiveCardsHostConfig] = await pageObjects.runHook('useAdaptiveCardsHostConfig');
30+
31+
expect(adaptiveCardsHostConfig.supportsInteractivity).toMatchInlineSnapshot(`true`);
32+
});
33+
34+
test('setter should be undefined', async () => {
35+
const { pageObjects } = await setupWebDriver();
36+
const [_, setAdaptiveCardsHostConfig] = await pageObjects.runHook('useAdaptiveCardsHostConfig');
37+
38+
expect(setAdaptiveCardsHostConfig).toBeUndefined();
39+
});
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { timeouts } from '../constants.json';
2+
3+
// selenium-webdriver API doc:
4+
// https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html
5+
6+
jest.setTimeout(timeouts.test);
7+
8+
test('getter should return Adaptive Cards package set in props', async () => {
9+
const { pageObjects } = await setupWebDriver({
10+
props: {
11+
adaptiveCardsPackage: {
12+
__DUMMY__: 0
13+
}
14+
}
15+
});
16+
17+
const [adaptiveCardsPackage] = await pageObjects.runHook('useAdaptiveCardsPackage');
18+
19+
expect(adaptiveCardsPackage).toMatchInlineSnapshot(`
20+
Object {
21+
"__DUMMY__": 0,
22+
}
23+
`);
24+
});
25+
26+
test('getter should return default Adaptive Cards package if not set in props', async () => {
27+
const { pageObjects } = await setupWebDriver();
28+
29+
const [adaptiveCardsPackage] = await pageObjects.runHook('useAdaptiveCardsPackage', [], results =>
30+
results[0].AdaptiveCard.currentVersion.toString()
31+
);
32+
33+
expect(adaptiveCardsPackage).toMatchInlineSnapshot(`"1"`);
34+
});
35+
36+
test('setter should be undefined', async () => {
37+
const { pageObjects } = await setupWebDriver();
38+
const setAdaptiveCardsPackage = await pageObjects.runHook(
39+
'useAdaptiveCardsPackage',
40+
[],
41+
results => typeof results[1] === 'undefined'
42+
);
43+
44+
expect(setAdaptiveCardsPackage).toMatchInlineSnapshot(`true`);
45+
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { timeouts } from '../constants.json';
2+
3+
// selenium-webdriver API doc:
4+
// https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html
5+
6+
jest.setTimeout(timeouts.test);
7+
8+
test('renderMarkdown should use Markdown-It if not set in props', async () => {
9+
const { pageObjects } = await setupWebDriver();
10+
11+
await expect(pageObjects.runHook('useRenderMarkdownAsHTML', [], fn => fn('Hello, World!'))).resolves.toBe(
12+
'<p>Hello, World!</p>\n'
13+
);
14+
});
15+
16+
test('renderMarkdown should use custom Markdown transform function from props', async () => {
17+
const { pageObjects } = await setupWebDriver({
18+
props: {
19+
renderMarkdown: text => text.toUpperCase()
20+
}
21+
});
22+
23+
await expect(
24+
pageObjects.runHook('useRenderMarkdownAsHTML', [], fn => fn('Hello, World!'))
25+
).resolves.toMatchInlineSnapshot(`"HELLO, WORLD!"`);
26+
});

__tests__/scrollToBottom.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { By } from 'selenium-webdriver';
33
import { imageSnapshotOptions, timeouts } from './constants.json';
44

55
import minNumActivitiesShown from './setup/conditions/minNumActivitiesShown';
6+
import scrollToBottomButtonVisible from './setup/conditions/scrollToBottomButtonVisible';
67
import scrollToBottomCompleted from './setup/conditions/scrollToBottomCompleted';
78
import suggestedActionsShown from './setup/conditions/suggestedActionsShown';
89
import uiConnected from './setup/conditions/uiConnected';
@@ -48,6 +49,8 @@ test('clicking "New messages" button should scroll to end and stick to bottom',
4849
document.querySelector('[role="log"] > *').scrollTop = 0;
4950
});
5051

52+
await driver.wait(scrollToBottomButtonVisible(), timeouts.ui);
53+
5154
expect(await driver.takeScreenshot()).toMatchImageSnapshot(imageSnapshotOptions);
5255

5356
await pageObjects.clickScrollToBottomButton();
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { By, until } from 'selenium-webdriver';
2+
3+
export default function scrollToBottomButtonVisible() {
4+
return until.elementLocated(By.css(`[role="log"] > button:last-child`));
5+
}

packages/bundle/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"clean": "npm run clean:dist && npm run clean:lib",
2525
"clean:dist": "rimraf dist",
2626
"clean:lib": "rimraf lib",
27-
"eslint": "eslint src/**/*.js src/**/*.ts --ignore-pattern *.spec.[jt]sx? --ignore-pattern *.test.[jt]sx?",
27+
"eslint": "eslint src/**/*.js src/**/*.ts --ignore-pattern *.spec.[jt]sx? --ignore-pattern *.test.[jt]sx? --ignore-pattern __tests__",
2828
"prepublishOnly": "npm run build:typecheck && npm run build:babel && webpack-cli",
2929
"watch": "concurrently --names \"babel,typecheck,webpack\" \"npm run watch:babel\" \"npm run watch:typecheck\" \"npm run watch:webpack\"",
3030
"watch:babel": "npm run build:babel-instrumented -- --watch",

packages/bundle/src/FullReactWebChat.js

Lines changed: 58 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,79 @@
1-
import * as adaptiveCards from 'adaptivecards';
1+
import * as defaultAdaptiveCardsPackage from 'adaptivecards';
22
import BasicWebChat, { concatMiddleware } from 'botframework-webchat-component';
3-
import memoize from 'memoize-one';
43
import PropTypes from 'prop-types';
5-
import React from 'react';
4+
import React, { useEffect, useMemo } from 'react';
65

6+
import AdaptiveCardsContext from './adaptiveCards/AdaptiveCardsContext';
77
import createAdaptiveCardsAttachmentMiddleware from './adaptiveCards/createAdaptiveCardMiddleware';
8+
import createDefaultAdaptiveCardHostConfig from './adaptiveCards/Styles/adaptiveCardHostConfig';
89
import createStyleSet from './adaptiveCards/Styles/createStyleSetWithAdaptiveCards';
9-
import defaultAdaptiveCardHostConfig from './adaptiveCards/Styles/adaptiveCardHostConfig';
1010
import defaultRenderMarkdown from './renderMarkdown';
1111

1212
// Add additional props to <WebChat>, so it support additional features
13-
class FullReactWebChat extends React.Component {
14-
constructor(props) {
15-
super(props);
13+
const FullReactWebChat = ({
14+
adaptiveCardHostConfig,
15+
adaptiveCardsHostConfig,
16+
adaptiveCardsPackage,
17+
attachmentMiddleware,
18+
renderMarkdown,
19+
styleOptions,
20+
styleSet,
21+
...otherProps
22+
}) => {
23+
useEffect(() => {
24+
adaptiveCardHostConfig &&
25+
console.warn(
26+
'Web Chat: "adaptiveCardHostConfig" is deprecated. Please use "adaptiveCardsHostConfig" instead. "adaptiveCardHostConfig" will be removed on or after 2022-01-01.'
27+
);
28+
}, [adaptiveCardHostConfig]);
1629

17-
this.createAttachmentMiddleware = memoize(
18-
(adaptiveCardHostConfig, middlewareFromProps, styleOptions, renderMarkdown) =>
19-
concatMiddleware(
20-
middlewareFromProps,
21-
createAdaptiveCardsAttachmentMiddleware({
22-
adaptiveCardHostConfig: adaptiveCardHostConfig || defaultAdaptiveCardHostConfig(styleOptions),
23-
adaptiveCards,
24-
renderMarkdown
25-
})
26-
)
27-
);
30+
const patchedStyleSet = useMemo(() => styleSet || createStyleSet(styleOptions), [styleOptions, styleSet]);
31+
const { options: patchedStyleOptions } = patchedStyleSet;
2832

29-
this.memoizeStyleSet = memoize((styleSet, styleOptions) => styleSet || createStyleSet(styleOptions));
30-
this.memoizeRenderMarkdown = memoize((renderMarkdown, { options }) => markdown =>
31-
renderMarkdown(markdown, options)
32-
);
33-
}
33+
const patchedAdaptiveCardsHostConfig = useMemo(
34+
() => adaptiveCardsHostConfig || adaptiveCardHostConfig || createDefaultAdaptiveCardHostConfig(patchedStyleOptions),
35+
[adaptiveCardHostConfig, adaptiveCardsHostConfig, patchedStyleOptions]
36+
);
3437

35-
render() {
36-
const {
37-
adaptiveCardHostConfig,
38-
attachmentMiddleware,
39-
renderMarkdown,
40-
styleOptions,
41-
styleSet,
42-
...otherProps
43-
} = this.props;
38+
const patchedAdaptiveCardsPackage = useMemo(() => adaptiveCardsPackage || defaultAdaptiveCardsPackage, [
39+
adaptiveCardsPackage
40+
]);
4441

45-
const memoizedStyleSet = this.memoizeStyleSet(styleSet, styleOptions);
46-
const memoizedRenderMarkdown =
47-
renderMarkdown || this.memoizeRenderMarkdown(defaultRenderMarkdown, memoizedStyleSet);
42+
const patchedRenderMarkdown = useMemo(
43+
() => renderMarkdown || (markdown => defaultRenderMarkdown(markdown, patchedStyleOptions)),
44+
[renderMarkdown, patchedStyleOptions]
45+
);
4846

49-
return (
47+
const patchedAttachmentMiddleware = useMemo(
48+
() => concatMiddleware(attachmentMiddleware, createAdaptiveCardsAttachmentMiddleware()),
49+
[attachmentMiddleware]
50+
);
51+
52+
const adaptiveCardsContext = useMemo(
53+
() => ({
54+
adaptiveCardsPackage: patchedAdaptiveCardsPackage,
55+
hostConfig: patchedAdaptiveCardsHostConfig
56+
}),
57+
[patchedAdaptiveCardsHostConfig, patchedAdaptiveCardsPackage]
58+
);
59+
60+
return (
61+
<AdaptiveCardsContext.Provider value={adaptiveCardsContext}>
5062
<BasicWebChat
51-
attachmentMiddleware={this.createAttachmentMiddleware(
52-
adaptiveCardHostConfig,
53-
attachmentMiddleware,
54-
memoizedStyleSet.options,
55-
memoizedRenderMarkdown
56-
)}
57-
renderMarkdown={memoizedRenderMarkdown}
63+
attachmentMiddleware={patchedAttachmentMiddleware}
64+
renderMarkdown={patchedRenderMarkdown}
5865
styleOptions={styleOptions}
59-
styleSet={memoizedStyleSet}
66+
styleSet={patchedStyleSet}
6067
{...otherProps}
6168
/>
62-
);
63-
}
64-
}
69+
</AdaptiveCardsContext.Provider>
70+
);
71+
};
6572

6673
FullReactWebChat.defaultProps = {
6774
adaptiveCardHostConfig: undefined,
75+
adaptiveCardsHostConfig: undefined,
76+
adaptiveCardsPackage: undefined,
6877
attachmentMiddleware: undefined,
6978
renderMarkdown: undefined,
7079
styleOptions: undefined,
@@ -73,6 +82,8 @@ FullReactWebChat.defaultProps = {
7382

7483
FullReactWebChat.propTypes = {
7584
adaptiveCardHostConfig: PropTypes.any,
85+
adaptiveCardsHostConfig: PropTypes.any,
86+
adaptiveCardsPackage: PropTypes.any,
7687
attachmentMiddleware: PropTypes.func,
7788
renderMarkdown: PropTypes.func,
7889
styleOptions: PropTypes.any,

0 commit comments

Comments
 (0)