|
| 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 |
0 commit comments