Skip to content

Commit d6ba639

Browse files
compulimOEvgeny
andauthored
[Debug API] Add debugging capability (#5663)
* Initial commit * Refactor into files * Split internal and external native API * Rename * Remove ESLint * Simple hideKnownError * Simplify breakpoint and add debugger() * Make debugger a property * Refactor to Debug API * Remove obsoleted test * Rename to debug API * Add entry * Add debug API to ActivityRow * Simplify debug API * Add playground * Allow arguments * Fix Prettier * Fix precommit * Add tests for lockdown * Remove comment * Remove export of RootDebugAPI type * Remove no use before define rule * Add doc * Add footnote * Clean up * Clean up * Clean up public debug API * Secure breakpoint names * Fix types * Fix types * Fix types * Update screenshot * Update screenshot * Fix flakiness * Link to DEBUGGING.md * Fix path * Typo * Update docs/DEBUGGING.md Co-authored-by: Eugene <EOlonov@gmail.com> * Use microtask * Use Object.create(null) * Softer tone * Remove unused Infer* types * Rename privateDebugAPI to restrictedDebugAPI * Rename * Rename * Remove factory function, use class constructor directly * Add Object.freeze() * Rename to StoreDebugAPI, remove React context, use Registry WeakMap * Fix test --------- Co-authored-by: Eugene <EOlonov@gmail.com>
1 parent 94ad4f5 commit d6ba639

50 files changed

Lines changed: 995 additions & 85 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,9 @@ Breaking changes in this release:
143143
- Added loading animation for `copilot`, and `fluent` variants
144144
- New JSON-LD graph backend, by [@compulim](https://github.com/compulim) in PR [#5622](https://github.com/microsoft/BotFramework-WebChat/pull/5622)
145145
- Cleanup, by [@compulim](https://github.com/compulim) in PR [#5657](https://github.com/microsoft/BotFramework-WebChat/pull/5657)
146+
- New debug API, by [@compulim](https://github.com/compulim) in PR [#5663](https://github.com/microsoft/BotFramework-WebChat/pull/5663), see [`DEBUGGING.md`](docs/DEBUGGING.md) for more
147+
- Debug into element: open <kbd>F12</kbd>, select the subject in Element pane, type `$0.webChat.debugger`
148+
- Breakpoint: open <kbd>F12</kbd>, select the subject in Element pane, type `$0.webChat.breakpoint.incomingActivity`
146149

147150
### Changed
148151

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<!doctype html>
2+
<html lang="en-US">
3+
<head>
4+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
5+
</head>
6+
<body>
7+
<main id="webchat"></main>
8+
<script type="importmap">
9+
{
10+
"imports": {
11+
"botframework-webchat": "/__dist__/packages/bundle/static/botframework-webchat.js",
12+
"react": "/__dist__/packages/bundle/static/react.js",
13+
"react-dom": "/__dist__/packages/bundle/static/react-dom.js"
14+
}
15+
}
16+
</script>
17+
<script type="module">
18+
import '/test-harness.mjs';
19+
import '/test-page-object.mjs';
20+
21+
import { createStoreWithOptions, renderWebChat, testIds } from 'botframework-webchat';
22+
import { fn, spyOn } from 'https://esm.sh/jest-mock';
23+
import { waitFor } from 'https://esm.sh/@testduet/wait-for';
24+
25+
const {
26+
testHelpers: { createDirectLineEmulator }
27+
} = window;
28+
29+
testHelpers.hideKnownError();
30+
31+
// TODO: Should find ways to eliminate this line.
32+
window.WebChat = { createStoreWithOptions, testIds };
33+
34+
run(async function () {
35+
const { directLine, store } = createDirectLineEmulator();
36+
37+
renderWebChat({ directLine, role: 'complementary', store }, document.getElementById('webchat'));
38+
39+
await pageConditions.uiConnected();
40+
41+
await directLine.emulateIncomingActivity({
42+
id: 'a-00001',
43+
text: 'Hello, World!',
44+
type: 'message'
45+
});
46+
47+
await pageConditions.numActivitiesShown(1);
48+
49+
const [activityRowElement] = pageElements.activities();
50+
51+
// THEN: Make sure we are exposing the public version.
52+
expect('breakpoint' in activityRowElement.webChat).toBe(true);
53+
expect('debugger' in activityRowElement.webChat).toBe(true);
54+
55+
// THEN: Make sure we are not exposing the private version.
56+
expect('~types' in activityRowElement.webChat).toBe(false);
57+
expect('toPublic' in activityRowElement.webChat).toBe(false);
58+
expect('UNSAFE_callBreakpoint' in activityRowElement.webChat).toBe(false);
59+
60+
console.log(activityRowElement.webChat);
61+
62+
spyOn(activityRowElement.webChat.breakpoint, 'render');
63+
64+
// WHEN: Activity is updated to "Aloha!"
65+
await directLine.emulateIncomingActivity({
66+
from: {
67+
id: 'bot',
68+
role: 'bot'
69+
},
70+
id: 'a-00001',
71+
text: 'Aloha!',
72+
type: 'message'
73+
});
74+
75+
expect(activityRowElement.webChat.breakpoint.render).toHaveBeenCalledTimes(1);
76+
77+
expect(activityRowElement.webChat.breakpoint.render).toHaveBeenNthCalledWith(1, {
78+
activity: {
79+
from: {
80+
id: 'bot',
81+
role: 'bot'
82+
},
83+
id: expect.any(String),
84+
text: 'Aloha!',
85+
timestamp: expect.any(String),
86+
type: 'message',
87+
channelData: {
88+
'webchat:internal:received-at': expect.any(Number),
89+
'webchat:internal:local-id': expect.any(String),
90+
'webchat:internal:position': 1000
91+
}
92+
}
93+
});
94+
95+
activityRowElement.webChat.breakpoint.render.mockRestore();
96+
});
97+
</script>
98+
</body>
99+
</html>
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<!doctype html>
2+
<html lang="en-US">
3+
<head>
4+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
5+
</head>
6+
<body>
7+
<main id="webchat"></main>
8+
<script type="importmap">
9+
{
10+
"imports": {
11+
"botframework-webchat": "/__dist__/packages/bundle/static/botframework-webchat.js",
12+
"react": "/__dist__/packages/bundle/static/react.js",
13+
"react-dom": "/__dist__/packages/bundle/static/react-dom.js"
14+
}
15+
}
16+
</script>
17+
<script type="module">
18+
import '/test-harness.mjs';
19+
import '/test-page-object.mjs';
20+
21+
import { createStoreWithOptions, renderWebChat, testIds } from 'botframework-webchat';
22+
import { fn, spyOn } from 'https://esm.sh/jest-mock';
23+
import { waitFor } from 'https://esm.sh/@testduet/wait-for';
24+
25+
const {
26+
testHelpers: { createDirectLineEmulator }
27+
} = window;
28+
29+
testHelpers.hideKnownError();
30+
31+
// TODO: Should find ways to eliminate this line.
32+
window.WebChat = { createStoreWithOptions, testIds };
33+
34+
run(async function () {
35+
const { directLine, store } = createDirectLineEmulator();
36+
37+
renderWebChat({ directLine, role: 'complementary', store }, document.getElementById('webchat'));
38+
39+
await pageConditions.uiConnected();
40+
41+
const rootElement = document.querySelector('#webchat > *');
42+
43+
// THEN: Make sure we are exposing the public version.
44+
expect('breakpoint' in rootElement.webChat).toBe(true);
45+
expect('debugger' in rootElement.webChat).toBe(true);
46+
47+
// THEN: Make sure we are not exposing the private version.
48+
expect('~types' in rootElement.webChat).toBe(false);
49+
expect('toPublic' in rootElement.webChat).toBe(false);
50+
expect('UNSAFE_callBreakpoint' in rootElement.webChat).toBe(false);
51+
expect('UNSAFE_extendsDebugContextOnce' in rootElement.webChat).toBe(false);
52+
53+
spyOn(rootElement.webChat.breakpoint, 'incomingActivity');
54+
55+
await directLine.emulateIncomingActivity('Hello, World!');
56+
57+
expect(rootElement.webChat.breakpoint.incomingActivity).toHaveBeenCalledTimes(1);
58+
59+
expect(rootElement.webChat.breakpoint.incomingActivity).toHaveBeenNthCalledWith(
60+
1,
61+
{
62+
activities: [
63+
{
64+
channelData: {
65+
'webchat:internal:received-at': expect.any(Number),
66+
'webchat:internal:local-id': expect.any(String),
67+
'webchat:internal:position': 1000
68+
},
69+
from: {
70+
id: 'bot',
71+
role: 'bot'
72+
},
73+
id: expect.any(String),
74+
text: 'Hello, World!',
75+
timestamp: expect.any(String),
76+
type: 'message'
77+
}
78+
]
79+
},
80+
{
81+
activity: {
82+
channelData: {
83+
'webchat:internal:received-at': expect.any(Number),
84+
'webchat:internal:local-id': expect.any(String)
85+
},
86+
from: {
87+
id: 'bot',
88+
role: 'bot'
89+
},
90+
id: expect.any(String),
91+
text: 'Hello, World!',
92+
timestamp: expect.any(String),
93+
type: 'message'
94+
}
95+
}
96+
);
97+
98+
rootElement.webChat.breakpoint.incomingActivity.mockRestore();
99+
});
100+
</script>
101+
</body>
102+
</html>
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<!doctype html>
2+
<html lang="en-US">
3+
<head>
4+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
5+
</head>
6+
<body>
7+
<main id="webchat"></main>
8+
<script type="importmap">
9+
{
10+
"imports": {
11+
"botframework-webchat": "/__dist__/packages/bundle/static/botframework-webchat.js",
12+
"react": "/__dist__/packages/bundle/static/react.js",
13+
"react-dom": "/__dist__/packages/bundle/static/react-dom.js"
14+
}
15+
}
16+
</script>
17+
<script type="module">
18+
import '/test-harness.mjs';
19+
import '/test-page-object.mjs';
20+
21+
import { createStoreWithOptions, renderWebChat, testIds } from 'botframework-webchat';
22+
import { fn, spyOn } from 'https://esm.sh/jest-mock';
23+
import { waitFor } from 'https://esm.sh/@testduet/wait-for';
24+
25+
const {
26+
testHelpers: { createDirectLineEmulator }
27+
} = window;
28+
29+
testHelpers.hideKnownError();
30+
31+
// TODO: Should find ways to eliminate this line.
32+
window.WebChat = { createStoreWithOptions, testIds };
33+
34+
run(async function () {
35+
const { directLine, store } = createDirectLineEmulator();
36+
37+
renderWebChat({ directLine, role: 'complementary', store }, document.getElementById('webchat'));
38+
39+
await pageConditions.uiConnected();
40+
41+
await directLine.emulateIncomingActivity({
42+
attachments: [
43+
{
44+
content: {
45+
type: 'AdaptiveCard',
46+
body: [
47+
{
48+
type: 'TextBlock',
49+
text: 'You can choose one of the followings.'
50+
}
51+
],
52+
$schema: 'http://adaptivecards.io/schemas/adaptive-card.json',
53+
version: '1.2',
54+
actions: [
55+
{
56+
type: 'Action.Submit',
57+
title: 'What time is it?'
58+
},
59+
{
60+
type: 'Action.Submit',
61+
title: 'What is the weather?'
62+
}
63+
]
64+
},
65+
contentType: 'application/vnd.microsoft.card.adaptive'
66+
}
67+
],
68+
from: { id: 'bot', role: 'bot' },
69+
text: 'What can I do for you?',
70+
type: 'message'
71+
});
72+
73+
await pageConditions.numActivitiesShown(1);
74+
});
75+
</script>
76+
</body>
77+
</html>
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<!doctype html>
2+
<html lang="en-US">
3+
<head>
4+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
5+
</head>
6+
<body>
7+
<main id="webchat"></main>
8+
<script type="importmap">
9+
{
10+
"imports": {
11+
"botframework-webchat": "/__dist__/packages/bundle/static/botframework-webchat.js",
12+
"react": "/__dist__/packages/bundle/static/react.js",
13+
"react-dom": "/__dist__/packages/bundle/static/react-dom.js"
14+
}
15+
}
16+
</script>
17+
<script type="module">
18+
import '/test-harness.mjs';
19+
import '/test-page-object.mjs';
20+
21+
import { createStoreWithOptions, renderWebChat, testIds } from 'botframework-webchat';
22+
import { fn, spyOn } from 'https://esm.sh/jest-mock';
23+
import { waitFor } from 'https://esm.sh/@testduet/wait-for';
24+
25+
const {
26+
testHelpers: { createDirectLineEmulator }
27+
} = window;
28+
29+
testHelpers.hideKnownError();
30+
31+
// TODO: Should find ways to eliminate this line.
32+
window.WebChat = { createStoreWithOptions, testIds };
33+
34+
run(async function () {
35+
const { directLine, store } = createDirectLineEmulator();
36+
37+
renderWebChat({ directLine, role: 'complementary', store }, document.getElementById('webchat'));
38+
39+
await pageConditions.uiConnected();
40+
41+
const rootElement = document.querySelector('#webchat > *');
42+
43+
const originalDebugger = rootElement.webChat.debugger;
44+
const originalIncomingActivity = rootElement.webChat.breakpoint.incomingActivity;
45+
46+
// Scenario 1.
47+
// If locked down, should fail with:
48+
// "Cannot assign to read only property 'incomingActivity' of object '#<Object>'"
49+
const scenario1 = () => spyOn(rootElement.webChat.breakpoint, 'incomingActivity');
50+
51+
expect(scenario1).toThrow("Cannot assign to read only property 'incomingActivity' of object '[object Object]'");
52+
expect(rootElement.webChat.breakpoint.incomingActivity).toBe(originalIncomingActivity);
53+
54+
// Scenario 2.
55+
// If locked down, the breakpoint object should be frozen.
56+
// There are not errors thrown, the value is kept the same.
57+
const scenario2 = () => {
58+
rootElement.webChat = { breakpoint: { incomingActivity: 123 } };
59+
};
60+
61+
expect(typeof rootElement.webChat.breakpoint.incomingActivity).not.toBe(123);
62+
expect(rootElement.webChat.breakpoint.incomingActivity).toBe(originalIncomingActivity);
63+
64+
// Scenario 3.
65+
// If locked down, the debugger object should be frozen.
66+
const scenario3 = () => {
67+
Object.defineProperty(rootElement.webChat, 'debugger', {
68+
get() {
69+
return 123;
70+
}
71+
});
72+
};
73+
74+
expect(scenario3).toThrow('Cannot define property debugger, object is not extensible');
75+
expect(rootElement.webChat.debugger).toBe(originalDebugger);
76+
});
77+
</script>
78+
</body>
79+
</html>

__tests__/html2/hooks/useSendFiles.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
sendFiles([blob1, blob2]);
5454

5555
await pageConditions.numActivitiesShown(2);
56+
await pageConditions.allOutgoingActivitiesSent();
5657

5758
await host.snapshot('local');
5859
},

__tests__/html2/hooks/useSendMessage.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
sendMessage('Hello, World!');
4848

4949
await pageConditions.numActivitiesShown(2);
50+
await pageConditions.allOutgoingActivitiesSent();
5051

5152
await host.snapshot('local');
5253
},

__tests__/html2/hooks/useSendMessageBack.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
sendMessageBack({ hello: 'World!' }, 'Aloha!', 'Display text');
4848

4949
await pageConditions.numActivitiesShown(2);
50+
await pageConditions.allOutgoingActivitiesSent();
5051

5152
await host.snapshot('local');
5253
},

0 commit comments

Comments
 (0)