diff --git a/CHANGELOG.md b/CHANGELOG.md
index ddf6de48e1..e903c43847 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [Unreleased]
+### Fixed
+
+- Improved performance for `useActivityWithRenderer`, in PR [#5172](https://github.com/microsoft/BotFramework-WebChat/pull/5172), by [@OEvgeny](https://github.com/OEvgeny)
+- Fixes [#5162](https://github.com/microsoft/BotFramework-WebChat/issues/5162). Improved performance for `useActivityTreeWithRenderer`, in PR [#5163](https://github.com/microsoft/BotFramework-WebChat/pull/5163), by [@compulim](https://github.com/compulim)
+
### Changed
- Bumped all dependencies to the latest versions, by [@compulim](https://github.com/compulim) in PR [#5174](https://github.com/microsoft/BotFramework-WebChat/pull/5174)
@@ -128,7 +133,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Fixes missing exports of `useNotifications`, in PR [#5148](https://github.com/microsoft/BotFramework-WebChat/pull/5148), by [@compulim](https://github.com/compulim)
- Fixes suggested actions keyboard navigation skips actions after suggested actions got updated, in PR [#5150](https://github.com/microsoft/BotFramework-WebChat/pull/5150), by [@OEvgeny](https://github.com/OEvgeny)
- Fixes [#5155](https://github.com/microsoft/BotFramework-WebChat/issues/5155). Fixed "Super constructor null of anonymous class is not a constructor" error in CDN bundle by bumping to [`webpack@5.91.0`](https://www.npmjs.com/package/webpack/v/5.91.0), in PR [#5156](https://github.com/microsoft/BotFramework-WebChat/pull/5156), by [@compulim](https://github.com/compulim)
-- Improved performance for `useActivityWithRenderer`, in PR [#5172](https://github.com/microsoft/BotFramework-WebChat/pull/5172), by [@OEvgeny](https://github.com/OEvgeny)
### Changed
diff --git a/__tests__/html/performance/README.md b/__tests__/html/performance/README.md
new file mode 100644
index 0000000000..d01855da90
--- /dev/null
+++ b/__tests__/html/performance/README.md
@@ -0,0 +1 @@
+Tests in this folder may not have its corresponding `.js` file as we are still exploring how to write tests for tracking performance issues.
diff --git a/__tests__/html/performance/manyMessages.batched.html b/__tests__/html/performance/manyMessages.batched.html
new file mode 100644
index 0000000000..0c60fc9602
--- /dev/null
+++ b/__tests__/html/performance/manyMessages.batched.html
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/__tests__/html/performance/manyMessages.oneByOne.html b/__tests__/html/performance/manyMessages.oneByOne.html
new file mode 100644
index 0000000000..707a7f458f
--- /dev/null
+++ b/__tests__/html/performance/manyMessages.oneByOne.html
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/component/src/providers/ActivityTree/private/useActivityTreeWithRenderer.ts b/packages/component/src/providers/ActivityTree/private/useActivityTreeWithRenderer.ts
index e2fc09d5ac..db0f8c2a02 100644
--- a/packages/component/src/providers/ActivityTree/private/useActivityTreeWithRenderer.ts
+++ b/packages/component/src/providers/ActivityTree/private/useActivityTreeWithRenderer.ts
@@ -1,6 +1,7 @@
import { hooks } from 'botframework-webchat-api';
import { useMemo } from 'react';
+import type { WebChatActivity } from 'botframework-webchat-core';
import intersectionOf from '../../../Utils/intersectionOf';
import removeInline from '../../../Utils/removeInline';
import type { ActivityWithRenderer, ReadonlyActivityTree } from './types';
@@ -35,6 +36,10 @@ function validateAllEntriesTagged(entries: readonly T[], bins: readonly (read
function useActivityTreeWithRenderer(entries: readonly ActivityWithRenderer[]): ReadonlyActivityTree {
const groupActivities = useGroupActivities();
+ const entryMap: Map = useMemo(
+ () => new Map(entries.map(entry => [entry.activity, entry])),
+ [entries]
+ );
// We bin activities in 2 different ways:
// - `activitiesBySender` is a 2D array containing activities with same sender
@@ -45,7 +50,7 @@ function useActivityTreeWithRenderer(entries: readonly ActivityWithRenderer[]):
entriesBySender: readonly (readonly ActivityWithRenderer[])[];
entriesByStatus: readonly (readonly ActivityWithRenderer[])[];
}>(() => {
- const visibleActivities = entries.map(({ activity }) => activity);
+ const visibleActivities = [...entryMap.keys()];
const groupActivitiesResult = groupActivities({ activities: visibleActivities });
@@ -53,7 +58,7 @@ function useActivityTreeWithRenderer(entries: readonly ActivityWithRenderer[]):
const activitiesByStatus = groupActivitiesResult?.status || [];
const [entriesBySender, entriesByStatus] = [activitiesBySender, activitiesByStatus].map(bins =>
- bins.map(bin => bin.map(activity => entries.find(entry => entry.activity === activity)))
+ bins.map(bin => bin.map(activity => entryMap.get(activity)))
);
if (!validateAllEntriesTagged(visibleActivities, activitiesBySender)) {
@@ -72,7 +77,7 @@ function useActivityTreeWithRenderer(entries: readonly ActivityWithRenderer[]):
entriesBySender,
entriesByStatus
};
- }, [entries, groupActivities]);
+ }, [entryMap, groupActivities]);
// Create a tree of activities with 2 dimensions: sender, followed by status.
diff --git a/packages/test/harness/src/host/dev/hostOverrides/done.js b/packages/test/harness/src/host/dev/hostOverrides/done.js
index eb9aa4646b..7751cf3384 100644
--- a/packages/test/harness/src/host/dev/hostOverrides/done.js
+++ b/packages/test/harness/src/host/dev/hostOverrides/done.js
@@ -1,14 +1,11 @@
// In dev mode, draw a green tick when test succeeded.
-const checkAccessibility = require('../../common/host/checkAccessibility');
const dumpLogs = require('../../common/dumpLogs');
const override = require('../utils/override');
// Send the completion back to the browser console.
module.exports = (webDriver, done) =>
override(done, undefined, async () => {
- await checkAccessibility(webDriver)();
-
/* istanbul ignore next */
await webDriver.executeScript(() => {
console.log(
diff --git a/packages/test/page-object/src/globals/testHelpers/createDirectLineEmulator.js b/packages/test/page-object/src/globals/testHelpers/createDirectLineEmulator.js
index cbbcf098d6..6e00de3e51 100644
--- a/packages/test/page-object/src/globals/testHelpers/createDirectLineEmulator.js
+++ b/packages/test/page-object/src/globals/testHelpers/createDirectLineEmulator.js
@@ -1,11 +1,11 @@
-import createDeferred from 'p-defer';
import Observable from 'core-js/features/observable';
import random from 'math-random';
+import createDeferred from 'p-defer';
import updateIn from 'simple-update-in';
-import { createStoreWithOptions } from './createStore';
-import became from '../pageConditions/became';
import createDeferredObservable from '../../utils/createDeferredObservable';
+import became from '../pageConditions/became';
+import { createStoreWithOptions } from './createStore';
import shareObservable from './shareObservable';
function isNativeClock() {
@@ -119,7 +119,7 @@ export default function createDirectLineEmulator({ autoConnect = true, ponyfill
};
},
emulateConnected: connectedDeferred.resolve,
- emulateIncomingActivity: async activity => {
+ emulateIncomingActivity: async (activity, { skipWait } = {}) => {
if (typeof activity === 'string') {
activity = {
from: { id: 'bot', role: 'bot' },
@@ -145,11 +145,12 @@ export default function createDirectLineEmulator({ autoConnect = true, ponyfill
activityDeferredObservable.next(activity);
- await became(
- 'incoming activity appears in the store',
- () => store.getState().activities.find(activity => activity.id === id),
- 1000
- );
+ skipWait ||
+ (await became(
+ 'incoming activity appears in the store',
+ () => store.getState().activities.find(activity => activity.id === id),
+ 1000
+ ));
},
emulateOutgoingActivity: (activity, options) => {
if (typeof activity === 'string') {