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