Skip to content

Commit d358d0e

Browse files
Merge branch 'main' into directline-null-check-capabilities
2 parents c823861 + 6b157f1 commit d358d0e

18 files changed

Lines changed: 732 additions & 271 deletions

File tree

.github/CONTRIBUTING.md

Lines changed: 111 additions & 255 deletions
Large diffs are not rendered by default.

.github/CONTRIBUTING.old.md

Lines changed: 323 additions & 0 deletions
Large diffs are not rendered by default.

AGENTS.md

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# AGENTS.md for Bot Framework Web Chat
2+
3+
## Coding
4+
5+
### General
6+
7+
- Unless stated otherwise, avoid Node.js
8+
- Apply our latest coding style to every file changed
9+
- Avoid spaghetti code: on new feature with a similar existing feature, refactor existing one before writing new feature
10+
- This does not applies to test code
11+
- Avoid global pollution, do not modify `window` object
12+
- Markdown are always space-indented with 3 spaces
13+
- Use `/prettierrc.yml` to format
14+
- Sort names in property bags
15+
- Avoid one-use variable, inline them instead
16+
- More new lines to separate code with different responsibilities
17+
- Multiple lines of code that can run in random order, put them into a group, no need new line between them
18+
- Otherwise, lines of code that depends on each other in a specific order, separate them with new line
19+
- Prefer `&&` over if-statement if it is a oneliner
20+
- Importing relative file should always include file extension
21+
- Prefer truthy/false check like `!array.length` over `array.length === 0`
22+
- Prefer `numValue` over `valueCount` for "how many values are there"
23+
- Prefer uppercase for acronyms instead of Pascal case, e.g. `getURL()` over `getUrl()`
24+
- The only exception is `id`, e.g. `getId()` over `getID()`
25+
- Use fewer shorthands, only allow `min`, `max`, `num`
26+
27+
### Design
28+
29+
- Use immutability as much as possible, add `object.freeze()`
30+
- Follow W3C convention for API design
31+
- Extends `EventTarget` when eventing is needed, do not use Node.js `on`/`off`
32+
- Consider `IntersectionObserver`-like pattern for allow userland to subscribe to live changes
33+
- Allow ESNext code that is polyfilled by `core-js-pure` or `iter-fest` package
34+
35+
### Typing
36+
37+
- TypeScript is best-effort checking, use `valibot` for strict type checking
38+
- Use `valibot` for runtime type checker, never use `zod`
39+
- Assume all externally exported functions will receive unsafe/invalid input, always check with `valibot`
40+
- Avoid `any`
41+
- Avoid `as`, use `valibot` instead
42+
- If absolutely needed, use `satisifes X as Y` to make sure it is `X` before force casting to `Y`
43+
- Use as few `unknown` as possible
44+
- All optional properties must be `undefined`-able, i.e. use `value?: number | undefined` over `value?: number`
45+
- For functions exported outside of current file, make sure all arguments and return value are typed
46+
- If need to look inside the object to check for types, use `valibot`
47+
- E.g. `if (obj && typeof obj === 'object' && 'value' in obj && typeof obj.value === 'string')` should be replaced with `safeParse(object({ value: string }), obj).success`
48+
- Use `{ readonly value: string }` instead of `Readonly<{ value: string }>`
49+
- Use as much `readonly` as possible
50+
51+
### React template
52+
53+
```tsx
54+
import { reactNode, validateProps } from '@msinternal/botframework-webchat-react-valibot';
55+
import React from 'react';
56+
import { number, object, pipe, readonly } from 'valibot';
57+
58+
// Use valibot to validate props.
59+
const MyComponentPropsSchema = pipe(
60+
object({
61+
children: optional(reactNode())
62+
}),
63+
readonly()
64+
);
65+
66+
type MyComponentProps = InferInput<typeof MyComponentPropsSchema>;
67+
68+
// Use big function instead of arrow function.
69+
function MyComponent(props: MyComponentProps) {
70+
// Call `validateProps` to get a validated props.
71+
const { children } = validateProps(MyComponentPropsSchema, props);
72+
73+
// Use `<>` instead of `<Fragment>`.
74+
// Never export `children` by itself, always wrap with `<>`.
75+
return <>{children}</>;
76+
}
77+
78+
// Set `displayName`.
79+
MyComponent.displayName = 'MyComponent';
80+
81+
// Export as memoized/exotic component, export as default.
82+
export default memo(MyComponent);
83+
84+
// Export both schema and typing.
85+
export { MyComponentPropsSchema, type MyComponentProps };
86+
```
87+
88+
## Testing instructions
89+
90+
- Practice test-driven development
91+
- Build and run Docker to host WebDriver-controlled browser instances
92+
- Build container by `docker compose -f docker-compose-wsl2.yml build --build-arg REGISTRY=mcr.microsoft.com`
93+
- Start container by `docker compose -f docker-compose-wsl2.yml up --detach --scale chrome=2`
94+
- Run `curl http://localhost:4444/wd/hub/status` and wait until `.value.ready` become `true`
95+
- Run `npm test`, or `npm test -- --testPathPattern test-html-file.html` to focus on one
96+
- Test files are HTML files under `/__tests__/html2/`, they are not JS
97+
- Follow other HTML files under the same folder on how to write tests
98+
- Page conditions, page elements, and page objects can be found at `/packages/test/page-object/src/globals`
99+
- Use `expect` instead of `assert`
100+
- Use `@testduet/given-when-then` package instead of xUnit style `describe`/`before`/`test`/`after`
101+
- Prefer integration/end-to-end testing than unit testing
102+
- Use as realistic setup as possible, such as using `msw` than mocking calls
103+
104+
## PR instructions
105+
106+
- Run new test and all of them must be green
107+
- Run `npm run precommit` to make sure it pass all linting process
108+
- Add changelog entry to `CHANGELOG.md`, follow our existing format

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,7 @@ Breaking changes in this release:
391391
- Downgraded graph upsert conflict checks, by [@compulim](https://github.com/compulim) in PR [#5674](https://github.com/microsoft/BotFramework-WebChat/pull/5674)
392392
- Fixed virtual keyboard should show up on tap after being suppressed, in iOS 26.2, by [@compulim](https://github.com/compulim) in PR [#5678](https://github.com/microsoft/BotFramework-WebChat/pull/5678)
393393
- Fixed compatibility with `create-react-app` by adding file extension to `core-js` imports, by [@compulim](https://github.com/compulim) in PR [#5680](https://github.com/microsoft/BotFramework-WebChat/pull/5680)
394+
- Fixed virtual keyboard should be collapsed after being suppressed, in iOS 26.3, by [@compulim](https://github.com/compulim) in PR [#5757](https://github.com/microsoft/BotFramework-WebChat/pull/5757)
394395

395396
## [4.18.0] - 2024-07-10
396397

HUMAN.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# HUMAN.md for Bot Framework Web Chat
2+
3+
## Coding
4+
5+
### Package management
6+
7+
- When adding dependencies, use `npm install`
8+
- Do not add it to workspace root
9+
- If it is an existing package, must use consistent version: either use existing version for the package or consider bumping every dependents to latest
10+
- Unless stated otherwise, avoid Node.js packages or polyfills
11+
- Use `ReadableStream`, `WritableStream`, `TransformStream`, instead of Node.js `buffer`
12+
- Use Web Cryptography instead of Node.js `crypto`
13+
- Do not use `fs`-like or `net`-like packages
14+
- Do not use any Browserify-like packages
15+
- Unless stated otherwise, verify newly added packages and transient packages must be either platform-neutral, browser-specific, or React-specific
16+
- Do not add external/publishing packages unless explicitly requested
17+
- Always prefix internal/non-publishing packages with `@msinternal/` to prevent package squatting
18+
19+
### Platform complexity level
20+
21+
This table list expect platform complexity in the running environment.
22+
23+
| Package family | Platform complexity | Neutral | React Native | Full Browser | React | Node.js |
24+
| ------------------------ | ------------------- | ------- | ------------ | ------------ | ----- | ------- |
25+
| `base` | 100 - Neutral ||||||
26+
| `core` | 100 - Neutral ||||||
27+
| `api` | 200 - React Native ||||||
28+
| `react-*` | 200 - React Native ||||||
29+
| `redux-*` | 200 - React Native ||||||
30+
| `component` | 300 - React (HTML) ||||||
31+
| `bundle` | 300 - React (HTML) ||||||
32+
| `fluent-theme`/`*-theme` | 300 - React (HTML) ||||||
33+
| `support` | 100 - Neutral ||||||
34+
35+
Descriptions of platform complexity:
36+
37+
- 100 - Neutral: JavaScript engine only, logic only, UI-agnostic, minimal
38+
- 200 - React Native: browser-like but not full browser, mobile app or web app
39+
- 300 - React (HTML): full browser, web app

__tests__/html2/fluentTheme/connectivityStatus.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@
2121
} = window; // Imports in UMD fashion.
2222

2323
const { directLine, store } = testHelpers.createDirectLineEmulator({ autoConnect: false });
24+
const styleOptions = { spinnerAnimationBackgroundImage: 'url(/assets/staticspinner.png)' };
2425

25-
const App = () => <ReactWebChat directLine={directLine} store={store} />;
26+
const App = () => <ReactWebChat directLine={directLine} store={store} styleOptions={styleOptions} />;
2627

2728
render(
2829
<FluentThemeProvider>
-14 Bytes
Loading

__tests__/html2/hooks/useFocus.sendBox.pure.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,13 @@
3939

4040
await renderHook();
4141

42-
expect(document.activeElement).not.toEqual(pageElements.sendBoxTextBox());
42+
expect(document.activeElement === pageElements.sendBoxTextBox()).toBe(false);
4343

4444
const focus = await renderHook(() => useFocus());
4545

46-
focus('sendBox');
46+
await focus('sendBox');
4747

48-
expect(document.activeElement).toEqual(pageElements.sendBoxTextBox());
48+
expect(document.activeElement === pageElements.sendBoxTextBox()).toBe(true);
4949
});
5050
</script>
5151
</body>
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<!doctype html>
2+
<html lang="en-US">
3+
<head>
4+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
5+
<script crossorigin="anonymous" src="/test-harness.js"></script>
6+
<script crossorigin="anonymous" src="/test-page-object.js"></script>
7+
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
8+
<script type="importmap">
9+
{
10+
"imports": {
11+
"jest-mock": "https://esm.sh/jest-mock"
12+
}
13+
}
14+
</script>
15+
</head>
16+
<body>
17+
<main id="webchat"></main>
18+
<script type="module">
19+
import { fn, spyOn } from 'jest-mock';
20+
21+
run(async function () {
22+
const {
23+
testHelpers: { createDirectLineEmulator }
24+
} = window;
25+
26+
const { directLine, store } = createDirectLineEmulator();
27+
28+
const timeline = [];
29+
30+
const originalRequestIdleCallback = window.requestIdleCallback;
31+
32+
const requestIdleCallback = spyOn(window, 'requestIdleCallback').mockImplementation(callback => {
33+
timeline.push('requestIdleCallback()');
34+
originalRequestIdleCallback.call(window, callback);
35+
});
36+
37+
WebChat.renderWebChat({ directLine, store }, document.getElementById('webchat'));
38+
39+
await pageConditions.uiConnected();
40+
41+
await directLine.actPostActivity(async () => {
42+
const sendBoxTextBox = pageElements.sendBoxTextBox();
43+
44+
const originalFocus = sendBoxTextBox.focus;
45+
const originalSetAttribute = sendBoxTextBox.setAttribute;
46+
47+
const focus = spyOn(sendBoxTextBox, 'focus').mockImplementation(() => {
48+
timeline.push('focus()');
49+
originalFocus.call(sendBoxTextBox);
50+
});
51+
52+
const setAttribute = spyOn(sendBoxTextBox, 'setAttribute').mockImplementation((name, value) => {
53+
timeline.push(`setAttribute(${JSON.stringify(name)}, ${JSON.stringify(value)})`);
54+
originalSetAttribute.call(sendBoxTextBox, name, value);
55+
});
56+
57+
await host.click(pageElements.sendBoxTextBox());
58+
await host.sendKeys('Hello, World!');
59+
60+
// WHEN: Click on the send button.
61+
await host.click(pageElements.sendButton());
62+
63+
expect(timeline).toEqual([
64+
'setAttribute(\"inputmode\", \"text\")', // THEN: `setAttribute()` is called when click on the text box.
65+
'setAttribute(\"inputmode\", \"none\")', // THEN: Tap on the send button should hide the virtual keyboard.
66+
'requestIdleCallback()', // THEN: Make sure there is a pause between `setAttribute()` and `focus()`
67+
'focus()' // THEN: Should focus on the send box.
68+
]);
69+
70+
expect(document.activeElement).toBe(sendBoxTextBox);
71+
});
72+
});
73+
</script>
74+
</body>
75+
</html>

__tests__/html2/transcript/navigation/useObserveTranscriptFocus.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!DOCTYPE html>
1+
<!doctype html>
22
<html lang="en-US">
33
<head>
44
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
@@ -75,7 +75,7 @@
7575
// THEN: It should send a "transcriptfocus" event with the third-last activity, which is a card activity (#29).
7676
expect(transcriptFocusActivityIDHistory).toEqual(['31', '30', '29']);
7777

78-
// WHEN: Pressing ENTER key while focusingo on the card activity (#29).
78+
// WHEN: Pressing ENTER key while focusing on the card activity (#29).
7979
await host.sendKeys('ENTER');
8080

8181
// THEN: It should not send another event because the transcript did not gain any new focus.

0 commit comments

Comments
 (0)