aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Colin McDonnell <colinmcd94@gmail.com> 2023-06-20 18:57:37 -0700
committerGravatar GitHub <noreply@github.com> 2023-06-20 18:57:37 -0700
commitadb451eec6b8286a4ee18b16b5b87644d5ef3020 (patch)
treef1bca25150bf1c9ad9b1ac2d5703b71e5953fc51
parent62639081c1b8ffef0dbf8d729d9d21e10ed41734 (diff)
downloadbun-adb451eec6b8286a4ee18b16b5b87644d5ef3020.tar.gz
bun-adb451eec6b8286a4ee18b16b5b87644d5ef3020.tar.zst
bun-adb451eec6b8286a4ee18b16b5b87644d5ef3020.zip
Docs for DOM testing and FileSink (#3330)
* Update websocket docs & jsdoc * Add info about user-specific data in ws * Document FileSink * Docs for happydom test * Updates
-rw-r--r--.prettierrc.cjs2
-rw-r--r--docs/api/file-io.md57
-rw-r--r--docs/api/websockets.md113
-rw-r--r--docs/cli/test.md2
-rw-r--r--docs/nav.ts3
-rw-r--r--docs/test/dom.md75
6 files changed, 203 insertions, 49 deletions
diff --git a/.prettierrc.cjs b/.prettierrc.cjs
index e330988a4..44f2bd933 100644
--- a/.prettierrc.cjs
+++ b/.prettierrc.cjs
@@ -6,7 +6,7 @@ module.exports = {
quoteProps: "preserve",
overrides: [
{
- files: "README.md",
+ files: ["*.md"],
options: {
printWidth: 80,
},
diff --git a/docs/api/file-io.md b/docs/api/file-io.md
index 07336d071..effc57580 100644
--- a/docs/api/file-io.md
+++ b/docs/api/file-io.md
@@ -202,6 +202,53 @@ const response = await fetch("https://bun.sh");
await Bun.write("index.html", response);
```
+## Incremental writing with `FileSink`
+
+Bun provides a native incremental file writing API called `FileSink`. To retrieve a `FileSink` instance from a `BunFile`:
+
+```ts
+const file = Bun.file("output.txt");
+const writer = file.writer();
+```
+
+To incrementally write to the file, call `.write()`.
+
+```ts
+const file = Bun.file("output.txt");
+const writer = file.writer();
+
+writer.write("it was the best of times\n");
+writer.write("it was the worst of times\n");
+```
+
+These chunks will be buffered internally. To flush the buffer to disk, use `.flush()`. This returns the number of flushed bytes.
+
+```ts
+writer.flush(); // write buffer to disk
+```
+
+The buffer will also auto-flush when the `FileSink`'s _high water mark_ is reached; that is, when its internal buffer is full. This value can be configured.
+
+```ts
+const file = Bun.file("output.txt");
+const writer = file.writer({ highWaterMark: 1024 * 1024 }); // 1MB
+```
+
+To flush the buffer and close the file:
+
+```ts
+writer.end();
+```
+
+Note that, by default, the `bun` process will stay alive until this `FileSink` is explicitly closed with `.end()`. To opt out of this behavior, you can "unref" the instance.
+
+```ts
+writer.unref();
+
+// to "re-ref" it later
+writer.ref();
+```
+
## Benchmarks
The following is a 3-line implementation of the Linux `cat` command.
@@ -250,5 +297,15 @@ interface BunFile {
stream(): Promise<ReadableStream>;
arrayBuffer(): Promise<ArrayBuffer>;
json(): Promise<any>;
+ writer(params: { highWaterMark?: number }): FileSink;
+}
+
+export interface FileSink {
+ write(chunk: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer): number;
+ flush(): number | Promise<number>;
+ end(error?: Error): number | Promise<number>;
+ start(options?: { highWaterMark?: number }): void;
+ ref(): void;
+ unref(): void;
}
```
diff --git a/docs/api/websockets.md b/docs/api/websockets.md
index d1e1d3831..b8b7f7a8e 100644
--- a/docs/api/websockets.md
+++ b/docs/api/websockets.md
@@ -12,41 +12,7 @@
Internally Bun's WebSocket implementation is built on [uWebSockets](https://github.com/uNetworking/uWebSockets).
{% /callout %}
-## Connect to a WebSocket server
-
-To connect to an external socket server, create an instance of `WebSocket` with the constructor.
-
-```ts
-const socket = new WebSocket("ws://localhost:3000");
-```
-
-Bun supports setting custom headers. This is a Bun-specific extension of the `WebSocket` standard. _This will not work in browsers._
-
-```ts
-const socket = new WebSocket("ws://localhost:3000", {
- headers: {
- // custom headers
- },
-});
-```
-
-To add event listeners to the socket:
-
-```ts
-// message is received
-socket.addEventListener("message", event => {});
-
-// socket opened
-socket.addEventListener("open", event => {});
-
-// socket closed
-socket.addEventListener("close", event => {});
-
-// error handler
-socket.addEventListener("error", event => {});
-```
-
-## Create a WebSocket server
+## Start a WebSocket server
Below is a simple WebSocket server built with `Bun.serve`, in which all incoming requests are [upgraded](https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism) to WebSocket connections in the `fetch` handler. The socket handlers are declared in the `websocket` parameter.
@@ -109,7 +75,7 @@ Bun.serve({
});
```
-## Sending messages
+### Sending messages
Each `ServerWebSocket` instance has a `.send()` method for sending messages to the client. It supports a range of input types.
@@ -119,7 +85,7 @@ ws.send(response.arrayBuffer()); // ArrayBuffer
ws.send(new Uint8Array([1, 2, 3])); // TypedArray | DataView
```
-## Headers
+### Headers
Once the upgrade succeeds, Bun will send a `101 Switching Protocols` response per the [spec](https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism). Additional `headers` can be attched to this `Response` in the call to `server.upgrade()`.
@@ -137,7 +103,7 @@ Bun.serve({
});
```
-## Contextual data
+### Contextual data
Contextual `data` can be attached to a new WebSocket in the `.upgrade()` call. This data is made available on the `ws.data` property inside the WebSocket handlers.
@@ -145,14 +111,16 @@ Contextual `data` can be attached to a new WebSocket in the `.upgrade()` call. T
type WebSocketData = {
createdAt: number;
channelId: string;
+ authToken: string;
};
// TypeScript: specify the type of `data`
Bun.serve<WebSocketData>({
fetch(req, server) {
+ // use a library to parse cookies
const cookies = parseCookies(req.headers.get("Cookie"));
server.upgrade(req, {
- // TS: this object must conform to WebSocketData
+ // this object must conform to WebSocketData
data: {
createdAt: Date.now(),
channelId: new URL(req.url).searchParams.get("channelId"),
@@ -165,10 +133,12 @@ Bun.serve<WebSocketData>({
websocket: {
// handler called when a message is received
async message(ws, message) {
- ws.data; // WebSocketData
+ const user = getUserFromToken(ws.data.authToken);
+
await saveMessageToDatabase({
channel: ws.data.channelId,
message: String(message),
+ userId: user.id,
});
},
},
@@ -185,9 +155,11 @@ socket.addEventListener("message", event => {
})
```
-The cookies that are currently set on the page will be sent with the WebSocket upgrade request and available on `req.headers` in the `fetch` handler. Parse these cookies to determine the identity of the connecting user and set the value of `data` accordingly.
+{% callout %}
+**Identifying users** — The cookies that are currently set on the page will be sent with the WebSocket upgrade request and available on `req.headers` in the `fetch` handler. Parse these cookies to determine the identity of the connecting user and set the value of `data` accordingly.
+{% /callout %}
-## Pub/Sub
+### Pub/Sub
Bun's `ServerWebSocket` implementation implements a native publish-subscribe API for topic-based broadcasting. Individual sockets can `.subscribe()` to a topic (specified with a string identifier) and `.publish()` messages to all other subscribers to that topic. This topic-based broadcast API is similar to [MQTT](https://en.wikipedia.org/wiki/MQTT) and [Redis Pub/Sub](https://redis.io/topics/pubsub).
@@ -199,7 +171,9 @@ const server = Bun.serve<{ username: string }>({
console.log(`upgrade!`);
const username = getUsernameFromReq(req);
const success = server.upgrade(req, { data: { username } });
- return success ? undefined : new Response("WebSocket upgrade error", { status: 400 });
+ return success
+ ? undefined
+ : new Response("WebSocket upgrade error", { status: 400 });
}
return new Response("Hello world");
@@ -226,9 +200,9 @@ const server = Bun.serve<{ username: string }>({
console.log(`Listening on ${server.hostname}:${server.port}`);
```
-Calling `.publish(data)` will send the message to all subscribers of a topic (excluding the socket that called `.publish()`).
+Calling `.publish(data)` will send the message to all subscribers of a topic _except_ the socket that called `.publish()`.
-## Compression
+### Compression
Per-message [compression](https://websockets.readthedocs.io/en/stable/topics/compression.html) can be enabled with the `perMessageDeflate` parameter.
@@ -250,7 +224,7 @@ ws.send("Hello world", true);
For fine-grained control over compression characteristics, refer to the [Reference](#reference).
-## Backpressure
+### Backpressure
The `.send(message)` method of `ServerWebSocket` returns a `number` indicating the result of the operation.
@@ -260,6 +234,42 @@ The `.send(message)` method of `ServerWebSocket` returns a `number` indicating t
This gives you better control over backpressure in your server.
+## Connect to a `Websocket` server
+
+To connect to an external socket server, either from a browser or from Bun, create an instance of `WebSocket` with the constructor.
+
+```ts
+const socket = new WebSocket("ws://localhost:3000");
+```
+
+In browsers, the cookies that are currently set on the page will be sent with the WebSocket upgrade request. This is a standard feature of the `WebSocket` API.
+
+For convenience, Bun lets you setting custom headers directly in the constructor. This is a Bun-specific extension of the `WebSocket` standard. _This will not work in browsers._
+
+```ts
+const socket = new WebSocket("ws://localhost:3000", {
+ headers: {
+ // custom headers
+ },
+});
+```
+
+To add event listeners to the socket:
+
+```ts
+// message is received
+socket.addEventListener("message", event => {});
+
+// socket opened
+socket.addEventListener("open", event => {});
+
+// socket closed
+socket.addEventListener("close", event => {});
+
+// error handler
+socket.addEventListener("error", event => {});
+```
+
## Reference
```ts
@@ -267,7 +277,10 @@ namespace Bun {
export function serve(params: {
fetch: (req: Request, server: Server) => Response | Promise<Response>;
websocket?: {
- message: (ws: ServerWebSocket, message: string | ArrayBuffer | Uint8Array) => void;
+ message: (
+ ws: ServerWebSocket,
+ message: string | ArrayBuffer | Uint8Array,
+ ) => void;
open?: (ws: ServerWebSocket) => void;
close?: (ws: ServerWebSocket) => void;
error?: (ws: ServerWebSocket, error: Error) => void;
@@ -297,7 +310,11 @@ type Compressor =
interface Server {
pendingWebsockets: number;
- publish(topic: string, data: string | ArrayBufferView | ArrayBuffer, compress?: boolean): number;
+ publish(
+ topic: string,
+ data: string | ArrayBufferView | ArrayBuffer,
+ compress?: boolean,
+ ): number;
upgrade(
req: Request,
options?: {
diff --git a/docs/cli/test.md b/docs/cli/test.md
index 74a241ef2..d19a45a12 100644
--- a/docs/cli/test.md
+++ b/docs/cli/test.md
@@ -77,6 +77,8 @@ Bun is compatible with popular UI testing libraries:
- [DOM Testing Library](https://testing-library.com/docs/dom-testing-library/intro/)
- [React Testing Library](https://testing-library.com/docs/react-testing-library/intro)
+See [Test > DOM Testing](/docs/test/dom) for complete documentation.
+
## Performance
Bun's test runner is fast.
diff --git a/docs/nav.ts b/docs/nav.ts
index 94e58230a..12333e6da 100644
--- a/docs/nav.ts
+++ b/docs/nav.ts
@@ -190,6 +190,9 @@ export default {
page("test/snapshots", "Snapshots", {
description: "Add lifecycle hooks to your tests that run before/after each test or test run",
}),
+ page("test/dom", "DOM testing", {
+ description: "Write headless tests for UI and React/Vue/Svelte/Lit components with happy-dom",
+ }),
page("test/hot", "Watch mode", {
description: "Reload your tests automatically on change.",
}),
diff --git a/docs/test/dom.md b/docs/test/dom.md
new file mode 100644
index 000000000..3eb897745
--- /dev/null
+++ b/docs/test/dom.md
@@ -0,0 +1,75 @@
+Bun's test runner plays well with existing component and DOM testing libraries, including React Testing Library and [`happy-dom`](https://github.com/capricorn86/happy-dom).
+
+## `happy-dom`
+
+For writing headless tests for your frontend code and components, we recommend [`happy-dom`](https://github.com/capricorn86/happy-dom). Happy DOM implements a complete set of HTML and DOM APIs in plain JavaScript, making it possible to simulate a browser environment with high fidelity.
+
+To get started install the `@happy-dom/global-registrator` package as a dev dependency.
+
+```bash
+$ bun add -d @happy-dom/global-registrator
+```
+
+We'll be using Bun's _preload_ functionality to register the `happy-dom` globals before running our tests. This step will make browser APIs like `document` available in the global scope. Create a file called `happydom.ts` in the root of your project and add the following code:
+
+```ts
+import { GlobalRegistrator } from "@happy-dom/global-registrator";
+
+GlobalRegistrator.register();
+```
+
+To preload this file before `bun test`, open or create a `bunfig.toml` file and add the following lines.
+
+```toml
+[test]
+preload = "./happydom.ts"
+```
+
+This will execute `happydom.ts` when you run `bun test`. Now you can write tests that use browser APIs like `document` and `window`.
+
+```ts#dom.test.ts
+import {test, expect} from 'bun:test';
+
+test('dom test', () => {
+ document.body.innerHTML = `<button>My button</button>`;
+ const button = document.querySelector('button');
+ expect(button?.innerText).toEqual('My button');
+});
+```
+
+Depending on your `tsconfig.json` setup, you may see a `"Cannot find name 'document'"` type error in the code above. To "inject" the types for `document` and other browser APIs, add the following [triple-slash directive](https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html) to the top of any test file.
+
+```ts-diff#dom.test.ts
++ /// <reference lib="dom" />
+
+ import {test, expect} from 'bun:test';
+
+ test('dom test', () => {
+ document.body.innerHTML = `<button>My button</button>`;
+ const button = document.querySelector('button');
+ expect(button?.innerText).toEqual('My button');
+ });
+```
+
+Let's run this test with `bun test`:
+
+```bash
+$ bun test
+bun test v0.x.y
+
+dom.test.ts:
+✓ dom test [0.82ms]
+
+ 1 pass
+ 0 fail
+ 1 expect() calls
+Ran 1 tests across 1 files. 1 total [125.00ms]
+```
+
+<!-- ## React Testing Library
+
+Once you've set up `happy-dom` as described above, you can use it with React Testing Library. To get started, install the `@testing-library/react` package as a dev dependency.
+
+```bash
+$ bun add -d @testing-library/react
+``` -->