Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
8b0c9f4
Add sorting upsert algorithm
compulim Nov 18, 2025
314441f
With webchat:internal:position
compulim Nov 18, 2025
19fcfed
Update comment
compulim Nov 18, 2025
4195a6b
Redux store backcompat
compulim Nov 19, 2025
72197a2
Add given-when-then
compulim Nov 19, 2025
e605f9c
Allow HowTo without position
compulim Nov 19, 2025
a1a504a
Upsert livestream activity based on sequence number
compulim Nov 19, 2025
aec404e
Should not move livestream if timestamp is undefined
compulim Nov 19, 2025
75d8287
Fix tests
compulim Nov 19, 2025
fc072e6
Clean up Object.freeze()
compulim Nov 19, 2025
c88d8ee
Fix path
compulim Nov 19, 2025
ef734e8
Allow reuse position
compulim Nov 19, 2025
b6ea346
FIx update activity channel data
compulim Nov 20, 2025
67cbec5
No reorder when concluding livestream does not have timestamp
compulim Nov 20, 2025
821e623
Upsert as-is if no HowTo.position
compulim Nov 20, 2025
c18febb
Always reuse position including livestreaming
compulim Nov 20, 2025
720afbf
Fix @id
compulim Nov 20, 2025
d2e9dc7
Fix typing indicator test with duplicate activity ID
compulim Nov 20, 2025
826890c
Fix typing indicator scrolling test with duplicate activity ID
compulim Nov 20, 2025
8df5f83
Fix typing indicator test with duplicate livestream activity ID
compulim Nov 20, 2025
283303d
Fix collapsible test have bad livestreaming metadata
compulim Nov 20, 2025
0987656
Remove CoT
compulim Nov 20, 2025
8790431
Skip activity for finalized livestream
compulim Nov 20, 2025
fb343fe
Update position for plain activity
compulim Nov 20, 2025
f5ee565
Remove timestamp: undefined
compulim Nov 20, 2025
3d591c3
Update timestamp algorithm
compulim Nov 20, 2025
7854dc6
Sort by logical timestamp, followed by local timestamp
compulim Nov 20, 2025
84449fd
Fix test: livestream finalize should move it to bottom
compulim Nov 21, 2025
184441c
Honor timestamp on finalizing livestream activity
compulim Nov 21, 2025
7d96a42
Fix dupe ID
compulim Nov 21, 2025
92ce42b
Add a clock tick before sending another message
compulim Nov 21, 2025
d72be2a
Patch all activities with internal channel data and fix typing entrie…
compulim Nov 21, 2025
bf7b117
Clean up
compulim Nov 21, 2025
de2a60f
Update snapshots
compulim Nov 21, 2025
c3c166b
Fix test: finalizing livestream will update timestamp
compulim Nov 21, 2025
e8f863b
Add tick between outgoing activities
compulim Nov 21, 2025
36f6016
Fix unit test
compulim Nov 21, 2025
2312c42
Fix typings
compulim Nov 21, 2025
0290e6e
Rename perma ID to local ID
compulim Nov 21, 2025
2b3ddb3
Add comment
compulim Nov 21, 2025
a5053a0
Fix test: part grouping will always have updated timestamp
compulim Nov 21, 2025
72f1db4
Rename permaId to localId
compulim Nov 21, 2025
dfe3033
Fix test: part grouping should use max timestamp
compulim Nov 21, 2025
079a67d
Initial delete activity
compulim Nov 21, 2025
e295020
Rename activityInternalId to activityLocalId
compulim Nov 21, 2025
033bd37
Add clean up after delete
compulim Nov 21, 2025
9d4b846
Delete livestream activities
compulim Nov 21, 2025
32c0395
Delete activities in part grouping with livestream
compulim Nov 21, 2025
b3af866
Reorganize tests
compulim Nov 21, 2025
825dd23
Wire up delete activity
compulim Nov 21, 2025
0505a64
Add clientActivityIdToLocalId index
compulim Nov 21, 2025
222af7b
Update doc
compulim Nov 21, 2025
c08681c
Fix ESLint
compulim Nov 21, 2025
edb9e01
Add *.spec.* and *.test.*
compulim Nov 21, 2025
290787c
Revert
compulim Nov 21, 2025
c84d2b2
Clean up comment
compulim Nov 21, 2025
ac2abfc
Use standard way to parse @id
compulim Nov 21, 2025
7a5ed2a
Remove early return
compulim Nov 21, 2025
9d94d3d
Add multiple part grouping test
compulim Nov 21, 2025
36971af
Remove unnecessary findBeforeAfter
compulim Nov 21, 2025
331b829
Typo
compulim Nov 21, 2025
172dcaa
Update doc
compulim Nov 21, 2025
12d42af
Fix part grouping ID
compulim Nov 21, 2025
f69df40
Update test: position to undefined should be unmoved
compulim Nov 21, 2025
9308ec3
Refactor property types
compulim Nov 21, 2025
c448b87
Use property functions
compulim Nov 21, 2025
287afdf
Fix part ID
compulim Nov 21, 2025
fee17b6
Local ID is blank node identifier
compulim Nov 22, 2025
f907902
Use node identifier schema for local ID
compulim Nov 22, 2025
3fdd28c
Fix import path
compulim Nov 22, 2025
e9416a7
Fix local ID
compulim Nov 22, 2025
003a9b9
Clean up
compulim Nov 22, 2025
95dcdc5
Add typing to Map
compulim Nov 22, 2025
ec9888a
Clean up
compulim Nov 22, 2025
24f81e6
Clean up with generateLocalIdInActivity
compulim Nov 22, 2025
5c4cd4a
Remove unused BlankNodeIdentifier
compulim Nov 22, 2025
068f16b
Clean up
compulim Nov 22, 2025
1ea2b1c
Use queryLocalIdFromActivity for generateLocalIdInActivity
compulim Nov 22, 2025
74cc311
Patch activity on POST_ACTIVITY_PENDING
compulim Nov 24, 2025
c12afba
Update PR number
compulim Nov 25, 2025
b58b263
Incorporate PR changes
compulim Nov 25, 2025
37ce80f
Add related read
compulim Nov 25, 2025
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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ Notes: web developers are advised to use [`~` (tilde range)](https://github.com/
- Fixed citation links are not properly matched against markdown links, in PR [#5614](https://github.com/microsoft/BotFramework-WebChat/pull/5614), by [@OEvgeny](https://github.com/OEvgeny)
- Fixed `botframework-webchat/decorator` import in legacy CommonJS environments, in [#5616](https://github.com/microsoft/BotFramework-WebChat/pull/5616), by [@OEvgeny](https://github.com/OEvgeny)
- Fixed `npm start` for efficiency and reliability, in PR [#5621](https://github.com/microsoft/BotFramework-WebChat/pull/5621) and [#5629](https://github.com/microsoft/BotFramework-WebChat/pull/5629), by [@compulim](https://github.com/compulim)
- Fixed activity sorting introduced in PR [#5622](https://github.com/microsoft/BotFramework-WebChat/pull/5622), part grouping, and livestreaming, by [@compulim](https://github.com/compulim) in PR [#XXX](https://github.com/microsoft/BotFramework-WebChat/pull/XXX)
- Fixed activity sorting introduced in PR [#5622](https://github.com/microsoft/BotFramework-WebChat/pull/5622), part grouping, and livestreaming, by [@compulim](https://github.com/compulim) in PR [#5635](https://github.com/microsoft/BotFramework-WebChat/pull/5635)

### Removed

Expand Down
82 changes: 40 additions & 42 deletions __tests__/html2/activity/collapsible.html

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
)
).resolveAll();

clock.tick(1000);

const { resolveAll: resolveAll1 } = await directLine.emulateOutgoingActivity(
'Elit adipisicing laborum sit anim.'
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
await sendMessage1.echoBack();
sendMessage1.resolvePostActivity();

clock.tick(1000);

const sendMessage2 = await directLine.emulateOutgoingActivity('A retry prompt must show on this activity.');

clock.tick(1000);
Expand All @@ -61,6 +63,8 @@
await sendMessage3.echoBack();
sendMessage3.resolvePostActivity();

clock.tick(1000);

const sendMessage4 = await directLine.emulateOutgoingActivity(
'The timestamp is shown because the next activity is not sent. When it is sent, the timestamp will be hidden.'
);
Expand Down
108 changes: 108 additions & 0 deletions __tests__/html2/activityOrdering/livestreamWithMovingTimestamp.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<!doctype html>
<html lang="en-US">
<head>
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
</head>
<body>
<main id="webchat"></main>
<script type="importmap">
{
"imports": {
"botframework-webchat": "/__dist__/packages/bundle/static/botframework-webchat.js",
"react": "/__dist__/packages/bundle/static/react.js",
"react-dom": "/__dist__/packages/bundle/static/react-dom.js"
}
}
</script>
<script type="module">
import '/test-harness.mjs';
import '/test-page-object.mjs';

import { createDirectLine, createStoreWithOptions, renderWebChat } from 'botframework-webchat';
import { version } from 'react';

run(async function () {
const {
testHelpers: { createDirectLineEmulator }
} = window;

// TODO: This is for `createDirectLineEmulator` only, should find ways to eliminate this line.
window.WebChat = { createStoreWithOptions };

const { directLine, store } = createDirectLineEmulator();

renderWebChat({ directLine, store }, document.getElementById('webchat'));

await pageConditions.uiConnected();

await directLine.emulateIncomingActivity({
from: { role: 'bot' },
id: 'a-00001',
text: 'Hello, World!',
timestamp: new Date(86_400_000).toISOString()
});

await directLine.emulateIncomingActivity({
channelData: {
streamSequence: 1,
streamType: 'streaming'
},
from: { role: 'bot' },
id: 'a-00002',
text: '`t=undefined`\n\nA quick',
timestamp: undefined,
type: 'typing'
});

expect(pageElements.activityContents().map(({ textContent }) => textContent)).toEqual([
'Hello, World!',
't=undefined\nA quick'
]);

// THEN: Screenshot should show no timestamp below the second message.
await host.snapshot('local');

await directLine.emulateIncomingActivity({
channelData: {
streamId: 'a-00002',
streamSequence: 2,
streamType: 'streaming'
},
from: { role: 'bot' },
id: 'a-00003',
text: '`t=0`\n\nA quick brown fox',
timestamp: new Date(0).toISOString(),
type: 'typing'
});

expect(pageElements.activityContents().map(({ textContent }) => textContent)).toEqual([
'Hello, World!',
't=0\nA quick brown fox'
]);

// THEN: Should show the livestream message on second position as it should not be moved.
await host.snapshot('local');

await directLine.emulateIncomingActivity({
channelData: {
streamId: 'a-00002',
streamType: 'final'
},
from: { role: 'bot' },
id: 'a-00004',
text: '`t=0`\n\nA quick brown fox jumped over the lazy dogs.',
timestamp: new Date(0).toISOString(),
type: 'message'
});

// THEN: Should have the livestream message as the first one.
expect(pageElements.activityContents().map(({ textContent }) => textContent)).toEqual([
't=0\nA quick brown fox jumped over the lazy dogs.',
'Hello, World!'
]);

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.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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 @@ -42,7 +42,7 @@
'@id': '',
'@type': 'Message',
type: 'https://schema.org/Message',
isPartOf: { '@id': 'c-00001', '@type': 'HowTo' },
isPartOf: { '@id': '_:c-00001', '@type': 'HowTo' },
position: 1
}
],
Expand Down Expand Up @@ -83,7 +83,7 @@
'@id': '',
'@type': 'Message',
type: 'https://schema.org/Message',
isPartOf: { '@id': 'c-00001', '@type': 'HowTo' },
isPartOf: { '@id': '_:c-00001', '@type': 'HowTo' },
position: 2
}
],
Expand All @@ -95,9 +95,9 @@
});

expect(pageElements.activityContents().map(({ textContent }) => textContent)).toEqual([
'a-00002: Hello, World at t = 1',
'a-00001: Chain 1 thought 1 at t = 0',
'a-00003: Chain 1 thought 2 at t = 2',
'a-00002: Hello, World at t = 1'
'a-00003: Chain 1 thought 2 at t = 2'
]);

await host.snapshot('local');
Expand All @@ -109,7 +109,7 @@
'@id': '',
'@type': 'Message',
type: 'https://schema.org/Message',
isPartOf: { '@id': 'c-00002', '@type': 'HowTo' },
isPartOf: { '@id': '_:c-00002', '@type': 'HowTo' },
position: 1
}
],
Expand All @@ -121,9 +121,9 @@
});

expect(pageElements.activityContents().map(({ textContent }) => textContent)).toEqual([
'a-00002: Hello, World at t = 1',
'a-00001: Chain 1 thought 1 at t = 0',
'a-00003: Chain 1 thought 2 at t = 2',
'a-00002: Hello, World at t = 1',
'a-00004: Chain 2 thought 1 at t = 3'
]);

Expand All @@ -136,23 +136,24 @@
'@id': '',
'@type': 'Message',
type: 'https://schema.org/Message',
isPartOf: { '@id': 'c-00002', '@type': 'HowTo' },
isPartOf: { '@id': '_:c-00002', '@type': 'HowTo' },
position: 2
}
],
from: { role: 'bot' },
id: 'a-00005',
text: 'a-00005: Chain 2 thought 2 at t = 0',
// Intentionally roll back the date.
// It should be grouped and not appear before "Hello, World!'
// It should be grouped but not appear before "Hello, World!"
// This is because the part grouping timestamp is max of all parts, i.e. 3 * 86_400_000.
timestamp: new Date(0).toISOString(),
type: 'message'
});

expect(pageElements.activityContents().map(({ textContent }) => textContent)).toEqual([
'a-00002: Hello, World at t = 1',
'a-00001: Chain 1 thought 1 at t = 0',
'a-00003: Chain 1 thought 2 at t = 2',
'a-00002: Hello, World at t = 1',
'a-00004: Chain 2 thought 1 at t = 3',
'a-00005: Chain 2 thought 2 at t = 0'
]);
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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,156 @@
<!doctype html>
<html lang="en-US">
<head>
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
</head>
<body>
<main id="webchat"></main>
<script type="importmap">
{
"imports": {
"botframework-webchat": "/__dist__/packages/bundle/static/botframework-webchat.js",
"jest-mock": "https://esm.sh/jest-mock",
"react": "/__dist__/packages/bundle/static/react.js",
"react-dom": "/__dist__/packages/bundle/static/react-dom.js"
}
}
</script>
<script type="module">
import '/test-harness.mjs';
import '/test-page-object.mjs';
import { fn, spyOn } from 'jest-mock';

import { createDirectLine, createStoreWithOptions, renderWebChat } from 'botframework-webchat';
import { version } from 'react';

const IGNORING_KEYWORDS = [
'Cannot update a component',
'Support for defaultProps',
'ReactDOM.render is no longer supported',
'To locate the bad setState()',
'has been deprecated, use `import {'
];

const error = console.error.bind(console);

spyOn(console, 'error').mockImplementation((...args) => {
const [message] = args;

if (typeof message === 'string' && IGNORING_KEYWORDS.some(keywords => message.includes(keywords))) {
return;
}

error(...args);
});

run(async function () {
const {
testHelpers: { createDirectLineEmulator }
} = window;

// TODO: This is for `createDirectLineEmulator` only, should find ways to eliminate this line.
window.WebChat = { createStoreWithOptions };

const { directLine, store } = createDirectLineEmulator();

renderWebChat({ directLine, store }, document.getElementById('webchat'));

await pageConditions.uiConnected();

await directLine.emulateIncomingActivity({
channelData: {
streamSequence: 1,
streamType: 'streaming'
},
entities: [
{
'@context': 'https://schema.org',
'@id': '',
'@type': 'Message',
type: 'https://schema.org/Message',
isPartOf: { '@id': '_:c-00001', '@type': 'HowTo' }
}
],
from: { role: 'bot' },
id: 'a-00001',
text: 'Task 1 at t=0',
timestamp: new Date(0).toISOString(),
type: 'typing'
});

expect(pageElements.activityContents().map(({ textContent }) => textContent)).toEqual(['Task 1 at t=0']);

await host.snapshot('local');

await directLine.emulateIncomingActivity({
channelData: {
streamId: 'a-00001',
streamSequence: 2,
streamType: 'streaming'
},
entities: [
{
'@context': 'https://schema.org',
'@id': '',
'@type': 'Message',
type: 'https://schema.org/Message',
isPartOf: { '@id': '_:c-00001', '@type': 'HowTo' }
}
],
from: { role: 'bot' },
id: 'a-00002',
text: 'Task 1 at t=10',
timestamp: new Date(10).toISOString(),
type: 'typing'
});

expect(pageElements.activityContents().map(({ textContent }) => textContent)).toEqual(['Task 1 at t=10']);

await host.snapshot('local');

await directLine.emulateIncomingActivity({
entities: [
{
'@context': 'https://schema.org',
'@id': '',
'@type': 'Message',
type: 'https://schema.org/Message',
isPartOf: { '@id': '_:c-00001', '@type': 'HowTo' }
}
],
from: { role: 'bot' },
id: 'a-00003',
text: 'Task 2 at t=20',
timestamp: new Date(20).toISOString(),
type: 'message'
});

expect(pageElements.activityContents().map(({ textContent }) => textContent)).toEqual([
'Task 1 at t=10',
'Task 2 at t=20'
]);

await host.snapshot('local');

await directLine.emulateIncomingActivity({
from: { role: 'bot' },
id: 'a-00004',
text: 'Hello, World at t=15',
timestamp: new Date(15).toISOString()
});

// Part grouping timestamp is t=20 (max of all parts.)
// "Hello, World" is t=15.
// Thus, "Hello, World" appear before part grouping.

expect(pageElements.activityContents().map(({ textContent }) => textContent)).toEqual([
'Hello, World at t=15',
'Task 1 at t=10',
'Task 2 at t=20'
]);

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.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/html2/livestream/activityOrder.entity.html.snap-5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading