aboutsummaryrefslogtreecommitdiff
path: root/test/js
diff options
context:
space:
mode:
Diffstat (limited to 'test/js')
-rw-r--r--test/js/bun/console/console-iterator-run.js3
-rw-r--r--test/js/bun/console/console-iterator.test.js63
-rw-r--r--test/js/bun/dns/resolve-dns.test.ts46
-rw-r--r--test/js/bun/ffi/ffi-test.c143
-rw-r--r--test/js/bun/ffi/ffi.test.fixture.callback.c293
-rw-r--r--test/js/bun/ffi/ffi.test.fixture.receiver.c295
-rw-r--r--test/js/bun/ffi/ffi.test.js618
-rw-r--r--test/js/bun/globals.test.js51
-rw-r--r--test/js/bun/http/bun-server.test.ts171
-rw-r--r--test/js/bun/http/fetch.js.txt46
-rw-r--r--test/js/bun/http/fixture.html.gzbin0 -> 16139 bytes
-rw-r--r--test/js/bun/http/proxy.test.js69
-rw-r--r--test/js/bun/http/serve.leak.ts29
-rw-r--r--test/js/bun/http/serve.test.ts981
-rw-r--r--test/js/bun/io/bun-streams-test-fifo.sh8
-rw-r--r--test/js/bun/io/bun-write.test.js274
-rw-r--r--test/js/bun/io/emptyFile0
-rw-r--r--test/js/bun/io/fetch.js.txt46
-rw-r--r--test/js/bun/jsc/bun-jsc.test.js108
-rw-r--r--test/js/bun/jsc/shadow.test.js10
-rw-r--r--test/js/bun/net/echo.js76
-rw-r--r--test/js/bun/net/socket.test.ts130
-rw-r--r--test/js/bun/net/tcp-server.test.ts222
-rw-r--r--test/js/bun/plugin/hello.svelte5
-rw-r--r--test/js/bun/plugin/hello2.svelte5
-rw-r--r--test/js/bun/plugin/plugins.d.ts11
-rw-r--r--test/js/bun/plugin/plugins.test.ts311
-rw-r--r--test/js/bun/resolve/baz.js2
-rw-r--r--test/js/bun/resolve/file-importing-nonexistent-file.js1
-rw-r--r--test/js/bun/resolve/first.mjs8
-rw-r--r--test/js/bun/resolve/import-meta.test.js164
-rw-r--r--test/js/bun/resolve/import-require-tla.js7
-rw-r--r--test/js/bun/resolve/import.live.decl.js4
-rw-r--r--test/js/bun/resolve/import.live.rexport-require.js1
-rw-r--r--test/js/bun/resolve/import.live.rexport.js2
-rw-r--r--test/js/bun/resolve/png/test-png-import.test.js7
-rw-r--r--test/js/bun/resolve/png/test-png.png0
-rw-r--r--test/js/bun/resolve/require-js-top-level-await.js1
-rw-r--r--test/js/bun/resolve/require-js.js2
-rw-r--r--test/js/bun/resolve/require-js2.js1
-rw-r--r--test/js/bun/resolve/require-json.json3
-rw-r--r--test/js/bun/resolve/require-referenceerror.snapshot.js5
-rw-r--r--test/js/bun/resolve/resolve-error.test.ts11
-rw-r--r--test/js/bun/resolve/resolve-typescript-file.tsx1
-rw-r--r--test/js/bun/resolve/resolve.test.js229
-rw-r--r--test/js/bun/resolve/second-child.mjs5
-rw-r--r--test/js/bun/resolve/second.mjs7
-rw-r--r--test/js/bun/resolve/startEnd.mjs6
-rw-r--r--test/js/bun/resolve/third.mjs4
-rw-r--r--test/js/bun/resolve/toml/toml-fixture.toml39
-rw-r--r--test/js/bun/resolve/toml/toml.test.js26
-rw-r--r--test/js/bun/spawn/bash-echo.sh3
-rw-r--r--test/js/bun/spawn/bun-spawn-test.js21
-rw-r--r--test/js/bun/spawn/exit-code-0.js1
-rw-r--r--test/js/bun/spawn/exit-code-1.js1
-rw-r--r--test/js/bun/spawn/exit-code-await-throw-1.js3
-rw-r--r--test/js/bun/spawn/exit-code-unhandled-throw.js3
-rw-r--r--test/js/bun/spawn/exit-code.test.ts23
-rw-r--r--test/js/bun/spawn/exit.js2
-rw-r--r--test/js/bun/spawn/spawn-streaming-stdin.test.ts57
-rw-r--r--test/js/bun/spawn/spawn-streaming-stdout-repro.js5
-rw-r--r--test/js/bun/spawn/spawn-streaming-stdout.test.ts42
-rw-r--r--test/js/bun/spawn/spawn.test.ts380
-rw-r--r--test/js/bun/spawn/stdin-repro.js10
-rw-r--r--test/js/bun/spawn/stdio-test-instance-a-lot.js19
-rw-r--r--test/js/bun/spawn/stdio-test-instance.js5
-rw-r--r--test/js/bun/sqlite/northwind.testdbbin0 -> 602112 bytes
-rw-r--r--test/js/bun/sqlite/sql-raw.test.js55
-rw-r--r--test/js/bun/sqlite/sqlite-cross-process.js45
-rw-r--r--test/js/bun/sqlite/sqlite.test.js529
-rw-r--r--test/js/bun/test/bigint.test.js14
-rw-r--r--test/js/bun/test/bun-test.test.ts6
-rw-r--r--test/js/bun/test/jest-doesnt-auto-import.js12
-rw-r--r--test/js/bun/test/jest-hooks.test.ts195
-rw-r--r--test/js/bun/test/nested-describes.test.ts34
-rw-r--r--test/js/bun/test/test-auto-import-jest-globals.test.js24
-rw-r--r--test/js/bun/test/test-test.test.ts2098
-rw-r--r--test/js/bun/util/arraybuffersink.test.ts64
-rw-r--r--test/js/bun/util/concat.test.js43
-rw-r--r--test/js/bun/util/empty.js1
-rw-r--r--test/js/bun/util/escapeHTML.test.js91
-rw-r--r--test/js/bun/util/fileUrl.test.js16
-rw-r--r--test/js/bun/util/filesink.test.ts141
-rw-r--r--test/js/bun/util/filesystem_router.test.ts354
-rw-r--r--test/js/bun/util/hash.test.js47
-rw-r--r--test/js/bun/util/index-of-line.test.ts37
-rw-r--r--test/js/bun/util/inspect.test.js315
-rw-r--r--test/js/bun/util/mmap.test.js69
-rw-r--r--test/js/bun/util/peek.test.ts42
-rw-r--r--test/js/bun/util/reportError.test.js25
-rw-r--r--test/js/bun/util/sleep.js10
-rw-r--r--test/js/bun/util/sleepSync.test.ts32
-rw-r--r--test/js/bun/util/unsafe.test.js51
-rw-r--r--test/js/bun/util/which.test.ts65
-rwxr-xr-xtest/js/bun/wasm/hello-wasi.wasmbin0 -> 131 bytes
-rw-r--r--test/js/bun/wasm/wasi.test.js14
-rw-r--r--test/js/bun/wasm/wasm-return-1-test.zig5
-rw-r--r--test/js/bun/wasm/wasm.js1
-rw-r--r--test/js/bun/websocket/websocket-server.test.ts954
-rw-r--r--test/js/first_party/undici/undici.test.ts140
-rw-r--r--test/js/node/assert/assert-test.test.ts11
-rw-r--r--test/js/node/buffer.test.js2563
-rw-r--r--test/js/node/child_process/child-process-stdio.test.js112
-rw-r--r--test/js/node/child_process/child_process-node.test.js481
-rw-r--r--test/js/node/child_process/child_process.test.ts347
-rw-r--r--test/js/node/child_process/readFileSync.txt1
-rw-r--r--test/js/node/child_process/spawned-child.js29
-rw-r--r--test/js/node/crypto/crypto-scrypt.test.js232
-rw-r--r--test/js/node/crypto/crypto.test.js106
-rw-r--r--test/js/node/crypto/node-crypto.test.js29
-rw-r--r--test/js/node/dirname.test.js9
-rw-r--r--test/js/node/disabled-module.test.js38
-rw-r--r--test/js/node/dns/dns.node.mjs0
-rw-r--r--test/js/node/dns/node-dns.test.js148
-rw-r--r--test/js/node/events/event-emitter.test.ts169
-rw-r--r--test/js/node/events/node-builtins.test.js18
-rw-r--r--test/js/node/fs/export-*-from.ts1
-rw-r--r--test/js/node/fs/export-from.ts1
-rw-r--r--test/js/node/fs/fs-stream.js21
l---------test/js/node/fs/fs-stream.link.js1
-rw-r--r--test/js/node/fs/fs.test.ts1013
-rw-r--r--test/js/node/fs/readFileSync.txt1
-rw-r--r--test/js/node/fs/test.txt0
-rw-r--r--test/js/node/fs/writeFileSync.txt1
-rw-r--r--test/js/node/harness.test.js169
-rw-r--r--test/js/node/harness.ts201
-rw-r--r--test/js/node/http/node-http.fixme.ts604
-rw-r--r--test/js/node/module/node-module-module.test.js5
-rw-r--r--test/js/node/net/node-net.test.ts268
-rw-r--r--test/js/node/os/os.test.js150
-rw-r--r--test/js/node/path/path.test.js426
-rw-r--r--test/js/node/process/print-process-args.js4
-rw-r--r--test/js/node/process/process-args.test.js40
-rw-r--r--test/js/node/process/process-nexttick.js84
-rw-r--r--test/js/node/process/process-nexttick.test.js99
-rw-r--r--test/js/node/process/process-stdin-echo.js11
-rw-r--r--test/js/node/process/process-stdio.test.ts128
-rw-r--r--test/js/node/process/process.test.js185
-rw-r--r--test/js/node/readline/readline.node.test.ts2001
-rw-r--r--test/js/node/readline/readline_promises.node.test.ts51
-rw-r--r--test/js/node/stream/bufferlist.test.ts221
-rw-r--r--test/js/node/stream/node-stream-uint8array.test.ts111
-rw-r--r--test/js/node/stream/node-stream.test.js86
-rw-r--r--test/js/node/string_decoder/string-decoder.test.js243
-rw-r--r--test/js/node/timers/node-timers.test.ts17
-rw-r--r--test/js/node/util/test-util-types.test.js240
-rw-r--r--test/js/node/util/util-promisify.test.js304
-rw-r--r--test/js/node/util/util.test.js269
-rw-r--r--test/js/node/v8/capture-stack-trace.test.js303
-rw-r--r--test/js/node/zlib/fixture.html.gzbin0 -> 16139 bytes
-rw-r--r--test/js/node/zlib/zlib.test.js39
-rwxr-xr-xtest/js/third_party/body-parser/bun.lockbbin0 -> 19805 bytes
-rw-r--r--test/js/third_party/body-parser/express-body-parser-test.test.ts59
-rw-r--r--test/js/third_party/body-parser/package.json9
-rwxr-xr-xtest/js/third_party/esbuild/bun.lockbbin0 -> 8425 bytes
-rw-r--r--test/js/third_party/esbuild/esbuild-child_process.test.ts17
-rw-r--r--test/js/third_party/esbuild/esbuild-test.js37
-rw-r--r--test/js/third_party/esbuild/package.json6
-rwxr-xr-xtest/js/third_party/napi_create_external/bun.lockbbin0 -> 1496 bytes
-rw-r--r--test/js/third_party/napi_create_external/napi-create-external.test.ts194
-rw-r--r--test/js/third_party/napi_create_external/package.json13
-rw-r--r--test/js/third_party/react-dom/react-dom-server.bun.cjs2670
-rw-r--r--test/js/third_party/react-dom/react-dom.test.tsx285
-rw-r--r--test/js/third_party/svelte/bun-loader-svelte.ts18
-rw-r--r--test/js/third_party/svelte/hello.svelte5
-rw-r--r--test/js/third_party/svelte/package.json4
-rw-r--r--test/js/third_party/svelte/svelte.test.ts21
-rw-r--r--test/js/web/abort/abort-signal-timeout.test.js12
-rw-r--r--test/js/web/console/console-log.expected.txt46
-rw-r--r--test/js/web/console/console-log.js54
-rw-r--r--test/js/web/console/console-log.test.ts20
-rw-r--r--test/js/web/crypto/web-crypto.test.ts91
-rw-r--r--test/js/web/encoding/text-decoder.test.js243
-rw-r--r--test/js/web/encoding/text-encoder.test.js281
-rw-r--r--test/js/web/encoding/utf8-encoding-fixture.binbin0 -> 4456448 bytes
-rw-r--r--test/js/web/fetch/body-mixin-errors.test.ts17
-rw-r--r--test/js/web/fetch/body-stream.test.ts451
-rw-r--r--test/js/web/fetch/fetch-gzip.test.ts181
-rw-r--r--test/js/web/fetch/fetch.js.txt46
-rw-r--r--test/js/web/fetch/fetch.test.ts935
-rw-r--r--test/js/web/fetch/fetch_headers.test.js66
-rw-r--r--test/js/web/fetch/fixture.html1428
-rw-r--r--test/js/web/fetch/fixture.html.gzbin0 -> 16139 bytes
-rw-r--r--test/js/web/html/FormData.test.ts410
-rw-r--r--test/js/web/html/form-data-fixture.txt1
-rw-r--r--test/js/web/streams/bun-streams-test-fifo.sh8
-rw-r--r--test/js/web/streams/fetch.js.txt46
-rw-r--r--test/js/web/streams/streams.test.js630
-rw-r--r--test/js/web/timers/microtask.test.js74
-rw-r--r--test/js/web/timers/performance.test.js22
-rw-r--r--test/js/web/timers/setImmediate.test.js47
-rw-r--r--test/js/web/timers/setInterval.test.js61
-rw-r--r--test/js/web/timers/setTimeout.test.js173
-rw-r--r--test/js/web/url/url.test.ts137
-rw-r--r--test/js/web/util/atob.test.js77
-rw-r--r--test/js/web/web-globals.test.js156
-rw-r--r--test/js/web/websocket/websocket-subprocess.ts13
-rw-r--r--test/js/web/websocket/websocket.test.js263
-rw-r--r--test/js/workerd/html-rewriter.test.js303
199 files changed, 31875 insertions, 0 deletions
diff --git a/test/js/bun/console/console-iterator-run.js b/test/js/bun/console/console-iterator-run.js
new file mode 100644
index 000000000..7664c85a1
--- /dev/null
+++ b/test/js/bun/console/console-iterator-run.js
@@ -0,0 +1,3 @@
+for await (const line of console) {
+ console.write(line);
+}
diff --git a/test/js/bun/console/console-iterator.test.js b/test/js/bun/console/console-iterator.test.js
new file mode 100644
index 000000000..89894fd72
--- /dev/null
+++ b/test/js/bun/console/console-iterator.test.js
@@ -0,0 +1,63 @@
+import { spawnSync, spawn } from "bun";
+import { describe, expect, it } from "bun:test";
+import { bunExe } from "harness";
+
+describe("should work for static input", () => {
+ const inputs = [
+ "hello world",
+ "hello world\n",
+ "hello world\n\n",
+ "hello world\n\n\n",
+ "Hello\nWorld\n",
+ "1",
+ "💕 Red Heart ✨ Sparkles 🔥 Fire\n💕 Red Heart ✨ Sparkles\n💕 Red Heart\n💕\n\nnormal",
+ ];
+
+ for (let input of inputs) {
+ it(input.replaceAll("\n", "\\n"), () => {
+ const { stdout } = spawnSync({
+ cmd: [bunExe(), import.meta.dir + "/" + "console-iterator-run.js"],
+ stdin: Buffer.from(input),
+ env: {
+ BUN_DEBUG_QUIET_LOGS: "1",
+ },
+ });
+ expect(stdout.toString()).toBe(input.replaceAll("\n", ""));
+ });
+ }
+});
+
+describe("should work for streaming input", () => {
+ const inputs = [
+ "hello world",
+ "hello world\n",
+ "hello world\n\n",
+ "hello world\n\n\n",
+ "Hello\nWorld\n",
+ "1",
+ "💕 Red Heart ✨ Sparkles 🔥 Fire\n 💕 Red Heart ✨ Sparkles\n 💕 Red Heart\n 💕 \n\nnormal",
+ ];
+
+ for (let input of inputs) {
+ it(input.replaceAll("\n", "\\n"), async () => {
+ const proc = spawn({
+ cmd: [bunExe(), import.meta.dir + "/" + "console-iterator-run.js"],
+ stdin: "pipe",
+ stdout: "pipe",
+ env: {
+ BUN_DEBUG_QUIET_LOGS: "1",
+ },
+ });
+ const { stdout, stdin } = proc;
+ stdin.write(input.slice(0, (input.length / 2) | 0));
+ stdin.flush();
+ await new Promise(resolve => setTimeout(resolve, 1));
+ stdin.write(input.slice((input.length / 2) | 0));
+ stdin.flush();
+ stdin.end();
+
+ expect(await new Response(stdout).text()).toBe(input.replaceAll("\n", ""));
+ proc.kill(0);
+ });
+ }
+});
diff --git a/test/js/bun/dns/resolve-dns.test.ts b/test/js/bun/dns/resolve-dns.test.ts
new file mode 100644
index 000000000..52534da13
--- /dev/null
+++ b/test/js/bun/dns/resolve-dns.test.ts
@@ -0,0 +1,46 @@
+import { dns } from "bun";
+import { describe, expect, it, test } from "bun:test";
+import { withoutAggressiveGC } from "harness";
+
+describe("dns.lookup", () => {
+ const backends = [process.platform === "darwin" ? "system" : undefined, "libc", "c-ares"].filter(Boolean);
+ for (let backend of backends) {
+ it(backend + " parallell x 10", async () => {
+ const promises = [];
+ for (let i = 0; i < 10; i++) {
+ promises.push(dns.lookup("localhost", { backend }));
+ }
+ const results = (await Promise.all(promises)).flat();
+ withoutAggressiveGC(() => {
+ for (let { family, address } of results) {
+ if (family === 4) {
+ expect(address).toBe("127.0.0.1");
+ } else if (family === 6) {
+ expect(address).toBe("::1");
+ } else {
+ throw new Error("Unknown family");
+ }
+ }
+ });
+ });
+
+ it(backend + " remote", async () => {
+ const [first, second] = await dns.lookup("google.com", { backend });
+ console.log(first, second);
+ });
+ it.skip(backend + " local", async () => {
+ const [first, second] = await dns.lookup("localhost", { backend });
+ console.log(first, second);
+ });
+
+ it(backend + " failing domain throws an error without taking a very long time", async () => {
+ try {
+ await dns.lookup("yololololololo1234567.com", { backend });
+ throw 42;
+ } catch (e) {
+ expect(typeof e).not.toBe("number");
+ expect(e.code).toBe("DNS_ENOTFOUND");
+ }
+ });
+ }
+});
diff --git a/test/js/bun/ffi/ffi-test.c b/test/js/bun/ffi/ffi-test.c
new file mode 100644
index 000000000..0fe227385
--- /dev/null
+++ b/test/js/bun/ffi/ffi-test.c
@@ -0,0 +1,143 @@
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+bool returns_true();
+bool returns_false();
+char returns_42_char();
+float returns_42_float();
+double returns_42_double();
+uint8_t returns_42_uint8_t();
+int8_t returns_neg_42_int8_t();
+uint16_t returns_42_uint16_t();
+uint32_t returns_42_uint32_t();
+uint64_t returns_42_uint64_t();
+int16_t returns_neg_42_int16_t();
+int32_t returns_neg_42_int32_t();
+int64_t returns_neg_42_int64_t();
+
+bool cb_identity_true(bool (*cb)());
+bool cb_identity_false(bool (*cb)());
+char cb_identity_42_char(char (*cb)());
+float cb_identity_42_float(float (*cb)());
+double cb_identity_42_double(double (*cb)());
+uint8_t cb_identity_42_uint8_t(uint8_t (*cb)());
+int8_t cb_identity_neg_42_int8_t(int8_t (*cb)());
+uint16_t cb_identity_42_uint16_t(uint16_t (*cb)());
+uint32_t cb_identity_42_uint32_t(uint32_t (*cb)());
+uint64_t cb_identity_42_uint64_t(uint64_t (*cb)());
+int16_t cb_identity_neg_42_int16_t(int16_t (*cb)());
+int32_t cb_identity_neg_42_int32_t(int32_t (*cb)());
+int64_t cb_identity_neg_42_int64_t(int64_t (*cb)());
+
+bool identity_bool_true();
+bool identity_bool_false();
+char identity_char(char a);
+float identity_float(float a);
+bool identity_bool(bool ident);
+double identity_double(double a);
+int8_t identity_int8_t(int8_t a);
+int16_t identity_int16_t(int16_t a);
+int32_t identity_int32_t(int32_t a);
+int64_t identity_int64_t(int64_t a);
+uint8_t identity_uint8_t(uint8_t a);
+uint16_t identity_uint16_t(uint16_t a);
+uint32_t identity_uint32_t(uint32_t a);
+uint64_t identity_uint64_t(uint64_t a);
+
+char add_char(char a, char b);
+float add_float(float a, float b);
+double add_double(double a, double b);
+int8_t add_int8_t(int8_t a, int8_t b);
+int16_t add_int16_t(int16_t a, int16_t b);
+int32_t add_int32_t(int32_t a, int32_t b);
+int64_t add_int64_t(int64_t a, int64_t b);
+uint8_t add_uint8_t(uint8_t a, uint8_t b);
+uint16_t add_uint16_t(uint16_t a, uint16_t b);
+uint32_t add_uint32_t(uint32_t a, uint32_t b);
+uint64_t add_uint64_t(uint64_t a, uint64_t b);
+
+bool returns_false() { return false; }
+bool returns_true() { return true; }
+char returns_42_char() { return '*'; }
+double returns_42_double() { return (double)42.42; }
+float returns_42_float() { return 42.42f; }
+int16_t returns_neg_42_int16_t() { return -42; }
+int32_t returns_neg_42_int32_t() { return -42; }
+int64_t returns_neg_42_int64_t() { return -42; }
+int8_t returns_neg_42_int8_t() { return -42; }
+uint16_t returns_42_uint16_t() { return 42; }
+uint32_t returns_42_uint32_t() { return 42; }
+uint64_t returns_42_uint64_t() { return 42; }
+uint8_t returns_42_uint8_t() { return (uint8_t)42; }
+
+char identity_char(char a) { return a; }
+float identity_float(float a) { return a; }
+double identity_double(double a) { return a; }
+int8_t identity_int8_t(int8_t a) { return a; }
+int16_t identity_int16_t(int16_t a) { return a; }
+int32_t identity_int32_t(int32_t a) { return a; }
+int64_t identity_int64_t(int64_t a) { return a; }
+uint8_t identity_uint8_t(uint8_t a) { return a; }
+uint16_t identity_uint16_t(uint16_t a) { return a; }
+uint32_t identity_uint32_t(uint32_t a) { return a; }
+uint64_t identity_uint64_t(uint64_t a) { return a; }
+bool identity_bool(bool ident) { return ident; }
+void *identity_ptr(void *ident) { return ident; }
+
+char add_char(char a, char b) { return a + b; }
+float add_float(float a, float b) { return a + b; }
+double add_double(double a, double b) { return a + b; }
+int8_t add_int8_t(int8_t a, int8_t b) { return a + b; }
+int16_t add_int16_t(int16_t a, int16_t b) { return a + b; }
+int32_t add_int32_t(int32_t a, int32_t b) { return a + b; }
+int64_t add_int64_t(int64_t a, int64_t b) { return a + b; }
+uint8_t add_uint8_t(uint8_t a, uint8_t b) { return a + b; }
+uint16_t add_uint16_t(uint16_t a, uint16_t b) { return a + b; }
+uint32_t add_uint32_t(uint32_t a, uint32_t b) { return a + b; }
+uint64_t add_uint64_t(uint64_t a, uint64_t b) { return a + b; }
+
+void *ptr_should_point_to_42_as_int32_t();
+void *ptr_should_point_to_42_as_int32_t() {
+ int32_t *ptr = malloc(sizeof(int32_t));
+ *ptr = 42;
+ return ptr;
+}
+
+static uint8_t buffer_with_deallocator[128];
+static int deallocatorCalled;
+void deallocator(void *ptr, void *userData) { deallocatorCalled++; }
+void *getDeallocatorCallback() {
+ deallocatorCalled = 0;
+ return &deallocator;
+}
+void *getDeallocatorBuffer() {
+ deallocatorCalled = 0;
+ return &buffer_with_deallocator;
+}
+int getDeallocatorCalledCount() { return deallocatorCalled; }
+
+bool is_null(int32_t *ptr) { return ptr == NULL; }
+bool does_pointer_equal_42_as_int32_t(int32_t *ptr);
+bool does_pointer_equal_42_as_int32_t(int32_t *ptr) { return *ptr == 42; }
+
+void *return_a_function_ptr_to_function_that_returns_true();
+void *return_a_function_ptr_to_function_that_returns_true() {
+ return (void *)&returns_true;
+}
+
+bool cb_identity_true(bool (*cb)()) { return cb(); }
+
+bool cb_identity_false(bool (*cb)()) { return cb(); }
+char cb_identity_42_char(char (*cb)()) { return cb(); }
+float cb_identity_42_float(float (*cb)()) { return cb(); }
+double cb_identity_42_double(double (*cb)()) { return cb(); }
+uint8_t cb_identity_42_uint8_t(uint8_t (*cb)()) { return cb(); }
+int8_t cb_identity_neg_42_int8_t(int8_t (*cb)()) { return cb(); }
+uint16_t cb_identity_42_uint16_t(uint16_t (*cb)()) { return cb(); }
+uint32_t cb_identity_42_uint32_t(uint32_t (*cb)()) { return cb(); }
+uint64_t cb_identity_42_uint64_t(uint64_t (*cb)()) { return cb(); }
+int16_t cb_identity_neg_42_int16_t(int16_t (*cb)()) { return cb(); }
+int32_t cb_identity_neg_42_int32_t(int32_t (*cb)()) { return cb(); }
+int64_t cb_identity_neg_42_int64_t(int64_t (*cb)()) { return cb(); } \ No newline at end of file
diff --git a/test/js/bun/ffi/ffi.test.fixture.callback.c b/test/js/bun/ffi/ffi.test.fixture.callback.c
new file mode 100644
index 000000000..624f6cdd0
--- /dev/null
+++ b/test/js/bun/ffi/ffi.test.fixture.callback.c
@@ -0,0 +1,293 @@
+#define JS_GLOBAL_OBJECT (void*)0x0000000000000000ULL
+#define IS_CALLBACK 1
+// This file is part of Bun!
+// You can find the original source:
+// https://github.com/oven-sh/bun/blob/main/src/bun.js/api/FFI.h#L2
+//
+// clang-format off
+// This file is only compatible with 64 bit CPUs
+// It must be kept in sync with JSCJSValue.h
+// https://github.com/oven-sh/WebKit/blob/72c2052b781cbfd4af867ae79ac9de460e392fba/Source/JavaScriptCore/runtime/JSCJSValue.h#L455-L458
+#ifdef IS_CALLBACK
+#define INJECT_BEFORE int c = 500; // This is a callback, so we need to inject code before the call
+#endif
+#define IS_BIG_ENDIAN 0
+#define USE_JSVALUE64 1
+#define USE_JSVALUE32_64 0
+
+#define ZIG_REPR_TYPE int64_t
+
+
+// /* 7.18.1.1 Exact-width integer types */
+typedef unsigned char uint8_t;
+typedef signed char int8_t;
+typedef short int16_t;
+typedef unsigned short uint16_t;
+typedef int int32_t;
+typedef unsigned int uint32_t;
+typedef long long int64_t;
+typedef unsigned long long uint64_t;
+typedef unsigned long long size_t;
+typedef long intptr_t;
+typedef uint64_t uintptr_t;
+typedef _Bool bool;
+
+#define true 1
+#define false 0
+
+
+#ifdef INJECT_BEFORE
+// #include <stdint.h>
+#endif
+// #include <tcclib.h>
+
+// This value is 2^49, used to encode doubles such that the encoded value will
+// begin with a 15-bit pattern within the range 0x0002..0xFFFC.
+#define DoubleEncodeOffsetBit 49
+#define DoubleEncodeOffset (1ll << DoubleEncodeOffsetBit)
+#define OtherTag 0x2
+#define BoolTag 0x4
+#define UndefinedTag 0x8
+#define TagValueFalse (OtherTag | BoolTag | false)
+#define TagValueTrue (OtherTag | BoolTag | true)
+#define TagValueUndefined (OtherTag | UndefinedTag)
+#define TagValueNull (OtherTag)
+#define NotCellMask NumberTag | OtherTag
+
+#define MAX_INT32 2147483648
+#define MAX_INT52 9007199254740991
+
+// If all bits in the mask are set, this indicates an integer number,
+// if any but not all are set this value is a double precision number.
+#define NumberTag 0xfffe000000000000ll
+
+typedef void* JSCell;
+
+typedef union EncodedJSValue {
+ int64_t asInt64;
+
+#if USE_JSVALUE64
+ JSCell *ptr;
+#endif
+
+#if IS_BIG_ENDIAN
+ struct {
+ int32_t tag;
+ int32_t payload;
+ } asBits;
+#else
+ struct {
+ int32_t payload;
+ int32_t tag;
+ } asBits;
+#endif
+
+ void* asPtr;
+ double asDouble;
+
+ ZIG_REPR_TYPE asZigRepr;
+} EncodedJSValue;
+
+EncodedJSValue ValueUndefined = { TagValueUndefined };
+EncodedJSValue ValueTrue = { TagValueTrue };
+
+typedef void* JSContext;
+
+// Bun_FFI_PointerOffsetToArgumentsList is injected into the build
+// The value is generated in `make sizegen`
+// The value is 6.
+// On ARM64_32, the value is something else but it really doesn't matter for our case
+// However, I don't want this to subtly break amidst future upgrades to JavaScriptCore
+#define LOAD_ARGUMENTS_FROM_CALL_FRAME \
+ int64_t *argsPtr = (int64_t*)((size_t*)callFrame + Bun_FFI_PointerOffsetToArgumentsList)
+
+
+#ifdef IS_CALLBACK
+void* callback_ctx;
+ZIG_REPR_TYPE FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args);
+// We wrap
+static EncodedJSValue _FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args) __attribute__((__always_inline__));
+static EncodedJSValue _FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args) {
+ EncodedJSValue return_value;
+ return_value.asZigRepr = FFI_Callback_call(ctx, argCount, args);
+ return return_value;
+}
+#endif
+
+static bool JSVALUE_IS_CELL(EncodedJSValue val) __attribute__((__always_inline__));
+static bool JSVALUE_IS_INT32(EncodedJSValue val) __attribute__((__always_inline__));
+static bool JSVALUE_IS_NUMBER(EncodedJSValue val) __attribute__((__always_inline__));
+
+static uint64_t JSVALUE_TO_UINT64(EncodedJSValue value) __attribute__((__always_inline__));
+static int64_t JSVALUE_TO_INT64(EncodedJSValue value) __attribute__((__always_inline__));
+uint64_t JSVALUE_TO_UINT64_SLOW(EncodedJSValue value);
+int64_t JSVALUE_TO_INT64_SLOW(EncodedJSValue value);
+
+EncodedJSValue UINT64_TO_JSVALUE_SLOW(void* jsGlobalObject, uint64_t val);
+EncodedJSValue INT64_TO_JSVALUE_SLOW(void* jsGlobalObject, int64_t val);
+static EncodedJSValue UINT64_TO_JSVALUE(void* jsGlobalObject, uint64_t val) __attribute__((__always_inline__));
+static EncodedJSValue INT64_TO_JSVALUE(void* jsGlobalObject, int64_t val) __attribute__((__always_inline__));
+
+
+static EncodedJSValue INT32_TO_JSVALUE(int32_t val) __attribute__((__always_inline__));
+static EncodedJSValue DOUBLE_TO_JSVALUE(double val) __attribute__((__always_inline__));
+static EncodedJSValue FLOAT_TO_JSVALUE(float val) __attribute__((__always_inline__));
+static EncodedJSValue BOOLEAN_TO_JSVALUE(bool val) __attribute__((__always_inline__));
+static EncodedJSValue PTR_TO_JSVALUE(void* ptr) __attribute__((__always_inline__));
+
+static void* JSVALUE_TO_PTR(EncodedJSValue val) __attribute__((__always_inline__));
+static int32_t JSVALUE_TO_INT32(EncodedJSValue val) __attribute__((__always_inline__));
+static float JSVALUE_TO_FLOAT(EncodedJSValue val) __attribute__((__always_inline__));
+static double JSVALUE_TO_DOUBLE(EncodedJSValue val) __attribute__((__always_inline__));
+static bool JSVALUE_TO_BOOL(EncodedJSValue val) __attribute__((__always_inline__));
+
+static bool JSVALUE_IS_CELL(EncodedJSValue val) {
+ return !(val.asInt64 & NotCellMask);
+}
+
+static bool JSVALUE_IS_INT32(EncodedJSValue val) {
+ return (val.asInt64 & NumberTag) == NumberTag;
+}
+
+static bool JSVALUE_IS_NUMBER(EncodedJSValue val) {
+ return val.asInt64 & NumberTag;
+}
+
+
+// JSValue numbers-as-pointers are represented as a 52-bit integer
+// Previously, the pointer was stored at the end of the 64-bit value
+// Now, they're stored at the beginning of the 64-bit value
+// This behavior change enables the JIT to handle it better
+// It also is better readability when console.log(myPtr)
+static void* JSVALUE_TO_PTR(EncodedJSValue val) {
+ if (val.asInt64 == TagValueNull)
+ return 0;
+ val.asInt64 -= DoubleEncodeOffset;
+ size_t ptr = (size_t)val.asDouble;
+ return (void*)ptr;
+}
+
+static EncodedJSValue PTR_TO_JSVALUE(void* ptr) {
+ EncodedJSValue val;
+ if (ptr == 0)
+ {
+ val.asInt64 = TagValueNull;
+ return val;
+ }
+
+ val.asDouble = (double)(size_t)ptr;
+ val.asInt64 += DoubleEncodeOffset;
+ return val;
+}
+
+static EncodedJSValue DOUBLE_TO_JSVALUE(double val) {
+ EncodedJSValue res;
+ res.asDouble = val;
+ res.asInt64 += DoubleEncodeOffset;
+ return res;
+}
+
+static int32_t JSVALUE_TO_INT32(EncodedJSValue val) {
+ return val.asInt64;
+}
+
+static EncodedJSValue INT32_TO_JSVALUE(int32_t val) {
+ EncodedJSValue res;
+ res.asInt64 = NumberTag | (uint32_t)val;
+ return res;
+}
+
+
+
+
+static EncodedJSValue FLOAT_TO_JSVALUE(float val) {
+ return DOUBLE_TO_JSVALUE((double)val);
+}
+
+static EncodedJSValue BOOLEAN_TO_JSVALUE(bool val) {
+ EncodedJSValue res;
+ res.asInt64 = val ? TagValueTrue : TagValueFalse;
+ return res;
+}
+
+
+static double JSVALUE_TO_DOUBLE(EncodedJSValue val) {
+ val.asInt64 -= DoubleEncodeOffset;
+ return val.asDouble;
+}
+
+static float JSVALUE_TO_FLOAT(EncodedJSValue val) {
+ return (float)JSVALUE_TO_DOUBLE(val);
+}
+
+static bool JSVALUE_TO_BOOL(EncodedJSValue val) {
+ return val.asInt64 == TagValueTrue;
+}
+
+
+static uint64_t JSVALUE_TO_UINT64(EncodedJSValue value) {
+ if (JSVALUE_IS_INT32(value)) {
+ return (uint64_t)JSVALUE_TO_INT32(value);
+ }
+
+ if (JSVALUE_IS_NUMBER(value)) {
+ return (uint64_t)JSVALUE_TO_DOUBLE(value);
+ }
+
+ return JSVALUE_TO_UINT64_SLOW(value);
+}
+static int64_t JSVALUE_TO_INT64(EncodedJSValue value) {
+ if (JSVALUE_IS_INT32(value)) {
+ return (int64_t)JSVALUE_TO_INT32(value);
+ }
+
+ if (JSVALUE_IS_NUMBER(value)) {
+ return (int64_t)JSVALUE_TO_DOUBLE(value);
+ }
+
+ return JSVALUE_TO_INT64_SLOW(value);
+}
+
+static EncodedJSValue UINT64_TO_JSVALUE(void* jsGlobalObject, uint64_t val) {
+ if (val < MAX_INT32) {
+ return INT32_TO_JSVALUE((int32_t)val);
+ }
+
+ if (val < MAX_INT52) {
+ return DOUBLE_TO_JSVALUE((double)val);
+ }
+
+ return UINT64_TO_JSVALUE_SLOW(jsGlobalObject, val);
+}
+
+static EncodedJSValue INT64_TO_JSVALUE(void* jsGlobalObject, int64_t val) {
+ if (val >= -MAX_INT32 && val <= MAX_INT32) {
+ return INT32_TO_JSVALUE((int32_t)val);
+ }
+
+ if (val >= -MAX_INT52 && val <= MAX_INT52) {
+ return DOUBLE_TO_JSVALUE((double)val);
+ }
+
+ return INT64_TO_JSVALUE_SLOW(jsGlobalObject, val);
+}
+
+#ifndef IS_CALLBACK
+ZIG_REPR_TYPE JSFunctionCall(void* jsGlobalObject, void* callFrame);
+
+#endif
+
+
+// --- Generated Code ---
+
+
+/* --- The Callback Function */
+/* --- The Callback Function */
+bool my_callback_function(void* arg0);
+
+bool my_callback_function(void* arg0) {
+ ZIG_REPR_TYPE arguments[1];
+arguments[0] = PTR_TO_JSVALUE(arg0).asZigRepr;
+ return (bool)JSVALUE_TO_BOOL(_FFI_Callback_call((void*)0x0000000000000000ULL, 1, arguments));
+}
+
diff --git a/test/js/bun/ffi/ffi.test.fixture.receiver.c b/test/js/bun/ffi/ffi.test.fixture.receiver.c
new file mode 100644
index 000000000..0299a961b
--- /dev/null
+++ b/test/js/bun/ffi/ffi.test.fixture.receiver.c
@@ -0,0 +1,295 @@
+#define HAS_ARGUMENTS
+#define USES_FLOAT 1
+// This file is part of Bun!
+// You can find the original source:
+// https://github.com/oven-sh/bun/blob/main/src/bun.js/api/FFI.h#L2
+//
+// clang-format off
+// This file is only compatible with 64 bit CPUs
+// It must be kept in sync with JSCJSValue.h
+// https://github.com/oven-sh/WebKit/blob/72c2052b781cbfd4af867ae79ac9de460e392fba/Source/JavaScriptCore/runtime/JSCJSValue.h#L455-L458
+#ifdef IS_CALLBACK
+#define INJECT_BEFORE int c = 500; // This is a callback, so we need to inject code before the call
+#endif
+#define IS_BIG_ENDIAN 0
+#define USE_JSVALUE64 1
+#define USE_JSVALUE32_64 0
+
+#define ZIG_REPR_TYPE int64_t
+
+
+// /* 7.18.1.1 Exact-width integer types */
+typedef unsigned char uint8_t;
+typedef signed char int8_t;
+typedef short int16_t;
+typedef unsigned short uint16_t;
+typedef int int32_t;
+typedef unsigned int uint32_t;
+typedef long long int64_t;
+typedef unsigned long long uint64_t;
+typedef unsigned long long size_t;
+typedef long intptr_t;
+typedef uint64_t uintptr_t;
+typedef _Bool bool;
+
+#define true 1
+#define false 0
+
+
+#ifdef INJECT_BEFORE
+// #include <stdint.h>
+#endif
+// #include <tcclib.h>
+
+// This value is 2^49, used to encode doubles such that the encoded value will
+// begin with a 15-bit pattern within the range 0x0002..0xFFFC.
+#define DoubleEncodeOffsetBit 49
+#define DoubleEncodeOffset (1ll << DoubleEncodeOffsetBit)
+#define OtherTag 0x2
+#define BoolTag 0x4
+#define UndefinedTag 0x8
+#define TagValueFalse (OtherTag | BoolTag | false)
+#define TagValueTrue (OtherTag | BoolTag | true)
+#define TagValueUndefined (OtherTag | UndefinedTag)
+#define TagValueNull (OtherTag)
+#define NotCellMask NumberTag | OtherTag
+
+#define MAX_INT32 2147483648
+#define MAX_INT52 9007199254740991
+
+// If all bits in the mask are set, this indicates an integer number,
+// if any but not all are set this value is a double precision number.
+#define NumberTag 0xfffe000000000000ll
+
+typedef void* JSCell;
+
+typedef union EncodedJSValue {
+ int64_t asInt64;
+
+#if USE_JSVALUE64
+ JSCell *ptr;
+#endif
+
+#if IS_BIG_ENDIAN
+ struct {
+ int32_t tag;
+ int32_t payload;
+ } asBits;
+#else
+ struct {
+ int32_t payload;
+ int32_t tag;
+ } asBits;
+#endif
+
+ void* asPtr;
+ double asDouble;
+
+ ZIG_REPR_TYPE asZigRepr;
+} EncodedJSValue;
+
+EncodedJSValue ValueUndefined = { TagValueUndefined };
+EncodedJSValue ValueTrue = { TagValueTrue };
+
+typedef void* JSContext;
+
+// Bun_FFI_PointerOffsetToArgumentsList is injected into the build
+// The value is generated in `make sizegen`
+// The value is 6.
+// On ARM64_32, the value is something else but it really doesn't matter for our case
+// However, I don't want this to subtly break amidst future upgrades to JavaScriptCore
+#define LOAD_ARGUMENTS_FROM_CALL_FRAME \
+ int64_t *argsPtr = (int64_t*)((size_t*)callFrame + Bun_FFI_PointerOffsetToArgumentsList)
+
+
+#ifdef IS_CALLBACK
+void* callback_ctx;
+ZIG_REPR_TYPE FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args);
+// We wrap
+static EncodedJSValue _FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args) __attribute__((__always_inline__));
+static EncodedJSValue _FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args) {
+ EncodedJSValue return_value;
+ return_value.asZigRepr = FFI_Callback_call(ctx, argCount, args);
+ return return_value;
+}
+#endif
+
+static bool JSVALUE_IS_CELL(EncodedJSValue val) __attribute__((__always_inline__));
+static bool JSVALUE_IS_INT32(EncodedJSValue val) __attribute__((__always_inline__));
+static bool JSVALUE_IS_NUMBER(EncodedJSValue val) __attribute__((__always_inline__));
+
+static uint64_t JSVALUE_TO_UINT64(EncodedJSValue value) __attribute__((__always_inline__));
+static int64_t JSVALUE_TO_INT64(EncodedJSValue value) __attribute__((__always_inline__));
+uint64_t JSVALUE_TO_UINT64_SLOW(EncodedJSValue value);
+int64_t JSVALUE_TO_INT64_SLOW(EncodedJSValue value);
+
+EncodedJSValue UINT64_TO_JSVALUE_SLOW(void* jsGlobalObject, uint64_t val);
+EncodedJSValue INT64_TO_JSVALUE_SLOW(void* jsGlobalObject, int64_t val);
+static EncodedJSValue UINT64_TO_JSVALUE(void* jsGlobalObject, uint64_t val) __attribute__((__always_inline__));
+static EncodedJSValue INT64_TO_JSVALUE(void* jsGlobalObject, int64_t val) __attribute__((__always_inline__));
+
+
+static EncodedJSValue INT32_TO_JSVALUE(int32_t val) __attribute__((__always_inline__));
+static EncodedJSValue DOUBLE_TO_JSVALUE(double val) __attribute__((__always_inline__));
+static EncodedJSValue FLOAT_TO_JSVALUE(float val) __attribute__((__always_inline__));
+static EncodedJSValue BOOLEAN_TO_JSVALUE(bool val) __attribute__((__always_inline__));
+static EncodedJSValue PTR_TO_JSVALUE(void* ptr) __attribute__((__always_inline__));
+
+static void* JSVALUE_TO_PTR(EncodedJSValue val) __attribute__((__always_inline__));
+static int32_t JSVALUE_TO_INT32(EncodedJSValue val) __attribute__((__always_inline__));
+static float JSVALUE_TO_FLOAT(EncodedJSValue val) __attribute__((__always_inline__));
+static double JSVALUE_TO_DOUBLE(EncodedJSValue val) __attribute__((__always_inline__));
+static bool JSVALUE_TO_BOOL(EncodedJSValue val) __attribute__((__always_inline__));
+
+static bool JSVALUE_IS_CELL(EncodedJSValue val) {
+ return !(val.asInt64 & NotCellMask);
+}
+
+static bool JSVALUE_IS_INT32(EncodedJSValue val) {
+ return (val.asInt64 & NumberTag) == NumberTag;
+}
+
+static bool JSVALUE_IS_NUMBER(EncodedJSValue val) {
+ return val.asInt64 & NumberTag;
+}
+
+
+// JSValue numbers-as-pointers are represented as a 52-bit integer
+// Previously, the pointer was stored at the end of the 64-bit value
+// Now, they're stored at the beginning of the 64-bit value
+// This behavior change enables the JIT to handle it better
+// It also is better readability when console.log(myPtr)
+static void* JSVALUE_TO_PTR(EncodedJSValue val) {
+ if (val.asInt64 == TagValueNull)
+ return 0;
+ val.asInt64 -= DoubleEncodeOffset;
+ size_t ptr = (size_t)val.asDouble;
+ return (void*)ptr;
+}
+
+static EncodedJSValue PTR_TO_JSVALUE(void* ptr) {
+ EncodedJSValue val;
+ if (ptr == 0)
+ {
+ val.asInt64 = TagValueNull;
+ return val;
+ }
+
+ val.asDouble = (double)(size_t)ptr;
+ val.asInt64 += DoubleEncodeOffset;
+ return val;
+}
+
+static EncodedJSValue DOUBLE_TO_JSVALUE(double val) {
+ EncodedJSValue res;
+ res.asDouble = val;
+ res.asInt64 += DoubleEncodeOffset;
+ return res;
+}
+
+static int32_t JSVALUE_TO_INT32(EncodedJSValue val) {
+ return val.asInt64;
+}
+
+static EncodedJSValue INT32_TO_JSVALUE(int32_t val) {
+ EncodedJSValue res;
+ res.asInt64 = NumberTag | (uint32_t)val;
+ return res;
+}
+
+
+
+
+static EncodedJSValue FLOAT_TO_JSVALUE(float val) {
+ return DOUBLE_TO_JSVALUE((double)val);
+}
+
+static EncodedJSValue BOOLEAN_TO_JSVALUE(bool val) {
+ EncodedJSValue res;
+ res.asInt64 = val ? TagValueTrue : TagValueFalse;
+ return res;
+}
+
+
+static double JSVALUE_TO_DOUBLE(EncodedJSValue val) {
+ val.asInt64 -= DoubleEncodeOffset;
+ return val.asDouble;
+}
+
+static float JSVALUE_TO_FLOAT(EncodedJSValue val) {
+ return (float)JSVALUE_TO_DOUBLE(val);
+}
+
+static bool JSVALUE_TO_BOOL(EncodedJSValue val) {
+ return val.asInt64 == TagValueTrue;
+}
+
+
+static uint64_t JSVALUE_TO_UINT64(EncodedJSValue value) {
+ if (JSVALUE_IS_INT32(value)) {
+ return (uint64_t)JSVALUE_TO_INT32(value);
+ }
+
+ if (JSVALUE_IS_NUMBER(value)) {
+ return (uint64_t)JSVALUE_TO_DOUBLE(value);
+ }
+
+ return JSVALUE_TO_UINT64_SLOW(value);
+}
+static int64_t JSVALUE_TO_INT64(EncodedJSValue value) {
+ if (JSVALUE_IS_INT32(value)) {
+ return (int64_t)JSVALUE_TO_INT32(value);
+ }
+
+ if (JSVALUE_IS_NUMBER(value)) {
+ return (int64_t)JSVALUE_TO_DOUBLE(value);
+ }
+
+ return JSVALUE_TO_INT64_SLOW(value);
+}
+
+static EncodedJSValue UINT64_TO_JSVALUE(void* jsGlobalObject, uint64_t val) {
+ if (val < MAX_INT32) {
+ return INT32_TO_JSVALUE((int32_t)val);
+ }
+
+ if (val < MAX_INT52) {
+ return DOUBLE_TO_JSVALUE((double)val);
+ }
+
+ return UINT64_TO_JSVALUE_SLOW(jsGlobalObject, val);
+}
+
+static EncodedJSValue INT64_TO_JSVALUE(void* jsGlobalObject, int64_t val) {
+ if (val >= -MAX_INT32 && val <= MAX_INT32) {
+ return INT32_TO_JSVALUE((int32_t)val);
+ }
+
+ if (val >= -MAX_INT52 && val <= MAX_INT52) {
+ return DOUBLE_TO_JSVALUE((double)val);
+ }
+
+ return INT64_TO_JSVALUE_SLOW(jsGlobalObject, val);
+}
+
+#ifndef IS_CALLBACK
+ZIG_REPR_TYPE JSFunctionCall(void* jsGlobalObject, void* callFrame);
+
+#endif
+
+
+// --- Generated Code ---
+/* --- The Function To Call */
+float not_a_callback(float arg0);
+
+
+/* ---- Your Wrapper Function ---- */
+ZIG_REPR_TYPE JSFunctionCall(void* JS_GLOBAL_OBJECT, void* callFrame) {
+ LOAD_ARGUMENTS_FROM_CALL_FRAME;
+ EncodedJSValue arg0;
+ arg0.asInt64 = *argsPtr;
+ float return_value = not_a_callback( JSVALUE_TO_FLOAT(arg0));
+
+ return FLOAT_TO_JSVALUE(return_value).asZigRepr;
+}
+
diff --git a/test/js/bun/ffi/ffi.test.js b/test/js/bun/ffi/ffi.test.js
new file mode 100644
index 000000000..8b31280c9
--- /dev/null
+++ b/test/js/bun/ffi/ffi.test.js
@@ -0,0 +1,618 @@
+import { afterAll, describe, expect, it } from "bun:test";
+import { existsSync } from "fs";
+import {
+ CFunction,
+ CString,
+ dlopen as _dlopen,
+ JSCallback,
+ ptr,
+ read,
+ toArrayBuffer,
+ toBuffer,
+ viewSource,
+} from "bun:ffi";
+
+const dlopen = (...args) => {
+ try {
+ return _dlopen(...args);
+ } catch (err) {
+ console.error("To enable this test, run `make compile-ffi-test`.");
+ throw err;
+ }
+};
+const ok = existsSync("/tmp/bun-ffi-test.dylib");
+
+it("ffi print", async () => {
+ await Bun.write(
+ import.meta.dir + "/ffi.test.fixture.callback.c",
+ viewSource(
+ {
+ returns: "bool",
+ args: ["ptr"],
+ },
+ true,
+ ),
+ );
+ await Bun.write(
+ import.meta.dir + "/ffi.test.fixture.receiver.c",
+ viewSource(
+ {
+ not_a_callback: {
+ returns: "float",
+ args: ["float"],
+ },
+ },
+ false,
+ )[0],
+ );
+ expect(
+ viewSource(
+ {
+ returns: "int8_t",
+ args: [],
+ },
+ true,
+ ).length > 0,
+ ).toBe(true);
+ expect(
+ viewSource(
+ {
+ a: {
+ returns: "int8_t",
+ args: [],
+ },
+ },
+ false,
+ ).length > 0,
+ ).toBe(true);
+});
+
+function getTypes(fast) {
+ const int64_t = fast ? "i64_fast" : "int64_t";
+ const uint64_t = fast ? "u64_fast" : "uint64_t";
+ return {
+ returns_true: {
+ returns: "bool",
+ args: [],
+ },
+ returns_false: {
+ returns: "bool",
+ args: [],
+ },
+ returns_42_char: {
+ returns: "char",
+ args: [],
+ },
+ returns_42_float: {
+ returns: "float",
+ args: [],
+ },
+ returns_42_double: {
+ returns: "double",
+ args: [],
+ },
+ returns_42_uint8_t: {
+ returns: "uint8_t",
+ args: [],
+ },
+ returns_neg_42_int8_t: {
+ returns: "int8_t",
+ args: [],
+ },
+ returns_42_uint16_t: {
+ returns: "uint16_t",
+ args: [],
+ },
+ returns_42_uint32_t: {
+ returns: "uint32_t",
+ args: [],
+ },
+ returns_42_uint64_t: {
+ returns: uint64_t,
+ args: [],
+ },
+ returns_neg_42_int16_t: {
+ returns: "int16_t",
+ args: [],
+ },
+ returns_neg_42_int32_t: {
+ returns: "int32_t",
+ args: [],
+ },
+ returns_neg_42_int64_t: {
+ returns: int64_t,
+ args: [],
+ },
+
+ identity_char: {
+ returns: "char",
+ args: ["char"],
+ },
+ identity_float: {
+ returns: "float",
+ args: ["float"],
+ },
+ identity_bool: {
+ returns: "bool",
+ args: ["bool"],
+ },
+ identity_double: {
+ returns: "double",
+ args: ["double"],
+ },
+ identity_int8_t: {
+ returns: "int8_t",
+ args: ["int8_t"],
+ },
+ identity_int16_t: {
+ returns: "int16_t",
+ args: ["int16_t"],
+ },
+ identity_int32_t: {
+ returns: "int32_t",
+ args: ["int32_t"],
+ },
+ identity_int64_t: {
+ returns: int64_t,
+ args: [int64_t],
+ },
+ identity_uint8_t: {
+ returns: "uint8_t",
+ args: ["uint8_t"],
+ },
+ identity_uint16_t: {
+ returns: "uint16_t",
+ args: ["uint16_t"],
+ },
+ identity_uint32_t: {
+ returns: "uint32_t",
+ args: ["uint32_t"],
+ },
+ identity_uint64_t: {
+ returns: uint64_t,
+ args: [uint64_t],
+ },
+
+ add_char: {
+ returns: "char",
+ args: ["char", "char"],
+ },
+ add_float: {
+ returns: "float",
+ args: ["float", "float"],
+ },
+ add_double: {
+ returns: "double",
+ args: ["double", "double"],
+ },
+ add_int8_t: {
+ returns: "int8_t",
+ args: ["int8_t", "int8_t"],
+ },
+ add_int16_t: {
+ returns: "int16_t",
+ args: ["int16_t", "int16_t"],
+ },
+ add_int32_t: {
+ returns: "int32_t",
+ args: ["int32_t", "int32_t"],
+ },
+ add_int64_t: {
+ returns: int64_t,
+ args: [int64_t, int64_t],
+ },
+ add_uint8_t: {
+ returns: "uint8_t",
+ args: ["uint8_t", "uint8_t"],
+ },
+ add_uint16_t: {
+ returns: "uint16_t",
+ args: ["uint16_t", "uint16_t"],
+ },
+ add_uint32_t: {
+ returns: "uint32_t",
+ args: ["uint32_t", "uint32_t"],
+ },
+
+ is_null: {
+ returns: "bool",
+ args: ["ptr"],
+ },
+
+ does_pointer_equal_42_as_int32_t: {
+ returns: "bool",
+ args: ["ptr"],
+ },
+
+ ptr_should_point_to_42_as_int32_t: {
+ returns: "ptr",
+ args: [],
+ },
+ identity_ptr: {
+ returns: "ptr",
+ args: ["ptr"],
+ },
+ add_uint64_t: {
+ returns: uint64_t,
+ args: [uint64_t, uint64_t],
+ },
+
+ cb_identity_true: {
+ returns: "bool",
+ args: ["ptr"],
+ },
+ cb_identity_false: {
+ returns: "bool",
+ args: ["ptr"],
+ },
+ cb_identity_42_char: {
+ returns: "char",
+ args: ["ptr"],
+ },
+ cb_identity_42_float: {
+ returns: "float",
+ args: ["ptr"],
+ },
+ cb_identity_42_double: {
+ returns: "double",
+ args: ["ptr"],
+ },
+ cb_identity_42_uint8_t: {
+ returns: "uint8_t",
+ args: ["ptr"],
+ },
+ cb_identity_neg_42_int8_t: {
+ returns: "int8_t",
+ args: ["ptr"],
+ },
+ cb_identity_42_uint16_t: {
+ returns: "uint16_t",
+ args: ["ptr"],
+ },
+ cb_identity_42_uint32_t: {
+ returns: "uint32_t",
+ args: ["ptr"],
+ },
+ cb_identity_42_uint64_t: {
+ returns: uint64_t,
+ args: ["ptr"],
+ },
+ cb_identity_neg_42_int16_t: {
+ returns: "int16_t",
+ args: ["ptr"],
+ },
+ cb_identity_neg_42_int32_t: {
+ returns: "int32_t",
+ args: ["ptr"],
+ },
+ cb_identity_neg_42_int64_t: {
+ returns: int64_t,
+ args: ["ptr"],
+ },
+
+ return_a_function_ptr_to_function_that_returns_true: {
+ returns: "ptr",
+ args: [],
+ },
+
+ getDeallocatorCalledCount: {
+ returns: "int32_t",
+ args: [],
+ },
+ getDeallocatorCallback: {
+ returns: "ptr",
+ args: [],
+ },
+ getDeallocatorBuffer: {
+ returns: "ptr",
+ args: [],
+ },
+ };
+}
+
+function ffiRunner(fast) {
+ describe("FFI runner" + (fast ? " (fast int)" : ""), () => {
+ const types = getTypes(fast);
+ const {
+ symbols: {
+ returns_true,
+ returns_false,
+ return_a_function_ptr_to_function_that_returns_true,
+ returns_42_char,
+ returns_42_float,
+ returns_42_double,
+ returns_42_uint8_t,
+ returns_neg_42_int8_t,
+ returns_42_uint16_t,
+ returns_42_uint32_t,
+ returns_42_uint64_t,
+ returns_neg_42_int16_t,
+ returns_neg_42_int32_t,
+ returns_neg_42_int64_t,
+ identity_char,
+ identity_float,
+ identity_bool,
+ identity_double,
+ identity_int8_t,
+ identity_int16_t,
+ identity_int32_t,
+ identity_int64_t,
+ identity_uint8_t,
+ identity_uint16_t,
+ identity_uint32_t,
+ identity_uint64_t,
+ add_char,
+ add_float,
+ add_double,
+ add_int8_t,
+ add_int16_t,
+ add_int32_t,
+ add_int64_t,
+ add_uint8_t,
+ add_uint16_t,
+ identity_ptr,
+ add_uint32_t,
+ add_uint64_t,
+ is_null,
+ does_pointer_equal_42_as_int32_t,
+ ptr_should_point_to_42_as_int32_t,
+ cb_identity_true,
+ cb_identity_false,
+ cb_identity_42_char,
+ cb_identity_42_float,
+ cb_identity_42_double,
+ cb_identity_42_uint8_t,
+ cb_identity_neg_42_int8_t,
+ cb_identity_42_uint16_t,
+ cb_identity_42_uint32_t,
+ cb_identity_42_uint64_t,
+ cb_identity_neg_42_int16_t,
+ cb_identity_neg_42_int32_t,
+ cb_identity_neg_42_int64_t,
+ getDeallocatorCalledCount,
+ getDeallocatorCallback,
+ getDeallocatorBuffer,
+ },
+ close,
+ } = dlopen("/tmp/bun-ffi-test.dylib", types);
+ it("primitives", () => {
+ Bun.gc(true);
+ expect(returns_true()).toBe(true);
+ Bun.gc(true);
+ expect(returns_false()).toBe(false);
+
+ expect(returns_42_char()).toBe(42);
+ if (fast) expect(returns_42_uint64_t().valueOf()).toBe(42);
+ else expect(returns_42_uint64_t().valueOf()).toBe(42n);
+ Bun.gc(true);
+ expect(Math.fround(returns_42_float())).toBe(Math.fround(42.41999804973602));
+ expect(returns_42_double()).toBe(42.42);
+ expect(returns_42_uint8_t()).toBe(42);
+ expect(returns_neg_42_int8_t()).toBe(-42);
+ expect(returns_42_uint16_t()).toBe(42);
+ expect(returns_42_uint32_t()).toBe(42);
+ if (fast) expect(returns_42_uint64_t()).toBe(42);
+ else expect(returns_42_uint64_t()).toBe(42n);
+ expect(returns_neg_42_int16_t()).toBe(-42);
+ expect(returns_neg_42_int32_t()).toBe(-42);
+ expect(identity_int32_t(10)).toBe(10);
+ Bun.gc(true);
+ if (fast) expect(returns_neg_42_int64_t()).toBe(-42);
+ else expect(returns_neg_42_int64_t()).toBe(-42n);
+
+ expect(identity_char(10)).toBe(10);
+
+ expect(identity_float(10.199999809265137)).toBe(10.199999809265137);
+
+ expect(identity_bool(true)).toBe(true);
+
+ expect(identity_bool(false)).toBe(false);
+ expect(identity_double(10.100000000000364)).toBe(10.100000000000364);
+
+ expect(identity_int8_t(10)).toBe(10);
+ expect(identity_int16_t(10)).toBe(10);
+
+ if (fast) expect(identity_int64_t(10)).toBe(10);
+ else expect(identity_int64_t(10)).toBe(10n);
+ expect(identity_uint8_t(10)).toBe(10);
+ expect(identity_uint16_t(10)).toBe(10);
+ expect(identity_uint32_t(10)).toBe(10);
+ if (fast) expect(identity_uint64_t(10)).toBe(10);
+ else expect(identity_uint64_t(10)).toBe(10n);
+ Bun.gc(true);
+ var bigArray = new BigUint64Array(8);
+ new Uint8Array(bigArray.buffer).fill(255);
+ var bigIntArray = new BigInt64Array(bigArray.buffer);
+ expect(identity_uint64_t(bigArray[0])).toBe(bigArray[0]);
+ expect(identity_uint64_t(bigArray[0] - BigInt(1))).toBe(bigArray[0] - BigInt(1));
+ if (fast) {
+ expect(add_uint64_t(BigInt(-1) * bigArray[0], bigArray[0])).toBe(0);
+ expect(add_uint64_t(BigInt(-1) * bigArray[0] + BigInt(10), bigArray[0])).toBe(10);
+ } else {
+ expect(add_uint64_t(BigInt(-1) * bigArray[0], bigArray[0])).toBe(0n);
+ expect(add_uint64_t(BigInt(-1) * bigArray[0] + BigInt(10), bigArray[0])).toBe(10n);
+ }
+ if (fast) {
+ expect(identity_uint64_t(0)).toBe(0);
+ expect(identity_uint64_t(100)).toBe(100);
+ expect(identity_uint64_t(BigInt(100))).toBe(100);
+
+ expect(identity_int64_t(bigIntArray[0])).toBe(-1);
+ expect(identity_int64_t(bigIntArray[0] - BigInt(1))).toBe(-2);
+ } else {
+ expect(identity_uint64_t(0)).toBe(0n);
+ expect(identity_uint64_t(100)).toBe(100n);
+ expect(identity_uint64_t(BigInt(100))).toBe(100n);
+
+ expect(identity_int64_t(bigIntArray[0])).toBe(bigIntArray[0]);
+ expect(identity_int64_t(bigIntArray[0] - BigInt(1))).toBe(bigIntArray[0] - BigInt(1));
+ }
+ Bun.gc(true);
+ expect(add_char.native(1, 1)).toBe(2);
+
+ expect(add_float(2.4, 2.8)).toBe(Math.fround(5.2));
+ expect(add_double(4.2, 0.1)).toBe(4.3);
+ expect(add_int8_t(1, 1)).toBe(2);
+ expect(add_int16_t(1, 1)).toBe(2);
+ expect(add_int32_t(1, 1)).toBe(2);
+ if (fast) expect(add_int64_t(1, 1)).toBe(2);
+ else expect(add_int64_t(1n, 1n)).toBe(2n);
+ expect(add_uint8_t(1, 1)).toBe(2);
+ expect(add_uint16_t(1, 1)).toBe(2);
+ expect(add_uint32_t(1, 1)).toBe(2);
+ Bun.gc(true);
+ expect(is_null(null)).toBe(true);
+ const cptr = ptr_should_point_to_42_as_int32_t();
+ expect(cptr != 0).toBe(true);
+ expect(typeof cptr === "number").toBe(true);
+ expect(does_pointer_equal_42_as_int32_t(cptr)).toBe(true);
+ const buffer = toBuffer(cptr, 0, 4);
+ expect(buffer.readInt32(0)).toBe(42);
+ expect(new DataView(toArrayBuffer(cptr, 0, 4), 0, 4).getInt32(0, true)).toBe(42);
+ expect(ptr(buffer)).toBe(cptr);
+ expect(new CString(cptr, 0, 1).toString()).toBe("*");
+ expect(identity_ptr(cptr)).toBe(cptr);
+ const second_ptr = ptr(new Buffer(8));
+ expect(identity_ptr(second_ptr)).toBe(second_ptr);
+ });
+
+ it("CFunction", () => {
+ var myCFunction = new CFunction({
+ ptr: return_a_function_ptr_to_function_that_returns_true(),
+ returns: "bool",
+ });
+ expect(myCFunction()).toBe(true);
+ });
+
+ const typeMap = {
+ int8_t: -8,
+ int16_t: -16,
+ int32_t: -32,
+ int64_t: -64n,
+ uint8_t: 8,
+ uint16_t: 16,
+ uint32_t: 32,
+ uint64_t: 64n,
+ float: 32.5,
+ double: 64.5,
+ ptr: 0xdeadbeef,
+ "void*": null,
+ };
+
+ it("JSCallback", () => {
+ var toClose = new JSCallback(
+ input => {
+ return input;
+ },
+ {
+ returns: "bool",
+ args: ["bool"],
+ },
+ );
+ expect(toClose.ptr > 0).toBe(true);
+ toClose.close();
+ expect(toClose.ptr === null).toBe(true);
+ });
+
+ describe("callbacks", () => {
+ // Return types, 1 argument
+ for (let [returnName, returnValue] of Object.entries(typeMap)) {
+ it("fn(" + returnName + ") " + returnName, () => {
+ var roundtripFunction = new CFunction({
+ ptr: new JSCallback(
+ input => {
+ return input;
+ },
+ {
+ returns: returnName,
+ args: [returnName],
+ },
+ ).ptr,
+ returns: returnName,
+ args: [returnName],
+ });
+ expect(roundtripFunction(returnValue)).toBe(returnValue);
+ });
+ }
+ // Return types, no args
+ for (let [name, value] of Object.entries(typeMap)) {
+ it("fn() " + name, () => {
+ var roundtripFunction = new CFunction({
+ ptr: new JSCallback(() => value, {
+ returns: name,
+ }).ptr,
+ returns: name,
+ });
+ expect(roundtripFunction()).toBe(value);
+ });
+ }
+ });
+
+ describe("threadsafe callback", done => {
+ // 1 arg, threadsafe
+ for (let [name, value] of Object.entries(typeMap)) {
+ it("fn(" + name + ") " + name, async () => {
+ const cb = new JSCallback(
+ arg1 => {
+ expect(arg1).toBe(value);
+ },
+ {
+ args: [name],
+ threadsafe: true,
+ },
+ );
+ var roundtripFunction = new CFunction({
+ ptr: cb.ptr,
+ returns: "void",
+ args: [name],
+ });
+ roundtripFunction(value);
+ await 1;
+ });
+ }
+ });
+
+ afterAll(() => {
+ close();
+ });
+ });
+}
+
+it("read", () => {
+ const buffer = new BigInt64Array(16);
+ const dataView = new DataView(buffer.buffer);
+ const addr = ptr(buffer);
+
+ for (let i = 0; i < buffer.length; i++) {
+ buffer[i] = BigInt(i);
+ expect(read.intptr(addr, i * 8)).toBe(Number(dataView.getBigInt64(i * 8, true)));
+ expect(read.ptr(addr, i * 8)).toBe(Number(dataView.getBigUint64(i * 8, true)));
+ expect(read.f64(addr, i + 8)).toBe(dataView.getFloat64(i + 8, true));
+ expect(read.i64(addr, i * 8)).toBe(dataView.getBigInt64(i * 8, true));
+ expect(read.u64(addr, i * 8)).toBe(dataView.getBigUint64(i * 8, true));
+ }
+
+ for (let i = 0; i < buffer.byteLength - 4; i++) {
+ // read is intended to behave like DataView
+ // but instead of doing
+ // new DataView(toArrayBuffer(myPtr)).getInt8(0, true)
+ // you can do
+ // read.i8(myPtr, 0)
+ expect(read.i8(addr, i)).toBe(dataView.getInt8(i, true));
+ expect(read.i16(addr, i)).toBe(dataView.getInt16(i, true));
+ expect(read.i32(addr, i)).toBe(dataView.getInt32(i, true));
+ expect(read.u8(addr, i)).toBe(dataView.getUint8(i, true));
+ expect(read.u16(addr, i)).toBe(dataView.getUint16(i, true));
+ expect(read.u32(addr, i)).toBe(dataView.getUint32(i, true));
+ expect(read.f32(addr, i)).toBe(dataView.getFloat32(i, true));
+ }
+});
+
+if (ok) {
+ describe("run ffi", () => {
+ ffiRunner(false);
+ ffiRunner(true);
+ });
+} else {
+ it.skip("run ffi", () => {});
+}
diff --git a/test/js/bun/globals.test.js b/test/js/bun/globals.test.js
new file mode 100644
index 000000000..5d491cde7
--- /dev/null
+++ b/test/js/bun/globals.test.js
@@ -0,0 +1,51 @@
+import { it, describe, expect } from "bun:test";
+
+it("extendable", () => {
+ const classes = [Blob, TextDecoder, TextEncoder, Request, Response, Headers, HTMLRewriter, Bun.Transpiler, Buffer];
+ // None of these should error
+ for (let Class of classes) {
+ var Foo = class extends Class {};
+ var bar = new Foo();
+ expect(bar instanceof Class).toBe(true);
+ expect(!!Class.prototype).toBe(true);
+ expect(typeof Class.prototype).toBe("object");
+ }
+ expect(true).toBe(true);
+});
+
+it("writable", () => {
+ const classes = [
+ // ["Blob", Blob],
+ ["TextDecoder", TextDecoder],
+ // ["TextEncoder", TextEncoder],
+ ["Request", Request],
+ ["Response", Response],
+ ["Headers", Headers],
+ ["Buffer", Buffer],
+ // ["HTMLRewriter", HTMLRewriter],
+ // ["Transpiler", Bun.Transpiler],
+ ];
+ for (let [name, Class] of classes) {
+ globalThis[name] = 123;
+ expect(globalThis[name]).toBe(123);
+ globalThis[name] = Class;
+ expect(globalThis[name]).toBe(Class);
+ }
+});
+
+it("name", () => {
+ const classes = [
+ ["Blob", Blob],
+ ["TextDecoder", TextDecoder],
+ ["TextEncoder", TextEncoder],
+ ["Request", Request],
+ ["Response", Response],
+ ["Headers", Headers],
+ ["HTMLRewriter", HTMLRewriter],
+ ["Transpiler", Bun.Transpiler],
+ ["Buffer", Buffer],
+ ];
+ for (let [name, Class] of classes) {
+ expect(Class.name).toBe(name);
+ }
+});
diff --git a/test/js/bun/http/bun-server.test.ts b/test/js/bun/http/bun-server.test.ts
new file mode 100644
index 000000000..52574d2a3
--- /dev/null
+++ b/test/js/bun/http/bun-server.test.ts
@@ -0,0 +1,171 @@
+import { describe, expect, test } from "bun:test";
+
+describe("Server", () => {
+ test("returns active port when initializing server with 0 port", () => {
+ const server = Bun.serve({
+ fetch() {
+ return new Response("Hello");
+ },
+ port: 0,
+ });
+
+ expect(server.port).not.toBe(0);
+ expect(server.port).toBeDefined();
+ server.stop(true);
+ });
+
+ test("allows connecting to server", async () => {
+ const server = Bun.serve({
+ fetch() {
+ return new Response("Hello");
+ },
+ port: 0,
+ });
+
+ const response = await fetch(`http://${server.hostname}:${server.port}`);
+ expect(await response.text()).toBe("Hello");
+ server.stop(true);
+ });
+
+ test("abort signal on server", async () => {
+ {
+ let signalOnServer = false;
+ const server = Bun.serve({
+ async fetch(req) {
+ req.signal.addEventListener("abort", () => {
+ signalOnServer = true;
+ });
+ await Bun.sleep(15);
+ return new Response("Hello");
+ },
+ port: 0,
+ });
+
+ try {
+ await fetch(`http://${server.hostname}:${server.port}`, { signal: AbortSignal.timeout(10) });
+ } catch {}
+ expect(signalOnServer).toBe(true);
+ server.stop(true);
+ }
+ });
+
+ test("abort signal on server should only fire if aborted", async () => {
+ {
+ const abortController = new AbortController();
+
+ let signalOnServer = false;
+ const server = Bun.serve({
+ async fetch(req) {
+ req.signal.addEventListener("abort", () => {
+ signalOnServer = true;
+ });
+ return new Response("Hello");
+ },
+ port: 0,
+ });
+
+ try {
+ await fetch(`http://${server.hostname}:${server.port}`, { signal: abortController.signal });
+ } catch {}
+ expect(signalOnServer).toBe(false);
+ server.stop(true);
+ }
+ });
+
+ test("abort signal on server with direct stream", async () => {
+ {
+ let signalOnServer = false;
+ const abortController = new AbortController();
+
+ const server = Bun.serve({
+ async fetch(req) {
+ req.signal.addEventListener("abort", () => {
+ signalOnServer = true;
+ });
+ return new Response(
+ new ReadableStream({
+ type: "direct",
+ async pull(controller) {
+ abortController.abort();
+
+ const buffer = await Bun.file(import.meta.dir + "/fixture.html.gz").arrayBuffer();
+ controller.write(buffer);
+
+ //wait to detect the connection abortion
+ await Bun.sleep(15);
+
+ controller.close();
+ },
+ }),
+ {
+ headers: {
+ "Content-Encoding": "gzip",
+ "Content-Type": "text/html; charset=utf-8",
+ "Content-Length": "1",
+ },
+ },
+ );
+ },
+ port: 0,
+ });
+
+ try {
+ await fetch(`http://${server.hostname}:${server.port}`, { signal: abortController.signal });
+ } catch {}
+ await Bun.sleep(10);
+ expect(signalOnServer).toBe(true);
+ server.stop(true);
+ }
+ });
+
+ test("abort signal on server with stream", async () => {
+ {
+ let signalOnServer = false;
+ const abortController = new AbortController();
+
+ const server = Bun.serve({
+ async fetch(req) {
+ req.signal.addEventListener("abort", () => {
+ signalOnServer = true;
+ });
+ return new Response(
+ new ReadableStream({
+ async pull(controller) {
+ console.trace("here");
+ abortController.abort();
+
+ const buffer = await Bun.file(import.meta.dir + "/fixture.html.gz").arrayBuffer();
+ console.trace("here");
+ controller.enqueue(buffer);
+ console.trace("here");
+
+ //wait to detect the connection abortion
+ await Bun.sleep(15);
+ controller.close();
+ },
+ }),
+ {
+ headers: {
+ "Content-Encoding": "gzip",
+ "Content-Type": "text/html; charset=utf-8",
+ "Content-Length": "1",
+ },
+ },
+ );
+ },
+ port: 0,
+ });
+
+ try {
+ console.trace("here");
+ await fetch(`http://${server.hostname}:${server.port}`, { signal: abortController.signal });
+ } catch {}
+ await Bun.sleep(10);
+ console.trace("here");
+ expect(signalOnServer).toBe(true);
+ console.trace("here");
+ server.stop(true);
+ console.trace("here");
+ }
+ });
+});
diff --git a/test/js/bun/http/fetch.js.txt b/test/js/bun/http/fetch.js.txt
new file mode 100644
index 000000000..5a9b52fcf
--- /dev/null
+++ b/test/js/bun/http/fetch.js.txt
@@ -0,0 +1,46 @@
+<!doctype html>
+<html>
+<head>
+ <title>Example Domain</title>
+
+ <meta charset="utf-8" />
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <style type="text/css">
+ body {
+ background-color: #f0f0f2;
+ margin: 0;
+ padding: 0;
+ font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+
+ }
+ div {
+ width: 600px;
+ margin: 5em auto;
+ padding: 2em;
+ background-color: #fdfdff;
+ border-radius: 0.5em;
+ box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);
+ }
+ a:link, a:visited {
+ color: #38488f;
+ text-decoration: none;
+ }
+ @media (max-width: 700px) {
+ div {
+ margin: 0 auto;
+ width: auto;
+ }
+ }
+ </style>
+</head>
+
+<body>
+<div>
+ <h1>Example Domain</h1>
+ <p>This domain is for use in illustrative examples in documents. You may use this
+ domain in literature without prior coordination or asking for permission.</p>
+ <p><a href="https://www.iana.org/domains/example">More information...</a></p>
+</div>
+</body>
+</html>
diff --git a/test/js/bun/http/fixture.html.gz b/test/js/bun/http/fixture.html.gz
new file mode 100644
index 000000000..0bb85d4cb
--- /dev/null
+++ b/test/js/bun/http/fixture.html.gz
Binary files differ
diff --git a/test/js/bun/http/proxy.test.js b/test/js/bun/http/proxy.test.js
new file mode 100644
index 000000000..abe05133d
--- /dev/null
+++ b/test/js/bun/http/proxy.test.js
@@ -0,0 +1,69 @@
+import { afterAll, beforeAll, describe, expect, it } from "bun:test";
+import { gc } from "harness";
+
+let proxy, server;
+
+// TODO: Proxy with TLS requests
+
+beforeAll(() => {
+ proxy = Bun.serve({
+ async fetch(request) {
+ // if is not an proxy connection just drop it
+ if (!request.headers.has("proxy-connection")) {
+ return new Response("Bad Request", { status: 400 });
+ }
+
+ // simple http proxy
+ if (request.url.startsWith("http://")) {
+ return await fetch(request.url, {
+ method: request.method,
+ body: await request.text(),
+ });
+ }
+
+ // no TLS support here
+ return new Response("Bad Request", { status: 400 });
+ },
+ port: 54312,
+ });
+ server = Bun.serve({
+ async fetch(request) {
+ if (request.method === "POST") {
+ const text = await request.text();
+ return new Response(text, { status: 200 });
+ }
+ return new Response("Hello, World", { status: 200 });
+ },
+ port: 54322,
+ });
+});
+
+afterAll(() => {
+ server.stop();
+ proxy.stop();
+});
+
+describe("proxy", () => {
+ const requests = [
+ [new Request("http://localhost:54322"), "fetch() GET with non-TLS Proxy", "http://localhost:54312"],
+ [
+ new Request("http://localhost:54322", {
+ method: "POST",
+ body: "Hello, World",
+ }),
+ "fetch() POST with non-TLS Proxy",
+ "http://localhost:54312",
+ ],
+ ];
+ for (let [request, name, proxy] of requests) {
+ gc();
+ it(name, async () => {
+ gc();
+ const response = await fetch(request, { verbose: true, proxy });
+ gc();
+ const text = await response.text();
+ gc();
+ expect(text).toBe("Hello, World");
+ });
+ }
+});
diff --git a/test/js/bun/http/serve.leak.ts b/test/js/bun/http/serve.leak.ts
new file mode 100644
index 000000000..09935d24d
--- /dev/null
+++ b/test/js/bun/http/serve.leak.ts
@@ -0,0 +1,29 @@
+import { heapStats } from "bun:jsc";
+var prevCounts;
+export default {
+ fetch(req) {
+ const out = {};
+ const counts = heapStats().objectTypeCounts;
+ for (const key in counts) {
+ if (prevCounts) {
+ if (prevCounts[key] && counts[key] > prevCounts[key]) {
+ out[key] = counts[key];
+ }
+ } else {
+ if (counts[key] > 1) {
+ out[key] = counts[key];
+ }
+ }
+ }
+ prevCounts = counts;
+ if (req.url.includes("gc")) {
+ Bun.gc(false);
+ }
+
+ return new Response(JSON.stringify(out), {
+ headers: {
+ "content-type": "application/json",
+ },
+ });
+ },
+};
diff --git a/test/js/bun/http/serve.test.ts b/test/js/bun/http/serve.test.ts
new file mode 100644
index 000000000..c049aad1a
--- /dev/null
+++ b/test/js/bun/http/serve.test.ts
@@ -0,0 +1,981 @@
+import { file, gc, Serve, serve, Server } from "bun";
+import { afterEach, describe, it, expect, afterAll } from "bun:test";
+import { readFileSync, writeFileSync } from "fs";
+import { resolve } from "path";
+
+afterEach(() => gc(true));
+
+const count = 200;
+let port = 10000;
+let server: Server | undefined;
+
+async function runTest({ port, ...serverOptions }: Serve<any>, test: (server: Serve<any>) => Promise<void> | void) {
+ if (server) {
+ server.reload({ ...serverOptions, port: 0 });
+ } else {
+ while (!server) {
+ try {
+ server = serve({ ...serverOptions, port: 0 });
+ break;
+ } catch (e: any) {
+ if (e?.message !== `Failed to start server `) {
+ throw e;
+ }
+ }
+ }
+ }
+
+ await test(server);
+}
+
+afterAll(() => {
+ if (server) {
+ server.stop(true);
+ server = undefined;
+ }
+});
+
+[100, 101, 418, 999].forEach(statusCode => {
+ it(`should response with HTTP status code (${statusCode})`, async () => {
+ await runTest(
+ {
+ fetch() {
+ return new Response("Foo Bar", { status: statusCode });
+ },
+ },
+ async server => {
+ const response = await fetch(`http://${server.hostname}:${server.port}`);
+ expect(response.status).toBe(statusCode);
+ expect(await response.text()).toBe("Foo Bar");
+ },
+ );
+ });
+});
+
+[-200, 42, 12345, Math.PI].forEach(statusCode => {
+ it(`should ignore invalid HTTP status code (${statusCode})`, async () => {
+ await runTest(
+ {
+ fetch() {
+ return new Response("Foo Bar", { status: statusCode });
+ },
+ },
+ async server => {
+ const response = await fetch(`http://${server.hostname}:${server.port}`);
+ expect(response.status).toBe(200);
+ expect(await response.text()).toBe("Foo Bar");
+ },
+ );
+ });
+});
+
+it("should display a welcome message when the response value type is incorrect", async () => {
+ await runTest(
+ {
+ fetch(req) {
+ return Symbol("invalid response type");
+ },
+ },
+ async server => {
+ const response = await fetch(`http://${server.hostname}:${server.port}`);
+ const text = await response.text();
+ expect(text).toContain("Welcome to Bun!");
+ },
+ );
+});
+
+it("request.signal works in trivial case", async () => {
+ var aborty = new AbortController();
+ var didAbort = false;
+ await runTest(
+ {
+ async fetch(req) {
+ req.signal.addEventListener("abort", () => {
+ didAbort = true;
+ });
+ expect(didAbort).toBe(false);
+ aborty.abort();
+ await Bun.sleep(2);
+ return new Response("Test failed!");
+ },
+ },
+ async server => {
+ try {
+ await fetch(`http://${server.hostname}:${server.port}`, { signal: aborty.signal });
+ throw new Error("Expected fetch to throw");
+ } catch (e: any) {
+ expect(e.name).toBe("AbortError");
+ }
+ await Bun.sleep(1);
+
+ expect(didAbort).toBe(true);
+ },
+ );
+});
+
+it("request.signal works in leaky case", async () => {
+ var aborty = new AbortController();
+ var didAbort = false;
+ var leaky;
+ await runTest(
+ {
+ async fetch(req) {
+ leaky = req;
+ expect(didAbort).toBe(false);
+ aborty.abort();
+ await Bun.sleep(2);
+ return new Response("Test failed!");
+ },
+ },
+ async server => {
+ try {
+ const resp = fetch(`http://${server.hostname}:${server.port}`, { signal: aborty.signal });
+
+ await Bun.sleep(1);
+
+ leaky.signal.addEventListener("abort", () => {
+ didAbort = true;
+ });
+
+ await resp;
+
+ throw new Error("Expected fetch to throw");
+ } catch (e: any) {
+ expect(e.name).toBe("AbortError");
+ }
+
+ await Bun.sleep(1);
+
+ expect(didAbort).toBe(true);
+ },
+ );
+});
+
+it("should work for a file", async () => {
+ const fixture = resolve(import.meta.dir, "./fetch.js.txt");
+ const textToExpect = readFileSync(fixture, "utf-8");
+ await runTest(
+ {
+ fetch(req) {
+ return new Response(file(fixture));
+ },
+ },
+ async server => {
+ const response = await fetch(`http://${server.hostname}:${server.port}`);
+ expect(await response.text()).toBe(textToExpect);
+ },
+ );
+});
+
+it("request.url should log successfully", async () => {
+ const fixture = resolve(import.meta.dir, "./fetch.js.txt");
+ const textToExpect = readFileSync(fixture, "utf-8");
+ var expected;
+ await runTest(
+ {
+ fetch(req) {
+ expect(Bun.inspect(req).includes(expected)).toBe(true);
+ return new Response(file(fixture));
+ },
+ },
+ async server => {
+ expected = `http://localhost:${server.port}/helloooo`;
+ const response = await fetch(expected);
+ expect(response.url).toBe(expected);
+ expect(await response.text()).toBe(textToExpect);
+ },
+ );
+});
+
+it("request.url should be based on the Host header", async () => {
+ const fixture = resolve(import.meta.dir, "./fetch.js.txt");
+ const textToExpect = readFileSync(fixture, "utf-8");
+ await runTest(
+ {
+ fetch(req) {
+ expect(req.url).toBe("http://example.com/helloooo");
+ return new Response(file(fixture));
+ },
+ },
+ async server => {
+ const expected = `http://${server.hostname}:${server.port}/helloooo`;
+ const response = await fetch(expected, {
+ headers: {
+ Host: "example.com",
+ },
+ });
+ expect(response.url).toBe(expected);
+ expect(await response.text()).toBe(textToExpect);
+ },
+ );
+});
+
+describe("streaming", () => {
+ describe("error handler", () => {
+ it("throw on pull reports an error and close the connection", async () => {
+ var pass = false;
+ await runTest(
+ {
+ error(e) {
+ pass = true;
+ return new Response("PASS", { status: 555 });
+ },
+ fetch(req) {
+ return new Response(
+ new ReadableStream({
+ pull(controller) {
+ throw new Error("FAIL");
+ },
+ }),
+ );
+ },
+ },
+ async server => {
+ const response = await fetch(`http://${server.hostname}:${server.port}`);
+ if (response.status > 0) {
+ expect(response.status).toBe(555);
+ expect(await response.text()).toBe("PASS");
+ }
+ expect(pass).toBe(true);
+ },
+ );
+ });
+
+ it("throw on pull after writing should not call the error handler", async () => {
+ var pass = true;
+ await runTest(
+ {
+ error(e) {
+ pass = false;
+ return new Response("FAIL", { status: 555 });
+ },
+ fetch(req) {
+ return new Response(
+ new ReadableStream({
+ async pull(controller) {
+ controller.enqueue("PASS");
+ controller.close();
+ throw new Error("error");
+ },
+ }),
+ );
+ },
+ },
+ async server => {
+ const response = await fetch(`http://${server.hostname}:${server.port}`);
+ // connection terminated
+ expect(response.status).toBe(200);
+ expect(await response.text()).toBe("PASS");
+ expect(pass).toBe(true);
+ },
+ );
+ });
+ });
+
+ it("text from JS, one chunk", async () => {
+ const relative = new URL("./fetch.js.txt", import.meta.url);
+ const textToExpect = readFileSync(relative, "utf-8");
+ await runTest(
+ {
+ fetch(req) {
+ return new Response(
+ new ReadableStream({
+ start(controller) {
+ controller.enqueue(textToExpect);
+ controller.close();
+ },
+ }),
+ );
+ },
+ },
+ async server => {
+ const response = await fetch(`http://${server.hostname}:${server.port}`);
+ const text = await response.text();
+ expect(text.length).toBe(textToExpect.length);
+ expect(text).toBe(textToExpect);
+ },
+ );
+ });
+ it("text from JS, two chunks", async () => {
+ const fixture = resolve(import.meta.dir, "./fetch.js.txt");
+ const textToExpect = readFileSync(fixture, "utf-8");
+ await runTest(
+ {
+ fetch(req) {
+ return new Response(
+ new ReadableStream({
+ start(controller) {
+ controller.enqueue(textToExpect.substring(0, 100));
+ controller.enqueue(textToExpect.substring(100));
+ controller.close();
+ },
+ }),
+ );
+ },
+ },
+ async server => {
+ const response = await fetch(`http://${server.hostname}:${server.port}`);
+ expect(await response.text()).toBe(textToExpect);
+ },
+ );
+ });
+
+ it("Error handler is called when a throwing stream hasn't written anything", async () => {
+ await runTest(
+ {
+ error(e) {
+ return new Response("Test Passed", { status: 200 });
+ },
+
+ fetch(req) {
+ return new Response(
+ new ReadableStream({
+ start(controller) {
+ throw new Error("Test Passed");
+ },
+ }),
+ {
+ status: 404,
+ },
+ );
+ },
+ },
+ async server => {
+ const response = await fetch(`http://${server.hostname}:${server.port}`);
+ expect(response.status).toBe(200);
+ expect(await response.text()).toBe("Test Passed");
+ },
+ );
+ });
+
+ // Also verifies error handler reset in `.reload()` due to test above
+ it("text from JS throws on start with no error handler", async () => {
+ await runTest(
+ {
+ error: undefined,
+
+ fetch(req) {
+ return new Response(
+ new ReadableStream({
+ start(controller) {
+ throw new Error("Test Passed");
+ },
+ }),
+ {
+ status: 420,
+ headers: {
+ "x-what": "123",
+ },
+ },
+ );
+ },
+ },
+ async server => {
+ const response = await fetch(`http://${server.hostname}:${server.port}`);
+ expect(response.status).toBe(500);
+ },
+ );
+ });
+
+ it("text from JS throws on start has error handler", async () => {
+ var pass = false;
+ var err;
+ await runTest(
+ {
+ error(e) {
+ pass = true;
+ err = e;
+ return new Response("Fail", { status: 500 });
+ },
+ fetch(req) {
+ return new Response(
+ new ReadableStream({
+ start(controller) {
+ throw new TypeError("error");
+ },
+ }),
+ );
+ },
+ },
+ async server => {
+ const response = await fetch(`http://${server.hostname}:${server.port}`);
+ expect(response.status).toBe(500);
+ expect(await response.text()).toBe("Fail");
+ expect(pass).toBe(true);
+ expect(err?.name).toBe("TypeError");
+ expect(err?.message).toBe("error");
+ },
+ );
+ });
+
+ it("text from JS, 2 chunks, with delay", async () => {
+ const fixture = resolve(import.meta.dir, "./fetch.js.txt");
+ const textToExpect = readFileSync(fixture, "utf-8");
+ await runTest(
+ {
+ fetch(req) {
+ return new Response(
+ new ReadableStream({
+ start(controller) {
+ controller.enqueue(textToExpect.substring(0, 100));
+ queueMicrotask(() => {
+ controller.enqueue(textToExpect.substring(100));
+ controller.close();
+ });
+ },
+ }),
+ );
+ },
+ },
+ async server => {
+ const response = await fetch(`http://${server.hostname}:${server.port}`);
+ expect(await response.text()).toBe(textToExpect);
+ },
+ );
+ });
+
+ it("text from JS, 1 chunk via pull()", async () => {
+ const fixture = resolve(import.meta.dir, "./fetch.js.txt");
+ const textToExpect = readFileSync(fixture, "utf-8");
+ await runTest(
+ {
+ fetch(req) {
+ return new Response(
+ new ReadableStream({
+ pull(controller) {
+ controller.enqueue(textToExpect);
+ controller.close();
+ },
+ }),
+ );
+ },
+ },
+ async server => {
+ const response = await fetch(`http://${server.hostname}:${server.port}`);
+ const text = await response.text();
+ expect(text).toBe(textToExpect);
+ },
+ );
+ });
+
+ it("text from JS, 2 chunks, with delay in pull", async () => {
+ const fixture = resolve(import.meta.dir, "./fetch.js.txt");
+ const textToExpect = readFileSync(fixture, "utf-8");
+ await runTest(
+ {
+ fetch(req) {
+ return new Response(
+ new ReadableStream({
+ pull(controller) {
+ controller.enqueue(textToExpect.substring(0, 100));
+ queueMicrotask(() => {
+ controller.enqueue(textToExpect.substring(100));
+ controller.close();
+ });
+ },
+ }),
+ );
+ },
+ },
+ async server => {
+ const response = await fetch(`http://${server.hostname}:${server.port}`);
+ expect(await response.text()).toBe(textToExpect);
+ },
+ );
+ });
+
+ it("text from JS, 3 chunks, 1 empty, with delay in pull", async () => {
+ const textToExpect = "hello world";
+ const groups = [
+ ["hello", "", " world"],
+ ["", "hello ", "world"],
+ ["hello ", "world", ""],
+ ["hello world", "", ""],
+ ["", "", "hello world"],
+ ];
+ var count = 0;
+
+ for (const chunks of groups) {
+ await runTest(
+ {
+ fetch(req) {
+ return new Response(
+ new ReadableStream({
+ async pull(controller) {
+ for (let chunk of chunks) {
+ controller.enqueue(Buffer.from(chunk));
+ await 1;
+ }
+ await 1;
+ controller.close();
+ },
+ }),
+ );
+ },
+ },
+ async server => {
+ const response = await fetch(`http://${server.hostname}:${server.port}`);
+ expect(await response.text()).toBe(textToExpect);
+ count++;
+ },
+ );
+ }
+ expect(count).toBe(groups.length);
+ });
+
+ it("text from JS, 2 chunks, with async pull", async () => {
+ const fixture = resolve(import.meta.dir, "./fetch.js.txt");
+ const textToExpect = readFileSync(fixture, "utf-8");
+ await runTest(
+ {
+ fetch(req) {
+ return new Response(
+ new ReadableStream({
+ async pull(controller) {
+ controller.enqueue(textToExpect.substring(0, 100));
+ await Promise.resolve();
+ controller.enqueue(textToExpect.substring(100));
+ await Promise.resolve();
+ controller.close();
+ },
+ }),
+ );
+ },
+ },
+ async server => {
+ const response = await fetch(`http://${server.hostname}:${server.port}`);
+ expect(await response.text()).toBe(textToExpect);
+ },
+ );
+ });
+
+ it("text from JS, 10 chunks, with async pull", async () => {
+ const fixture = resolve(import.meta.dir, "./fetch.js.txt");
+ const textToExpect = readFileSync(fixture, "utf-8");
+ await runTest(
+ {
+ fetch(req) {
+ return new Response(
+ new ReadableStream({
+ async pull(controller) {
+ var remain = textToExpect;
+ for (let i = 0; i < 10 && remain.length > 0; i++) {
+ controller.enqueue(remain.substring(0, 100));
+ remain = remain.substring(100);
+ await new Promise(resolve => queueMicrotask(resolve));
+ }
+
+ controller.enqueue(remain);
+ controller.close();
+ },
+ }),
+ );
+ },
+ },
+ async server => {
+ const response = await fetch(`http://${server.hostname}:${server.port}`);
+ expect(await response.text()).toBe(textToExpect);
+ },
+ );
+ });
+});
+
+it("should work for a hello world", async () => {
+ await runTest(
+ {
+ fetch(req) {
+ return new Response(`Hello, world!`);
+ },
+ },
+ async server => {
+ const response = await fetch(`http://${server.hostname}:${server.port}`);
+ expect(await response.text()).toBe("Hello, world!");
+ },
+ );
+});
+
+it("should work for a blob", async () => {
+ const fixture = resolve(import.meta.dir, "./fetch.js.txt");
+ const textToExpect = readFileSync(fixture, "utf-8");
+ await runTest(
+ {
+ fetch(req) {
+ return new Response(new Blob([textToExpect]));
+ },
+ },
+ async server => {
+ const response = await fetch(`http://${server.hostname}:${server.port}`);
+ expect(await response.text()).toBe(textToExpect);
+ },
+ );
+});
+
+it("should work for a blob stream", async () => {
+ const fixture = resolve(import.meta.dir, "./fetch.js.txt");
+ const textToExpect = readFileSync(fixture, "utf-8");
+ await runTest(
+ {
+ fetch(req) {
+ return new Response(new Blob([textToExpect]).stream());
+ },
+ },
+ async server => {
+ const response = await fetch(`http://${server.hostname}:${server.port}`);
+ expect(await response.text()).toBe(textToExpect);
+ },
+ );
+});
+
+it("should work for a file stream", async () => {
+ const fixture = resolve(import.meta.dir, "./fetch.js.txt");
+ const textToExpect = readFileSync(fixture, "utf-8");
+ await runTest(
+ {
+ fetch(req) {
+ return new Response(file(fixture).stream());
+ },
+ },
+ async server => {
+ const response = await fetch(`http://${server.hostname}:${server.port}`);
+ expect(await response.text()).toBe(textToExpect);
+ },
+ );
+});
+
+it("fetch should work with headers", async () => {
+ const fixture = resolve(import.meta.dir, "./fetch.js.txt");
+ await runTest(
+ {
+ fetch(req) {
+ if (req.headers.get("X-Foo") !== "bar") {
+ return new Response("X-Foo header not set", { status: 500 });
+ }
+ return new Response(file(fixture), {
+ headers: { "X-Both-Ways": "1" },
+ });
+ },
+ },
+ async server => {
+ const response = await fetch(`http://${server.hostname}:${server.port}`, {
+ headers: {
+ "X-Foo": "bar",
+ },
+ });
+ expect(response.status).toBe(200);
+ expect(response.headers.get("X-Both-Ways")).toBe("1");
+ },
+ );
+});
+
+it(`should work for a file ${count} times serial`, async () => {
+ const fixture = resolve(import.meta.dir, "./fetch.js.txt");
+ const textToExpect = readFileSync(fixture, "utf-8");
+ await runTest(
+ {
+ async fetch(req) {
+ return new Response(file(fixture));
+ },
+ },
+ async server => {
+ for (let i = 0; i < count; i++) {
+ const response = await fetch(`http://${server.hostname}:${server.port}`);
+ expect(await response.text()).toBe(textToExpect);
+ }
+ },
+ );
+});
+
+it(`should work for ArrayBuffer ${count} times serial`, async () => {
+ const textToExpect = "hello";
+ await runTest(
+ {
+ fetch(req) {
+ return new Response(new TextEncoder().encode(textToExpect));
+ },
+ },
+ async server => {
+ for (let i = 0; i < count; i++) {
+ const response = await fetch(`http://${server.hostname}:${server.port}`);
+ expect(await response.text()).toBe(textToExpect);
+ }
+ },
+ );
+});
+
+describe("parallel", () => {
+ it(`should work for text ${count} times in batches of 5`, async () => {
+ const textToExpect = "hello";
+ await runTest(
+ {
+ fetch(req) {
+ return new Response(textToExpect);
+ },
+ },
+ async server => {
+ for (let i = 0; i < count; ) {
+ let responses = await Promise.all([
+ fetch(`http://${server.hostname}:${server.port}`),
+ fetch(`http://${server.hostname}:${server.port}`),
+ fetch(`http://${server.hostname}:${server.port}`),
+ fetch(`http://${server.hostname}:${server.port}`),
+ fetch(`http://${server.hostname}:${server.port}`),
+ ]);
+
+ for (let response of responses) {
+ expect(await response.text()).toBe(textToExpect);
+ }
+ i += responses.length;
+ }
+ },
+ );
+ });
+ it(`should work for Uint8Array ${count} times in batches of 5`, async () => {
+ const textToExpect = "hello";
+ await runTest(
+ {
+ fetch(req) {
+ return new Response(new TextEncoder().encode(textToExpect));
+ },
+ },
+ async server => {
+ for (let i = 0; i < count; ) {
+ let responses = await Promise.all([
+ fetch(`http://${server.hostname}:${server.port}`),
+ fetch(`http://${server.hostname}:${server.port}`),
+ fetch(`http://${server.hostname}:${server.port}`),
+ fetch(`http://${server.hostname}:${server.port}`),
+ fetch(`http://${server.hostname}:${server.port}`),
+ ]);
+
+ for (let response of responses) {
+ expect(await response.text()).toBe(textToExpect);
+ }
+ i += responses.length;
+ }
+ },
+ );
+ });
+});
+
+it("should support reloading", async () => {
+ const first = req => new Response("first");
+ const second = req => new Response("second");
+ await runTest(
+ {
+ fetch: first,
+ },
+ async server => {
+ const response = await fetch(`http://${server.hostname}:${server.port}`);
+ expect(await response.text()).toBe("first");
+ server.reload({ fetch: second });
+ const response2 = await fetch(`http://${server.hostname}:${server.port}`);
+ expect(await response2.text()).toBe("second");
+ },
+ );
+});
+
+describe("status code text", () => {
+ const fixture = {
+ 200: "OK",
+ 201: "Created",
+ 202: "Accepted",
+ 203: "Non-Authoritative Information",
+ 204: "No Content",
+ 205: "Reset Content",
+ 206: "Partial Content",
+ 207: "Multi-Status",
+ 208: "Already Reported",
+ 226: "IM Used",
+ 300: "Multiple Choices",
+ 301: "Moved Permanently",
+ 302: "Found",
+ 303: "See Other",
+ 304: "Not Modified",
+ 305: "Use Proxy",
+ 306: "Switch Proxy",
+ 307: "Temporary Redirect",
+ 308: "Permanent Redirect",
+ 400: "Bad Request",
+ 401: "Unauthorized",
+ 402: "Payment Required",
+ 403: "Forbidden",
+ 404: "Not Found",
+ 405: "Method Not Allowed",
+ 406: "Not Acceptable",
+ 407: "Proxy Authentication Required",
+ 408: "Request Timeout",
+ 409: "Conflict",
+ 410: "Gone",
+ 411: "Length Required",
+ 412: "Precondition Failed",
+ 413: "Payload Too Large",
+ 414: "URI Too Long",
+ 415: "Unsupported Media Type",
+ 416: "Range Not Satisfiable",
+ 417: "Expectation Failed",
+ 418: "I'm a Teapot",
+ 421: "Misdirected Request",
+ 422: "Unprocessable Entity",
+ 423: "Locked",
+ 424: "Failed Dependency",
+ 425: "Too Early",
+ 426: "Upgrade Required",
+ 428: "Precondition Required",
+ 429: "Too Many Requests",
+ 431: "Request Header Fields Too Large",
+ 451: "Unavailable For Legal Reasons",
+ 500: "Internal Server Error",
+ 501: "Not Implemented",
+ 502: "Bad Gateway",
+ 503: "Service Unavailable",
+ 504: "Gateway Timeout",
+ 505: "HTTP Version Not Supported",
+ 506: "Variant Also Negotiates",
+ 507: "Insufficient Storage",
+ 508: "Loop Detected",
+ 510: "Not Extended",
+ 511: "Network Authentication Required",
+ };
+
+ for (let code in fixture) {
+ it(`should return ${code} ${fixture[code]}`, async () => {
+ await runTest(
+ {
+ fetch(req) {
+ return new Response("hey", { status: +code });
+ },
+ },
+ async server => {
+ const response = await fetch(`http://${server.hostname}:${server.port}`);
+ expect(response.status).toBe(parseInt(code));
+ expect(response.statusText).toBe(fixture[code]);
+ },
+ );
+ });
+ }
+});
+
+it("should support multiple Set-Cookie headers", async () => {
+ await runTest(
+ {
+ fetch(req) {
+ return new Response("hello", {
+ headers: [
+ ["Another-Header", "1"],
+ ["Set-Cookie", "foo=bar"],
+ ["Set-Cookie", "baz=qux"],
+ ],
+ });
+ },
+ },
+ async server => {
+ const response = await fetch(`http://${server.hostname}:${server.port}`);
+ expect(response.headers.getAll("Set-Cookie")).toEqual(["foo=bar", "baz=qux"]);
+ expect(response.headers.get("Set-Cookie")).toEqual("foo=bar, baz=qux");
+
+ const cloned = response.clone().headers;
+ expect(response.headers.getAll("Set-Cookie")).toEqual(["foo=bar", "baz=qux"]);
+
+ response.headers.delete("Set-Cookie");
+ expect(response.headers.getAll("Set-Cookie")).toEqual([]);
+ response.headers.delete("Set-Cookie");
+ expect(cloned.getAll("Set-Cookie")).toEqual(["foo=bar", "baz=qux"]);
+ expect(new Headers(cloned).getAll("Set-Cookie")).toEqual(["foo=bar", "baz=qux"]);
+ },
+ );
+});
+
+describe("should support Content-Range with Bun.file()", () => {
+ // this must be a big file so we can test potentially multiple chunks
+ // more than 65 KB
+ const full = (function () {
+ const fixture = resolve(import.meta.dir + "/fetch.js.txt");
+ const chunk = readFileSync(fixture);
+ var whole = new Uint8Array(chunk.byteLength * 128);
+ for (var i = 0; i < 128; i++) {
+ whole.set(chunk, i * chunk.byteLength);
+ }
+ writeFileSync(fixture + ".big", whole);
+ return whole;
+ })();
+ const fixture = resolve(import.meta.dir + "/fetch.js.txt") + ".big";
+ const getServer = runTest.bind(null, {
+ fetch(req) {
+ const { searchParams } = new URL(req.url);
+ const start = Number(searchParams.get("start"));
+ const end = Number(searchParams.get("end"));
+ return new Response(Bun.file(fixture).slice(start, end));
+ },
+ });
+
+ const good = [
+ [0, 1],
+ [1, 2],
+ [0, 10],
+ [10, 20],
+ [0, Infinity],
+ [10, Infinity],
+ [NaN, Infinity],
+ [full.byteLength - 10, full.byteLength],
+ [full.byteLength - 10, full.byteLength - 1],
+ [full.byteLength - 1, full.byteLength],
+ [0, full.byteLength],
+ ] as const;
+
+ for (const [start, end] of good) {
+ it(`good range: ${start} - ${end}`, async () => {
+ await getServer(async server => {
+ const response = await fetch(`http://${server.hostname}:${server.port}/?start=${start}&end=${end}`, {
+ verbose: true,
+ });
+ expect(await response.arrayBuffer()).toEqual(full.buffer.slice(start, end));
+ expect(response.status).toBe(start > 0 || end < full.byteLength ? 206 : 200);
+ });
+ });
+ }
+
+ const emptyRanges = [
+ [0, 0],
+ [1, 1],
+ [10, 10],
+ [-Infinity, -Infinity],
+ [Infinity, Infinity],
+ [NaN, NaN],
+ [(full.byteLength / 2) | 0, (full.byteLength / 2) | 0],
+ [full.byteLength, full.byteLength],
+ [full.byteLength - 1, full.byteLength - 1],
+ ];
+
+ for (const [start, end] of emptyRanges) {
+ it(`empty range: ${start} - ${end}`, async () => {
+ await getServer(async server => {
+ const response = await fetch(`http://${server.hostname}:${server.port}/?start=${start}&end=${end}`);
+ const out = await response.arrayBuffer();
+ expect(out).toEqual(new ArrayBuffer(0));
+ expect(response.status).toBe(206);
+ });
+ });
+ }
+
+ const badRanges = [
+ [10, NaN],
+ [10, -Infinity],
+ [-(full.byteLength / 2) | 0, Infinity],
+ [-(full.byteLength / 2) | 0, -Infinity],
+ [full.byteLength + 100, full.byteLength],
+ [full.byteLength + 100, full.byteLength + 100],
+ [full.byteLength + 100, full.byteLength + 1],
+ [full.byteLength + 100, -full.byteLength],
+ ];
+
+ for (const [start, end] of badRanges) {
+ it(`bad range: ${start} - ${end}`, async () => {
+ await getServer(async server => {
+ const response = await fetch(`http://${server.hostname}:${server.port}/?start=${start}&end=${end}`);
+ const out = await response.arrayBuffer();
+ expect(out).toEqual(new ArrayBuffer(0));
+ expect(response.status).toBe(206);
+ });
+ });
+ }
+});
diff --git a/test/js/bun/io/bun-streams-test-fifo.sh b/test/js/bun/io/bun-streams-test-fifo.sh
new file mode 100644
index 000000000..57650ba1d
--- /dev/null
+++ b/test/js/bun/io/bun-streams-test-fifo.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+
+echoerr() { echo "$@" 1>&2; }
+
+echoerr "bun-streams-test-fifo.sh: starting"
+echo -e "$FIFO_TEST" >>${@: -1}
+echoerr "bun-streams-test-fifo.sh: ending"
+exit 0
diff --git a/test/js/bun/io/bun-write.test.js b/test/js/bun/io/bun-write.test.js
new file mode 100644
index 000000000..c3b9b187b
--- /dev/null
+++ b/test/js/bun/io/bun-write.test.js
@@ -0,0 +1,274 @@
+import fs from "fs";
+import { it, expect, describe } from "bun:test";
+import path from "path";
+import { gcTick, withoutAggressiveGC } from "harness";
+
+it("Bun.write blob", async () => {
+ await Bun.write(Bun.file("/tmp/response-file.test.txt"), Bun.file(path.join(import.meta.dir, "fetch.js.txt")));
+ await gcTick();
+ await Bun.write(Bun.file("/tmp/response-file.test.txt"), "blah blah blha");
+ await gcTick();
+ await Bun.write(Bun.file("/tmp/response-file.test.txt"), new Uint32Array(1024));
+ await gcTick();
+ await Bun.write("/tmp/response-file.test.txt", new Uint32Array(1024));
+ await gcTick();
+ expect(await Bun.write(new TextEncoder().encode("/tmp/response-file.test.txt"), new Uint32Array(1024))).toBe(
+ new Uint32Array(1024).byteLength,
+ );
+ await gcTick();
+});
+
+describe("large file", () => {
+ const fixtures = [
+ [
+ `/tmp/bun-test-large-file-${Date.now()}.txt`,
+ "https://www.iana.org/assignments/media-types/media-types.xhtml,".repeat(10000),
+ ],
+ ];
+
+ for (const [filename, content] of fixtures) {
+ it(`write ${filename} ${content.length} (text)`, async () => {
+ try {
+ unlinkSync(filename);
+ } catch (e) {}
+ await Bun.write(filename, content);
+ expect(await Bun.file(filename).text()).toBe(content);
+
+ try {
+ unlinkSync(filename);
+ } catch (e) {}
+ });
+
+ it(`write ${filename}.bytes ${content.length} (bytes)`, async () => {
+ try {
+ unlinkSync(filename + ".bytes");
+ } catch (e) {}
+ var bytes = new TextEncoder().encode(content);
+ const written = await Bun.write(filename + ".bytes", bytes);
+ expect(written).toBe(bytes.byteLength);
+ expect(new Buffer(await Bun.file(filename + ".bytes").arrayBuffer()).equals(bytes)).toBe(true);
+
+ try {
+ unlinkSync(filename + ".bytes");
+ } catch (e) {}
+ });
+
+ it(`write ${filename}.blob ${content.length} (Blob)`, async () => {
+ try {
+ unlinkSync(filename + ".blob");
+ } catch (e) {}
+ var bytes = new Blob([content]);
+ await Bun.write(filename + ".blob", bytes);
+ expect(await Bun.file(filename + ".blob").text()).toBe(content);
+
+ try {
+ unlinkSync(filename + ".blob");
+ } catch (e) {}
+ });
+ }
+});
+
+it("Bun.file not found returns ENOENT", async () => {
+ try {
+ await gcTick();
+ await Bun.file("/does/not/exist.txt").text();
+ await gcTick();
+ } catch (exception) {
+ expect(exception.code).toBe("ENOENT");
+ }
+ await gcTick();
+});
+
+it("Bun.write('out.txt', 'string')", async () => {
+ for (let erase of [true, false]) {
+ if (erase) {
+ try {
+ fs.unlinkSync(path.join("/tmp", "out.txt"));
+ } catch (e) {}
+ }
+ await gcTick();
+ expect(await Bun.write("/tmp/out.txt", "string")).toBe("string".length);
+ await gcTick();
+ const out = Bun.file("/tmp/out.txt");
+ await gcTick();
+ expect(await out.text()).toBe("string");
+ await gcTick();
+ expect(await out.text()).toBe(fs.readFileSync("/tmp/out.txt", "utf8"));
+ await gcTick();
+ }
+});
+
+it("Bun.file -> Bun.file", async () => {
+ try {
+ fs.unlinkSync(path.join("/tmp", "fetch.js.in"));
+ } catch (e) {}
+ await gcTick();
+ try {
+ fs.unlinkSync(path.join("/tmp", "fetch.js.out"));
+ } catch (e) {}
+ await gcTick();
+ const file = path.join(import.meta.dir, "fetch.js.txt");
+ await gcTick();
+ const text = fs.readFileSync(file, "utf8");
+ fs.writeFileSync("/tmp/fetch.js.in", text);
+ await gcTick();
+ {
+ const result = await Bun.write(Bun.file("/tmp/fetch.js.out"), Bun.file("/tmp/fetch.js.in"));
+ await gcTick();
+ expect(await Bun.file("/tmp/fetch.js.out").text()).toBe(text);
+ await gcTick();
+ }
+
+ {
+ await Bun.write(Bun.file("/tmp/fetch.js.in").slice(0, (text.length / 2) | 0), Bun.file("/tmp/fetch.js.out"));
+ expect(await Bun.file("/tmp/fetch.js.in").text()).toBe(text.substring(0, (text.length / 2) | 0));
+ }
+
+ {
+ await gcTick();
+ await Bun.write("/tmp/fetch.js.in", Bun.file("/tmp/fetch.js.out"));
+ await gcTick();
+ expect(await Bun.file("/tmp/fetch.js.in").text()).toBe(text);
+ }
+});
+
+it("Bun.file", async () => {
+ const file = path.join(import.meta.dir, "fetch.js.txt");
+ await gcTick();
+ expect(await Bun.file(file).text()).toBe(fs.readFileSync(file, "utf8"));
+ await gcTick();
+});
+
+it("Bun.file empty file", async () => {
+ const file = path.join(import.meta.dir, "emptyFile");
+ await gcTick();
+ const buffer = await Bun.file(file).arrayBuffer();
+ expect(buffer.byteLength).toBe(0);
+ await gcTick();
+});
+
+it("Bun.file as a Blob", async () => {
+ const filePath = path.join(import.meta.path, "../fetch.js.txt");
+ const fixture = fs.readFileSync(filePath, "utf8");
+ // this is a Blob object with the same interface as the one returned by fetch
+ // internally, instead of a byte array, it stores the file path!
+ // this enables several performance optimizations
+ var blob = Bun.file(filePath);
+ await gcTick();
+
+ // now it reads "./fetch.js.txt" from the filesystem
+ // it's lazy, only loads once we ask for it
+ // if it fails, the promise will reject at this point
+ expect(await blob.text()).toBe(fixture);
+ await gcTick();
+ // BEHAVIOR CHANGE IN BUN V0.3.0 - size is never set
+ // now that it's loaded, the size updates
+ // expect(blob.size).toBe(fixture.length);
+ // await gcTick();
+ // and it only loads once for _all_ blobs pointing to that file path
+ // until all references are released
+ expect((await blob.arrayBuffer()).byteLength).toBe(fixture.length);
+ await gcTick();
+
+ const array = new Uint8Array(await blob.arrayBuffer());
+ await gcTick();
+ const text = fixture;
+ withoutAggressiveGC(() => {
+ for (let i = 0; i < text.length; i++) {
+ expect(array[i]).toBe(text.charCodeAt(i));
+ }
+ });
+ await gcTick();
+ expect(blob.size).toBe(fixture.length);
+ blob = null;
+ await gcTick();
+ await new Promise(resolve => setTimeout(resolve, 1));
+ var blob = Bun.file(filePath);
+ expect(blob.size).toBe(fixture.length);
+});
+
+it("Response -> Bun.file", async () => {
+ const file = path.join(import.meta.dir, "fetch.js.txt");
+ await gcTick();
+ const text = fs.readFileSync(file, "utf8");
+ await gcTick();
+ const response = new Response(Bun.file(file));
+ await gcTick();
+ expect(await response.text()).toBe(text);
+ await gcTick();
+});
+
+it("Bun.file -> Response", async () => {
+ // ensure the file doesn't already exist
+ try {
+ fs.unlinkSync("/tmp/fetch.js.out");
+ } catch {}
+ await gcTick();
+ const file = path.join(import.meta.dir, "fetch.js.txt");
+ await gcTick();
+ const text = fs.readFileSync(file, "utf8");
+ await gcTick();
+ const resp = await fetch("https://example.com");
+ await gcTick();
+
+ expect(await Bun.write("/tmp/fetch.js.out", resp)).toBe(text.length);
+ await gcTick();
+ expect(await Bun.file("/tmp/fetch.js.out").text()).toBe(text);
+ await gcTick();
+});
+
+it("Response -> Bun.file -> Response -> text", async () => {
+ await gcTick();
+ const file = path.join(import.meta.dir, "fetch.js.txt");
+ await gcTick();
+ const text = fs.readFileSync(file, "utf8");
+ await gcTick();
+ const response = new Response(Bun.file(file));
+ await gcTick();
+ const response2 = response.clone();
+ await gcTick();
+ expect(await response2.text()).toBe(text);
+ await gcTick();
+});
+
+it("Bun.write('output.html', '')", async () => {
+ await Bun.write("/tmp/output.html", "lalalala");
+ expect(await Bun.write("/tmp/output.html", "")).toBe(0);
+ await Bun.write("/tmp/output.html", "lalalala");
+ expect(await Bun.file("/tmp/output.html").text()).toBe("lalalala");
+});
+
+it("Bun.write(Bun.stdout, 'Bun.write STDOUT TEST')", async () => {
+ expect(await Bun.write(Bun.stdout, "\nBun.write STDOUT TEST\n\n")).toBe(24);
+});
+
+it("Bun.write(Bun.stderr, 'Bun.write STDERR TEST')", async () => {
+ expect(await Bun.write(Bun.stderr, "\nBun.write STDERR TEST\n\n")).toBe(24);
+});
+
+it("Bun.write(Bun.stdout, new TextEncoder().encode('Bun.write STDOUT TEST'))", async () => {
+ expect(await Bun.write(Bun.stdout, new TextEncoder().encode("\nBun.write STDOUT TEST\n\n"))).toBe(24);
+});
+
+it("Bun.write(Bun.stderr, 'new TextEncoder().encode(Bun.write STDERR TEST'))", async () => {
+ expect(await Bun.write(Bun.stderr, new TextEncoder().encode("\nBun.write STDERR TEST\n\n"))).toBe(24);
+});
+
+// FLAKY TEST
+// Since Bun.file is resolved lazily, this needs to specifically be checked
+it.skip("Bun.write('output.html', HTMLRewriter.transform(Bun.file)))", async done => {
+ var rewriter = new HTMLRewriter();
+
+ rewriter.on("div", {
+ element(element) {
+ element.setInnerContent("<blink>it worked!</blink>", { html: true });
+ },
+ });
+ await Bun.write("/tmp/html-rewriter.txt.js", "<div>hello</div>");
+ var input = new Response(Bun.file("/tmp/html-rewriter.txt.js"));
+ var output = rewriter.transform(input);
+ const outpath = `/tmp/html-rewriter.${Date.now()}.html`;
+ await Bun.write(outpath, output);
+ expect(await Bun.file(outpath).text()).toBe("<div><blink>it worked!</blink></div>");
+ done();
+});
diff --git a/test/js/bun/io/emptyFile b/test/js/bun/io/emptyFile
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/test/js/bun/io/emptyFile
diff --git a/test/js/bun/io/fetch.js.txt b/test/js/bun/io/fetch.js.txt
new file mode 100644
index 000000000..5a9b52fcf
--- /dev/null
+++ b/test/js/bun/io/fetch.js.txt
@@ -0,0 +1,46 @@
+<!doctype html>
+<html>
+<head>
+ <title>Example Domain</title>
+
+ <meta charset="utf-8" />
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <style type="text/css">
+ body {
+ background-color: #f0f0f2;
+ margin: 0;
+ padding: 0;
+ font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+
+ }
+ div {
+ width: 600px;
+ margin: 5em auto;
+ padding: 2em;
+ background-color: #fdfdff;
+ border-radius: 0.5em;
+ box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);
+ }
+ a:link, a:visited {
+ color: #38488f;
+ text-decoration: none;
+ }
+ @media (max-width: 700px) {
+ div {
+ margin: 0 auto;
+ width: auto;
+ }
+ }
+ </style>
+</head>
+
+<body>
+<div>
+ <h1>Example Domain</h1>
+ <p>This domain is for use in illustrative examples in documents. You may use this
+ domain in literature without prior coordination or asking for permission.</p>
+ <p><a href="https://www.iana.org/domains/example">More information...</a></p>
+</div>
+</body>
+</html>
diff --git a/test/js/bun/jsc/bun-jsc.test.js b/test/js/bun/jsc/bun-jsc.test.js
new file mode 100644
index 000000000..6e6897eb3
--- /dev/null
+++ b/test/js/bun/jsc/bun-jsc.test.js
@@ -0,0 +1,108 @@
+import { describe, expect, it } from "bun:test";
+import {
+ describe as jscDescribe,
+ describeArray,
+ gcAndSweep,
+ fullGC,
+ edenGC,
+ heapSize,
+ heapStats,
+ memoryUsage,
+ getRandomSeed,
+ setRandomSeed,
+ isRope,
+ callerSourceOrigin,
+ noFTL,
+ noOSRExitFuzzing,
+ optimizeNextInvocation,
+ numberOfDFGCompiles,
+ releaseWeakRefs,
+ totalCompileTime,
+ getProtectedObjects,
+ reoptimizationRetryCount,
+ drainMicrotasks,
+ startRemoteDebugger,
+} from "bun:jsc";
+
+describe("bun:jsc", () => {
+ function count() {
+ var j = 0;
+ for (var i = 0; i < 999999; i++) {
+ j += i + 2;
+ }
+
+ return j;
+ }
+
+ it("describe", () => {
+ jscDescribe([]);
+ });
+ it("describeArray", () => {
+ describeArray([1, 2, 3]);
+ });
+ it("gcAndSweep", () => {
+ gcAndSweep();
+ });
+ it("fullGC", () => {
+ fullGC();
+ });
+ it("edenGC", () => {
+ edenGC();
+ });
+ it("heapSize", () => {
+ expect(heapSize() > 0).toBe(true);
+ });
+ it("heapStats", () => {
+ heapStats();
+ });
+ it("memoryUsage", () => {
+ memoryUsage();
+ });
+ it("getRandomSeed", () => {
+ getRandomSeed(2);
+ });
+ it("setRandomSeed", () => {
+ setRandomSeed(2);
+ });
+ it("isRope", () => {
+ expect(isRope("a" + 123 + "b")).toBe(true);
+ expect(isRope("abcdefgh")).toBe(false);
+ });
+ it("callerSourceOrigin", () => {
+ expect(callerSourceOrigin()).toBe(import.meta.url);
+ });
+ it("noFTL", () => {});
+ it("noOSRExitFuzzing", () => {});
+ it("optimizeNextInvocation", () => {
+ count();
+ optimizeNextInvocation(count);
+ count();
+ });
+ it("numberOfDFGCompiles", () => {
+ expect(numberOfDFGCompiles(count) > 0).toBe(true);
+ });
+ it("releaseWeakRefs", () => {
+ releaseWeakRefs();
+ });
+ it("totalCompileTime", () => {
+ totalCompileTime(count);
+ });
+ it("reoptimizationRetryCount", () => {
+ reoptimizationRetryCount(count);
+ });
+ it("drainMicrotasks", () => {
+ drainMicrotasks();
+ });
+ it("startRemoteDebugger", () => {
+ // try {
+ // startRemoteDebugger("");
+ // } catch (e) {
+ // if (process.platform !== "darwin") {
+ // throw e;
+ // }
+ // }
+ });
+ it("getProtectedObjects", () => {
+ expect(getProtectedObjects().length > 0).toBe(true);
+ });
+});
diff --git a/test/js/bun/jsc/shadow.test.js b/test/js/bun/jsc/shadow.test.js
new file mode 100644
index 000000000..3fffcac90
--- /dev/null
+++ b/test/js/bun/jsc/shadow.test.js
@@ -0,0 +1,10 @@
+import { describe, it, expect } from "bun:test";
+
+it("shadow realm works", () => {
+ const red = new ShadowRealm();
+ globalThis.someValue = 1;
+ // Affects only the ShadowRealm's global
+ const result = red.evaluate("globalThis.someValue = 2;");
+ expect(globalThis.someValue).toBe(1);
+ expect(result).toBe(2);
+});
diff --git a/test/js/bun/net/echo.js b/test/js/bun/net/echo.js
new file mode 100644
index 000000000..f02637739
--- /dev/null
+++ b/test/js/bun/net/echo.js
@@ -0,0 +1,76 @@
+function createOptions(type, message, closeOnDone) {
+ let buffers = [];
+ let report = function () {
+ report = function () {};
+ const data = new Uint8Array(
+ buffers.reduce(function (sum, buffer) {
+ return sum + buffer.length;
+ }, 0),
+ );
+ buffers.reduce(function (offset, buffer) {
+ data.set(buffer, offset);
+ return offset + buffer.length;
+ }, 0);
+ console.log(type, "GOT", new TextDecoder().decode(data));
+ };
+
+ let done = closeOnDone
+ ? function (socket, sent) {
+ socket.data[sent ? "sent" : "received"] = true;
+ if (socket.data.sent && socket.data.received) {
+ done = function () {};
+ closeOnDone(socket);
+ }
+ }
+ : function () {};
+
+ function drain(socket) {
+ const message = socket.data.message;
+ const written = socket.write(message);
+ if (written < message.length) {
+ socket.data.message = message.slice(written);
+ } else {
+ done(socket, true);
+ }
+ }
+
+ return {
+ hostname: "localhost",
+ port: 12345,
+ socket: {
+ close() {
+ report();
+ console.log(type, "CLOSED");
+ },
+ data(socket, buffer) {
+ buffers.push(buffer);
+ done(socket);
+ },
+ drain: drain,
+ // end() {
+ // report();
+ // console.log(type, "ENDED");
+ // },
+ error(socket, err) {
+ console.log(type, "ERRED", err);
+ },
+ open(socket) {
+ console.log(type, "OPENED");
+ drain(socket);
+ },
+ },
+ data: {
+ sent: false,
+ received: false,
+ message: message,
+ },
+ };
+}
+
+const server = Bun.listen(
+ createOptions("[Server]", "response", socket => {
+ server.stop();
+ socket.end();
+ }),
+);
+Bun.connect(createOptions("[Client]", "request"));
diff --git a/test/js/bun/net/socket.test.ts b/test/js/bun/net/socket.test.ts
new file mode 100644
index 000000000..881175b24
--- /dev/null
+++ b/test/js/bun/net/socket.test.ts
@@ -0,0 +1,130 @@
+import { expect, it } from "bun:test";
+import { bunExe } from "harness";
+import { connect, spawn } from "bun";
+
+it("should keep process alive only when active", async () => {
+ const { exited, stdout, stderr } = spawn({
+ cmd: [bunExe(), "echo.js"],
+ cwd: import.meta.dir,
+ stdout: "pipe",
+ stdin: null,
+ stderr: "pipe",
+ env: {
+ BUN_DEBUG_QUIET_LOGS: 1,
+ },
+ });
+
+ expect(await exited).toBe(0);
+ expect(await new Response(stderr).text()).toBe("");
+ var lines = (await new Response(stdout).text()).split(/\r?\n/);
+ expect(
+ lines.filter(function (line) {
+ return line.startsWith("[Server]");
+ }),
+ ).toEqual(["[Server] OPENED", "[Server] GOT request", "[Server] CLOSED"]);
+ expect(
+ lines.filter(function (line) {
+ return line.startsWith("[Client]");
+ }),
+ ).toEqual(["[Client] OPENED", "[Client] GOT response", "[Client] CLOSED"]);
+});
+
+it("listen() should throw connection error for invalid host", () => {
+ expect(() => {
+ const handlers = {
+ open(socket) {
+ socket.close();
+ },
+ data() {},
+ };
+
+ Bun.listen({
+ port: 4423,
+ hostname: "whatishtis.com",
+ socket: handlers,
+ });
+ }).toThrow();
+});
+
+it("should reject on connection error, calling both connectError() and rejecting the promise", done => {
+ var data = {};
+ connect({
+ data,
+ hostname: "localhost",
+ port: 55555,
+ socket: {
+ connectError(socket, error) {
+ expect(socket).toBeDefined();
+ expect(socket.data).toBe(data);
+ expect(error).toBeDefined();
+ expect(error.name).toBe("SystemError");
+ expect(error.message).toBe("Failed to connect");
+ },
+ data() {
+ done(new Error("Unexpected data()"));
+ },
+ drain() {
+ done(new Error("Unexpected drain()"));
+ },
+ close() {
+ done(new Error("Unexpected close()"));
+ },
+ end() {
+ done(new Error("Unexpected end()"));
+ },
+ error() {
+ done(new Error("Unexpected error()"));
+ },
+ open() {
+ done(new Error("Unexpected open()"));
+ },
+ },
+ }).then(
+ () => done(new Error("Promise should reject instead")),
+ err => {
+ expect(err).toBeDefined();
+ expect(err.name).toBe("SystemError");
+ expect(err.message).toBe("Failed to connect");
+
+ done();
+ },
+ );
+});
+
+// this also tests we mark the promise as handled if connectError() is called
+it("should handle connection error", done => {
+ var data = {};
+ connect({
+ data,
+ hostname: "localhost",
+ port: 55555,
+ socket: {
+ connectError(socket, error) {
+ expect(socket).toBeDefined();
+ expect(socket.data).toBe(data);
+ expect(error).toBeDefined();
+ expect(error.name).toBe("SystemError");
+ expect(error.message).toBe("Failed to connect");
+ done();
+ },
+ data() {
+ done(new Error("Unexpected data()"));
+ },
+ drain() {
+ done(new Error("Unexpected drain()"));
+ },
+ close() {
+ done(new Error("Unexpected close()"));
+ },
+ end() {
+ done(new Error("Unexpected end()"));
+ },
+ error() {
+ done(new Error("Unexpected error()"));
+ },
+ open() {
+ done(new Error("Unexpected open()"));
+ },
+ },
+ });
+});
diff --git a/test/js/bun/net/tcp-server.test.ts b/test/js/bun/net/tcp-server.test.ts
new file mode 100644
index 000000000..ba6927fe3
--- /dev/null
+++ b/test/js/bun/net/tcp-server.test.ts
@@ -0,0 +1,222 @@
+import { listen, connect, TCPSocketListener, SocketHandler } from "bun";
+import { describe, expect, it } from "bun:test";
+import * as JSC from "bun:jsc";
+
+var decoder = new TextDecoder();
+
+it("echo server 1 on 1", async () => {
+ // wrap it in a separate closure so the GC knows to clean it up
+ // the sockets & listener don't escape the closure
+ await (async function () {
+ var resolve, reject, serverResolve, serverReject;
+ var prom = new Promise((resolve1, reject1) => {
+ resolve = resolve1;
+ reject = reject1;
+ });
+ var serverProm = new Promise((resolve1, reject1) => {
+ serverResolve = resolve1;
+ serverReject = reject1;
+ });
+
+ var serverData, clientData;
+ const handlers = {
+ open(socket) {
+ socket.data.counter = 1;
+ if (!socket.data?.isServer) {
+ clientData = socket.data;
+ clientData.sendQueue = ["client: Hello World! " + 0];
+ if (!socket.write("client: Hello World! " + 0)) {
+ socket.data = { pending: "server: Hello World! " + 0 };
+ }
+ } else {
+ serverData = socket.data;
+ serverData.sendQueue = ["server: Hello World! " + 0];
+ }
+
+ if (clientData) clientData.other = serverData;
+ if (serverData) serverData.other = clientData;
+ if (clientData) clientData.other = serverData;
+ if (serverData) serverData.other = clientData;
+ },
+ data(socket, buffer) {
+ const msg = `${socket.data.isServer ? "server:" : "client:"} Hello World! ${socket.data.counter++}`;
+ socket.data.sendQueue.push(msg);
+
+ expect(decoder.decode(buffer)).toBe(socket.data.other.sendQueue.pop());
+
+ if (socket.data.counter > 10) {
+ if (!socket.data.finished) {
+ socket.data.finished = true;
+ if (socket.data.isServer) {
+ setTimeout(() => {
+ serverResolve();
+ socket.end();
+ }, 1);
+ } else {
+ setTimeout(() => {
+ resolve();
+ socket.end();
+ }, 1);
+ }
+ }
+ }
+
+ if (!socket.write(msg)) {
+ socket.data.pending = msg;
+ return;
+ }
+ },
+ error(socket, error) {
+ reject(error);
+ },
+ drain(socket) {
+ reject(new Error("Unexpected backpressure"));
+ },
+ } as SocketHandler<any>;
+
+ var server: TCPSocketListener<any> | undefined = listen({
+ socket: handlers,
+ hostname: "localhost",
+ port: 8084,
+
+ data: {
+ isServer: true,
+ counter: 0,
+ },
+ });
+ const clientProm = connect({
+ socket: handlers,
+ hostname: "localhost",
+ port: 8084,
+ data: {
+ counter: 0,
+ },
+ });
+ await Promise.all([prom, clientProm, serverProm]);
+ server.stop(true);
+ server = serverData = clientData = undefined;
+ })();
+});
+
+describe("tcp socket binaryType", () => {
+ var port = 8085;
+ const binaryType = ["arraybuffer", "uint8array", "buffer"] as const;
+ for (const type of binaryType) {
+ it(type, async () => {
+ // wrap it in a separate closure so the GC knows to clean it up
+ // the sockets & listener don't escape the closure
+ await (async function () {
+ var resolve, reject, serverResolve, serverReject;
+ var prom = new Promise((resolve1, reject1) => {
+ resolve = resolve1;
+ reject = reject1;
+ });
+ var serverProm = new Promise((resolve1, reject1) => {
+ serverResolve = resolve1;
+ serverReject = reject1;
+ });
+
+ var serverData, clientData;
+ const handlers = {
+ open(socket) {
+ socket.data.counter = 1;
+ if (!socket.data?.isServer) {
+ clientData = socket.data;
+ clientData.sendQueue = ["client: Hello World! " + 0];
+ if (!socket.write("client: Hello World! " + 0)) {
+ socket.data = { pending: "server: Hello World! " + 0 };
+ }
+ } else {
+ serverData = socket.data;
+ serverData.sendQueue = ["server: Hello World! " + 0];
+ }
+
+ if (clientData) clientData.other = serverData;
+ if (serverData) serverData.other = clientData;
+ if (clientData) clientData.other = serverData;
+ if (serverData) serverData.other = clientData;
+ },
+ data(socket, buffer) {
+ expect(
+ buffer instanceof
+ (type === "arraybuffer"
+ ? ArrayBuffer
+ : type === "uint8array"
+ ? Uint8Array
+ : type === "buffer"
+ ? Buffer
+ : Error),
+ ).toBe(true);
+ const msg = `${socket.data.isServer ? "server:" : "client:"} Hello World! ${socket.data.counter++}`;
+ socket.data.sendQueue.push(msg);
+
+ expect(decoder.decode(buffer)).toBe(socket.data.other.sendQueue.pop());
+
+ if (socket.data.counter > 10) {
+ if (!socket.data.finished) {
+ socket.data.finished = true;
+ if (socket.data.isServer) {
+ setTimeout(() => {
+ serverResolve();
+ socket.end();
+ }, 1);
+ } else {
+ setTimeout(() => {
+ resolve();
+ socket.end();
+ }, 1);
+ }
+ }
+ }
+
+ if (!socket.write(msg)) {
+ socket.data.pending = msg;
+ return;
+ }
+ },
+ error(socket, error) {
+ reject(error);
+ },
+ drain(socket) {
+ reject(new Error("Unexpected backpressure"));
+ },
+
+ binaryType: type,
+ } as SocketHandler<any>;
+
+ var server: TCPSocketListener<any> | undefined = listen({
+ socket: handlers,
+ hostname: "localhost",
+ port,
+ data: {
+ isServer: true,
+ counter: 0,
+ },
+ });
+
+ const clientProm = connect({
+ socket: handlers,
+ hostname: "localhost",
+ port,
+ data: {
+ counter: 0,
+ },
+ });
+ port++;
+
+ await Promise.all([prom, clientProm, serverProm]);
+ server.stop(true);
+ server = serverData = clientData = undefined;
+ })();
+ });
+ }
+});
+
+it("should not leak memory", () => {
+ // Tell the garbage collector for sure that we're done with the sockets
+ Bun.gc(true);
+ // assert we don't leak the sockets
+ // we expect 1 because that's the prototype / structure
+ expect(JSC.heapStats().objectTypeCounts.TCPSocket).toBe(1);
+ expect(JSC.heapStats().objectTypeCounts.Listener).toBe(1);
+});
diff --git a/test/js/bun/plugin/hello.svelte b/test/js/bun/plugin/hello.svelte
new file mode 100644
index 000000000..05dac4294
--- /dev/null
+++ b/test/js/bun/plugin/hello.svelte
@@ -0,0 +1,5 @@
+<script>
+ let name = "world";
+</script>
+
+<h1>Hello {name}!</h1>
diff --git a/test/js/bun/plugin/hello2.svelte b/test/js/bun/plugin/hello2.svelte
new file mode 100644
index 000000000..05dac4294
--- /dev/null
+++ b/test/js/bun/plugin/hello2.svelte
@@ -0,0 +1,5 @@
+<script>
+ let name = "world";
+</script>
+
+<h1>Hello {name}!</h1>
diff --git a/test/js/bun/plugin/plugins.d.ts b/test/js/bun/plugin/plugins.d.ts
new file mode 100644
index 000000000..aebfd952b
--- /dev/null
+++ b/test/js/bun/plugin/plugins.d.ts
@@ -0,0 +1,11 @@
+declare var objectModuleResult: any;
+declare var laterCode: any;
+
+declare module "beep:*";
+declare module "async:*";
+declare module "asyncret:*";
+declare module "asyncfail:*";
+declare module "async-obj:*";
+declare module "obj:*";
+declare module "delay:*";
+declare module "./*.svelte";
diff --git a/test/js/bun/plugin/plugins.test.ts b/test/js/bun/plugin/plugins.test.ts
new file mode 100644
index 000000000..2bac4b4d4
--- /dev/null
+++ b/test/js/bun/plugin/plugins.test.ts
@@ -0,0 +1,311 @@
+/// <reference types="./plugins" />
+import { plugin } from "bun";
+import { describe, expect, it } from "bun:test";
+import { resolve } from "path";
+
+plugin({
+ name: "boop beep beep",
+ setup(builder) {
+ builder.onResolve({ filter: /boop/, namespace: "beep" }, () => ({
+ path: "boop",
+ namespace: "beep",
+ }));
+
+ builder.onLoad({ filter: /boop/, namespace: "beep" }, () => ({
+ contents: `export default 42;`,
+ loader: "js",
+ }));
+ },
+});
+
+plugin({
+ name: "an object module",
+ setup(builder) {
+ globalThis.objectModuleResult ||= {
+ hello: "world",
+ };
+ builder.onResolve({ filter: /.*/, namespace: "obj" }, ({ path }) => ({
+ path,
+ namespace: "obj",
+ }));
+
+ builder.onLoad({ filter: /.*/, namespace: "obj" }, () => ({
+ exports: globalThis.objectModuleResult,
+ loader: "object",
+ }));
+ },
+});
+
+plugin({
+ name: "failing loader",
+ setup(builder) {
+ globalThis.failingObject ||= {};
+ builder.onResolve({ filter: /.*/, namespace: "fail" }, ({ path }) => ({
+ path,
+ namespace: "fail",
+ }));
+ builder.onLoad({ filter: /.*/, namespace: "fail" }, () => globalThis.failingObject);
+ },
+});
+
+plugin({
+ name: "delayed loader",
+ setup(builder) {
+ globalThis.laterCode = "";
+
+ builder.onResolve({ filter: /.*/, namespace: "delay" }, ({ path }) => ({
+ namespace: "delay",
+ path,
+ }));
+
+ builder.onLoad({ filter: /.*/, namespace: "delay" }, ({ path }) => ({
+ contents: (globalThis.laterCode ||= ""),
+ loader: "js",
+ }));
+ },
+});
+
+plugin({
+ name: "async onLoad",
+ setup(builder) {
+ globalThis.asyncOnLoad = "";
+
+ builder.onResolve({ filter: /.*/, namespace: "async" }, ({ path }) => ({
+ namespace: "async",
+ path,
+ }));
+
+ builder.onLoad({ filter: /.*/, namespace: "async" }, async ({ path }) => {
+ await Promise.resolve(1);
+ return new Promise((resolve, reject) => {
+ setTimeout(() => {
+ resolve({
+ contents: (globalThis.asyncOnLoad ||= ""),
+ loader: "js",
+ });
+ }, 1);
+ });
+ });
+
+ builder.onResolve({ filter: /.*/, namespace: "async-obj" }, ({ path }) => ({
+ namespace: "async-obj",
+ path,
+ }));
+ globalThis.asyncObject = {};
+ builder.onLoad({ filter: /.*/, namespace: "async-obj" }, async ({ path }) => {
+ await Promise.resolve(1);
+ return new Promise((resolve, reject) => {
+ setTimeout(() => {
+ resolve({
+ exports: (globalThis.asyncObject ||= {}),
+ loader: "object",
+ });
+ }, 1);
+ });
+ });
+
+ builder.onResolve({ filter: /.*/, namespace: "asyncfail" }, ({ path }) => ({
+ namespace: "asyncfail",
+ path,
+ }));
+
+ globalThis.asyncfail = false;
+ builder.onLoad({ filter: /.*/, namespace: "asyncfail" }, async ({ path }) => {
+ await Promise.resolve(1);
+ await 1;
+ throw globalThis.asyncfail;
+ });
+
+ builder.onResolve({ filter: /.*/, namespace: "asyncret" }, ({ path }) => ({
+ namespace: "asyncret",
+ path,
+ }));
+
+ globalThis.asyncret = 123;
+ builder.onLoad({ filter: /.*/, namespace: "asyncret" }, async ({ path }) => {
+ await 100;
+ await Promise.resolve(10);
+ return await globalThis.asyncret;
+ });
+ },
+});
+
+// This is to test that it works when imported from a separate file
+import "../../third_party/svelte";
+
+describe("require", () => {
+ it("SSRs `<h1>Hello world!</h1>` with Svelte", () => {
+ const { default: App } = require("./hello.svelte");
+ const { html } = App.render();
+
+ expect(html).toBe("<h1>Hello world!</h1>");
+ });
+
+ it("beep:boop returns 42", () => {
+ const result = require("beep:boop");
+ expect(result.default).toBe(42);
+ });
+
+ it("object module works", () => {
+ const result = require("obj:boop");
+ expect(result.hello).toBe(objectModuleResult.hello);
+ objectModuleResult.there = true;
+ const result2 = require("obj:boop2");
+ expect(result.there).toBe(undefined);
+ expect(result2.there).toBe(objectModuleResult.there);
+ expect(result2.there).toBe(true);
+ });
+});
+
+describe("dynamic import", () => {
+ it("SSRs `<h1>Hello world!</h1>` with Svelte", async () => {
+ const { default: App }: any = await import("./hello.svelte");
+
+ const { html } = App.render();
+
+ expect(html).toBe("<h1>Hello world!</h1>");
+ });
+
+ it("beep:boop returns 42", async () => {
+ const result = await import("beep:boop");
+ expect(result.default).toBe(42);
+ });
+
+ it("async:onLoad returns 42", async () => {
+ globalThis.asyncOnLoad = "export default 42;";
+ const result = await import("async:hello42");
+ expect(result.default).toBe(42);
+ });
+
+ it("async object loader returns 42", async () => {
+ globalThis.asyncObject = { foo: 42, default: 43 };
+ const result = await import("async-obj:hello42");
+ expect(result.foo).toBe(42);
+ expect(result.default).toBe(43);
+ });
+});
+
+describe("import statement", () => {
+ it("SSRs `<h1>Hello world!</h1>` with Svelte", async () => {
+ laterCode = `
+import Hello from "${resolve(import.meta.dir, "hello2.svelte")}";
+export default Hello;
+`;
+ const { default: SvelteApp } = await import("delay:hello2.svelte");
+ const { html } = SvelteApp.render();
+
+ expect(html).toBe("<h1>Hello world!</h1>");
+ });
+});
+
+describe("errors", () => {
+ it("valid loaders work", () => {
+ const validLoaders = ["js", "jsx", "ts", "tsx"];
+ const inputs = ["export default 'hi';", "export default 'hi';", "export default 'hi';", "export default 'hi';"];
+ for (let i = 0; i < validLoaders.length; i++) {
+ const loader = validLoaders[i];
+ const input = inputs[i];
+ globalThis.failingObject = { contents: input, loader };
+ expect(require(`fail:my-file-${loader}`).default).toBe("hi");
+ }
+ });
+
+ it("invalid loaders throw", () => {
+ const invalidLoaders = ["blah", "blah2", "blah3", "blah4"];
+ const inputs = ["body { background: red; }", "<h1>hi</h1>", '{"hi": "there"}', "hi"];
+ for (let i = 0; i < invalidLoaders.length; i++) {
+ const loader = invalidLoaders[i];
+ const input = inputs[i];
+ globalThis.failingObject = { contents: input, loader };
+ try {
+ require(`fail:my-file-${loader}`);
+ throw -1;
+ } catch (e: any) {
+ if (e === -1) {
+ throw new Error("Expected error");
+ }
+ expect(e.message.length > 0).toBe(true);
+ }
+ }
+ });
+
+ it("transpiler errors work", () => {
+ const invalidLoaders = ["ts"];
+ const inputs = ["const x: string = -NaNAn../!!;"];
+ for (let i = 0; i < invalidLoaders.length; i++) {
+ const loader = invalidLoaders[i];
+ const input = inputs[i];
+ globalThis.failingObject = { contents: input, loader };
+ try {
+ require(`fail:my-file-${loader}-3`);
+ throw -1;
+ } catch (e: any) {
+ if (e === -1) {
+ throw new Error("Expected error");
+ }
+ expect(e.message.length > 0).toBe(true);
+ }
+ }
+ });
+
+ it("invalid async return value", async () => {
+ try {
+ globalThis.asyncret = { wat: true };
+ await import("asyncret:my-file");
+ throw -1;
+ } catch (e: any) {
+ if (e === -1) {
+ throw new Error("Expected error");
+ }
+
+ expect(e.message.length > 0).toBe(true);
+ }
+ });
+
+ it("async errors work", async () => {
+ try {
+ globalThis.asyncfail = new Error("async error");
+ await import("asyncfail:my-file");
+ throw -1;
+ } catch (e: any) {
+ if (e === -1) {
+ throw new Error("Expected error");
+ }
+ expect(e.message.length > 0).toBe(true);
+ }
+ });
+
+ it("invalid onLoad objects throw", () => {
+ const invalidOnLoadObjects = [
+ {},
+ { contents: -1 },
+ { contents: "", loader: -1 },
+ { contents: "", loader: "klz", resolveDir: -1 },
+ ];
+ for (let i = 0; i < invalidOnLoadObjects.length; i++) {
+ globalThis.failingObject = invalidOnLoadObjects[i];
+ try {
+ require(`fail:my-file-${i}-2`);
+ throw -1;
+ } catch (e: any) {
+ if (e === -1) {
+ throw new Error("Expected error");
+ }
+ expect(e.message.length > 0).toBe(true);
+ }
+ }
+ });
+
+ it("async transpiler errors work", async () => {
+ try {
+ globalThis.asyncOnLoad = `const x: string = -NaNAn../!!;`;
+ await import("async:fail");
+ throw -1;
+ } catch (e: any) {
+ if (e === -1) {
+ throw new Error("Expected error");
+ }
+ expect(e.message.length > 0).toBe(true);
+ }
+ });
+});
diff --git a/test/js/bun/resolve/baz.js b/test/js/bun/resolve/baz.js
new file mode 100644
index 000000000..5837bb3bb
--- /dev/null
+++ b/test/js/bun/resolve/baz.js
@@ -0,0 +1,2 @@
+// this file is used in resolve.test.js
+export default {};
diff --git a/test/js/bun/resolve/file-importing-nonexistent-file.js b/test/js/bun/resolve/file-importing-nonexistent-file.js
new file mode 100644
index 000000000..f805e78bc
--- /dev/null
+++ b/test/js/bun/resolve/file-importing-nonexistent-file.js
@@ -0,0 +1 @@
+import "./does-not-exist.js.js.js.js";
diff --git a/test/js/bun/resolve/first.mjs b/test/js/bun/resolve/first.mjs
new file mode 100644
index 000000000..17021c623
--- /dev/null
+++ b/test/js/bun/resolve/first.mjs
@@ -0,0 +1,8 @@
+import { end, start } from "./startEnd.mjs";
+
+start("First");
+
+import "./second.mjs";
+import "./third.mjs";
+
+end("First");
diff --git a/test/js/bun/resolve/import-meta.test.js b/test/js/bun/resolve/import-meta.test.js
new file mode 100644
index 000000000..f87c8f89f
--- /dev/null
+++ b/test/js/bun/resolve/import-meta.test.js
@@ -0,0 +1,164 @@
+import { it, expect } from "bun:test";
+import { mkdirSync, rmSync, writeFileSync } from "node:fs";
+import * as Module from "node:module";
+import sync from "./require-json.json.js";
+
+const { path, dir } = import.meta;
+
+it("primordials are not here!", () => {
+ expect(import.meta.primordials === undefined).toBe(true);
+});
+
+it("import.meta.resolveSync", () => {
+ expect(import.meta.resolveSync("./" + import.meta.file, import.meta.path)).toBe(path);
+ const require = Module.createRequire(import.meta.path);
+ expect(require.resolve(import.meta.path)).toBe(path);
+ expect(require.resolve("./" + import.meta.file)).toBe(path);
+
+ // check it works with URL objects
+ expect(Module.createRequire(new URL(import.meta.url)).resolve(import.meta.path)).toBe(import.meta.path);
+});
+
+it("require with a query string works on dynamically created content", () => {
+ rmSync("/tmp/bun-test-import-meta-dynamic-dir", {
+ recursive: true,
+ force: true,
+ });
+ try {
+ const require = Module.createRequire("/tmp/bun-test-import-meta-dynamic-dir/foo.js");
+ try {
+ require("./bar.js?query=123.js");
+ } catch (e) {
+ expect(e.name).toBe("ResolveError");
+ }
+
+ mkdirSync("/tmp/bun-test-import-meta-dynamic-dir", { recursive: true });
+
+ writeFileSync("/tmp/bun-test-import-meta-dynamic-dir/bar.js", "export default 'hello';", "utf8");
+
+ expect(require("./bar.js?query=123.js").default).toBe("hello");
+ } catch (e) {
+ throw e;
+ } finally {
+ rmSync("/tmp/bun-test-import-meta-dynamic-dir", {
+ recursive: true,
+ force: true,
+ });
+ }
+});
+
+it("import.meta.require (json)", () => {
+ expect(import.meta.require("./require-json.json").hello).toBe(sync.hello);
+ const require = Module.createRequire(import.meta.path);
+ expect(require("./require-json.json.js").hello).toBe(sync.hello);
+});
+
+it("const f = require;require(json)", () => {
+ const f = require;
+ console.log(f);
+ expect(f("./require-json.json").hello).toBe(sync.hello);
+});
+
+it("Module.createRequire().resolve", () => {
+ const expected = Bun.resolveSync("./require-json.json", import.meta.dir);
+
+ const createdRequire = Module.createRequire(import.meta.path);
+ const result = createdRequire.resolve("./require-json.json");
+
+ expect(result).toBe(expected);
+});
+
+// this is stubbed out
+it("Module._nodeModulePaths()", () => {
+ const expected = Module._nodeModulePaths();
+ expect(!!expected).toBe(true);
+});
+
+// this isn't used in bun but exists anyway
+// we just want it to not be undefined
+it("Module._cache", () => {
+ const expected = typeof Module._cache === "object" && Module._cache;
+ expect(!!expected).toBe(true);
+});
+
+it("Module._resolveFileName()", () => {
+ const expected = Bun.resolveSync(import.meta.path, "/");
+ const result = Module._resolveFileName(import.meta.path, "/", true);
+ expect(result).toBe(expected);
+});
+
+it("Module.createRequire(file://url).resolve(file://url)", () => {
+ const expected = Bun.resolveSync("./require-json.json", import.meta.dir);
+
+ const createdRequire = Module.createRequire(import.meta.url);
+ const result1 = createdRequire.resolve("./require-json.json");
+ const result2 = createdRequire.resolve("file://./require-json.json");
+
+ expect(result1).toBe(expected);
+ expect(result2).toBe(expected);
+});
+
+it("import.meta.require.resolve", () => {
+ const expected = Bun.resolveSync("./require-json.json", import.meta.dir);
+ var { resolve } = import.meta.require;
+ const result = resolve("./require-json.json");
+ expect(result).toBe(expected);
+});
+
+it("import.meta.require (javascript)", () => {
+ expect(import.meta.require("./require-js.js").hello).toBe(sync.hello);
+ const require = Module.createRequire(import.meta.path);
+ expect(require("./require-js.js").hello).toBe(sync.hello);
+});
+
+it("import() require + TLA", async () => {
+ expect((await import("./import-require-tla.js")).foo).toBe("bar");
+});
+
+it("import.meta.require (javascript, live bindings)", () => {
+ var Source = import.meta.require("./import.live.decl.js");
+
+ // require transpiles to import.meta.require
+ var ReExport = require("./import.live.rexport.js");
+
+ // dynamic require (string interpolation that way forces it to be dynamic)
+ var ReExportDynamic = require(`./import.live.${"rexport".split("").join("")}.js`);
+
+ expect(Source.foo).toBe(1);
+ Source.setFoo(Source.foo + 1);
+
+ expect(ReExport.foo).toBe(2);
+ expect(Source.foo).toBe(2);
+ expect(ReExportDynamic.foo).toBe(2);
+
+ Source.setFoo(Source.foo + 1);
+
+ var { Namespace } = require("./import.live.rexport-require.js");
+
+ expect(Namespace).toBe(Source);
+ expect(ReExport.foo).toBe(3);
+ expect(Source.foo).toBe(3);
+ expect(Namespace.foo).toBe(3);
+
+ ReExport.setFoo(ReExport.foo + 1);
+
+ expect(ReExport.foo).toBe(4);
+ expect(Source.foo).toBe(4);
+ expect(Namespace.foo).toBe(4);
+});
+
+it("import.meta.dir", () => {
+ expect(dir.endsWith("/bun/test/js/bun/resolve")).toBe(true);
+});
+
+it("import.meta.path", () => {
+ expect(path.endsWith("/bun/test/js/bun/resolve/import-meta.test.js")).toBe(true);
+});
+
+it('require("bun") works', () => {
+ expect(require("bun")).toBe(Bun);
+});
+
+it('import("bun") works', async () => {
+ expect(await import("bun")).toBe(Bun);
+});
diff --git a/test/js/bun/resolve/import-require-tla.js b/test/js/bun/resolve/import-require-tla.js
new file mode 100644
index 000000000..732fc34dd
--- /dev/null
+++ b/test/js/bun/resolve/import-require-tla.js
@@ -0,0 +1,7 @@
+const Fs = require("fs");
+
+const DirEnt = Fs.Dirent;
+
+await Promise.resolve(123);
+
+export const foo = "bar";
diff --git a/test/js/bun/resolve/import.live.decl.js b/test/js/bun/resolve/import.live.decl.js
new file mode 100644
index 000000000..46e67c9bd
--- /dev/null
+++ b/test/js/bun/resolve/import.live.decl.js
@@ -0,0 +1,4 @@
+export var foo = 1;
+export function setFoo(val) {
+ foo = val;
+}
diff --git a/test/js/bun/resolve/import.live.rexport-require.js b/test/js/bun/resolve/import.live.rexport-require.js
new file mode 100644
index 000000000..10c993e08
--- /dev/null
+++ b/test/js/bun/resolve/import.live.rexport-require.js
@@ -0,0 +1 @@
+export const Namespace = import.meta.require("./import.live.decl.js");
diff --git a/test/js/bun/resolve/import.live.rexport.js b/test/js/bun/resolve/import.live.rexport.js
new file mode 100644
index 000000000..6709c3466
--- /dev/null
+++ b/test/js/bun/resolve/import.live.rexport.js
@@ -0,0 +1,2 @@
+export { foo, setFoo } from "./import.live.decl.js";
+import { foo as bar } from "./import.live.decl.js";
diff --git a/test/js/bun/resolve/png/test-png-import.test.js b/test/js/bun/resolve/png/test-png-import.test.js
new file mode 100644
index 000000000..ca2d0b9ce
--- /dev/null
+++ b/test/js/bun/resolve/png/test-png-import.test.js
@@ -0,0 +1,7 @@
+import { expect, test } from "bun:test";
+import { resolve } from "path";
+import MyPNG from "./test-png.png";
+
+test("png import", () => {
+ expect(MyPNG).toBe(resolve(__dirname, "./test-png.png"));
+});
diff --git a/test/js/bun/resolve/png/test-png.png b/test/js/bun/resolve/png/test-png.png
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/test/js/bun/resolve/png/test-png.png
diff --git a/test/js/bun/resolve/require-js-top-level-await.js b/test/js/bun/resolve/require-js-top-level-await.js
new file mode 100644
index 000000000..16bff41c3
--- /dev/null
+++ b/test/js/bun/resolve/require-js-top-level-await.js
@@ -0,0 +1 @@
+export const fail = await 1;
diff --git a/test/js/bun/resolve/require-js.js b/test/js/bun/resolve/require-js.js
new file mode 100644
index 000000000..36fc31432
--- /dev/null
+++ b/test/js/bun/resolve/require-js.js
@@ -0,0 +1,2 @@
+import { hello } from "./require-js2.js";
+export { hello };
diff --git a/test/js/bun/resolve/require-js2.js b/test/js/bun/resolve/require-js2.js
new file mode 100644
index 000000000..518e69641
--- /dev/null
+++ b/test/js/bun/resolve/require-js2.js
@@ -0,0 +1 @@
+export const hello = -123;
diff --git a/test/js/bun/resolve/require-json.json b/test/js/bun/resolve/require-json.json
new file mode 100644
index 000000000..6414edc0e
--- /dev/null
+++ b/test/js/bun/resolve/require-json.json
@@ -0,0 +1,3 @@
+{
+ "hello": -123
+}
diff --git a/test/js/bun/resolve/require-referenceerror.snapshot.js b/test/js/bun/resolve/require-referenceerror.snapshot.js
new file mode 100644
index 000000000..c52af3d22
--- /dev/null
+++ b/test/js/bun/resolve/require-referenceerror.snapshot.js
@@ -0,0 +1,5 @@
+/**
+ * https://github.com/oven-sh/bun/issues/685
+ */
+import { v4 as uuidv4 } from "uuid";
+Bun.inspect(uuidv4());
diff --git a/test/js/bun/resolve/resolve-error.test.ts b/test/js/bun/resolve/resolve-error.test.ts
new file mode 100644
index 000000000..cddd2c051
--- /dev/null
+++ b/test/js/bun/resolve/resolve-error.test.ts
@@ -0,0 +1,11 @@
+import { expect, it, describe } from "bun:test";
+
+describe("ResolveError", () => {
+ it("position object does not segfault", async () => {
+ try {
+ await import("./file-importing-nonexistent-file.js");
+ } catch (e: any) {
+ expect(Bun.inspect(e.position).length > 0).toBe(true);
+ }
+ });
+});
diff --git a/test/js/bun/resolve/resolve-typescript-file.tsx b/test/js/bun/resolve/resolve-typescript-file.tsx
new file mode 100644
index 000000000..ff8b4c563
--- /dev/null
+++ b/test/js/bun/resolve/resolve-typescript-file.tsx
@@ -0,0 +1 @@
+export default {};
diff --git a/test/js/bun/resolve/resolve.test.js b/test/js/bun/resolve/resolve.test.js
new file mode 100644
index 000000000..305a374a2
--- /dev/null
+++ b/test/js/bun/resolve/resolve.test.js
@@ -0,0 +1,229 @@
+import { it, expect } from "bun:test";
+import { mkdirSync, writeFileSync } from "fs";
+import { join, resolve } from "path";
+
+function resolveFrom(from) {
+ return specifier => import.meta.resolveSync(specifier, from);
+}
+
+it("#imports", async () => {
+ await writePackageJSONImportsFixture();
+
+ const baz = await import.meta.resolve("#foo", join(await import.meta.resolve("package-json-imports/baz"), "../"));
+ expect(baz.endsWith("foo/private-foo.js")).toBe(true);
+
+ const subpath = await import.meta.resolve(
+ "#foo/bar",
+ join(await import.meta.resolve("package-json-imports/baz"), "../"),
+ );
+ expect(subpath.endsWith("foo/private-foo.js")).toBe(true);
+
+ const react = await import.meta.resolve(
+ "#internal-react",
+ join(await import.meta.resolve("package-json-imports/baz"), "../"),
+ );
+ expect(react.endsWith("/react/index.js")).toBe(true);
+
+ // Check that #foo is not resolved to the package.json file.
+ try {
+ await import.meta.resolve("#foo");
+ throw new Error("Test failed");
+ } catch (exception) {
+ expect(exception instanceof ResolveError).toBe(true);
+ expect(exception.referrer).toBe(import.meta.path);
+ expect(exception.name).toBe("ResolveError");
+ }
+
+ // Chcek that package-json-imports/#foo doesn't work
+ try {
+ await import.meta.resolve("package-json-imports/#foo");
+ throw new Error("Test failed");
+ } catch (exception) {
+ expect(exception instanceof ResolveError).toBe(true);
+ expect(exception.referrer).toBe(import.meta.path);
+ expect(exception.name).toBe("ResolveError");
+ }
+});
+
+it("#imports with wildcard", async () => {
+ await writePackageJSONImportsFixture();
+ const run = resolveFrom(resolve(import.meta.dir + "/node_modules/package-json-imports/package.json"));
+
+ const wildcard = resolve(import.meta.dir + "/node_modules/package-json-imports/foo/wildcard.js");
+ expect(run("#foo/wildcard.js")).toBe(wildcard);
+ expect(run("#foo/extensionless/wildcard")).toBe(wildcard);
+});
+
+it("import.meta.resolve", async () => {
+ expect(await import.meta.resolve("./resolve.test.js")).toBe(import.meta.path);
+
+ expect(await import.meta.resolve("./resolve.test.js", import.meta.path)).toBe(import.meta.path);
+
+ expect(
+ // optional second param can be any path, including a dir
+ await import.meta.resolve("./bun.js/resolve.test.js", join(import.meta.path, "../")),
+ ).toBe(import.meta.path);
+
+ // can be a package path
+ expect((await import.meta.resolve("react", import.meta.path)).length > 0).toBe(true);
+
+ // file extensions are optional
+ expect(await import.meta.resolve("./resolve.test")).toBe(import.meta.path);
+
+ // works with tsconfig.json "paths"
+ expect(await import.meta.resolve("foo/bar")).toBe(join(import.meta.path, "../baz.js"));
+ expect(await import.meta.resolve("@faasjs/baz")).toBe(join(import.meta.path, "../baz.js"));
+
+ // works with package.json "exports"
+ writePackageJSONExportsFixture();
+ expect(await import.meta.resolve("package-json-exports/baz")).toBe(
+ join(import.meta.path, "../node_modules/package-json-exports/foo/bar.js"),
+ );
+
+ // if they never exported /package.json, allow reading from it too
+ expect(await import.meta.resolve("package-json-exports/package.json")).toBe(
+ join(import.meta.path, "../node_modules/package-json-exports/package.json"),
+ );
+
+ // if an unnecessary ".js" extension was added, try against /baz
+ expect(await import.meta.resolve("package-json-exports/baz.js")).toBe(
+ join(import.meta.path, "../node_modules/package-json-exports/foo/bar.js"),
+ );
+
+ // works with TypeScript compiler edgecases like:
+ // - If the file ends with .js and it doesn't exist, try again with .ts and .tsx
+ expect(await import.meta.resolve("./resolve-typescript-file.js")).toBe(
+ join(import.meta.path, "../resolve-typescript-file.tsx"),
+ );
+ expect(await import.meta.resolve("./resolve-typescript-file.tsx")).toBe(
+ join(import.meta.path, "../resolve-typescript-file.tsx"),
+ );
+
+ // throws a ResolveError on failure
+ try {
+ await import.meta.resolve("THIS FILE DOESNT EXIST");
+ throw new Error("Test failed");
+ } catch (exception) {
+ expect(exception instanceof ResolveError).toBe(true);
+ expect(exception.referrer).toBe(import.meta.path);
+ expect(exception.name).toBe("ResolveError");
+ }
+});
+
+// the slightly lower level API, which doesn't prefill the second param
+// and expects a directory instead of a filepath
+it("Bun.resolve", async () => {
+ expect(await Bun.resolve("./resolve.test.js", import.meta.dir)).toBe(import.meta.path);
+});
+
+// synchronous
+it("Bun.resolveSync", () => {
+ expect(Bun.resolveSync("./resolve.test.js", import.meta.dir)).toBe(import.meta.path);
+});
+
+it("self-referencing imports works", async () => {
+ await writePackageJSONExportsFixture();
+
+ const baz = await import.meta.resolve("package-json-exports/baz");
+ const namespace = await import.meta.resolve("package-json-exports/references-baz");
+ Loader.registry.delete(baz);
+ Loader.registry.delete(namespace);
+ var a = await import(baz);
+ var b = await import(namespace);
+ expect(a.bar).toBe(1);
+ expect(b.bar).toBe(1);
+
+ Loader.registry.delete(baz);
+ Loader.registry.delete(namespace);
+ var a = await import("package-json-exports/baz");
+ var b = await import("package-json-exports/references-baz");
+ expect(a.bar).toBe(1);
+ expect(b.bar).toBe(1);
+
+ Loader.registry.delete(baz);
+ Loader.registry.delete(namespace);
+ var a = import.meta.require("package-json-exports/baz");
+ var b = import.meta.require("package-json-exports/references-baz");
+ expect(a.bar).toBe(1);
+ expect(b.bar).toBe(1);
+
+ Loader.registry.delete(baz);
+ Loader.registry.delete(namespace);
+ var a = import.meta.require(baz);
+ var b = import.meta.require(namespace);
+ expect(a.bar).toBe(1);
+ expect(b.bar).toBe(1);
+
+ // test that file:// works
+ Loader.registry.delete(baz);
+ Loader.registry.delete(namespace);
+ var a = import.meta.require("file://" + baz);
+ var b = import.meta.require("file://" + namespace);
+ expect(a.bar).toBe(1);
+ expect(b.bar).toBe(1);
+});
+
+function writePackageJSONExportsFixture() {
+ try {
+ mkdirSync(join(import.meta.dir, "./node_modules/package-json-exports/foo"), {
+ recursive: true,
+ });
+ } catch (exception) {}
+ writeFileSync(join(import.meta.dir, "./node_modules/package-json-exports/foo/bar.js"), "export const bar = 1;");
+ writeFileSync(
+ join(import.meta.dir, "./node_modules/package-json-exports/foo/references-baz.js"),
+ "export {bar} from 'package-json-exports/baz';",
+ );
+ writeFileSync(
+ join(import.meta.dir, "./node_modules/package-json-exports/package.json"),
+ JSON.stringify(
+ {
+ name: "package-json-exports",
+ exports: {
+ "./baz": "./foo/bar.js",
+ "./references-baz": "./foo/references-baz.js",
+ },
+ },
+ null,
+ 2,
+ ),
+ );
+}
+
+function writePackageJSONImportsFixture() {
+ try {
+ mkdirSync(join(import.meta.dir, "./node_modules/package-json-imports/foo"), {
+ recursive: true,
+ });
+ } catch (exception) {}
+ writeFileSync(join(import.meta.dir, "./node_modules/package-json-imports/foo/bar.js"), "export const bar = 1;");
+ writeFileSync(
+ join(import.meta.dir, "./node_modules/package-json-imports/foo/wildcard.js"),
+ "export const wildcard = 1;",
+ );
+ writeFileSync(
+ join(import.meta.dir, "./node_modules/package-json-imports/foo/private-foo.js"),
+ "export {bar} from 'package-json-imports/#foo';",
+ );
+ writeFileSync(
+ join(import.meta.dir, "./node_modules/package-json-imports/package.json"),
+ JSON.stringify(
+ {
+ name: "package-json-imports",
+ exports: {
+ "./baz": "./foo/bar.js",
+ },
+ imports: {
+ "#foo/bar": "./foo/private-foo.js",
+ "#foo/*.js": "./foo/*.js",
+ "#foo/extensionless/*": "./foo/*.js",
+ "#foo": "./foo/private-foo.js",
+
+ "#internal-react": "react",
+ },
+ },
+ null,
+ 2,
+ ),
+ );
+}
diff --git a/test/js/bun/resolve/second-child.mjs b/test/js/bun/resolve/second-child.mjs
new file mode 100644
index 000000000..5fb06ed45
--- /dev/null
+++ b/test/js/bun/resolve/second-child.mjs
@@ -0,0 +1,5 @@
+import { start, end } from "./startEnd.mjs";
+
+start("Second (nested import)");
+
+end("Second (nested import)");
diff --git a/test/js/bun/resolve/second.mjs b/test/js/bun/resolve/second.mjs
new file mode 100644
index 000000000..888eb11b9
--- /dev/null
+++ b/test/js/bun/resolve/second.mjs
@@ -0,0 +1,7 @@
+import { start, end } from "./startEnd.mjs";
+
+start("Second");
+
+import "./second-child.mjs";
+
+end("Second");
diff --git a/test/js/bun/resolve/startEnd.mjs b/test/js/bun/resolve/startEnd.mjs
new file mode 100644
index 000000000..8b5549802
--- /dev/null
+++ b/test/js/bun/resolve/startEnd.mjs
@@ -0,0 +1,6 @@
+export function start(name) {
+ console.log(`[start] ${name}`);
+}
+export function end(name) {
+ console.log(`[end] ${name}`);
+}
diff --git a/test/js/bun/resolve/third.mjs b/test/js/bun/resolve/third.mjs
new file mode 100644
index 000000000..f5ba5cc84
--- /dev/null
+++ b/test/js/bun/resolve/third.mjs
@@ -0,0 +1,4 @@
+import { end, start } from "./startEnd.mjs";
+
+start("Third");
+end("Third");
diff --git a/test/js/bun/resolve/toml/toml-fixture.toml b/test/js/bun/resolve/toml/toml-fixture.toml
new file mode 100644
index 000000000..090563ef7
--- /dev/null
+++ b/test/js/bun/resolve/toml/toml-fixture.toml
@@ -0,0 +1,39 @@
+
+framework = "next"
+origin = "http://localhost:5000"
+inline.array = [1234, 4, 5, 6]
+
+
+[macros]
+react-relay = { "graphql" = "node_modules/bun-macro-relay/bun-macro-relay.tsx" }
+
+[install.scopes]
+"@mybigcompany2" = { "token" = "123456", "url" = "https://registry.mybigcompany.com" }
+"@mybigcompany3" = { "token" = "123456", "url" = "https://registry.mybigcompany.com", "three" = 4 }
+
+
+[install.scopes."@mybigcompany"]
+token = "123456"
+url = "https://registry.mybigcompany.com"
+
+[bundle.packages]
+"@emotion/react" = true
+
+
+[dev]
+foo = 123
+"foo.bar" = "baz"
+"abba.baba" = "baba"
+dabba = -123
+doo = 123.456
+one.two.three = 4
+
+[[array]]
+entry_one = "one"
+entry_two = "two"
+
+[[array]]
+entry_one = "three"
+
+[[array.nested]]
+entry_one = "four"
diff --git a/test/js/bun/resolve/toml/toml.test.js b/test/js/bun/resolve/toml/toml.test.js
new file mode 100644
index 000000000..17d167476
--- /dev/null
+++ b/test/js/bun/resolve/toml/toml.test.js
@@ -0,0 +1,26 @@
+import { describe, it, expect } from "bun:test";
+import { gc } from "harness";
+
+it("syntax", async () => {
+ gc();
+
+ const toml = (await import("./toml-fixture.toml")).default;
+ gc();
+
+ expect(toml.framework).toBe("next");
+ expect(toml.bundle.packages["@emotion/react"]).toBe(true);
+ expect(toml.array[0].entry_one).toBe("one");
+ expect(toml.array[0].entry_two).toBe("two");
+ expect(toml.array[1].entry_one).toBe("three");
+ expect(toml.array[1].entry_two).toBe(undefined);
+ expect(toml.array[1].nested[0].entry_one).toBe("four");
+ expect(toml.dev.one.two.three).toBe(4);
+ expect(toml.dev.foo).toBe(123);
+ expect(toml.inline.array[0]).toBe(1234);
+ expect(toml.inline.array[1]).toBe(4);
+ expect(toml.dev["foo.bar"]).toBe("baz");
+ expect(toml.install.scopes["@mybigcompany"].url).toBe("https://registry.mybigcompany.com");
+ expect(toml.install.scopes["@mybigcompany2"].url).toBe("https://registry.mybigcompany.com");
+ expect(toml.install.scopes["@mybigcompany3"].three).toBe(4);
+ gc();
+});
diff --git a/test/js/bun/spawn/bash-echo.sh b/test/js/bun/spawn/bash-echo.sh
new file mode 100644
index 000000000..57bca4b01
--- /dev/null
+++ b/test/js/bun/spawn/bash-echo.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+myvar=$(cat /dev/stdin)
+echo -e "$myvar"
diff --git a/test/js/bun/spawn/bun-spawn-test.js b/test/js/bun/spawn/bun-spawn-test.js
new file mode 100644
index 000000000..1617a8588
--- /dev/null
+++ b/test/js/bun/spawn/bun-spawn-test.js
@@ -0,0 +1,21 @@
+const EventEmitter = import.meta.require("events");
+class TestClass extends EventEmitter {
+ #handle = null;
+ spawn() {
+ this.#handle = Bun.spawn(["pwd"], {
+ cwd: "/tmp",
+ onExit: this.#handleOnExit.bind(this),
+ });
+ }
+ #handleOnExit(code) {
+ console.log(code);
+ this.emit("exit");
+ }
+}
+
+const testClass = new TestClass();
+testClass.spawn();
+testClass.on("exit", () => {
+ console.log("exiting");
+ process.exit(0);
+});
diff --git a/test/js/bun/spawn/exit-code-0.js b/test/js/bun/spawn/exit-code-0.js
new file mode 100644
index 000000000..dcbbff6c9
--- /dev/null
+++ b/test/js/bun/spawn/exit-code-0.js
@@ -0,0 +1 @@
+process.exit(0);
diff --git a/test/js/bun/spawn/exit-code-1.js b/test/js/bun/spawn/exit-code-1.js
new file mode 100644
index 000000000..6cee2e1e7
--- /dev/null
+++ b/test/js/bun/spawn/exit-code-1.js
@@ -0,0 +1 @@
+process.exit(1);
diff --git a/test/js/bun/spawn/exit-code-await-throw-1.js b/test/js/bun/spawn/exit-code-await-throw-1.js
new file mode 100644
index 000000000..6b8c42eab
--- /dev/null
+++ b/test/js/bun/spawn/exit-code-await-throw-1.js
@@ -0,0 +1,3 @@
+await (async function () {
+ throw 42;
+})();
diff --git a/test/js/bun/spawn/exit-code-unhandled-throw.js b/test/js/bun/spawn/exit-code-unhandled-throw.js
new file mode 100644
index 000000000..e8f5ca4cb
--- /dev/null
+++ b/test/js/bun/spawn/exit-code-unhandled-throw.js
@@ -0,0 +1,3 @@
+(async function () {
+ throw 42;
+})();
diff --git a/test/js/bun/spawn/exit-code.test.ts b/test/js/bun/spawn/exit-code.test.ts
new file mode 100644
index 000000000..cda76a395
--- /dev/null
+++ b/test/js/bun/spawn/exit-code.test.ts
@@ -0,0 +1,23 @@
+import { describe, expect, it, test } from "bun:test";
+import { bunExe } from "harness";
+import { spawnSync } from "bun";
+
+it("process.exit(1) works", () => {
+ const { exitCode } = spawnSync([bunExe(), import.meta.dir + "/exit-code-1.js"]);
+ expect(exitCode).toBe(1);
+});
+
+it("await on a thrown value reports exit code 1", () => {
+ const { exitCode } = spawnSync([bunExe(), import.meta.dir + "/exit-code-await-throw-1.js"]);
+ expect(exitCode).toBe(1);
+});
+
+it("unhandled promise rejection reports exit code 1", () => {
+ const { exitCode } = spawnSync([bunExe(), import.meta.dir + "/exit-code-unhandled-throw.js"]);
+ expect(exitCode).toBe(1);
+});
+
+it("process.exit(0) works", () => {
+ const { exitCode } = spawnSync([bunExe(), import.meta.dir + "/exit-code-0.js"]);
+ expect(exitCode).toBe(0);
+});
diff --git a/test/js/bun/spawn/exit.js b/test/js/bun/spawn/exit.js
new file mode 100644
index 000000000..fb28b1fb4
--- /dev/null
+++ b/test/js/bun/spawn/exit.js
@@ -0,0 +1,2 @@
+process.exit(0);
+throw new Error("Well that didn't work");
diff --git a/test/js/bun/spawn/spawn-streaming-stdin.test.ts b/test/js/bun/spawn/spawn-streaming-stdin.test.ts
new file mode 100644
index 000000000..e2d346ec8
--- /dev/null
+++ b/test/js/bun/spawn/spawn-streaming-stdin.test.ts
@@ -0,0 +1,57 @@
+import { it, test, expect } from "bun:test";
+import { spawn } from "bun";
+import { bunExe, bunEnv, gcTick } from "harness";
+import { closeSync, openSync } from "fs";
+
+const N = 100;
+test("spawn can write to stdin multiple chunks", async () => {
+ const maxFD = openSync("/dev/null", "w");
+ for (let i = 0; i < N; i++) {
+ var exited;
+ await (async function () {
+ const proc = spawn({
+ cmd: [bunExe(), import.meta.dir + "/stdin-repro.js"],
+ stdout: "pipe",
+ stdin: "pipe",
+ stderr: Bun.file("/tmp/out.log"),
+ env: bunEnv,
+ });
+ exited = proc.exited;
+ var counter = 0;
+ var inCounter = 0;
+ var chunks = [];
+ const prom = (async function () {
+ try {
+ for await (var chunk of proc.stdout) {
+ chunks.push(chunk);
+ }
+ } catch (e) {
+ console.log(e.stack);
+ throw e;
+ }
+ })();
+
+ const prom2 = (async function () {
+ while (true) {
+ proc.stdin.write("Wrote to stdin!\n");
+ inCounter++;
+ await new Promise(resolve => setTimeout(resolve, 8));
+
+ if (inCounter === 4) break;
+ }
+ proc.stdin.end();
+ })();
+
+ await Promise.all([prom, prom2]);
+ expect(Buffer.concat(chunks).toString().trim()).toBe("Wrote to stdin!\n".repeat(4).trim());
+ await proc.exited;
+ })();
+ }
+
+ closeSync(maxFD);
+ const newMaxFD = openSync("/dev/null", "w");
+ closeSync(newMaxFD);
+
+ // assert we didn't leak any file descriptors
+ expect(newMaxFD).toBe(maxFD);
+});
diff --git a/test/js/bun/spawn/spawn-streaming-stdout-repro.js b/test/js/bun/spawn/spawn-streaming-stdout-repro.js
new file mode 100644
index 000000000..3976ff095
--- /dev/null
+++ b/test/js/bun/spawn/spawn-streaming-stdout-repro.js
@@ -0,0 +1,5 @@
+var writer = Bun.stdout.writer();
+setInterval(() => {
+ writer.write("Wrote to stdout\n");
+ writer.flush();
+}, 20);
diff --git a/test/js/bun/spawn/spawn-streaming-stdout.test.ts b/test/js/bun/spawn/spawn-streaming-stdout.test.ts
new file mode 100644
index 000000000..75e36ca2c
--- /dev/null
+++ b/test/js/bun/spawn/spawn-streaming-stdout.test.ts
@@ -0,0 +1,42 @@
+import { it, test, expect } from "bun:test";
+import { spawn } from "bun";
+import { bunExe, bunEnv, gcTick } from "harness";
+import { closeSync, openSync } from "fs";
+
+test("spawn can read from stdout multiple chunks", async () => {
+ gcTick(true);
+ const maxFD = openSync("/dev/null", "w");
+ closeSync(maxFD);
+
+ for (let i = 0; i < 10; i++)
+ await (async function () {
+ var exited;
+ const proc = spawn({
+ cmd: [bunExe(), import.meta.dir + "/spawn-streaming-stdout-repro.js"],
+ stdin: "ignore",
+ stdout: "pipe",
+ stderr: "ignore",
+ env: bunEnv,
+ });
+ var chunks = [];
+ let counter = 0;
+ try {
+ for await (var chunk of proc.stdout) {
+ chunks.push(chunk);
+ counter++;
+ if (counter > 3) break;
+ }
+ } catch (e) {
+ console.log(e.stack);
+ throw e;
+ }
+ expect(counter).toBe(4);
+ // TODO: fix bug with returning SIGHUP instead of exit code 1
+ proc.kill();
+ expect(Buffer.concat(chunks).toString()).toBe("Wrote to stdout\n".repeat(4));
+ })();
+
+ const newMaxFD = openSync("/dev/null", "w");
+ closeSync(newMaxFD);
+ expect(newMaxFD).toBe(maxFD);
+});
diff --git a/test/js/bun/spawn/spawn.test.ts b/test/js/bun/spawn/spawn.test.ts
new file mode 100644
index 000000000..876985e66
--- /dev/null
+++ b/test/js/bun/spawn/spawn.test.ts
@@ -0,0 +1,380 @@
+import { ArrayBufferSink, readableStreamToText, spawn, spawnSync, write } from "bun";
+import { describe, expect, it } from "bun:test";
+import { gcTick as _gcTick, bunEnv } from "harness";
+import { rmdirSync, unlinkSync, rmSync, writeFileSync } from "node:fs";
+
+for (let [gcTick, label] of [
+ [_gcTick, "gcTick"],
+ // [() => {}, "no gc tick"],
+] as const) {
+ Bun.gc(true);
+ describe(label, () => {
+ describe("spawnSync", () => {
+ const hugeString = "hello".repeat(10000).slice();
+
+ it("as an array", () => {
+ const { stdout } = spawnSync(["echo", "hi"]);
+ gcTick();
+ // stdout is a Buffer
+ const text = stdout!.toString();
+ expect(text).toBe("hi\n");
+ gcTick();
+ });
+
+ it("Uint8Array works as stdin", async () => {
+ const { stdout, stderr } = spawnSync({
+ cmd: ["cat"],
+ stdin: new TextEncoder().encode(hugeString),
+ });
+ gcTick();
+ expect(stdout!.toString()).toBe(hugeString);
+ expect(stderr!.byteLength).toBe(0);
+ gcTick();
+ });
+
+ it("check exit code", async () => {
+ const { exitCode: exitCode1 } = spawnSync({
+ cmd: ["ls"],
+ });
+ gcTick();
+ const { exitCode: exitCode2 } = spawnSync({
+ cmd: ["false"],
+ });
+ gcTick();
+ expect(exitCode1).toBe(0);
+ expect(exitCode2).toBe(1);
+ gcTick();
+ });
+ });
+
+ describe("spawn", () => {
+ const hugeString = "hello".repeat(10000).slice();
+
+ it("as an array", async () => {
+ gcTick();
+ await (async () => {
+ const { stdout } = spawn(["echo", "hello"], {
+ stdout: "pipe",
+ stderr: null,
+ stdin: null,
+ });
+ gcTick();
+ const text = await new Response(stdout).text();
+ expect(text).toBe("hello\n");
+ })();
+ gcTick();
+ });
+
+ it("as an array with options object", async () => {
+ gcTick();
+ const { stdout } = spawn(["printenv", "FOO"], {
+ cwd: "/tmp",
+ env: {
+ ...process.env,
+ FOO: "bar",
+ },
+ stdin: null,
+ stdout: "pipe",
+ stderr: null,
+ });
+ gcTick();
+ const text = await new Response(stdout).text();
+ expect(text).toBe("bar\n");
+ gcTick();
+ });
+
+ it("Uint8Array works as stdin", async () => {
+ rmSync("/tmp/out.123.txt", { force: true });
+ gcTick();
+ const { exited } = spawn({
+ cmd: ["cat"],
+ stdin: new TextEncoder().encode(hugeString),
+ stdout: Bun.file("/tmp/out.123.txt"),
+ });
+ gcTick();
+ await exited;
+ expect(require("fs").readFileSync("/tmp/out.123.txt", "utf8")).toBe(hugeString);
+ gcTick();
+ });
+
+ it("check exit code", async () => {
+ const exitCode1 = await spawn({
+ cmd: ["ls"],
+ }).exited;
+ gcTick();
+ const exitCode2 = await spawn({
+ cmd: ["false"],
+ }).exited;
+ gcTick();
+ expect(exitCode1).toBe(0);
+ expect(exitCode2).toBe(1);
+ gcTick();
+ });
+
+ it("nothing to stdout and sleeping doesn't keep process open 4ever", async () => {
+ const proc = spawn({
+ cmd: ["sleep", "0.1"],
+ });
+ gcTick();
+ for await (const _ of proc.stdout!) {
+ throw new Error("should not happen");
+ }
+ gcTick();
+ });
+
+ it("check exit code from onExit", async () => {
+ for (let i = 0; i < 1000; i++) {
+ var exitCode1, exitCode2;
+ await new Promise<void>(resolve => {
+ var counter = 0;
+ spawn({
+ cmd: ["ls"],
+ stdin: "ignore",
+ stdout: "ignore",
+ stderr: "ignore",
+ onExit(subprocess, code) {
+ exitCode1 = code;
+ counter++;
+ if (counter === 2) {
+ resolve();
+ }
+ },
+ });
+
+ spawn({
+ cmd: ["false"],
+ stdin: "ignore",
+ stdout: "ignore",
+ stderr: "ignore",
+ onExit(subprocess, code) {
+ exitCode2 = code;
+ counter++;
+
+ if (counter === 2) {
+ resolve();
+ }
+ },
+ });
+ });
+
+ expect(exitCode1).toBe(0);
+ expect(exitCode2).toBe(1);
+ }
+ });
+
+ it("Blob works as stdin", async () => {
+ rmSync("/tmp/out.123.txt", { force: true });
+ gcTick();
+ const { exited } = spawn({
+ cmd: ["cat"],
+ stdin: new Blob([new TextEncoder().encode(hugeString)]),
+ stdout: Bun.file("/tmp/out.123.txt"),
+ });
+
+ await exited;
+ expect(await Bun.file("/tmp/out.123.txt").text()).toBe(hugeString);
+ });
+
+ it("Bun.file() works as stdout", async () => {
+ rmSync("/tmp/out.123.txt", { force: true });
+ gcTick();
+ const { exited } = spawn({
+ cmd: ["echo", "hello"],
+ stdout: Bun.file("/tmp/out.123.txt"),
+ });
+
+ await exited;
+ gcTick();
+ expect(await Bun.file("/tmp/out.123.txt").text()).toBe("hello\n");
+ });
+
+ it("Bun.file() works as stdin", async () => {
+ await write(Bun.file("/tmp/out.456.txt"), "hello there!");
+ gcTick();
+ const { stdout } = spawn({
+ cmd: ["cat"],
+ stdout: "pipe",
+ stdin: Bun.file("/tmp/out.456.txt"),
+ });
+ gcTick();
+ expect(await readableStreamToText(stdout!)).toBe("hello there!");
+ });
+
+ it("Bun.file() works as stdin and stdout", async () => {
+ writeFileSync("/tmp/out.456.txt", "hello!");
+ gcTick();
+ writeFileSync("/tmp/out.123.txt", "wrong!");
+ gcTick();
+
+ const { exited } = spawn({
+ cmd: ["cat"],
+ stdout: Bun.file("/tmp/out.123.txt"),
+ stdin: Bun.file("/tmp/out.456.txt"),
+ });
+ gcTick();
+ await exited;
+ expect(await Bun.file("/tmp/out.456.txt").text()).toBe("hello!");
+ gcTick();
+ expect(await Bun.file("/tmp/out.123.txt").text()).toBe("hello!");
+ });
+
+ it("stdout can be read", async () => {
+ await Bun.write("/tmp/out.txt", hugeString);
+ gcTick();
+ const { stdout } = spawn({
+ cmd: ["cat", "/tmp/out.txt"],
+ stdout: "pipe",
+ });
+
+ gcTick();
+
+ const text = await readableStreamToText(stdout!);
+ gcTick();
+ expect(text).toBe(hugeString);
+ });
+
+ it("kill(1) works", async () => {
+ const process = spawn({
+ cmd: ["bash", "-c", "sleep 1000"],
+ stdout: "pipe",
+ });
+ gcTick();
+ const prom = process.exited;
+ process.kill(1);
+ await prom;
+ });
+
+ it("kill() works", async () => {
+ const process = spawn({
+ cmd: ["bash", "-c", "sleep 1000"],
+ stdout: "pipe",
+ });
+ gcTick();
+ const prom = process.exited;
+ process.kill();
+ await prom;
+ });
+
+ it("stdin can be read and stdout can be written", async () => {
+ const proc = spawn({
+ cmd: ["bash", import.meta.dir + "/bash-echo.sh"],
+ stdout: "pipe",
+ stdin: "pipe",
+ lazy: true,
+ stderr: "inherit",
+ });
+
+ var stdout = proc.stdout!;
+ var reader = stdout.getReader();
+ proc.stdin!.write("hey\n");
+ await proc.stdin!.end();
+ var text = "";
+
+ reader;
+ var done = false,
+ value;
+
+ while (!done) {
+ ({ value, done } = await reader.read());
+ if (value) text += new TextDecoder().decode(value);
+ if (done && text.length === 0) {
+ reader.releaseLock();
+ reader = stdout.getReader();
+ done = false;
+ }
+ }
+
+ expect(text.trim().length).toBe("hey".length);
+ expect(text.trim()).toBe("hey");
+ gcTick();
+ await proc.exited;
+ });
+
+ describe("pipe", () => {
+ function huge() {
+ return spawn({
+ cmd: ["echo", hugeString],
+ stdout: "pipe",
+ stdin: "pipe",
+ stderr: "inherit",
+ lazy: true,
+ });
+ }
+
+ function helloWorld() {
+ return spawn({
+ cmd: ["echo", "hello"],
+ stdout: "pipe",
+ stdin: "ignore",
+ });
+ }
+
+ const fixtures = [
+ [helloWorld, "hello"],
+ [huge, hugeString],
+ ] as const;
+
+ for (const [callback, fixture] of fixtures) {
+ describe(fixture.slice(0, 12), () => {
+ describe("should should allow reading stdout", () => {
+ it("before exit", async () => {
+ const process = callback();
+ const output = await readableStreamToText(process.stdout!);
+ await process.exited;
+ const expected = fixture + "\n";
+
+ expect(output.length).toBe(expected.length);
+ expect(output).toBe(expected);
+ });
+
+ it("before exit (chunked)", async () => {
+ const process = callback();
+ var sink = new ArrayBufferSink();
+ var any = false;
+ await (async function () {
+ var reader = process.stdout?.getReader();
+
+ reader?.closed.then(
+ a => {
+ console.log("Closed!");
+ },
+ err => {
+ console.log("Closed!", err);
+ },
+ );
+ var done = false,
+ value;
+ while (!done) {
+ ({ value, done } = await reader!.read());
+
+ if (value) {
+ any = true;
+ sink.write(value);
+ }
+ }
+ })();
+ expect(any).toBe(true);
+
+ const expected = fixture + "\n";
+
+ const output = await new Response(sink.end()).text();
+ expect(output.length).toBe(expected.length);
+ await process.exited;
+ expect(output).toBe(expected);
+ });
+
+ it("after exit", async () => {
+ const process = callback();
+ await process.exited;
+ const output = await readableStreamToText(process.stdout!);
+ const expected = fixture + "\n";
+ expect(output.length).toBe(expected.length);
+ expect(output).toBe(expected);
+ });
+ });
+ });
+ }
+ });
+ });
+ });
+}
diff --git a/test/js/bun/spawn/stdin-repro.js b/test/js/bun/spawn/stdin-repro.js
new file mode 100644
index 000000000..02840d00b
--- /dev/null
+++ b/test/js/bun/spawn/stdin-repro.js
@@ -0,0 +1,10 @@
+var stdout = Bun.stdout.writer();
+console.error("Started");
+var count = 0;
+for await (let chunk of Bun.stdin.stream()) {
+ const str = new Buffer(chunk).toString();
+ stdout.write(str);
+ stdout.flush();
+ count++;
+}
+console.error("Finished with", count);
diff --git a/test/js/bun/spawn/stdio-test-instance-a-lot.js b/test/js/bun/spawn/stdio-test-instance-a-lot.js
new file mode 100644
index 000000000..71815ddea
--- /dev/null
+++ b/test/js/bun/spawn/stdio-test-instance-a-lot.js
@@ -0,0 +1,19 @@
+import { ArrayBufferSink } from "bun";
+
+const sink = new ArrayBufferSink();
+
+sink.write("hello");
+sink.write(" ");
+sink.write("world");
+sink.write(new TextEncoder().encode("hello again|"));
+sink.write(new TextEncoder().encode("😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"));
+
+const string = Buffer.from(sink.end()).toString().repeat(9999);
+
+if (process.env.TEST_STDIO_STRING) {
+ const result = string;
+ process.stdout.write(result);
+} else {
+ const result = Buffer.from(string);
+ process.stdout.write(result);
+}
diff --git a/test/js/bun/spawn/stdio-test-instance.js b/test/js/bun/spawn/stdio-test-instance.js
new file mode 100644
index 000000000..fd820dc25
--- /dev/null
+++ b/test/js/bun/spawn/stdio-test-instance.js
@@ -0,0 +1,5 @@
+process.stdout.write("hello");
+process.stdout.write(" ");
+process.stdout.write("world");
+process.stdout.write(new TextEncoder().encode("hello again|"));
+process.stdout.write(new TextEncoder().encode("😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"));
diff --git a/test/js/bun/sqlite/northwind.testdb b/test/js/bun/sqlite/northwind.testdb
new file mode 100644
index 000000000..66f01fdd2
--- /dev/null
+++ b/test/js/bun/sqlite/northwind.testdb
Binary files differ
diff --git a/test/js/bun/sqlite/sql-raw.test.js b/test/js/bun/sqlite/sql-raw.test.js
new file mode 100644
index 000000000..9b6312dda
--- /dev/null
+++ b/test/js/bun/sqlite/sql-raw.test.js
@@ -0,0 +1,55 @@
+import { expect, it } from "bun:test";
+
+var SQL = globalThis[Symbol.for("Bun.lazy")]("sqlite");
+const dbPath = import.meta.dir + "/northwind.testdb";
+
+it("works", () => {
+ const handle = SQL.open(dbPath);
+
+ const stmt = SQL.prepare(handle, 'SELECT * FROM "Orders" WHERE OrderDate > date($date)');
+ expect(stmt.toString()).toBe(`SELECT * FROM "Orders" WHERE OrderDate > date(NULL)`);
+
+ expect(
+ Array.isArray(
+ stmt.all({
+ // do the conversion this way so that this test runs in multiple timezones
+ $date: new Date(new Date(1996, 8, 1, 0, 0, 0, 0).toUTCString()).toISOString(),
+ }),
+ ),
+ ).toBe(true);
+ expect(stmt.toString()).toBe(`SELECT * FROM "Orders" WHERE OrderDate > date('1996-09-01T07:00:00.000Z')`);
+
+ var ran = stmt.run({
+ $date: new Date(new Date(1997, 8, 1, 0, 0, 0, 0).toUTCString()).toISOString(),
+ });
+ expect(Array.isArray(ran)).toBe(false);
+ expect(ran === undefined).toBe(true);
+ expect(stmt.toString()).toBe(`SELECT * FROM "Orders" WHERE OrderDate > date('1997-09-01T07:00:00.000Z')`);
+
+ expect(
+ Array.isArray(
+ stmt.get({
+ $date: new Date(new Date(1998, 8, 1, 0, 0, 0, 0).toUTCString()).toISOString(),
+ }),
+ ),
+ ).toBe(false);
+ expect(stmt.toString()).toBe(`SELECT * FROM "Orders" WHERE OrderDate > date('1998-09-01T07:00:00.000Z')`);
+ expect(stmt.paramsCount).toBe(1);
+ expect(stmt.columnsCount).toBe(14);
+ expect(stmt.columns.length).toBe(14);
+ stmt.finalize();
+ SQL.close(handle);
+});
+
+it("SQL.run works", () => {
+ const handle = SQL.open(dbPath);
+ expect(typeof handle).toBe("number");
+
+ expect(
+ SQL.run(handle, 'SELECT * FROM "Orders" WHERE OrderDate > date($date)', {
+ $date: new Date(1996, 8, 1).toISOString(),
+ }),
+ ).toBe(undefined);
+
+ SQL.close(handle);
+});
diff --git a/test/js/bun/sqlite/sqlite-cross-process.js b/test/js/bun/sqlite/sqlite-cross-process.js
new file mode 100644
index 000000000..d5b9b87b5
--- /dev/null
+++ b/test/js/bun/sqlite/sqlite-cross-process.js
@@ -0,0 +1,45 @@
+// https://github.com/oven-sh/bun/issues/1366
+import { Database } from "bun:sqlite";
+import { rmSync } from "fs";
+
+const dir = process.env.SQLITE_DIR;
+
+rmSync(dir + "get-persist.sqlite", { force: true });
+
+var db = Database.open(dir + "get-persist.sqlite", { create: true });
+
+// Note, I've played with various values and it doesn't seem to change
+// the behavior. The "beter-sqlite3" npm package does not exhibit this
+// bug, so it doesn't seem to be a general SQLite thing.
+db.run(`PRAGMA journal_mode = WAL`);
+db.run(`PRAGMA synchrounous = NORMAL`);
+
+db.run(
+ `CREATE TABLE IF NOT EXISTS examples (
+ id TEXT PRIMARY KEY
+ )`,
+);
+
+// This persists, but if you place this call
+db.run(
+ `
+ INSERT INTO examples
+ VALUES ('hello')
+ ON CONFLICT (id) DO
+ UPDATE SET id='hello'
+ RETURNING id
+ `,
+);
+
+db.query(`SELECT id FROM examples WHERE id='hello'`).get().id;
+db.query(
+ `
+INSERT INTO examples
+VALUES ('world')
+ON CONFLICT (id) DO
+ UPDATE SET id='world'
+RETURNING id
+`,
+).get();
+
+process.exit(0);
diff --git a/test/js/bun/sqlite/sqlite.test.js b/test/js/bun/sqlite/sqlite.test.js
new file mode 100644
index 000000000..a23c2f037
--- /dev/null
+++ b/test/js/bun/sqlite/sqlite.test.js
@@ -0,0 +1,529 @@
+import { expect, it, describe } from "bun:test";
+import { Database, constants } from "bun:sqlite";
+import { existsSync, fstat, realpathSync, rmSync, writeFileSync } from "fs";
+import { spawnSync } from "bun";
+import { bunExe } from "harness";
+import { tmpdir } from "os";
+var encode = text => new TextEncoder().encode(text);
+
+it("Database.open", () => {
+ // in a folder which doesn't exist
+ try {
+ Database.open("/this/database/does/not/exist.sqlite", constants.SQLITE_OPEN_READWRITE);
+ throw new Error("Expected an error to be thrown");
+ } catch (error) {
+ expect(error.message).toBe("unable to open database file");
+ }
+
+ // in a file which doesn't exist
+ try {
+ Database.open(`/tmp/database-${Math.random()}.sqlite`, constants.SQLITE_OPEN_READWRITE);
+ throw new Error("Expected an error to be thrown");
+ } catch (error) {
+ expect(error.message).toBe("unable to open database file");
+ }
+
+ // in a file which doesn't exist
+ try {
+ Database.open(`/tmp/database-${Math.random()}.sqlite`, { readonly: true });
+ throw new Error("Expected an error to be thrown");
+ } catch (error) {
+ expect(error.message).toBe("unable to open database file");
+ }
+
+ // in a file which doesn't exist
+ try {
+ Database.open(`/tmp/database-${Math.random()}.sqlite`, { readwrite: true });
+ throw new Error("Expected an error to be thrown");
+ } catch (error) {
+ expect(error.message).toBe("unable to open database file");
+ }
+
+ // create works
+ {
+ var db = Database.open(`/tmp/database-${Math.random()}.sqlite`, {
+ create: true,
+ });
+ db.close();
+ }
+
+ // this should not throw
+ // it creates an in-memory db
+ new Database().close();
+});
+
+it("upsert cross-process, see #1366", () => {
+ const dir = realpathSync(tmpdir()) + "/";
+ const { exitCode } = spawnSync([bunExe(), import.meta.dir + "/sqlite-cross-process.js"], {
+ env: {
+ SQLITE_DIR: dir,
+ },
+ stderr: "inherit",
+ });
+ expect(exitCode).toBe(0);
+
+ const db2 = Database.open(dir + "get-persist.sqlite");
+
+ expect(db2.query(`SELECT id FROM examples`).all()).toEqual([{ id: "hello" }, { id: "world" }]);
+});
+
+it("creates", () => {
+ const db = Database.open(":memory:");
+ db.exec(
+ "CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT, value INTEGER, created TEXT, deci FLOAT, blobby BLOB)",
+ );
+ const stmt = db.prepare("INSERT INTO test (name, value, deci, created, blobby) VALUES (?, ?, ?, ?, ?)");
+
+ stmt.run(["foo", 1, Math.fround(1.111), new Date(1995, 12, 19).toISOString(), encode("Hello World")]);
+ stmt.run(["bar", 2, Math.fround(2.222), new Date(1995, 12, 19).toISOString(), encode("Hello World")]);
+ stmt.run(["baz", 3, Math.fround(3.333), new Date(1995, 12, 19).toISOString(), encode("Hello World")]);
+
+ stmt.finalize();
+
+ const stmt2 = db.prepare("SELECT * FROM test");
+ expect(JSON.stringify(stmt2.get())).toBe(
+ JSON.stringify({
+ id: 1,
+ name: "foo",
+ value: 1,
+ created: new Date(1995, 12, 19).toISOString(),
+ deci: Math.fround(1.111),
+ blobby: encode("Hello World"),
+ }),
+ );
+
+ expect(JSON.stringify(stmt2.all())).toBe(
+ JSON.stringify([
+ {
+ id: 1,
+ name: "foo",
+ value: 1,
+ created: new Date(1995, 12, 19).toISOString(),
+ deci: Math.fround(1.111),
+ blobby: encode("Hello World"),
+ },
+ {
+ id: 2,
+ name: "bar",
+ value: 2,
+ created: new Date(1995, 12, 19).toISOString(),
+ deci: Math.fround(2.222),
+ blobby: encode("Hello World"),
+ },
+ {
+ id: 3,
+ name: "baz",
+ value: 3,
+ created: new Date(1995, 12, 19).toISOString(),
+ deci: Math.fround(3.333),
+ blobby: encode("Hello World"),
+ },
+ ]),
+ );
+ expect(stmt2.run()).toBe(undefined);
+
+ // not necessary to run but it's a good practice
+ stmt2.finalize();
+});
+
+it("int52", () => {
+ const db = Database.open(":memory:");
+ db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, int64 INTEGER)");
+ db.run("INSERT INTO test (int64) VALUES (?)", Number.MAX_SAFE_INTEGER);
+ expect(db.query("SELECT * FROM test").get().int64).toBe(Number.MAX_SAFE_INTEGER);
+});
+
+it("typechecks", () => {
+ const db = Database.open(":memory:");
+ db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)");
+ db.exec('INSERT INTO test (name) VALUES ("Hello")');
+ db.exec('INSERT INTO test (name) VALUES ("World")');
+
+ const q = db.prepare("SELECT * FROM test WHERE (name = ?)");
+
+ var expectfail = val => {
+ try {
+ q.run([val]);
+ throw new Error("Expected error");
+ } catch (e) {
+ expect(e.message !== "Expected error").toBe(true);
+ expect(e.name).toBe("TypeError");
+ }
+
+ try {
+ q.all([val]);
+ throw new Error("Expected error");
+ } catch (e) {
+ expect(e.message !== "Expected error").toBe(true);
+ expect(e.name).toBe("TypeError");
+ }
+
+ try {
+ q.get([val]);
+ throw new Error("Expected error");
+ } catch (e) {
+ expect(e.message !== "Expected error").toBe(true);
+ expect(e.name).toBe("TypeError");
+ }
+ };
+
+ expectfail(Symbol("oh hai"));
+ expectfail(new Date());
+ expectfail(class Foo {});
+ expectfail(() => class Foo {});
+ expectfail(new RangeError("what"));
+ expectfail(new Map());
+ expectfail(new Map([["foo", "bar"]]));
+ expectfail(new Set());
+ expectfail(new Set([1, 2, 3]));
+});
+
+it("db.query supports TypedArray", () => {
+ const db = Database.open(":memory:");
+
+ db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, blobby BLOB)");
+
+ const stmt = db.prepare("INSERT INTO test (blobby) VALUES (?)");
+ stmt.run([encode("Hello World")]);
+ stmt.finalize();
+
+ const stmt2 = db.prepare("SELECT * FROM test");
+ expect(JSON.stringify(stmt2.get())).toBe(
+ JSON.stringify({
+ id: 1,
+ blobby: encode("Hello World"),
+ }),
+ );
+
+ const stmt3 = db.prepare("SELECT * FROM test WHERE (blobby = ?)");
+
+ expect(JSON.stringify(stmt3.get([encode("Hello World")]))).toBe(
+ JSON.stringify({
+ id: 1,
+ blobby: encode("Hello World"),
+ }),
+ );
+
+ expect(JSON.stringify(db.query("SELECT * FROM test WHERE (blobby = ?)").get([encode("Hello World")]))).toBe(
+ JSON.stringify({
+ id: 1,
+ blobby: encode("Hello World"),
+ }),
+ );
+
+ expect(stmt3.get([encode("Hello World NOT")])).toBe(null);
+});
+
+it("supports serialize/deserialize", () => {
+ const db = Database.open(":memory:");
+ db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)");
+ db.exec('INSERT INTO test (name) VALUES ("Hello")');
+ db.exec('INSERT INTO test (name) VALUES ("World")');
+
+ const input = db.serialize();
+ const db2 = new Database(input);
+
+ const stmt = db2.prepare("SELECT * FROM test");
+ expect(JSON.stringify(stmt.get())).toBe(
+ JSON.stringify({
+ id: 1,
+ name: "Hello",
+ }),
+ );
+
+ expect(JSON.stringify(stmt.all())).toBe(
+ JSON.stringify([
+ {
+ id: 1,
+ name: "Hello",
+ },
+ {
+ id: 2,
+ name: "World",
+ },
+ ]),
+ );
+ db2.exec("insert into test (name) values ('foo')");
+ expect(JSON.stringify(stmt.all())).toBe(
+ JSON.stringify([
+ {
+ id: 1,
+ name: "Hello",
+ },
+ {
+ id: 2,
+ name: "World",
+ },
+ {
+ id: 3,
+ name: "foo",
+ },
+ ]),
+ );
+
+ const db3 = new Database(input, { readonly: true });
+ try {
+ db3.exec("insert into test (name) values ('foo')");
+ throw new Error("Expected error");
+ } catch (e) {
+ expect(e.message).toBe("attempt to write a readonly database");
+ }
+});
+
+it("db.query()", () => {
+ const db = Database.open(":memory:");
+ db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)");
+
+ expect(db[Symbol.for("Bun.Database.cache.count")]).toBe(0);
+
+ var q = db.query("SELECT * FROM test WHERE name = ?");
+ expect(q.get("Hello") === null).toBe(true);
+
+ db.exec('INSERT INTO test (name) VALUES ("Hello")');
+ db.exec('INSERT INTO test (name) VALUES ("World")');
+
+ var rows = db.query("SELECT * FROM test WHERE name = ?").all(["Hello"]);
+
+ expect(JSON.stringify(rows)).toBe(JSON.stringify([{ id: 1, name: "Hello" }]));
+
+ rows = db.query("SELECT * FROM test WHERE name = ?").all(["World"]);
+
+ // if this fails, it means the query caching failed to update
+ expect(JSON.stringify(rows)).toBe(JSON.stringify([{ id: 2, name: "World" }]));
+
+ rows = db.query("SELECT * FROM test WHERE name = ?").all(["Hello"]);
+ expect(JSON.stringify(rows)).toBe(JSON.stringify([{ id: 1, name: "Hello" }]));
+
+ // check that the query is cached
+ expect(db[Symbol.for("Bun.Database.cache.count")]).toBe(1);
+
+ db.clearQueryCache();
+
+ // check clearing the cache decremented the counter
+ expect(db[Symbol.for("Bun.Database.cache.count")]).toBe(0);
+
+ q.finalize();
+ try {
+ // check clearing the cache decremented the counter
+
+ q.all(["Hello"]);
+ throw new Error("Should have thrown");
+ } catch (e) {
+ expect(e.message !== "Should have thrown").toBe(true);
+ }
+
+ // check that invalid queries are not cached
+ // and invalid queries throw
+ try {
+ db.query("SELECT * FROM BACON", ["Hello"]).all();
+ throw new Error("Should have thrown");
+ } catch (e) {
+ expect(e.message !== "Should have thrown").toBe(true);
+ expect(db[Symbol.for("Bun.Database.cache.count")]).toBe(0);
+ }
+
+ // check that it supports multiple arguments
+ expect(JSON.stringify(db.query("SELECT * FROM test where (name = ? OR name = ?)").all(["Hello", "Fooooo"]))).toBe(
+ JSON.stringify([{ id: 1, name: "Hello" }]),
+ );
+ expect(JSON.stringify(db.query("SELECT * FROM test where (name = ? OR name = ?)").all("Hello", "Fooooo"))).toBe(
+ JSON.stringify([{ id: 1, name: "Hello" }]),
+ );
+
+ // throws if insufficeint arguments
+ try {
+ db.query("SELECT * FROM test where (name = ? OR name = ?)").all("Hello");
+ } catch (e) {
+ expect(e.message).toBe("Expected 2 values, got 1");
+ }
+
+ // named parameters
+ expect(
+ JSON.stringify(
+ db.query("SELECT * FROM test where (name = $hello OR name = $goodbye)").all({
+ $hello: "Hello",
+ $goodbye: "Fooooo",
+ }),
+ ),
+ ).toBe(JSON.stringify([{ id: 1, name: "Hello" }]));
+
+ db.close();
+
+ // Check that a closed database doesn't crash
+ // and does throw an error when trying to run a query
+ try {
+ db.query("SELECT * FROM test WHERE name = ?").all(["Hello"]);
+ throw new Error("Should have thrown");
+ } catch (e) {
+ expect(e.message !== "Should have thrown").toBe(true);
+ }
+
+ // check that we can call close multiple times
+ // it should not throw so that your code doesn't break
+ db.close();
+ db.close();
+ db.close();
+});
+
+it("db.transaction()", () => {
+ const db = Database.open(":memory:");
+
+ db.exec("CREATE TABLE cats (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE, age INTEGER)");
+
+ const insert = db.prepare("INSERT INTO cats (name, age) VALUES (@name, @age)");
+
+ expect(db.inTransaction).toBe(false);
+ const insertMany = db.transaction(cats => {
+ expect(db.inTransaction).toBe(true);
+ try {
+ for (const cat of cats) insert.run(cat);
+ } catch (exception) {
+ throw exception;
+ }
+ });
+
+ try {
+ insertMany([
+ { "@name": "Joey", "@age": 2 },
+ { "@name": "Sally", "@age": 4 },
+ { "@name": "Junior", "@age": 1 },
+ { "@name": "Sally", "@age": 4 },
+ ]);
+ throw new Error("Should have thrown");
+ } catch (exception) {
+ expect(exception.message).toBe("constraint failed");
+ }
+
+ expect(db.inTransaction).toBe(false);
+ expect(db.query("SELECT * FROM cats").all().length).toBe(0);
+
+ expect(db.inTransaction).toBe(false);
+ insertMany([
+ { "@name": "Joey", "@age": 2 },
+ { "@name": "Sally", "@age": 4 },
+ { "@name": "Junior", "@age": 1 },
+ ]);
+ expect(db.inTransaction).toBe(false);
+ expect(db.query("SELECT * FROM cats").all().length).toBe(3);
+ expect(db.inTransaction).toBe(false);
+});
+
+// this bug was fixed by ensuring FinalObject has no more than 64 properties
+it("inlineCapacity #987", async () => {
+ const path = "/tmp/bun-987.db";
+ if (!existsSync(path)) {
+ const arrayBuffer = await (await fetch("https://github.com/oven-sh/bun/files/9265429/logs.log")).arrayBuffer();
+ writeFileSync(path, arrayBuffer);
+ }
+
+ const db = new Database(path);
+
+ const query = `SELECT
+ media.mid,
+ UPPER(media.name) as name,
+ media.url,
+ media.duration,
+ time(media.duration, 'unixepoch') AS durationStr,
+ sum(totalDurations) AS totalDurations,
+ sum(logs.views) AS views,
+ total.venues,
+ total.devices,
+ SUM(CASE WHEN day = '01' THEN logs.views ELSE 0 END) as 'vi01', SUM(CASE WHEN day = '02' THEN logs.views ELSE 0 END) as 'vi02', SUM(CASE WHEN day = '03' THEN logs.views ELSE 0 END) as 'vi03', SUM(CASE WHEN day = '04' THEN logs.views ELSE 0 END) as 'vi04', SUM(CASE WHEN day = '05' THEN logs.views ELSE 0 END) as 'vi05', SUM(CASE WHEN day = '06' THEN logs.views ELSE 0 END) as 'vi06', SUM(CASE WHEN day = '07' THEN logs.views ELSE 0 END) as 'vi07', SUM(CASE WHEN day = '08' THEN logs.views ELSE 0 END) as 'vi08', SUM(CASE WHEN day = '09' THEN logs.views ELSE 0 END) as 'vi09', SUM(CASE WHEN day = '10' THEN logs.views ELSE 0 END) as 'vi10', SUM(CASE WHEN day = '11' THEN logs.views ELSE 0 END) as 'vi11', SUM(CASE WHEN day = '12' THEN logs.views ELSE 0 END) as 'vi12', SUM(CASE WHEN day = '13' THEN logs.views ELSE 0 END) as 'vi13', SUM(CASE WHEN day = '14' THEN logs.views ELSE 0 END) as 'vi14', SUM(CASE WHEN day = '15' THEN logs.views ELSE 0 END) as 'vi15', SUM(CASE WHEN day = '16' THEN logs.views ELSE 0 END) as 'vi16', SUM(CASE WHEN day = '17' THEN logs.views ELSE 0 END) as 'vi17', SUM(CASE WHEN day = '18' THEN logs.views ELSE 0 END) as 'vi18', SUM(CASE WHEN day = '19' THEN logs.views ELSE 0 END) as 'vi19', SUM(CASE WHEN day = '20' THEN logs.views ELSE 0 END) as 'vi20', SUM(CASE WHEN day = '21' THEN logs.views ELSE 0 END) as 'vi21', SUM(CASE WHEN day = '22' THEN logs.views ELSE 0 END) as 'vi22', SUM(CASE WHEN day = '23' THEN logs.views ELSE 0 END) as 'vi23', SUM(CASE WHEN day = '24' THEN logs.views ELSE 0 END) as 'vi24', SUM(CASE WHEN day = '25' THEN logs.views ELSE 0 END) as 'vi25', SUM(CASE WHEN day = '26' THEN logs.views ELSE 0 END) as 'vi26', SUM(CASE WHEN day = '27' THEN logs.views ELSE 0 END) as 'vi27', SUM(CASE WHEN day = '28' THEN logs.views ELSE 0 END) as 'vi28', SUM(CASE WHEN day = '29' THEN logs.views ELSE 0 END) as 'vi29', SUM(CASE WHEN day = '30' THEN logs.views ELSE 0 END) as 'vi30', MAX(CASE WHEN day = '01' THEN logs.venues ELSE 0 END) as 've01', MAX(CASE WHEN day = '02' THEN logs.venues ELSE 0 END) as 've02', MAX(CASE WHEN day = '03' THEN logs.venues ELSE 0 END) as 've03', MAX(CASE WHEN day = '04' THEN logs.venues ELSE 0 END) as 've04', MAX(CASE WHEN day = '05' THEN logs.venues ELSE 0 END) as 've05', MAX(CASE WHEN day = '06' THEN logs.venues ELSE 0 END) as 've06', MAX(CASE WHEN day = '07' THEN logs.venues ELSE 0 END) as 've07', MAX(CASE WHEN day = '08' THEN logs.venues ELSE 0 END) as 've08', MAX(CASE WHEN day = '09' THEN logs.venues ELSE 0 END) as 've09', MAX(CASE WHEN day = '10' THEN logs.venues ELSE 0 END) as 've10', MAX(CASE WHEN day = '11' THEN logs.venues ELSE 0 END) as 've11', MAX(CASE WHEN day = '12' THEN logs.venues ELSE 0 END) as 've12', MAX(CASE WHEN day = '13' THEN logs.venues ELSE 0 END) as 've13', MAX(CASE WHEN day = '14' THEN logs.venues ELSE 0 END) as 've14', MAX(CASE WHEN day = '15' THEN logs.venues ELSE 0 END) as 've15', MAX(CASE WHEN day = '16' THEN logs.venues ELSE 0 END) as 've16', MAX(CASE WHEN day = '17' THEN logs.venues ELSE 0 END) as 've17', MAX(CASE WHEN day = '18' THEN logs.venues ELSE 0 END) as 've18', MAX(CASE WHEN day = '19' THEN logs.venues ELSE 0 END) as 've19', MAX(CASE WHEN day = '20' THEN logs.venues ELSE 0 END) as 've20', MAX(CASE WHEN day = '21' THEN logs.venues ELSE 0 END) as 've21', MAX(CASE WHEN day = '22' THEN logs.venues ELSE 0 END) as 've22', MAX(CASE WHEN day = '23' THEN logs.venues ELSE 0 END) as 've23', MAX(CASE WHEN day = '24' THEN logs.venues ELSE 0 END) as 've24', MAX(CASE WHEN day = '25' THEN logs.venues ELSE 0 END) as 've25', MAX(CASE WHEN day = '26' THEN logs.venues ELSE 0 END) as 've26', MAX(CASE WHEN day = '27' THEN logs.venues ELSE 0 END) as 've27', MAX(CASE WHEN day = '28' THEN logs.venues ELSE 0 END) as 've28', MAX(CASE WHEN day = '29' THEN logs.venues ELSE 0 END) as 've29', MAX(CASE WHEN day = '30' THEN logs.venues ELSE 0 END) as 've30', MAX(CASE WHEN day = '01' THEN logs.devices ELSE 0 END) as 'de01', MAX(CASE WHEN day = '02' THEN logs.devices ELSE 0 END) as 'de02', MAX(CASE WHEN day = '03' THEN logs.devices ELSE 0 END) as 'de03', MAX(CASE WHEN day = '04' THEN logs.devices ELSE 0 END) as 'de04', MAX(CASE WHEN day = '05' THEN logs.devices ELSE 0 END) as 'de05', MAX(CASE WHEN day = '06' THEN logs.devices ELSE 0 END) as 'de06', MAX(CASE WHEN day = '07' THEN logs.devices ELSE 0 END) as 'de07', MAX(CASE WHEN day = '08' THEN logs.devices ELSE 0 END) as 'de08', MAX(CASE WHEN day = '09' THEN logs.devices ELSE 0 END) as 'de09', MAX(CASE WHEN day = '10' THEN logs.devices ELSE 0 END) as 'de10', MAX(CASE WHEN day = '11' THEN logs.devices ELSE 0 END) as 'de11', MAX(CASE WHEN day = '12' THEN logs.devices ELSE 0 END) as 'de12', MAX(CASE WHEN day = '13' THEN logs.devices ELSE 0 END) as 'de13', MAX(CASE WHEN day = '14' THEN logs.devices ELSE 0 END) as 'de14', MAX(CASE WHEN day = '15' THEN logs.devices ELSE 0 END) as 'de15', MAX(CASE WHEN day = '16' THEN logs.devices ELSE 0 END) as 'de16', MAX(CASE WHEN day = '17' THEN logs.devices ELSE 0 END) as 'de17', MAX(CASE WHEN day = '18' THEN logs.devices ELSE 0 END) as 'de18', MAX(CASE WHEN day = '19' THEN logs.devices ELSE 0 END) as 'de19', MAX(CASE WHEN day = '20' THEN logs.devices ELSE 0 END) as 'de20', MAX(CASE WHEN day = '21' THEN logs.devices ELSE 0 END) as 'de21', MAX(CASE WHEN day = '22' THEN logs.devices ELSE 0 END) as 'de22', MAX(CASE WHEN day = '23' THEN logs.devices ELSE 0 END) as 'de23', MAX(CASE WHEN day = '24' THEN logs.devices ELSE 0 END) as 'de24', MAX(CASE WHEN day = '25' THEN logs.devices ELSE 0 END) as 'de25', MAX(CASE WHEN day = '26' THEN logs.devices ELSE 0 END) as 'de26', MAX(CASE WHEN day = '27' THEN logs.devices ELSE 0 END) as 'de27', MAX(CASE WHEN day = '28' THEN logs.devices ELSE 0 END) as 'de28', MAX(CASE WHEN day = '29' THEN logs.devices ELSE 0 END) as 'de29', MAX(CASE WHEN day = '30' THEN logs.devices ELSE 0 END) as 'de30'
+ FROM
+ (
+ SELECT
+ logs.mid,
+ sum(logs.duration) AS totalDurations,
+ strftime ('%d', START, 'unixepoch', 'localtime') AS day,
+ count(*) AS views,
+ count(DISTINCT did) AS devices,
+ count(DISTINCT vid) AS venues
+ FROM
+ logs
+ WHERE strftime('%m-%Y', start, 'unixepoch', 'localtime')='06-2022'
+ GROUP BY
+ day,
+ logs.mid
+ ) logs
+ INNER JOIN media ON media.id = logs.mid
+ INNER JOIN (
+ SELECT
+ mid,
+ count(DISTINCT vid) as venues,
+ count(DISTINCT did) as devices
+ FROM
+ logs
+ WHERE strftime('%m-%Y', start, 'unixepoch', 'localtime')='06-2022'
+ GROUP by
+ mid
+ ) total ON logs.mid = total.mid
+ ORDER BY
+ name`;
+
+ expect(Object.keys(db.query(query).all()[0]).length).toBe(99);
+});
+
+// https://github.com/oven-sh/bun/issues/1553
+it("latin1 supplement chars", () => {
+ const db = new Database();
+ db.run("CREATE TABLE IF NOT EXISTS foo (id INTEGER PRIMARY KEY AUTOINCREMENT, greeting TEXT)");
+ db.run("INSERT INTO foo (greeting) VALUES (?)", "Welcome to bun!");
+ db.run("INSERT INTO foo (greeting) VALUES (?)", "Español");
+ db.run("INSERT INTO foo (greeting) VALUES (?)", "¿Qué sucedió?");
+
+ expect(db.query("SELECT * FROM foo").all()).toEqual([
+ {
+ id: 1,
+ greeting: "Welcome to bun!",
+ },
+ {
+ id: 2,
+ greeting: "Español",
+ },
+ {
+ id: 3,
+ greeting: "¿Qué sucedió?",
+ },
+ ]);
+
+ // test that it doesn't break when we do a structure transition
+ db.query("SELECT * FROM foo").all()[0].booop = true;
+ db.query("SELECT * FROM foo").all()[0].beep = true;
+ expect(db.query("SELECT * FROM foo").all()).toEqual([
+ {
+ id: 1,
+ greeting: "Welcome to bun!",
+ },
+ {
+ id: 2,
+ greeting: "Español",
+ },
+ {
+ id: 3,
+ greeting: "¿Qué sucedió?",
+ },
+ ]);
+});
+
+describe("Database.run", () => {
+ it("should not throw error `not an error` when provided query containing only whitespace", () => {
+ const db = Database.open(":memory:");
+ db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)");
+
+ expect(db[Symbol.for("Bun.Database.cache.count")]).toBe(0);
+
+ var q = db.query("SELECT * FROM test WHERE name = ?");
+ expect(q.get("Hello") === null).toBe(true);
+
+ db.exec('INSERT INTO test (name) VALUES ("Hello")');
+ db.exec('INSERT INTO test (name) VALUES ("World")');
+
+ try {
+ db.run(" ");
+ expect(true).toBeFalsy();
+ } catch (e) {
+ expect(e.message).not.toBe("not an error");
+ expect(e.message).toBe("Query contained no valid SQL statement; likely empty query.");
+ }
+ });
+});
diff --git a/test/js/bun/test/bigint.test.js b/test/js/bun/test/bigint.test.js
new file mode 100644
index 000000000..46ad50b84
--- /dev/null
+++ b/test/js/bun/test/bigint.test.js
@@ -0,0 +1,14 @@
+import { describe, expect, it } from "bun:test";
+
+describe("BigInt", () => {
+ it("compares correctly (literal)", () => {
+ expect(42n).toBe(42n);
+ });
+
+ it("compares correctly (object)", () => {
+ expect(BigInt(42n)).toBe(BigInt(42n));
+ expect(42n).toBe(BigInt(42n));
+ expect(BigInt(Bun.inspect(42n).substring(0, 2))).toBe(BigInt(42n));
+ expect(BigInt(42n).valueOf()).toBe(BigInt(42n));
+ });
+});
diff --git a/test/js/bun/test/bun-test.test.ts b/test/js/bun/test/bun-test.test.ts
new file mode 100644
index 000000000..cc6bf644a
--- /dev/null
+++ b/test/js/bun/test/bun-test.test.ts
@@ -0,0 +1,6 @@
+import { expect, test } from "bun:test";
+
+test("Bun.version", () => {
+ expect(process.versions.bun).toBe(Bun.version);
+ expect(process.revision).toBe(Bun.revision);
+});
diff --git a/test/js/bun/test/jest-doesnt-auto-import.js b/test/js/bun/test/jest-doesnt-auto-import.js
new file mode 100644
index 000000000..4d4a02b37
--- /dev/null
+++ b/test/js/bun/test/jest-doesnt-auto-import.js
@@ -0,0 +1,12 @@
+export function getJestGlobals() {
+ return {
+ describe: typeof describe === "function" ? describe : undefined,
+ it: typeof it === "function" ? it : undefined,
+ test: typeof test === "function" ? test : undefined,
+ expect: typeof expect === "function" ? expect : undefined,
+ beforeAll: typeof beforeAll === "function" ? beforeAll : undefined,
+ beforeEach: typeof beforeEach === "function" ? beforeEach : undefined,
+ afterAll: typeof afterAll === "function" ? afterAll : undefined,
+ afterEach: typeof afterEach === "function" ? afterEach : undefined,
+ };
+}
diff --git a/test/js/bun/test/jest-hooks.test.ts b/test/js/bun/test/jest-hooks.test.ts
new file mode 100644
index 000000000..c99dc7759
--- /dev/null
+++ b/test/js/bun/test/jest-hooks.test.ts
@@ -0,0 +1,195 @@
+import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from "bun:test";
+
+describe("test jest hooks in bun-test", () => {
+ describe("test beforeAll hook", () => {
+ let animal = "tiger";
+
+ beforeAll(() => {
+ animal = "lion";
+ });
+
+ it("string should be set by hook", () => {
+ expect(animal).toEqual("lion");
+ });
+ });
+
+ describe("test beforeEach hook", () => {
+ let animal = "tiger";
+
+ beforeEach(() => {
+ animal = "lion";
+ });
+
+ it("string should be set by hook", () => {
+ expect(animal).toEqual("lion");
+ animal = "dog";
+ });
+
+ it("string should be re-set by hook", () => {
+ expect(animal).toEqual("lion");
+ });
+ });
+
+ describe("test afterEach hook", () => {
+ let animal = "tiger";
+
+ afterEach(() => {
+ animal = "lion";
+ });
+
+ it("string should not be set by hook", () => {
+ expect(animal).toEqual("tiger");
+ animal = "dog";
+ });
+
+ it("string should be set by hook", () => {
+ expect(animal).toEqual("lion");
+ });
+ });
+
+ describe("test afterAll hook", () => {
+ let animal = "tiger";
+
+ describe("test afterAll hook", () => {
+ afterAll(() => {
+ animal = "lion";
+ });
+
+ it("string should not be set by hook", () => {
+ expect(animal).toEqual("tiger");
+ animal = "dog";
+ });
+ });
+
+ it("string should be set by hook", () => {
+ expect(animal).toEqual("lion");
+ });
+ });
+
+ describe("test async hooks", async () => {
+ let beforeAllCalled = 0;
+ let beforeEachCalled = 0;
+ let afterAllCalled = 0;
+ let afterEachCalled = 0;
+
+ beforeAll(async () => {
+ beforeAllCalled += await 1;
+ });
+
+ beforeEach(async () => {
+ beforeEachCalled += await 1;
+ });
+
+ afterAll(async () => {
+ afterAllCalled += await 1;
+ });
+
+ afterEach(async () => {
+ afterEachCalled += await 1;
+ });
+
+ it("should run after beforeAll()", () => {
+ expect(beforeAllCalled).toBe(1);
+ expect(beforeEachCalled).toBe(1);
+ expect(afterAllCalled).toBe(0);
+ expect(afterEachCalled).toBe(0);
+ });
+
+ it("should run after beforeEach()", () => {
+ expect(beforeAllCalled).toBe(1);
+ expect(beforeEachCalled).toBe(2);
+ expect(afterAllCalled).toBe(0);
+ expect(afterEachCalled).toBe(1);
+ });
+ });
+
+ describe("test done callback in hooks", () => {
+ let beforeAllCalled = 0;
+ let beforeEachCalled = 0;
+ let afterAllCalled = 0;
+ let afterEachCalled = 0;
+
+ beforeAll(done => {
+ setImmediate(() => {
+ beforeAllCalled++;
+ done();
+ });
+ });
+
+ beforeEach(done => {
+ setImmediate(() => {
+ beforeEachCalled++;
+ done();
+ });
+ });
+
+ afterAll(done => {
+ setImmediate(() => {
+ afterAllCalled++;
+ done();
+ });
+ });
+
+ afterEach(done => {
+ setImmediate(() => {
+ afterEachCalled++;
+ done();
+ });
+ });
+
+ it("should run after beforeAll()", () => {
+ expect(beforeAllCalled).toBe(1);
+ expect(beforeEachCalled).toBe(1);
+ expect(afterAllCalled).toBe(0);
+ expect(afterEachCalled).toBe(0);
+ });
+
+ it("should run after beforeEach()", () => {
+ expect(beforeAllCalled).toBe(1);
+ expect(beforeEachCalled).toBe(2);
+ expect(afterAllCalled).toBe(0);
+ expect(afterEachCalled).toBe(1);
+ });
+ });
+
+ describe("test async hooks with done()", () => {
+ let beforeAllCalled = 0;
+ let beforeEachCalled = 0;
+ let afterAllCalled = 0;
+ let afterEachCalled = 0;
+
+ beforeAll(async done => {
+ beforeAllCalled += await 1;
+ setTimeout(done, 1);
+ });
+
+ beforeEach(async done => {
+ beforeEachCalled += await 1;
+ setTimeout(done, 1);
+ });
+
+ afterAll(async done => {
+ afterAllCalled += await 1;
+ setTimeout(done, 1);
+ });
+
+ afterEach(async done => {
+ afterEachCalled += await 1;
+ setTimeout(done, 1);
+ });
+
+ it("should run after beforeAll()", () => {
+ expect(beforeAllCalled).toBe(1);
+ expect(beforeEachCalled).toBe(1);
+ expect(afterAllCalled).toBe(0);
+ expect(afterEachCalled).toBe(0);
+ });
+
+ it("should run after beforeEach()", () => {
+ expect(beforeAllCalled).toBe(1);
+ expect(beforeEachCalled).toBe(2);
+ expect(afterAllCalled).toBe(0);
+ expect(afterEachCalled).toBe(1);
+ });
+ });
+});
diff --git a/test/js/bun/test/nested-describes.test.ts b/test/js/bun/test/nested-describes.test.ts
new file mode 100644
index 000000000..636714fdc
--- /dev/null
+++ b/test/js/bun/test/nested-describes.test.ts
@@ -0,0 +1,34 @@
+import { describe, expect, test } from "bun:test";
+
+/*
+In this test we want the tests to print out the following on a success.
+Each success / fail should show the path of describe and test scopes
+
+✓ outer most describe > mid describe 1 > inner most describe 1 > first
+✓ outer most describe > mid describe 1 > inner most describe 2 > second
+✓ outer most describe > mid describe 2 > inner most describe 3 > first
+
+@TODO add testing for this, would require to read the test console output
+*/
+
+describe("outer most describe", () => {
+ describe("mid describe 1", () => {
+ describe("inner most describe 1", () => {
+ test("first", () => {
+ expect(5).toEqual(5);
+ });
+ });
+ describe("inner most describe 2", () => {
+ test("second", () => {
+ expect(5).toEqual(5);
+ });
+ });
+ });
+ describe("mid describe 2", () => {
+ describe("inner most describe 3", () => {
+ test("third", () => {
+ expect(5).toEqual(5);
+ });
+ });
+ });
+});
diff --git a/test/js/bun/test/test-auto-import-jest-globals.test.js b/test/js/bun/test/test-auto-import-jest-globals.test.js
new file mode 100644
index 000000000..5baeae43e
--- /dev/null
+++ b/test/js/bun/test/test-auto-import-jest-globals.test.js
@@ -0,0 +1,24 @@
+test("Jest auto imports", () => {
+ expect(true).toBe(true);
+ expect(typeof describe).toBe("function");
+ expect(typeof it).toBe("function");
+ expect(typeof test).toBe("function");
+ expect(typeof expect).toBe("function");
+ expect(typeof beforeAll).toBe("function");
+ expect(typeof beforeEach).toBe("function");
+ expect(typeof afterAll).toBe("function");
+ expect(typeof afterEach).toBe("function");
+});
+
+test("Jest's globals aren't available in every file", async () => {
+ const jestGlobals = await import("./jest-doesnt-auto-import.js");
+
+ expect(typeof jestGlobals.describe).toBe("undefined");
+ expect(typeof jestGlobals.it).toBe("undefined");
+ expect(typeof jestGlobals.test).toBe("undefined");
+ expect(typeof jestGlobals.expect).toBe("undefined");
+ expect(typeof jestGlobals.beforeAll).toBe("undefined");
+ expect(typeof jestGlobals.beforeEach).toBe("undefined");
+ expect(typeof jestGlobals.afterAll).toBe("undefined");
+ expect(typeof jestGlobals.afterEach).toBe("undefined");
+});
diff --git a/test/js/bun/test/test-test.test.ts b/test/js/bun/test/test-test.test.ts
new file mode 100644
index 000000000..b834d2152
--- /dev/null
+++ b/test/js/bun/test/test-test.test.ts
@@ -0,0 +1,2098 @@
+import { spawn, spawnSync } from "bun";
+import { describe, expect, it, test } from "bun:test";
+import { bunExe, bunEnv } from "harness";
+import { mkdirSync, realpathSync, rmSync, writeFileSync } from "fs";
+import { mkdtemp, rm, writeFile } from "fs/promises";
+import { tmpdir } from "os";
+import { join } from "path";
+
+test("toStrictEqual() vs toEqual()", () => {
+ expect([1, , 3]).toEqual([1, , 3]);
+ expect({}).toEqual({});
+ expect({}).toStrictEqual({});
+ expect({}).toEqual({ a: undefined });
+ expect({}).not.toStrictEqual({ a: undefined });
+
+ class C {
+ hi = 34;
+ }
+ class D {
+ hi = 34;
+ }
+ let c = new C();
+ let d = new D();
+
+ expect(d).toEqual(c);
+ expect(d).not.toStrictEqual(c);
+ expect({ a: 1, b: undefined }).toEqual({ a: 1 });
+ expect({ a: 1 }).toEqual({ a: 1, b: undefined });
+ expect({ a: 1, b: undefined }).toEqual({ a: 1, b: undefined });
+
+ expect({ a: 1, b: undefined }).not.toStrictEqual({ a: 1 });
+ expect({ a: 1 }).not.toStrictEqual({ a: 1, b: undefined });
+ expect({ a: 1, b: undefined }).toStrictEqual({ a: 1, b: undefined });
+
+ expect({ a: 1, b: null }).not.toEqual({ a: 1 });
+ expect({ a: 1 }).not.toEqual({ a: 1, b: null });
+ expect({ a: 1, b: null }).toEqual({ a: 1, b: null });
+
+ expect({ a: 1 }).not.toEqual({ a: true });
+ expect({ a: 1 }).not.toEqual({ a: "1" });
+ expect({ a: 1 }).not.toEqual({ a: 1, b: 2 });
+ expect({ a: 1, b: 2 }).not.toEqual({ a: 1 });
+ expect({ a: 1 }).not.toStrictEqual({ a: true });
+ expect({ a: 1 }).not.toStrictEqual({ a: "1" });
+ expect({ a: 1 }).not.toStrictEqual({ a: 1, b: 2 });
+ expect({ a: 1, b: 2 }).not.toStrictEqual({ a: 1 });
+ expect({ a: 1 }).toStrictEqual({ a: 1 });
+
+ expect([1, undefined, 3]).toEqual([1, undefined, 3]);
+ expect([1, undefined, 3]).toStrictEqual([1, undefined, 3]);
+ expect([1, undefined, 3]).not.toEqual([1, 2, 3]);
+ expect([1, undefined, 3]).not.toStrictEqual([1, 2, 3]);
+ expect([1, undefined, 3]).not.toEqual([1, 2]);
+ expect([1, undefined, 3]).not.toStrictEqual([1, 2]);
+ expect([1, undefined, 3]).not.toEqual([1]);
+ expect([1, undefined, 3]).not.toStrictEqual([1]);
+ expect([1, undefined, 3]).not.toEqual([]);
+ expect([1, undefined, 3]).not.toStrictEqual([]);
+ expect([1, undefined, 3]).not.toEqual([1, 3]);
+ expect([1, undefined, 3]).not.toStrictEqual([1, 3]);
+
+ expect([1, null, 3]).toEqual([1, null, 3]);
+ expect([1, null, 3]).toStrictEqual([1, null, 3]);
+ expect([1, null, 3]).not.toEqual([1, 2, 3]);
+ expect([1, null, 3]).not.toStrictEqual([1, 2, 3]);
+ expect([1, null, 3]).not.toEqual([1, 2]);
+ expect([1, null, 3]).not.toStrictEqual([1, 2]);
+ expect([1, null, 3]).not.toEqual([1]);
+ expect([1, null, 3]).not.toStrictEqual([1]);
+ expect([1, null, 3]).not.toEqual([]);
+ expect([1, null, 3]).not.toStrictEqual([]);
+ expect([1, null, 3]).not.toEqual([1, 3]);
+ expect([1, null, 3]).not.toStrictEqual([1, 3]);
+
+ expect([, 1]).toEqual([, 1]);
+ expect([, 1]).toStrictEqual([, 1]);
+ expect([, 1]).not.toEqual([1]);
+ expect([1]).not.toEqual([, 1]);
+ expect([, 1]).not.toStrictEqual([1]);
+ expect([1]).not.toStrictEqual([, 1]);
+ expect([, 1]).toEqual([undefined, 1]);
+ expect([, 1]).not.toStrictEqual([undefined, 1]);
+ expect([, 1]).not.toEqual([null, 1]);
+ expect([, 1]).not.toStrictEqual([null, 1]);
+ expect([undefined, 1]).toEqual([, 1]);
+ expect([undefined, 1]).not.toStrictEqual([, 1]);
+ expect([null, 1]).not.toEqual([, 1]);
+ expect([null, 1]).not.toStrictEqual([, 1]);
+ expect([undefined, 1]).toEqual([undefined, 1]);
+ expect([undefined, 1]).toStrictEqual([undefined, 1]);
+
+ expect([0, , 2]).toEqual([0, undefined, 2]);
+ expect([, "boo2"]).toEqual([undefined, "boo2"]);
+ expect([, "boo"]).toEqual([, "boo"]);
+ expect([, 1]).toEqual([undefined, 1]);
+
+ const s1 = Symbol("test1");
+ const s2 = Symbol("test2");
+
+ let a = { a: 1, b: 2 };
+ let b = { a: 1, b: 2 };
+ a[s1] = 1;
+ b[s1] = 1;
+ a[s2] = undefined;
+ b[s2] = null;
+ expect(a).not.toEqual(b);
+ class F extends String {
+ constructor() {
+ super();
+ }
+ }
+
+ let f = new F("hello");
+ let j = new String("hello");
+ expect(f).not.toEqual(j);
+ class LaCroix {
+ constructor(flavor) {
+ this.flavor = flavor;
+ }
+ }
+ expect(new LaCroix("pamplemousse")).not.toStrictEqual({
+ flavor: "pamplemousse",
+ });
+ expect(new LaCroix("pamplemousse")).toEqual({ flavor: "pamplemousse" });
+
+ expect([, 1]).not.toStrictEqual([undefined, 1]);
+
+ expect([0, , 2]).toEqual([0, undefined, 2]);
+ expect([, "boo2"]).toEqual([undefined, "boo2"]);
+ expect([, "boo"]).toEqual([, "boo"]);
+ expect([, 1]).toEqual([undefined, 1]);
+});
+
+function f1() {
+ return "hello!";
+}
+function f2() {
+ return "hey!";
+}
+test("deepEquals regex", () => {
+ expect(/a/imu).toEqual(/a/imu);
+ expect(/a/imu).not.toEqual(/ab/imu);
+
+ expect(new RegExp("s", "g")).toEqual(new RegExp("s", "g"));
+ expect(new RegExp("s", "g")).not.toEqual(new RegExp("s", "i"));
+});
+
+test("toThrow", () => {
+ expect(() => {
+ throw new Error("hello");
+ }).toThrow("hello");
+
+ var err = new Error("bad");
+ expect(() => {
+ throw err;
+ }).toThrow(err);
+
+ expect(() => {
+ throw new Error("good");
+ }).toThrow();
+
+ expect(() => {
+ throw new Error("foo");
+ }).toThrow(/oo/);
+
+ expect(() =>
+ expect(() => {
+ throw new Error("bar");
+ }).toThrow(/baz/),
+ ).toThrow("/baz/");
+
+ expect(() => {
+ return true;
+ }).not.toThrow();
+
+ expect(() => {
+ return true;
+ }).not.toThrow(err);
+});
+
+test("deepEquals derived strings and strings", () => {
+ let a = new String("hello");
+ let b = "hello";
+ expect(a).toEqual(a);
+ expect(b).toEqual(b);
+ expect(a).not.toEqual(b);
+ expect(b).not.toEqual(a);
+
+ class F extends String {
+ constructor() {
+ super();
+ }
+ }
+
+ let f = new F("hello");
+ expect(f).toEqual(f);
+ expect(f).not.toEqual(b);
+ expect(b).not.toEqual(f);
+
+ let j = new String("hello");
+ expect(f).not.toEqual(j);
+
+ class G extends String {
+ constructor() {
+ super();
+ this.x = 0;
+ }
+ }
+
+ let g = new G("hello");
+ expect(g).not.toEqual(f);
+ expect(f).not.toEqual(g);
+ expect(g).toEqual(g);
+ expect(g).not.toEqual(b);
+ expect(b).not.toEqual(g);
+ expect(g).not.toEqual(a);
+});
+
+test("deepEquals throw getters", () => {
+ let a = {
+ get x() {
+ throw new Error("a");
+ },
+ };
+
+ let b = {
+ get x() {
+ return 3;
+ },
+ };
+
+ try {
+ expect(a).not.toEqual(b);
+ } catch (e) {
+ expect(e.message).toContain("a");
+ }
+
+ class B {
+ get x() {
+ throw new Error("b");
+ }
+ }
+
+ class C {
+ get x() {
+ return 3;
+ }
+ }
+
+ expect(() => {
+ expect(new B()).not.toEqual(new C());
+ }).toThrow();
+
+ let o = [
+ {
+ get x() {
+ throw new Error("c");
+ },
+ },
+ ];
+
+ let p = [
+ {
+ get x() {
+ return 3;
+ },
+ },
+ ];
+
+ try {
+ expect(o).not.toEqual(p);
+ } catch (e) {
+ expect(e.message).toContain("c");
+ }
+
+ const s = Symbol("s");
+ let q = {
+ get x() {
+ throw new Error("d");
+ },
+ };
+ q[s] = 3;
+
+ let r = {
+ get x() {
+ return 3;
+ },
+ };
+ r[s] = 3;
+
+ try {
+ expect(q).not.toEqual(r);
+ } catch (e) {
+ expect(e.message).toContain("d");
+ }
+});
+
+test("deepEquals large object", () => {
+ let o = {};
+ for (let i = 0; i < 65; i++) {
+ o["bun" + i] = i;
+ }
+ expect(o).toEqual(o);
+ let b = {};
+ for (let i = 0; i < 63; i++) {
+ b["bun" + i] = i;
+ }
+ expect(b).toEqual(b);
+ expect(o).not.toEqual(b);
+ expect(b).not.toEqual(o);
+
+ let c = { d: [Array(o)] };
+ let d = { d: [Array(b)] };
+ expect(c).toEqual(c);
+ expect(d).toEqual(d);
+ expect(c).not.toEqual(d);
+ expect(d).not.toEqual(c);
+
+ let e = { d: [Array(o), Array(o)] };
+ let f = { d: [Array(b), Array(b)] };
+ expect(e).toEqual(e);
+ expect(f).toEqual(f);
+ expect(e).not.toEqual(f);
+ expect(f).not.toEqual(e);
+
+ let p = [];
+ p[0] = {};
+ for (let i = 0; i < 1000; i++) {
+ p[0]["bun" + i] = i;
+ }
+ let q = [];
+ q[0] = {};
+ for (let i = 0; i < 1000; i++) {
+ q[0]["bun" + i] = i;
+ }
+ expect(p).toEqual(p);
+ expect(q).toEqual(q);
+
+ q[0].bun789 = 788;
+ expect(p).not.toEqual(q);
+ expect(q).not.toEqual(p);
+
+ let r = { d: {} };
+ let s = { d: {} };
+ for (let i = 0; i < 1000; i++) {
+ r.d["bun" + i] = i;
+ s.d["bun" + i] = i;
+ }
+
+ expect(r).toEqual(r);
+ expect(s).toEqual(s);
+
+ r.d.bun790 = 791;
+ expect(r).not.toEqual(s);
+ expect(s).not.toEqual(r);
+
+ let t = [];
+ t[5] = {};
+ let u = [];
+ u[5] = {};
+ for (let i = 0; i < 1000; i++) {
+ t[5]["bun" + i] = i;
+ }
+ for (let i = 0; i < 30; i++) {
+ u[5]["bun" + i] = i;
+ }
+ expect(t).toEqual(t);
+ expect(u).toEqual(u);
+ expect(t).not.toEqual(u);
+ expect(u).not.toEqual(t);
+
+ let v = { j: {} };
+ let w = { j: {} };
+ for (let i = 0; i < 1000; i++) {
+ v.j["bun" + i] = i;
+ w.j["bun" + i] = i;
+ }
+
+ expect(v).toEqual(v);
+ expect(w).toEqual(w);
+
+ v.j.bun999 = 1000;
+ expect(v).not.toEqual(w);
+ expect(w).not.toEqual(v);
+ expect(v).toEqual(v);
+
+ v.j.bun999 = 999;
+ w.j.bun0 = 1;
+ expect(v).not.toEqual(w);
+ expect(w).not.toEqual(v);
+ expect(v).toEqual(v);
+ expect(w).toEqual(w);
+});
+
+test("deepEquals - Date", () => {
+ let d = new Date();
+ expect(d).toEqual(d);
+ let b = d;
+ expect(b).toEqual(d);
+ d.setFullYear(1998);
+ expect(b).toEqual(d);
+ expect(b).not.toEqual(new Date());
+
+ var date = new Date();
+ date.setFullYear(1995);
+ expect(new Date()).not.toEqual(date);
+});
+
+test("deepEquals toString and functions", () => {
+ expect({ toString: f1 }).toEqual({
+ toString: f1,
+ });
+ expect({ toString: f1 }).not.toEqual({
+ toString: f2,
+ });
+
+ expect(f1).toEqual(f1);
+ expect(f1).not.toEqual(f2);
+});
+
+test("deepEquals set and map", () => {
+ let e = new Map();
+ e.set("a", 1);
+ e.set("b", 2);
+ e.set("c", 3);
+ e.set(8, 6);
+
+ let d = new Map();
+ d.set("a", 1);
+ d.set("b", 2);
+ d.set("c", 3);
+ d.set(8, 6);
+
+ expect(e).toEqual(d);
+ expect(d).toEqual(e);
+
+ let f = new Map();
+ f.set("a", 1);
+ f.set("b", 2);
+ f.set("c", 3);
+ f.set(8, 7);
+ expect(e).not.toEqual(f);
+
+ let g = new Map();
+ g.set({ a: { b: { c: 89 } } }, 1);
+
+ let h = new Map();
+ h.set({ a: { b: { c: 89 } } }, 1);
+ expect(g).toEqual(h);
+
+ let i = new Map();
+ i.set({ a: { b: { c: 89 } } }, 1);
+ i.set({ a: { b: { c: 89 } } }, 1);
+ expect(g).not.toEqual(i);
+
+ let j = new Map();
+ j.set({ a: { b: { c: 89 } } }, 1);
+ j.set({ a: { b: { c: 89 } } }, 1);
+ expect(i).toEqual(j);
+
+ let p = new Map();
+ p.set({ a: { b: { c: 90 } } }, 1);
+ expect(p).not.toEqual(g);
+
+ let q = new Map();
+ q.set({ a: { b: { c: 90 } } }, { a: { b: 45 } });
+
+ let r = new Map();
+ r.set({ a: { b: { c: 90 } } }, { a: { b: 45 } });
+ expect(q).toEqual(r);
+
+ let s = new Map();
+ s.set({ a: { b: { c: 90 } } }, { a: { b: 49 } });
+ expect(q).not.toEqual(s);
+
+ const u = { a: 1, b: 2 };
+
+ let a = new Set();
+ a.add({ a: 1 });
+ a.add([1, 2, 3]);
+ a.add("hello");
+ a.add(89);
+
+ let b = new Set();
+ b.add({ a: 1 });
+ b.add("hello");
+ b.add([1, 2, 3]);
+ b.add(89);
+ expect(a).toEqual(b);
+ expect(b).toEqual(a);
+ let c = new Set();
+ c.add(89);
+ c.add("hello");
+ c.add({ a: 1 });
+ c.add([1, 2, 3, 4]);
+ expect(a).not.toEqual(c);
+});
+
+test("deepEquals - symbols", () => {
+ const x = [5, 6];
+ x[99] = 7;
+
+ const y = [5, 6];
+ y[99] = 7;
+
+ expect(x).toEqual(y);
+
+ const s1 = Symbol("test1");
+ const s2 = Symbol("test2");
+
+ const o = { a: 1 };
+ o[s1] = 45;
+ o[99] = 99;
+ o[s2] = 3;
+
+ const k = { a: 1 };
+ k[99] = 99;
+ k[s2] = 3;
+ k[s1] = 45;
+
+ expect(o).toEqual(k);
+});
+
+test("toEqual objects and arrays", () => {
+ expect("hello").toEqual("hello");
+ const s1 = Symbol("test1");
+ const s2 = Symbol("test2");
+
+ expect({ a: 1, b: 2 }).toEqual({ b: 2, a: 1 });
+ expect([1, 2, 3]).toEqual([1, 2, 3]);
+ expect({ a: 1, b: 2 }).not.toEqual({ b: 2, a: 1, c: 3 });
+ expect([1, 2, 3]).not.toEqual([1, 2, 3, 4]);
+ expect({ a: 1, b: 2, c: 3 }).not.toEqual({ a: 1, b: 2 });
+ expect([1, 2, 3, 4]).not.toEqual([1, 2, 3]);
+
+ let a = [{ a: 1 }, { b: 2, c: 3, d: 4 }, { e: 5, f: 6 }];
+ let b = [{ a: 1 }, { b: 2, c: 3, d: 4 }, { e: 5, f: 6 }];
+ expect(a).toEqual(b);
+ expect(b).toEqual(a);
+ a[0].a = 2;
+ expect(a).not.toEqual(b);
+ expect(b).not.toEqual(a);
+
+ let c = { [Symbol("test")]: 1 };
+ let d = { [Symbol("test")]: 1 };
+ expect(c).not.toEqual(d);
+ expect(d).not.toEqual(c);
+
+ a = { [s1]: 1 };
+ a[s1] = 1;
+ b = { [s2]: 1 };
+ b[s2] = 1;
+ expect(a).not.toEqual(b);
+ expect(b).not.toEqual(a);
+
+ a = {};
+ b = {};
+ a[s1] = 1;
+ b[s1] = 1;
+ expect(a).toEqual(b);
+
+ a = {};
+ b = {};
+ a[s1] = 1;
+ b[s1] = 2;
+ expect(a).not.toEqual(b);
+
+ a = {};
+ b = {};
+ a[s1] = 1;
+ b[s1] = 1;
+ a[s2] = 2;
+ b[s2] = 2;
+ expect(a).toEqual(b);
+
+ a = {};
+ b = {};
+ a[s1] = 1;
+ b[s1] = 1;
+ a[s2] = 2;
+ b[s2] = 3;
+ expect(a).not.toEqual(b);
+
+ a = { a: 1, b: 2 };
+ b = { a: 1, b: 2 };
+ a[s1] = 1;
+ b[s1] = 1;
+ expect(a).toEqual(b);
+
+ a = { a: 2, b: 2 };
+ b = { a: 1, b: 2 };
+ a[s1] = 1;
+ b[s1] = 1;
+ expect(a).not.toEqual(b);
+
+ // do the same tests for arrays
+ a = [{ a: 1 }, { b: 2, c: 3, d: 4 }, { e: 5, f: 6 }];
+ b = [{ a: 1 }, { b: 2, c: 3, d: 4 }, { e: 5, f: 6 }];
+ expect(a).toEqual(b);
+ expect(b).toEqual(a);
+ a[0].a = 2;
+ expect(a).not.toEqual(b);
+ expect(b).not.toEqual(a);
+
+ a = [1, 2, 3];
+ b = [1, 2, 3];
+ a[s1] = 1;
+ b[s1] = 1;
+ expect(a).toEqual(b);
+
+ a = [1, 2, 3];
+ b = [1, 2, 3];
+ a[s1] = 1;
+ b[s1] = 2;
+ expect(a).not.toEqual(b);
+
+ a = [1, 2, 3];
+ b = [1, 2, 3];
+ a[s1] = 1;
+ b[s1] = 1;
+ a[s2] = 2;
+ b[s2] = 2;
+ expect(a).toEqual(b);
+
+ a = [1, 2, 3];
+ b = [1, 2, 3];
+ a[s1] = 1;
+ b[s1] = 1;
+ a[s2] = 2;
+ b[s2] = 3;
+ expect(a).not.toEqual(b);
+
+ a = [1, 2, 3];
+ b = [1, 2, 3];
+ a[s1] = 1;
+ b[s1] = 1;
+ expect(a).toEqual(b);
+
+ a = [2, 2, 3];
+ b = [1, 2, 3];
+ a[s1] = 1;
+ b[s1] = 1;
+ expect(a).not.toEqual(b);
+
+ // do the same tests for objects and arrays with null and undefined
+ a = { a: 1, b: 2 };
+ b = { a: 1, b: 2 };
+ a[s1] = 1;
+ b[s1] = 1;
+ a[s2] = null;
+ b[s2] = undefined;
+ expect(a).not.toEqual(b);
+
+ a = { a: 1, b: 2 };
+ b = { a: 1, b: 2 };
+ a[s1] = 1;
+ b[s1] = 1;
+ a[s2] = undefined;
+ b[s2] = null;
+ expect(a).not.toEqual(b);
+
+ a = { a: 1, b: 2 };
+ b = { a: 1, b: 2 };
+ a[s1] = 1;
+ b[s1] = 1;
+ a[s2] = null;
+ b[s2] = null;
+ expect(a).toEqual(b);
+
+ a = { a: 1, b: 2 };
+ b = { a: 1, b: 2 };
+ a[s1] = 1;
+ b[s1] = 1;
+ a[s2] = undefined;
+ b[s2] = undefined;
+ expect(a).toEqual(b);
+
+ a = [1, 2, 3];
+ b = [1, 2, 3];
+ a[s1] = 1;
+ b[s1] = 1;
+ a[s2] = null;
+ b[s2] = undefined;
+ expect(a).not.toEqual(b);
+
+ a = [1, 2, 3];
+ b = [1, 2, 3];
+ a[s1] = 1;
+ b[s1] = 1;
+ a[s2] = undefined;
+ b[s2] = null;
+ expect(a).not.toEqual(b);
+
+ a = [1, 2, 3];
+ b = [1, 2, 3];
+ a[s1] = 1;
+ b[s1] = 1;
+ a[s2] = null;
+ b[s2] = null;
+ expect(a).toEqual(b);
+
+ a = [1, 2, 3];
+ b = [1, 2, 3];
+ a[s1] = 1;
+ b[s1] = 1;
+ a[s2] = undefined;
+ b[s2] = undefined;
+ expect(a).toEqual(b);
+
+ // similar tests for indexed objects
+ a = { 0: 1, 1: 2, 2: 3 };
+ b = { 0: 1, 1: 2, 2: 3 };
+ a[s1] = 1;
+ b[s1] = 1;
+ expect(a).toEqual(b);
+
+ a = { 0: 1, 1: 2, 2: 3 };
+ b = { 0: 1, 1: 2, 2: 3 };
+ a[s1] = 1;
+ b[s1] = 1;
+ a[s2] = 2;
+ b[s2] = 3;
+ expect(a).not.toEqual(b);
+
+ a = { 0: 1, 1: 3, 2: 3 };
+ b = { 0: 1, 1: 2, 2: 3 };
+ a[s1] = 1;
+ b[s1] = 1;
+ a[s2] = 2;
+ b[s2] = 2;
+ expect(a).not.toEqual(b);
+
+ a = [1, 2, 3];
+ b = [1, 2, 3, 4];
+ expect(a).not.toEqual(b);
+
+ a = [1, 2, 3, 4];
+ b = [1, 2, 3];
+ expect(a).not.toEqual(b);
+
+ a = { a: 1, b: 2 };
+ b = { a: 1, b: 2, c: 3 };
+ expect(a).not.toEqual(b);
+
+ a = { a: 1, b: 2, c: 3 };
+ b = { a: 1, b: 2 };
+ expect(a).not.toEqual(b);
+});
+
+test("symbol based keys in arrays are processed correctly", () => {
+ const mySymbol = Symbol("test");
+
+ const actual1 = [];
+ actual1[mySymbol] = 3;
+
+ const actual2 = [];
+ actual2[mySymbol] = 4;
+
+ const expected = [];
+ expected[mySymbol] = 3;
+
+ expect(actual2).not.toEqual(expected);
+ expect(actual1).toEqual(expected);
+});
+
+test("non-enumerable members should be skipped during equal", () => {
+ const actual = {
+ x: 3,
+ };
+ Object.defineProperty(actual, "test", {
+ enumerable: false,
+ value: 5,
+ });
+ expect(actual).toEqual({ x: 3 });
+});
+
+test("non-enumerable symbolic members should be skipped during equal", () => {
+ const actual = {
+ x: 3,
+ };
+ const mySymbol = Symbol("test");
+ Object.defineProperty(actual, mySymbol, {
+ enumerable: false,
+ value: 5,
+ });
+ expect(actual).toEqual({ x: 3 });
+});
+
+test("properties with the same circularity are equal", () => {
+ const a = {};
+ a.x = a;
+ const b = {};
+ b.x = b;
+ expect(a).toEqual(b);
+ expect(b).toEqual(a);
+
+ const c = {
+ x: a,
+ };
+ const d = {
+ x: b,
+ };
+
+ expect(d).toEqual(c);
+ expect(c).toEqual(d);
+});
+
+test("toEqual() - arrays", () => {
+ expect([1, 2, 3]).toEqual([1, 2, 3]);
+ expect([1, 2, 3, 4]).not.toEqual([1, 2, 3]);
+});
+
+test("properties with different circularity are not equal", () => {
+ const a = {};
+ a.x = { y: a };
+ const b = {};
+ const bx = {};
+ b.x = bx;
+ bx.y = bx;
+ expect(a).not.toEqual(b);
+ expect(b).not.toEqual(a);
+
+ const c = {};
+ c.x = a;
+ const d = {};
+ d.x = b;
+ expect(c).not.toEqual(d);
+ expect(d).not.toEqual(c);
+});
+
+test("are not equal if circularity is not on the same property", () => {
+ const a = {};
+ const b = {};
+ a.a1 = a;
+ b.a1 = {};
+ b.a1.a1 = a;
+
+ expect(a).not.toEqual(b);
+ expect(b).not.toEqual(a);
+
+ const c = {};
+ c.x = { x: c };
+ const d = {};
+ d.x = d;
+
+ expect(d).not.toEqual(c);
+ expect(c).not.toEqual(d);
+});
+
+test("random isEqual tests", () => {
+ expect(1).toEqual(1);
+ expect(1).not.toEqual(2);
+ expect(1).not.toEqual("1");
+ expect(1).not.toEqual(true);
+ expect(1).not.toEqual(false);
+ expect(1).not.toEqual(null);
+ expect(1).not.toEqual(undefined);
+ expect(1).not.toEqual({});
+ expect(1).not.toEqual([]);
+ expect(1).not.toEqual([1]);
+ expect(1).not.toEqual([1, 2]);
+ expect(1).not.toEqual([1, 2, 3]);
+ expect(1).not.toEqual([1, 2, 3, 4]);
+ expect(1).not.toEqual([1, 2, 3, 4, 5]);
+ expect(1).not.toEqual([1, 2, 3, 4, 5, 6]);
+ expect(1).not.toEqual([1, 2, 3, 4, 5, 6, 7]);
+ expect(1).not.toEqual([1, 2, 3, 4, 5, 6, 7, 8]);
+ expect(1).not.toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9]);
+ expect(1).not.toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
+ expect(1).not.toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
+ expect(1).not.toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
+ expect(1).not.toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]);
+ expect(1).not.toEqual([1, 2, 3, 4, 5, 6, 7, 8]);
+
+ // test toEquals for objects with getters and setters
+
+ expect([]).toEqual([]);
+ expect([1]).toEqual([1]);
+ expect([1, 2]).toEqual([1, 2]);
+ expect([1, 2, 3]).toEqual([1, 2, 3]);
+ expect({}).toEqual({});
+ expect({}).not.toEqual([]);
+ expect([]).not.toEqual({});
+
+ const obj = {
+ get a() {
+ return 1;
+ },
+ };
+ expect(obj).toEqual({ a: 1 });
+ expect({ a: 1 }).toEqual(obj);
+ expect(obj).not.toEqual({ a: 2 });
+ expect({ a: 2 }).not.toEqual(obj);
+
+ let a = new Set();
+ a.add([1, 2, 3]);
+ a.add("hello");
+ a.add({ a: 1 });
+ a.add(89);
+ let b = new Set();
+ b.add(89);
+ b.add({ a: 1 });
+ b.add("hello");
+ b.add([1, 2, 3]);
+ expect(a).toEqual(b);
+ expect(b).toEqual(a);
+ let c = new Set();
+ c.add(89);
+ c.add("helo");
+ c.add({ a: 1 });
+ c.add([1, 2, 3]);
+ expect(a).not.toEqual(c);
+
+ a = new Map();
+ a.set(1, 89);
+ a.set("hello", 2);
+ a.set({ a: 1 }, 3);
+ a.set([1, 2, 3], 4);
+ b = new Map();
+ b.set(1, 89);
+ b.set("hello", 2);
+ b.set({ a: 1 }, 3);
+ b.set([1, 2, 3], 4);
+ expect(a).toEqual(b);
+ expect(b).toEqual(a);
+ c = new Map();
+ c.set({ a: 1 }, 3);
+ c.set(1, 80);
+ c.set([1, 2, 3], 4);
+ c.set("hello", 2);
+ expect(a).not.toEqual(c);
+
+ a = new Set();
+ a.add(89);
+ a.add("hello");
+ a.add({ a: 1 });
+ a.add([1, 2, 3]);
+ a.add(a);
+ b = new Set();
+ b.add(89);
+ b.add("hello");
+ b.add(b);
+ b.add({ a: 1 });
+ b.add([1, 2, 3]);
+ expect(a).toEqual(b);
+ expect(b).toEqual(a);
+});
+
+test("testing Bun.deepEquals() using isEqual()", () => {
+ const t = new Uint8Array([1, 2, 3, 4, 5]);
+ expect(t).toEqual(t.slice());
+
+ expect(t.subarray(1)).toEqual(t.slice(1));
+ expect(t.subarray(1, 9)).toEqual(t.slice().subarray(1, 9));
+
+ var a = { foo: 1, bar: 2, baz: null };
+ var b = { foo: 1, bar: 2, baz: null };
+ a.baz = a;
+ b.baz = b;
+ expect(a).toEqual(b);
+
+ var a = { car: 1, cdr: { car: 2, cdr: null } };
+ var b = { car: 1, cdr: { car: 2, cdr: null } };
+ a.cdr.cdr = a;
+ b.cdr.cdr = b.cdr;
+ expect(a).not.toEqual(b);
+
+ expect(1n).not.toEqual(1);
+ expect(1).not.toEqual(1n);
+ expect(1n).toEqual(1n);
+ expect(undefined).not.toEqual([]);
+
+ var a = [1, 2, 3, null];
+ var b = [1, 2, 3, null];
+ a[3] = b;
+ b[3] = a;
+ expect(a).toEqual(b);
+
+ var a = [1, 2, 3, null];
+ var b = [1, 2, 3, null];
+ a[3] = a;
+ b[3] = a;
+ expect(a).toEqual(b);
+
+ var a = [1, [2, [3, null]]];
+ var b = [1, [2, [3, null]]];
+ a[1][1][1] = a;
+ b[1][1][1] = b[1][1];
+ expect(a).not.toEqual(b);
+
+ const foo = [1];
+ foo[1] = foo;
+
+ expect(foo).toEqual([1, foo]);
+
+ expect(1).toEqual(1);
+ expect([1]).toEqual([1]);
+
+ // expect(a).toEqual(a);
+ expect([1, 2, 3]).toEqual([1, 2, 3]);
+
+ let o = { a: 1, b: 2 };
+ expect(o).toEqual(o);
+ expect(o).toEqual({ a: 1, b: 2 });
+ expect(o).toEqual({ b: 2, a: 1 });
+ expect({ a: 1, b: 2 }).toEqual(o);
+ expect({ b: 2, a: 1 }).toEqual(o);
+ expect(o).not.toEqual({ a: 1, b: 2, c: 3 });
+ expect({ a: 1, b: 2, c: 3, d: 4 }).not.toEqual(o);
+ expect({ a: 1, b: 2 }).toEqual({ a: 1, b: 2 });
+ expect({ a: 1, b: 2 }).not.toEqual({ a: 1 });
+
+ expect("a").toEqual("a");
+ expect("aaaa").toEqual("aaaa");
+ expect("aaaa").not.toEqual("aaaaa");
+ expect("aaaa").not.toEqual("aaba");
+ expect("a").not.toEqual("b");
+
+ expect(undefined).not.toEqual(null);
+ expect(null).not.toEqual(undefined);
+ expect(undefined).not.toEqual(0);
+ expect(0).not.toEqual(undefined);
+ expect(null).not.toEqual(0);
+ expect(0).not.toEqual(null);
+ expect(undefined).not.toEqual("");
+ expect("").not.toEqual(undefined);
+ expect(null).not.toEqual("");
+ expect("").not.toEqual(null);
+ expect(undefined).not.toEqual(false);
+ expect(false).not.toEqual(undefined);
+ expect(null).not.toEqual(false);
+ expect(false).not.toEqual(null);
+ expect(undefined).not.toEqual(true);
+ expect(true).not.toEqual(undefined);
+ expect(null).not.toEqual(true);
+ expect(true).not.toEqual(null);
+ expect([]).not.toEqual(undefined);
+ expect(null).not.toEqual([]);
+ expect([]).not.toEqual(null);
+
+ expect(0).toEqual(0);
+ expect(-0).toEqual(-0);
+ expect(0).not.toEqual(-0);
+ expect(-0).not.toEqual(0);
+
+ expect(NaN).toEqual(NaN);
+
+ expect(null).toEqual(null);
+ expect(undefined).toEqual(undefined);
+
+ expect(1).toEqual(1);
+ expect(1).not.toEqual(2);
+
+ expect(NaN).toEqual(NaN);
+ expect(NaN).toEqual(0 / 0);
+ expect(Infinity).toEqual(Infinity);
+ expect(Infinity).toEqual(1 / 0);
+ expect(-Infinity).toEqual(-Infinity);
+ expect(-Infinity).toEqual(-1 / 0);
+});
+
+test("toHaveProperty() - emojis", () => {
+ expect({ "👍": "thumbs up" }).toHaveProperty("👍", "thumbs up");
+ expect({ "👩‍👩‍👧‍👧": "family" }).toHaveProperty("👩‍👩‍👧‍👧", "family");
+ expect({ "😶‍🌫️": "fog" }).toHaveProperty("😶‍🌫️", "fog");
+ expect({ "👩‍❤️‍👨": "couple" }).toHaveProperty("👩‍❤️‍👨", "couple");
+ expect({ "👩‍❤️‍👨‍👨‍👧‍👧": "family" }).toHaveProperty("👩‍❤️‍👨‍👨‍👧‍👧", "family");
+ expect({ "👩‍❤️‍👨‍👨‍👧": "family" }).toHaveProperty("👩‍❤️‍👨‍👨‍👧", "family");
+ expect({ "👩‍❤️‍👨‍👨‍👧": "family" }).not.toHaveProperty("👩‍❤️‍👨‍👨‍👧‍👧", "family");
+
+ // emojis in array
+ expect(["👍", "👎"]).toHaveProperty("0", "👍");
+ expect(["👍", "👎"]).toHaveProperty("1", "👎");
+ expect(["👍", "👎"]).not.toHaveProperty("0", "👎");
+ expect(["👍", "👎"]).not.toHaveProperty("1", "👍");
+ expect(["👩‍❤️‍👨‍👨‍👧‍👧"]).toHaveProperty("0", "👩‍❤️‍👨‍👨‍👧‍👧");
+ expect(["👩‍❤️‍👨‍👨‍👧‍👧"]).toHaveProperty([0], "👩‍❤️‍👨‍👨‍👧‍👧");
+ expect(["😶‍🌫️"]).toHaveProperty([0], "😶‍🌫️");
+});
+
+test("toHaveProperty() - dot and bracket notation edge cases", () => {
+ expect({ a: 1 }).not.toHaveProperty(".");
+ expect({ a: 1 }).not.toHaveProperty("]");
+ expect({ a: 1 }).not.toHaveProperty("[");
+ expect({ a: 1 }).not.toHaveProperty("[]");
+ expect({ a: 1 }).not.toHaveProperty("[[]]");
+ expect({ a: 1 }).not.toHaveProperty("[[");
+ expect({ a: 1 }).not.toHaveProperty("]]");
+ expect({ a: 1 }).not.toHaveProperty("[]]");
+ expect({ a: 1 }).not.toHaveProperty("[[]");
+ expect({ a: 1 }).not.toHaveProperty(".]");
+ expect({ a: 1 }).not.toHaveProperty(".[");
+ expect({ "": 1 }).toHaveProperty("[.", 1);
+ expect({ a: 1 }).not.toHaveProperty("[.");
+ expect({ a: 1 }).not.toHaveProperty("].");
+ expect({ a: 1 }).not.toHaveProperty("].[");
+ expect({ a: 1 }).not.toHaveProperty("].]");
+ expect({ a: 1 }).not.toHaveProperty("[.]");
+ expect({ a: 1 }).not.toHaveProperty("[.[");
+
+ expect([1]).toHaveProperty("[0]", 1);
+ expect([1]).toHaveProperty("[0][", 1);
+ expect([1]).toHaveProperty("[0]]", 1);
+ expect([1]).toHaveProperty("[0][[", 1);
+ expect([1]).toHaveProperty("[][[[0]", 1);
+ expect([1]).toHaveProperty("[][[[]][[][][.0", 1);
+ expect([1]).toHaveProperty("[][[[]][[][][.[][[][[[][][0", 1);
+ expect([1]).not.toHaveProperty("......1.............", 1);
+ expect([1]).not.toHaveProperty("......0.............", 1);
+ expect([1]).not.toHaveProperty(".0", 1);
+ expect([1]).not.toHaveProperty("0.", 1);
+ expect([{ "": 1 }]).toHaveProperty("0.", 1);
+ expect({ "": { "": 1 } }).toHaveProperty(".", 1);
+ expect({ "": { "": { "": 1 } } }).toHaveProperty("..", 1);
+ expect({ "": { "": { "": 1 } } }).not.toHaveProperty(".", 1);
+ expect({ "": { "": { "": 1 } } }).not.toHaveProperty("...", 1);
+ expect({ "": { "": { "": 1 } } }).not.toHaveProperty("....", 1);
+ expect([1]).toHaveProperty("0.[[[][][]][[[][[]]]]", 1);
+ expect([1]).not.toHaveProperty("[0].", 1);
+ expect([1]).toHaveProperty("0", 1);
+ expect([1]).toHaveProperty("[].0", 1);
+ expect([1]).toHaveProperty("[.0", 1);
+ expect([1]).toHaveProperty("].0", 1);
+ expect([1]).toHaveProperty("0[]][[[]", 1);
+ expect([1]).toHaveProperty("[[]][[[][][0", 1);
+ expect([1]).toHaveProperty("0", 1);
+ expect([1]).toHaveProperty("0.[", 1);
+ expect([1]).not.toHaveProperty("0........[", 1);
+ expect([1]).not.toHaveProperty("0..[", 1);
+ expect([1]).not.toHaveProperty(".0", 1);
+ expect([1]).toHaveProperty("[].0", 1);
+ expect([1]).not.toHaveProperty("[]..0", 1);
+ expect([1]).toHaveProperty("[.][.[[.]]]]].[.[].].]]]]].].].0", 1);
+ expect([1]).not.toHaveProperty("[.][.[[.]]]]].[.[].].]]0]]].].].", 1);
+ expect([1]).toHaveProperty("[.][.[[.]]]]].[.[].].]]0]]].].]", 1);
+ expect([1]).not.toHaveProperty("[.][.[[..]]]]].[.[].].]]0]]].].]", 1);
+ expect([1]).toHaveProperty("[.][.[[.]]]]].[.[].].0.]]]]].].]", 1);
+ expect([1]).not.toHaveProperty("[.][.[[.]]]]].[.[].].0.]]] ]].].]", 1);
+ expect([1]).not.toHaveProperty("0 ", 1);
+ expect([1]).not.toHaveProperty(" 0 ", 1);
+ expect([1]).not.toHaveProperty(" 0[] ", 1);
+ expect([1]).not.toHaveProperty(" 0] ", 1);
+ expect([1]).not.toHaveProperty(" .[0]", 1);
+
+ expect({ "": 1 }).not.toHaveProperty(".", 1);
+ expect({ "": 1 }).not.toHaveProperty("]", 1);
+ expect({ "": 1 }).not.toHaveProperty("[", 1);
+ expect({ "": 1 }).toHaveProperty("", 1);
+
+ expect({ "": 1 }).not.toHaveProperty("..", 1);
+ expect({ "": { "": 1 } }).not.toHaveProperty("..", 1);
+ expect([{ "": 1 }]).toHaveProperty("0.", 1);
+ expect([{ "": 1 }]).not.toHaveProperty(".0.", 1);
+ expect({ "": [1] }).toHaveProperty(".0", 1);
+ expect({ "": [1] }).not.toHaveProperty("..0", 1);
+ expect([{ "": 1 }]).not.toHaveProperty("0..", 1);
+ expect([{ "": { "": 1 } }]).toHaveProperty("0..", 1);
+
+ expect([1]).not.toHaveProperty("[0].", 1);
+ expect([1]).not.toHaveProperty("[0][0]", 1);
+ expect({ a: [1] }).toHaveProperty("a[[[[[[[[[0]]]", 1);
+ expect({ "[[[": 0 }).not.toHaveProperty("[[[", 0);
+});
+
+test("toHaveProperty() - with string or array", () => {
+ const a = new Array(["a", "b", "c"]);
+ expect(a).toHaveProperty("0.1", "b");
+ const b = new Array("a", "b", "c");
+ expect({ a: { b: { c: 1 } } }).toHaveProperty(b);
+ const c = {
+ a: { b: 1 },
+ "a.b": 2,
+ };
+ const d = new Array("a.b");
+ expect(c).toHaveProperty(d, 2);
+ const houseForSale = {
+ bath: true,
+ bedrooms: 4,
+ kitchen: {
+ amenities: ["oven", "stove", "washer"],
+ area: 20,
+ wallColor: "white",
+ "nice.oven": true,
+ },
+ livingroom: {
+ amenities: [
+ {
+ couch: [
+ ["large", { dimensions: [20, 20] }],
+ ["small", { dimensions: [10, 10] }],
+ ],
+ },
+ ],
+ },
+ sunroom: "yes",
+ "ceiling.height": 20,
+ "entrance.window": 3,
+ entrance: { window: 5 },
+ };
+ expect(houseForSale).toHaveProperty("entrance.window", 5);
+ expect(houseForSale).toHaveProperty(["entrance", "window"], 5);
+ expect(houseForSale).toHaveProperty(["entrance.window"], 3);
+ expect(houseForSale).toHaveProperty("bath");
+ expect(houseForSale).not.toHaveProperty("jacuzzi");
+ // expect(houseForSale).toHaveProperty("jacuzzi");
+ // expect(houseForSale).not.toHaveProperty("bath");
+ expect(houseForSale).toHaveProperty("bath", true);
+ expect(houseForSale).not.toHaveProperty("bath", false);
+ // expect(houseForSale).toHaveProperty("bath", false);
+ // expect(houseForSale).not.toHaveProperty("bath", true);
+ expect(houseForSale).toHaveProperty("bedrooms", 4);
+ expect(houseForSale).toHaveProperty(["sunroom"], "yes");
+ expect(houseForSale).toHaveProperty("kitchen.area", 20);
+ expect(houseForSale).toHaveProperty("kitchen.amenities", ["oven", "stove", "washer"]);
+ expect(houseForSale).not.toHaveProperty(["kitchen", "area"], 21);
+ expect(houseForSale).toHaveProperty(["kitchen", "area"], 20);
+ expect(houseForSale).not.toHaveProperty(["kitchen", "area"], 29);
+ expect(houseForSale).toHaveProperty(["kitchen", "amenities"], ["oven", "stove", "washer"]);
+ expect(houseForSale).toHaveProperty("kitchen.amenities[2]", "washer");
+ expect(houseForSale).toHaveProperty(["kitchen", "amenities", 1], "stove");
+ expect(houseForSale).toHaveProperty(["kitchen", "amenities", 0], "oven");
+ expect(houseForSale).toHaveProperty("livingroom.amenities[0].couch[0][1].dimensions[0]", 20);
+ expect(houseForSale).toHaveProperty(["kitchen", "nice.oven"]);
+ expect(houseForSale).not.toHaveProperty(["kitchen", "open"]);
+ expect(houseForSale).toHaveProperty(["ceiling.height"], 20);
+ expect({ a: { b: 1 } }).toHaveProperty("a.b");
+ expect({ a: [2, 3, 4] }).toHaveProperty("a.0");
+ expect({ a: [2, 3, 4] }).toHaveProperty("a.1");
+ expect({ a: [2, 3, 4] }).toHaveProperty("a.2");
+ expect({ a: [2, 3, 4] }).toHaveProperty("a[1]");
+ expect([2, 3, 4]).toHaveProperty("1");
+ expect([2, 3, 4]).toHaveProperty("[1]");
+ expect([2, [6, 9], 4]).toHaveProperty("1.1");
+ expect([2, [6, 9], 4]).toHaveProperty("1[1]");
+ expect([2, [6, 9], 4]).toHaveProperty("[1].1");
+ expect([2, [6, 9], 4]).toHaveProperty("[1][1]");
+ expect([2, [6, 9], 4]).toHaveProperty([0], 2);
+ expect({ a: { b: 1 } }).toHaveProperty("a.b");
+ expect({ a: [1, 2, [3, { b: 1 }]] }).toHaveProperty("a.2.1.b");
+ expect({ a: [1, 2, [3, { b: 1 }]] }).toHaveProperty("a");
+ expect({ a: [1, 2, [3, { b: 1 }]] }).toHaveProperty("a[2][1].b");
+ expect({ a: [1, 2, [3, { b: 1 }]] }).toHaveProperty("a[2][1]");
+ expect({ a: [1, 2, [3, { b: 1 }]] }).not.toHaveProperty("a[2][1].c");
+ expect("test").toHaveProperty("length");
+ expect({}).toHaveProperty("constructor");
+ expect({}).toHaveProperty("constructor.name");
+ expect({}).toHaveProperty("constructor.name", "Object");
+ expect(new Date()).toHaveProperty("getTime");
+});
+
+test("toHaveProperty() - all", () => {
+ expect({ a: 1 }).toHaveProperty("a");
+ expect({ a: 1 }).toHaveProperty("a", 1);
+ expect({ a: 1 }).not.toHaveProperty("b");
+ expect({ a: 1 }).not.toHaveProperty("a", 2);
+
+ // test with object with property "a" with all types of values (including undefined)
+ expect({ a: undefined }).toHaveProperty("a");
+ expect({ a: null }).toHaveProperty("a");
+ expect({ a: 0 }).toHaveProperty("a");
+ expect({ a: false }).toHaveProperty("a");
+ expect({ a: "" }).toHaveProperty("a");
+ expect({ a: {} }).toHaveProperty("a");
+ expect({ a: [] }).toHaveProperty("a");
+ expect({ a: () => {} }).toHaveProperty("a");
+
+ // test with object with property "a" with all types of values (including undefined)
+ expect({ a: undefined }).toHaveProperty("a", undefined);
+ expect({ a: null }).toHaveProperty("a", null);
+ expect({ a: 0 }).toHaveProperty("a", 0);
+ expect({ a: false }).toHaveProperty("a", false);
+ expect({ a: "" }).toHaveProperty("a", "");
+ expect({ a: {} }).toHaveProperty("a", {});
+ expect({ a: [] }).toHaveProperty("a", []);
+ expect({ a: () => {} }).not.toHaveProperty("a", () => {});
+
+ // test with object with property "a" with all types of values (including undefined)
+
+ expect({ a: undefined }).not.toHaveProperty("a", null);
+ expect({ a: null }).not.toHaveProperty("a", undefined);
+ expect({ a: 0 }).not.toHaveProperty("a", null);
+ expect({ a: false }).not.toHaveProperty("a", null);
+ expect({ a: "" }).not.toHaveProperty("a", null);
+ expect({ a: {} }).not.toHaveProperty("a", null);
+ expect({ a: [] }).not.toHaveProperty("a", null);
+ expect({ a: () => {} }).not.toHaveProperty("a", null);
+
+ expect({ a: undefined }).not.toHaveProperty("a", 0);
+ expect({ a: null }).not.toHaveProperty("a", 0);
+ expect({ a: 0 }).not.toHaveProperty("a", 1);
+ expect({ a: false }).not.toHaveProperty("a", 0);
+ expect({ a: "" }).not.toHaveProperty("a", 0);
+ expect({ a: {} }).not.toHaveProperty("a", 0);
+ expect({ a: [] }).not.toHaveProperty("a", 0);
+ expect({ a: () => {} }).not.toHaveProperty("a", 0);
+
+ expect({ a: undefined }).not.toHaveProperty("a", false);
+ expect({ a: null }).not.toHaveProperty("a", false);
+ expect({ a: 0 }).not.toHaveProperty("a", false);
+ expect({ a: false }).not.toHaveProperty("a", true);
+ expect({ a: "" }).not.toHaveProperty("a", false);
+ expect({ a: {} }).not.toHaveProperty("a", false);
+ expect({ a: [] }).not.toHaveProperty("a", false);
+ expect({ a: () => {} }).not.toHaveProperty("a", false);
+
+ expect({ a: undefined }).not.toHaveProperty("a", "");
+ expect({ a: null }).not.toHaveProperty("a", "");
+ expect({ a: 0 }).not.toHaveProperty("a", "");
+ expect({ a: false }).not.toHaveProperty("a", "");
+ expect({ a: "" }).not.toHaveProperty("a", "a");
+ expect({ a: {} }).not.toHaveProperty("a", "");
+ expect({ a: [] }).not.toHaveProperty("a", "");
+ expect({ a: () => {} }).not.toHaveProperty("a", "");
+
+ expect({ a: undefined }).not.toHaveProperty("a", {});
+ expect({ a: null }).not.toHaveProperty("a", {});
+ expect({ a: 0 }).not.toHaveProperty("a", {});
+ expect({ a: false }).not.toHaveProperty("a", {});
+ expect({ a: "" }).not.toHaveProperty("a", {});
+ expect({ a: {} }).not.toHaveProperty("a", { a: 1 });
+ expect({ a: [] }).not.toHaveProperty("a", {});
+ expect({ a: () => {} }).not.toHaveProperty("a", {});
+
+ // test object with property "a" with value set, map, string
+ expect({ a: new Set([1, 2, 3]) }).toHaveProperty("a", new Set([3, 2, 1]));
+ expect({ a: new Map([{ a: 1 }, { b: 2 }, { c: 3 }]) }).toHaveProperty("a", new Map([{ c: 3 }, { b: 2 }, { a: 1 }]));
+ expect({ a: new String("a") }).toHaveProperty("a", new String("a"));
+ expect({ a: new String("a") }).not.toHaveProperty("a", "a");
+ expect({ a: new String("a") }).not.toHaveProperty("a", "b");
+ expect({ a: new String("a") }).not.toHaveProperty("a", new String("b"));
+ expect({ a: new String("a") }).not.toHaveProperty("a", new Number(1));
+ expect({ a: new String("a") }).not.toHaveProperty("a", new Boolean(true));
+ expect({ a: new String("a") }).not.toHaveProperty("a", new Boolean(false));
+ expect({ a: new String("a") }).not.toHaveProperty("a", new Object());
+ expect({ a: new String("a") }).not.toHaveProperty("a", new Array());
+ expect({ a: new String("a") }).not.toHaveProperty("a", new Function());
+ expect({ a: new String("a") }).not.toHaveProperty("a", new Date());
+ expect({ a: new String("a") }).not.toHaveProperty("a", new RegExp());
+ expect({ a: new String("a") }).not.toHaveProperty("a", new Error());
+ expect({ a: new String("a") }).not.toHaveProperty("a", new Promise(() => {}));
+ expect({ a: new String("a") }).not.toHaveProperty("a", new WeakSet());
+ expect({ a: new String("a") }).not.toHaveProperty("a", new WeakMap());
+ expect({ a: new String("a") }).not.toHaveProperty("a", Symbol("a"));
+ expect({ a: new String("a") }).not.toHaveProperty("a", new Int8Array());
+ expect({ a: new String("a") }).not.toHaveProperty("a", new Uint8Array());
+ expect({ a: new String("a") }).not.toHaveProperty("a", new Uint8ClampedArray());
+ expect({ a: new String("a") }).not.toHaveProperty("a", new Int16Array());
+ expect({ a: new String("a") }).not.toHaveProperty("a", new Uint16Array());
+ expect({ a: new String("a") }).not.toHaveProperty("a", new Int32Array());
+ expect({ a: new String("a") }).not.toHaveProperty("a", new Uint32Array());
+ expect({ a: new String("a") }).not.toHaveProperty("a", new Float32Array());
+ expect({ a: new String("a") }).not.toHaveProperty("a", new Float64Array());
+ expect({ a: new String("a") }).not.toHaveProperty("a", new BigInt64Array());
+ expect({ a: new String("a") }).not.toHaveProperty("a", new BigUint64Array());
+ expect({ a: new String("a") }).not.toHaveProperty("a", new ArrayBuffer());
+ expect({ a: new String("a") }).not.toHaveProperty("a", new SharedArrayBuffer());
+ expect({ a: new String("a") }).not.toHaveProperty("a", new DataView(new ArrayBuffer(1)));
+
+ // test property equality with sets, maps, objects, arrays, and String
+ expect({ a: new Set([1, 2, 3]) }).toHaveProperty("a", new Set([1, 2, 3]));
+ expect({ a: new Map([{ a: 1 }, { b: 2 }, { c: 3 }]) }).toHaveProperty("a", new Map([{ a: 1 }, { b: 2 }, { c: 3 }]));
+ expect({ a: { a: 1, b: 2, c: 3 } }).toHaveProperty("a", { a: 1, b: 2, c: 3 });
+ expect({ a: [1, 2, 3] }).toHaveProperty("a", [1, 2, 3]);
+ expect({ a: "a" }).toHaveProperty("a", "a");
+ expect({ a: new String("a") }).toHaveProperty("a", new String("a"));
+ expect({ a: new String("a") }).not.toHaveProperty("a", "a");
+});
+
+test("toBe()", () => {
+ const a = 1;
+ const b = 1;
+ expect(a).toBe(a);
+ expect(a).toBe(b);
+ expect(a).toBe(1);
+ expect(1).toBe(a);
+ expect(b).toBe(a);
+
+ const c = { a: 1 };
+ const d = { a: 1 };
+ expect(c).toBe(c);
+ expect(c).not.toBe(d);
+ expect(c).not.toBe({ a: 1 });
+ expect({ a: 1 }).not.toBe(c);
+ expect(d).not.toBe(c);
+
+ expect(1).toBe(1);
+ // expect(1).not.toBe(1);
+
+ expect(1).not.toBe(2);
+ expect(1).not.toBe("1");
+ expect("hello test").toBe("hello test");
+ expect("hello test").not.toBe("hello test2");
+});
+
+test("toHaveLength()", () => {
+ expect({ length: Number.MAX_SAFE_INTEGER }).toHaveLength(Number.MAX_SAFE_INTEGER);
+ expect("123").toHaveLength(3);
+ expect([1, 2, 3]).toHaveLength(3);
+ expect([1, 2, 3]).not.toHaveLength(2);
+ expect("123").not.toHaveLength(2);
+ expect({ length: 3 }).toHaveLength(3);
+ expect({ length: 3 }).not.toHaveLength(2);
+ expect({ length: 3 }).not.toHaveLength(Number.MAX_SAFE_INTEGER);
+ expect({ length: Number.MAX_SAFE_INTEGER }).not.toHaveLength(Number.MAX_SAFE_INTEGER - 1);
+ expect({ length: 3.3 }).not.toHaveLength(3);
+ expect("123").not.toHaveLength(-0);
+});
+
+test("toContain()", () => {
+ const s1 = new String("123");
+ expect(s1).not.toContain("12");
+ const s2 = "123";
+ expect(s2).toContain("12");
+
+ expect("test").toContain("es");
+ expect("test").toContain("est");
+ // expect("test").not.toContain("test");
+ expect(["test", "es"]).toContain("es");
+ expect("").toContain("");
+ expect([""]).toContain("");
+
+ expect(["lemon", "lime"]).not.toContain("orange");
+ expect("citrus fruits").toContain("fruit");
+
+ const a = new Uint16Array([1, 2, 3]);
+ expect(a).toContain(2);
+ expect(a).not.toContain(4);
+ expect([2, "2335", 5, true, false, null, undefined]).toContain(5);
+ expect([2, "2335", 5, true, false, null, undefined]).toContain("2335");
+ expect([2, "2335", 5, true, false, null, undefined]).toContain(true);
+ expect([2, "2335", 5, true, false, null, undefined]).toContain(false);
+ expect([2, "2335", 5, true, false, null, undefined]).toContain(null);
+ expect([2, "2335", 5, true, false, null, undefined]).toContain(undefined);
+ expect([2, "2335", 5, true, false, null, undefined]).not.toContain(3);
+ expect([2, "2335", 5, true, false, null, undefined]).not.not.not.toContain(3);
+
+ // expect([4, 5, 6]).not.toContain(5);
+
+ expect([]).not.toContain([]);
+});
+
+test("toBeTruthy()", () => {
+ expect("test").toBeTruthy();
+ expect(true).toBeTruthy();
+ expect(1).toBeTruthy();
+ expect({}).toBeTruthy();
+ expect([]).toBeTruthy();
+ expect(() => {}).toBeTruthy();
+ // expect(() => {}).not.toBeTruthy();
+
+ expect("").not.toBeTruthy();
+ expect(0).not.toBeTruthy();
+ expect(-0).not.toBeTruthy();
+ expect(NaN).not.toBeTruthy();
+ expect(0n).not.toBeTruthy();
+ expect(false).not.toBeTruthy();
+ expect(null).not.toBeTruthy();
+ expect(undefined).not.toBeTruthy();
+});
+
+test("toBeUndefined()", () => {
+ expect(undefined).toBeUndefined();
+ // expect(undefined).not.toBeUndefined();
+
+ expect(null).not.toBeUndefined();
+ expect(null).not.not.not.toBeUndefined();
+ expect(0).not.toBeUndefined();
+ expect("hello defined").not.toBeUndefined();
+});
+
+test("toBeNaN()", () => {
+ expect(NaN).toBeNaN();
+ // expect(NaN).not.toBeNaN();
+
+ expect(0).not.toBeNaN();
+ expect("hello not NaN").not.toBeNaN();
+});
+
+test("toBeNull()", () => {
+ expect(null).toBeNull();
+ // expect(null).not.toBeNull();
+
+ expect(undefined).not.toBeNull();
+ expect(0).not.toBeNull();
+ expect("hello not null").not.toBeNull();
+});
+
+test("toBeDefined()", () => {
+ expect(0).toBeDefined();
+ expect("hello defined").toBeDefined();
+ expect(null).toBeDefined();
+ // expect(null).not.toBeDefined();
+
+ expect(undefined).not.toBeDefined();
+});
+
+test("toBeFalsy()", () => {
+ expect("").toBeFalsy();
+ expect(0).toBeFalsy();
+ expect(-0).toBeFalsy();
+ expect(NaN).toBeFalsy();
+ expect(0n).toBeFalsy();
+ expect(false).toBeFalsy();
+ expect(null).toBeFalsy();
+ expect(undefined).toBeFalsy();
+ // expect(undefined).not.toBeFalsy();
+
+ expect("hello not falsy").not.toBeFalsy();
+ expect("hello not falsy").not.not.not.toBeFalsy();
+ expect(1).not.toBeFalsy();
+ expect(true).not.toBeFalsy();
+ expect({}).not.toBeFalsy();
+ expect([]).not.toBeFalsy();
+ expect(() => {}).not.toBeFalsy();
+});
+
+test("toBeGreaterThan()", () => {
+ expect(3n).toBeGreaterThan(2);
+ expect(Number.MAX_VALUE).not.toBeGreaterThan(Number.MAX_VALUE);
+ expect(1).not.toBeGreaterThan(BigInt(Number.MAX_VALUE));
+ expect(1).not.toBeGreaterThan(Number.MAX_SAFE_INTEGER);
+ expect(1).not.toBeGreaterThan(BigInt(Number.MAX_SAFE_INTEGER));
+ expect(Number.MAX_SAFE_INTEGER).not.toBeGreaterThan(Number.MAX_SAFE_INTEGER);
+ expect(BigInt(Number.MAX_SAFE_INTEGER)).not.toBeGreaterThan(BigInt(Number.MAX_SAFE_INTEGER));
+
+ expect(Infinity).toBeGreaterThan(-Infinity);
+ expect(-Infinity).not.toBeGreaterThan(Infinity);
+
+ expect(NaN).not.toBeGreaterThan(NaN);
+ expect(NaN).not.toBeGreaterThan(-Infinity);
+
+ expect(10).toBeGreaterThan(9);
+ expect(10).not.toBeGreaterThan(10);
+ expect(10).not.toBeGreaterThan(11);
+ expect(10).not.toBeGreaterThan(Infinity);
+ expect(10).toBeGreaterThan(-Infinity);
+ expect(10).not.toBeGreaterThan(NaN);
+ expect(10).toBeGreaterThan(0);
+ expect(10).toBeGreaterThan(-0);
+ expect(10).toBeGreaterThan(0.1);
+ expect(10).toBeGreaterThan(-0.1);
+ expect(10).toBeGreaterThan(0.9);
+ expect(10).toBeGreaterThan(-0.9);
+ expect(10).toBeGreaterThan(1);
+ expect(10).toBeGreaterThan(-1);
+ // switch the order
+ expect(9).not.toBeGreaterThan(10);
+ expect(10).not.toBeGreaterThan(10);
+ expect(11).toBeGreaterThan(10);
+ expect(Infinity).toBeGreaterThan(10);
+ expect(-Infinity).not.toBeGreaterThan(10);
+ expect(NaN).not.toBeGreaterThan(10);
+ expect(0).not.toBeGreaterThan(10);
+ expect(-0).not.toBeGreaterThan(10);
+ expect(0.1).not.toBeGreaterThan(10);
+ expect(-0.1).not.toBeGreaterThan(10);
+ expect(0.9).not.toBeGreaterThan(10);
+ expect(-0.9).not.toBeGreaterThan(10);
+ expect(1).not.toBeGreaterThan(10);
+ expect(-1).not.toBeGreaterThan(10);
+
+ // same tests but use bigints
+ expect(10n).toBeGreaterThan(9n);
+ expect(10n).not.toBeGreaterThan(10n);
+ expect(10n).not.toBeGreaterThan(11n);
+ expect(10n).not.toBeGreaterThan(Infinity);
+ expect(10n).toBeGreaterThan(-Infinity);
+ expect(10n).not.toBeGreaterThan(NaN);
+ expect(10n).toBeGreaterThan(0n);
+ expect(10n).toBeGreaterThan(-0n);
+ expect(10n).toBeGreaterThan(1n);
+ expect(10n).toBeGreaterThan(-1n);
+ // switch the order
+ expect(9n).not.toBeGreaterThan(10n);
+ expect(10n).not.toBeGreaterThan(10n);
+ expect(11n).toBeGreaterThan(10n);
+ expect(Infinity).toBeGreaterThan(10n);
+ expect(-Infinity).not.toBeGreaterThan(10n);
+ expect(NaN).not.toBeGreaterThan(10n);
+ expect(0n).not.toBeGreaterThan(10n);
+ expect(-0n).not.toBeGreaterThan(10n);
+ expect(1n).not.toBeGreaterThan(10n);
+ expect(-1n).not.toBeGreaterThan(10n);
+
+ // use bigints and numbers
+ expect(10n).toBeGreaterThan(9);
+ expect(10n).not.toBeGreaterThan(10);
+ expect(10n).not.toBeGreaterThan(11);
+ expect(10n).not.toBeGreaterThan(Infinity);
+ expect(10n).toBeGreaterThan(-Infinity);
+ expect(10n).not.toBeGreaterThan(NaN);
+ expect(10n).toBeGreaterThan(0);
+ expect(10n).toBeGreaterThan(-0);
+ expect(10n).toBeGreaterThan(0.1);
+ expect(10n).toBeGreaterThan(-0.1);
+ expect(10n).toBeGreaterThan(0.9);
+ expect(10n).toBeGreaterThan(-0.9);
+ expect(10n).toBeGreaterThan(1);
+ expect(10n).toBeGreaterThan(-1);
+ // switch the order
+ expect(9n).not.toBeGreaterThan(10);
+ expect(10n).not.toBeGreaterThan(10);
+ expect(11n).toBeGreaterThan(10);
+ expect(Infinity).toBeGreaterThan(10n);
+ expect(-Infinity).not.toBeGreaterThan(10n);
+ expect(NaN).not.toBeGreaterThan(10n);
+ expect(0n).not.toBeGreaterThan(10);
+ expect(-0n).not.toBeGreaterThan(10);
+ expect(1n).not.toBeGreaterThan(10);
+ expect(-1n).not.toBeGreaterThan(10);
+
+ expect(1n).not.toBeGreaterThan(1);
+ expect(1n).not.toBeGreaterThan(Number.MAX_SAFE_INTEGER);
+ expect(1n).not.toBeGreaterThan(Number.MAX_VALUE);
+ expect(1).not.toBeGreaterThan(1n);
+ expect(Number.MAX_SAFE_INTEGER).toBeGreaterThan(1n);
+ expect(Number.MAX_VALUE).toBeGreaterThan(1n);
+
+ expect(BigInt(Number.MAX_SAFE_INTEGER)).toBeGreaterThan(1n);
+ expect(BigInt(Number.MAX_VALUE)).toBeGreaterThan(1n);
+ expect(1n).not.toBeGreaterThan(BigInt(Number.MAX_SAFE_INTEGER));
+ expect(1n).not.toBeGreaterThan(BigInt(Number.MAX_VALUE));
+
+ expect(BigInt(Number.MAX_SAFE_INTEGER)).toBeGreaterThan(1);
+ expect(BigInt(Number.MAX_VALUE)).toBeGreaterThan(1);
+ expect(1).not.toBeGreaterThan(BigInt(Number.MAX_SAFE_INTEGER));
+});
+
+test("toBeGreaterThanOrEqual()", () => {
+ expect(Number.MAX_VALUE).toBeGreaterThanOrEqual(Number.MAX_VALUE);
+ expect(1).not.toBeGreaterThanOrEqual(Number.MAX_SAFE_INTEGER);
+ expect(1).not.toBeGreaterThanOrEqual(BigInt(Number.MAX_SAFE_INTEGER));
+ expect(1).not.toBeGreaterThanOrEqual(BigInt(Number.MAX_VALUE));
+ expect(Number.MAX_SAFE_INTEGER).toBeGreaterThanOrEqual(Number.MAX_SAFE_INTEGER);
+ expect(BigInt(Number.MAX_SAFE_INTEGER)).toBeGreaterThanOrEqual(BigInt(Number.MAX_SAFE_INTEGER));
+
+ expect(Infinity).toBeGreaterThanOrEqual(-Infinity);
+ expect(-Infinity).not.toBeGreaterThanOrEqual(Infinity);
+
+ expect(NaN).not.toBeGreaterThanOrEqual(NaN);
+ expect(NaN).not.toBeGreaterThanOrEqual(-Infinity);
+
+ expect(10).toBeGreaterThanOrEqual(9);
+ expect(10).toBeGreaterThanOrEqual(10);
+ expect(10).not.toBeGreaterThanOrEqual(11);
+ expect(10).not.toBeGreaterThanOrEqual(Infinity);
+ expect(10).toBeGreaterThanOrEqual(-Infinity);
+ expect(10).not.toBeGreaterThanOrEqual(NaN);
+ expect(10).toBeGreaterThanOrEqual(0);
+ expect(10).toBeGreaterThanOrEqual(-0);
+ expect(10).toBeGreaterThanOrEqual(0.1);
+ expect(10).toBeGreaterThanOrEqual(-0.1);
+ expect(10).toBeGreaterThanOrEqual(0.9);
+ expect(10).toBeGreaterThanOrEqual(-0.9);
+ expect(10).toBeGreaterThanOrEqual(1);
+ expect(10).toBeGreaterThanOrEqual(-1);
+ // switch the order
+ expect(9).not.toBeGreaterThanOrEqual(10);
+ expect(10).toBeGreaterThanOrEqual(10);
+ expect(11).toBeGreaterThanOrEqual(10);
+ expect(Infinity).toBeGreaterThanOrEqual(10);
+ expect(-Infinity).not.toBeGreaterThanOrEqual(10);
+ expect(NaN).not.toBeGreaterThanOrEqual(10);
+ expect(0).not.toBeGreaterThanOrEqual(10);
+ expect(-0).not.toBeGreaterThanOrEqual(10);
+ expect(0.1).not.toBeGreaterThanOrEqual(10);
+ expect(-0.1).not.toBeGreaterThanOrEqual(10);
+ expect(0.9).not.toBeGreaterThanOrEqual(10);
+ expect(-0.9).not.toBeGreaterThanOrEqual(10);
+ expect(1).not.toBeGreaterThanOrEqual(10);
+ expect(-1).not.toBeGreaterThanOrEqual(10);
+
+ // same tests but use bigints
+ expect(10n).toBeGreaterThanOrEqual(9n);
+ expect(10n).toBeGreaterThanOrEqual(10n);
+ expect(10n).not.toBeGreaterThanOrEqual(11n);
+ expect(10n).not.toBeGreaterThanOrEqual(Infinity);
+ expect(10n).toBeGreaterThanOrEqual(-Infinity);
+ expect(10n).not.toBeGreaterThanOrEqual(NaN);
+ expect(10n).toBeGreaterThanOrEqual(0n);
+ expect(10n).toBeGreaterThanOrEqual(-0n);
+ expect(10n).toBeGreaterThanOrEqual(1n);
+ expect(10n).toBeGreaterThanOrEqual(-1n);
+ // switch the order
+ expect(9n).not.toBeGreaterThanOrEqual(10n);
+ expect(10n).toBeGreaterThanOrEqual(10n);
+ expect(11n).toBeGreaterThanOrEqual(10n);
+ expect(Infinity).toBeGreaterThanOrEqual(10n);
+ expect(-Infinity).not.toBeGreaterThanOrEqual(10n);
+ expect(NaN).not.toBeGreaterThanOrEqual(10n);
+ expect(0n).not.toBeGreaterThanOrEqual(10n);
+ expect(-0n).not.toBeGreaterThanOrEqual(10n);
+ expect(1n).not.toBeGreaterThanOrEqual(10n);
+ expect(-1n).not.toBeGreaterThanOrEqual(10n);
+
+ // use bigints and numbers
+ expect(10n).toBeGreaterThanOrEqual(9);
+ expect(10n).toBeGreaterThanOrEqual(10);
+ expect(10n).not.toBeGreaterThanOrEqual(11);
+ expect(10n).not.toBeGreaterThanOrEqual(Infinity);
+ expect(10n).toBeGreaterThanOrEqual(-Infinity);
+ expect(10n).not.toBeGreaterThanOrEqual(NaN);
+ expect(10n).toBeGreaterThanOrEqual(0);
+ expect(10n).toBeGreaterThanOrEqual(-0);
+ expect(10n).toBeGreaterThanOrEqual(0.1);
+ expect(10n).toBeGreaterThanOrEqual(-0.1);
+ expect(10n).toBeGreaterThanOrEqual(0.9);
+ expect(10n).toBeGreaterThanOrEqual(-0.9);
+ expect(10n).toBeGreaterThanOrEqual(1);
+ expect(10n).toBeGreaterThanOrEqual(-1);
+ // switch the order
+ expect(9n).not.toBeGreaterThanOrEqual(10);
+ expect(10n).toBeGreaterThanOrEqual(10);
+ expect(11n).toBeGreaterThanOrEqual(10);
+ expect(Infinity).toBeGreaterThanOrEqual(10n);
+ expect(-Infinity).not.toBeGreaterThanOrEqual(10n);
+ expect(NaN).not.toBeGreaterThanOrEqual(10n);
+ expect(0n).not.toBeGreaterThanOrEqual(10);
+ expect(-0n).not.toBeGreaterThanOrEqual(10);
+ expect(1n).not.toBeGreaterThanOrEqual(10);
+ expect(-1n).not.toBeGreaterThanOrEqual(10);
+
+ expect(1n).toBeGreaterThanOrEqual(1);
+ expect(1n).not.toBeGreaterThanOrEqual(Number.MAX_SAFE_INTEGER);
+ expect(1n).not.toBeGreaterThanOrEqual(Number.MAX_VALUE);
+ expect(1).toBeGreaterThanOrEqual(1n);
+ expect(Number.MAX_SAFE_INTEGER).toBeGreaterThanOrEqual(1n);
+ expect(Number.MAX_VALUE).toBeGreaterThanOrEqual(1n);
+
+ expect(1).not.toBeGreaterThanOrEqual(BigInt(Number.MAX_VALUE));
+});
+
+test("toBeLessThan()", () => {
+ expect(3n).not.toBeLessThan(2);
+ expect(Number.MAX_VALUE).not.toBeLessThan(Number.MAX_VALUE);
+ expect(1).toBeLessThan(BigInt(Number.MAX_VALUE));
+ expect(1).toBeLessThan(Number.MAX_SAFE_INTEGER);
+ expect(1).toBeLessThan(BigInt(Number.MAX_SAFE_INTEGER));
+ expect(Number.MAX_SAFE_INTEGER).not.toBeLessThan(Number.MAX_SAFE_INTEGER);
+ expect(BigInt(Number.MAX_SAFE_INTEGER)).not.toBeLessThan(BigInt(Number.MAX_SAFE_INTEGER));
+
+ expect(Number.MAX_VALUE).not.toBeLessThan(BigInt(Number.MAX_VALUE));
+
+ expect(NaN).not.toBeLessThan(NaN);
+ expect(NaN).not.toBeLessThan(-Infinity);
+
+ expect(10).not.toBeLessThan(9);
+ expect(10).not.toBeLessThan(10);
+ expect(10).toBeLessThan(11);
+ expect(10).toBeLessThan(Infinity);
+ expect(10).not.toBeLessThan(-Infinity);
+ expect(10).not.toBeLessThan(NaN);
+ expect(10).not.toBeLessThan(0);
+ expect(10).not.toBeLessThan(-0);
+ expect(10).not.toBeLessThan(0.1);
+ expect(10).not.toBeLessThan(-0.1);
+ expect(10).not.toBeLessThan(0.9);
+ expect(10).not.toBeLessThan(-0.9);
+ expect(10).not.toBeLessThan(1);
+ expect(10).not.toBeLessThan(-1);
+ // switch the order
+ expect(9).toBeLessThan(10);
+ expect(10).not.toBeLessThan(10);
+ expect(11).not.toBeLessThan(10);
+ expect(Infinity).not.toBeLessThan(10);
+ expect(-Infinity).toBeLessThan(10);
+ expect(NaN).not.toBeLessThan(10);
+ expect(0).toBeLessThan(10);
+ expect(-0).toBeLessThan(10);
+ expect(0.1).toBeLessThan(10);
+ expect(-0.1).toBeLessThan(10);
+ expect(0.9).toBeLessThan(10);
+ expect(-0.9).toBeLessThan(10);
+ expect(1).toBeLessThan(10);
+ expect(-1).toBeLessThan(10);
+
+ // same tests but use bigints
+ expect(10n).not.toBeLessThan(9n);
+ expect(10n).not.toBeLessThan(10n);
+ expect(10n).toBeLessThan(11n);
+ expect(10n).toBeLessThan(Infinity);
+ expect(10n).not.toBeLessThan(-Infinity);
+ expect(10n).not.toBeLessThan(NaN);
+ expect(10n).not.toBeLessThan(0n);
+ expect(10n).not.toBeLessThan(-0n);
+ expect(10n).not.toBeLessThan(1n);
+ expect(10n).not.toBeLessThan(-1n);
+ // switch the order
+ expect(9n).toBeLessThan(10n);
+ expect(10n).not.toBeLessThan(10n);
+ expect(11n).not.toBeLessThan(10n);
+ expect(Infinity).not.toBeLessThan(10n);
+ expect(-Infinity).toBeLessThan(10n);
+ expect(NaN).not.toBeLessThan(10n);
+ expect(0n).toBeLessThan(10n);
+ expect(-0n).toBeLessThan(10n);
+ expect(1n).toBeLessThan(10n);
+ expect(-1n).toBeLessThan(10n);
+
+ // use bigints and numbers
+ expect(10n).not.toBeLessThan(9);
+ expect(10n).not.toBeLessThan(10);
+ expect(10n).toBeLessThan(11);
+ expect(10n).toBeLessThan(Infinity);
+ expect(10n).not.toBeLessThan(-Infinity);
+ expect(10n).not.toBeLessThan(NaN);
+ expect(10n).not.toBeLessThan(0);
+ expect(10n).not.toBeLessThan(-0);
+ expect(10n).not.toBeLessThan(0.1);
+ expect(10n).not.toBeLessThan(-0.1);
+ expect(10n).not.toBeLessThan(0.9);
+ expect(10n).not.toBeLessThan(-0.9);
+ expect(10n).not.toBeLessThan(1);
+ expect(10n).not.toBeLessThan(-1);
+ // switch the order
+ expect(9n).toBeLessThan(10);
+ expect(10n).not.toBeLessThan(10);
+ expect(11n).not.toBeLessThan(10);
+ expect(Infinity).not.toBeLessThan(10n);
+ expect(-Infinity).toBeLessThan(10n);
+ expect(NaN).not.toBeLessThan(10n);
+ expect(0n).toBeLessThan(10);
+ expect(-0n).toBeLessThan(10);
+ expect(1n).toBeLessThan(10);
+ expect(-1n).toBeLessThan(10);
+
+ expect(1n).not.toBeLessThan(1);
+ expect(1n).toBeLessThan(Number.MAX_SAFE_INTEGER);
+ expect(1n).toBeLessThan(Number.MAX_VALUE);
+ expect(1).not.toBeLessThan(1n);
+ expect(Number.MAX_SAFE_INTEGER).not.toBeLessThan(1n);
+ expect(Number.MAX_VALUE).not.toBeLessThan(1n);
+
+ expect(BigInt(Number.MAX_SAFE_INTEGER)).not.toBeLessThan(1n);
+ expect(BigInt(Number.MAX_VALUE)).not.toBeLessThan(1n);
+ expect(1n).toBeLessThan(BigInt(Number.MAX_SAFE_INTEGER));
+ expect(1n).toBeLessThan(BigInt(Number.MAX_VALUE));
+
+ expect(BigInt(Number.MAX_SAFE_INTEGER)).not.toBeLessThan(1);
+ expect(BigInt(Number.MAX_VALUE)).not.toBeLessThan(1);
+ expect(1).toBeLessThan(BigInt(Number.MAX_SAFE_INTEGER));
+});
+
+test("toBeLessThanOrEqual()", () => {
+ expect(3n).not.toBeLessThanOrEqual(2);
+ expect(Number.MAX_VALUE).toBeLessThanOrEqual(Number.MAX_VALUE);
+ expect(1).toBeLessThanOrEqual(BigInt(Number.MAX_VALUE));
+ expect(1).toBeLessThanOrEqual(Number.MAX_SAFE_INTEGER);
+ expect(1).toBeLessThanOrEqual(BigInt(Number.MAX_SAFE_INTEGER));
+ expect(Number.MAX_SAFE_INTEGER).toBeLessThanOrEqual(Number.MAX_SAFE_INTEGER);
+ expect(BigInt(Number.MAX_SAFE_INTEGER)).toBeLessThanOrEqual(BigInt(Number.MAX_SAFE_INTEGER));
+
+ expect(Number.MAX_VALUE).toBeLessThanOrEqual(BigInt(Number.MAX_VALUE));
+ expect(BigInt(Number.MAX_VALUE)).toBeLessThanOrEqual(Number.MAX_VALUE);
+
+ expect(NaN).not.toBeLessThanOrEqual(NaN);
+ expect(NaN).not.toBeLessThanOrEqual(-Infinity);
+
+ expect(10).not.toBeLessThanOrEqual(9);
+ expect(10).toBeLessThanOrEqual(10);
+ expect(10).toBeLessThanOrEqual(11);
+ expect(10).toBeLessThanOrEqual(Infinity);
+ expect(10).not.toBeLessThanOrEqual(-Infinity);
+ expect(10).not.toBeLessThanOrEqual(NaN);
+ expect(10).not.toBeLessThanOrEqual(0);
+ expect(10).not.toBeLessThanOrEqual(-0);
+ expect(10).not.toBeLessThanOrEqual(0.1);
+ expect(10).not.toBeLessThanOrEqual(-0.1);
+ expect(10).not.toBeLessThanOrEqual(0.9);
+ expect(10).not.toBeLessThanOrEqual(-0.9);
+ expect(10).not.toBeLessThanOrEqual(1);
+ expect(10).not.toBeLessThanOrEqual(-1);
+ // switch the order
+ expect(9).toBeLessThanOrEqual(10);
+ expect(10).toBeLessThanOrEqual(10);
+ expect(11).not.toBeLessThanOrEqual(10);
+ expect(Infinity).not.toBeLessThanOrEqual(10);
+ expect(-Infinity).toBeLessThanOrEqual(10);
+ expect(NaN).not.toBeLessThanOrEqual(10);
+ expect(0).toBeLessThanOrEqual(10);
+ expect(-0).toBeLessThanOrEqual(10);
+ expect(0.1).toBeLessThanOrEqual(10);
+ expect(-0.1).toBeLessThanOrEqual(10);
+ expect(0.9).toBeLessThanOrEqual(10);
+ expect(-0.9).toBeLessThanOrEqual(10);
+ expect(1).toBeLessThanOrEqual(10);
+ expect(-1).toBeLessThanOrEqual(10);
+
+ // same tests but use bigints
+ expect(10n).not.toBeLessThanOrEqual(9n);
+ expect(10n).toBeLessThanOrEqual(10n);
+ expect(10n).toBeLessThanOrEqual(11n);
+ expect(10n).toBeLessThanOrEqual(Infinity);
+ expect(10n).not.toBeLessThanOrEqual(-Infinity);
+ expect(10n).not.toBeLessThanOrEqual(NaN);
+ expect(10n).not.toBeLessThanOrEqual(0n);
+ expect(10n).not.toBeLessThanOrEqual(-0n);
+ expect(10n).not.toBeLessThanOrEqual(1n);
+ expect(10n).not.toBeLessThanOrEqual(-1n);
+ // switch the order
+ expect(9n).toBeLessThanOrEqual(10n);
+ expect(10n).toBeLessThanOrEqual(10n);
+ expect(11n).not.toBeLessThanOrEqual(10n);
+ expect(Infinity).not.toBeLessThanOrEqual(10n);
+ expect(-Infinity).toBeLessThanOrEqual(10n);
+ expect(NaN).not.toBeLessThanOrEqual(10n);
+ expect(0n).toBeLessThanOrEqual(10n);
+ expect(-0n).toBeLessThanOrEqual(10n);
+ expect(1n).toBeLessThanOrEqual(10n);
+ expect(-1n).toBeLessThanOrEqual(10n);
+
+ // use bigints and numbers
+ expect(10n).not.toBeLessThanOrEqual(9);
+ expect(10n).toBeLessThanOrEqual(10);
+ expect(10n).toBeLessThanOrEqual(11);
+ expect(10n).toBeLessThanOrEqual(Infinity);
+ expect(10n).not.toBeLessThanOrEqual(-Infinity);
+ expect(10n).not.toBeLessThanOrEqual(NaN);
+ expect(10n).not.toBeLessThanOrEqual(0);
+ expect(10n).not.toBeLessThanOrEqual(-0);
+ expect(10n).not.toBeLessThanOrEqual(0.1);
+ expect(10n).not.toBeLessThanOrEqual(-0.1);
+ expect(10n).not.toBeLessThanOrEqual(0.9);
+ expect(10n).not.toBeLessThanOrEqual(-0.9);
+ expect(10n).not.toBeLessThanOrEqual(1);
+ expect(10n).not.toBeLessThanOrEqual(-1);
+ // switch the order
+ expect(9n).toBeLessThanOrEqual(10);
+ expect(10n).toBeLessThanOrEqual(10);
+ expect(11n).not.toBeLessThanOrEqual(10);
+ expect(Infinity).not.toBeLessThanOrEqual(10n);
+ expect(-Infinity).toBeLessThanOrEqual(10n);
+ expect(NaN).not.toBeLessThanOrEqual(10n);
+ expect(0n).toBeLessThanOrEqual(10);
+ expect(-0n).toBeLessThanOrEqual(10);
+ expect(1n).toBeLessThanOrEqual(10);
+ expect(-1n).toBeLessThanOrEqual(10);
+
+ expect(1n).toBeLessThanOrEqual(1);
+ expect(1n).toBeLessThanOrEqual(Number.MAX_SAFE_INTEGER);
+ expect(1n).toBeLessThanOrEqual(Number.MAX_VALUE);
+ expect(1).toBeLessThanOrEqual(1n);
+ expect(Number.MAX_SAFE_INTEGER).not.toBeLessThanOrEqual(1n);
+ expect(Number.MAX_VALUE).not.toBeLessThanOrEqual(1n);
+
+ expect(BigInt(Number.MAX_SAFE_INTEGER)).not.toBeLessThanOrEqual(1n);
+ expect(BigInt(Number.MAX_VALUE)).not.toBeLessThanOrEqual(1n);
+ expect(1n).toBeLessThanOrEqual(BigInt(Number.MAX_SAFE_INTEGER));
+ expect(1n).toBeLessThanOrEqual(BigInt(Number.MAX_VALUE));
+
+ expect(BigInt(Number.MAX_SAFE_INTEGER)).not.toBeLessThanOrEqual(1);
+ expect(BigInt(Number.MAX_VALUE)).not.toBeLessThanOrEqual(1);
+ expect(1).toBeLessThanOrEqual(BigInt(Number.MAX_SAFE_INTEGER));
+});
+
+try {
+ test("test this doesnt crash");
+} catch (e) {}
+
+try {
+ test();
+} catch (e) {}
+
+describe("throw in describe scope doesn't enqueue tests after thrown", () => {
+ it("test enqueued before a describe scope throws is never run", () => {
+ throw new Error("This test failed");
+ });
+
+ class TestPass extends Error {
+ constructor(message) {
+ super(message);
+ this.name = "TestPass";
+ }
+ }
+
+ throw new TestPass("This test passed. Ignore the error message");
+
+ it("test enqueued after a describe scope throws is never run", () => {
+ throw new Error("This test failed");
+ });
+});
+
+it("a describe scope throwing doesn't cause all other tests in the file to fail", () => {
+ expect(true).toBe(true);
+});
+
+test("test async exceptions fail tests", () => {
+ const code = `
+ import {test, expect} from 'bun:test';
+ import {EventEmitter} from 'events';
+ test('test throwing inside an EventEmitter fails the test', () => {
+ const emitter = new EventEmitter();
+ emitter.on('event', () => {
+ throw new Error('test throwing inside an EventEmitter #FAIL001');
+ });
+ emitter.emit('event');
+ });
+
+ test('test throwing inside a queueMicrotask callback fails', async () => {
+
+ queueMicrotask(() => {
+ throw new Error('test throwing inside an EventEmitter #FAIL002');
+ });
+
+ await 1;
+ });
+
+ test('test throwing inside a process.nextTick callback fails', async () => {
+
+ process.nextTick(() => {
+ throw new Error('test throwing inside an EventEmitter #FAIL003');
+ });
+
+ await 1;
+ });
+
+ test('test throwing inside a setTimeout', async () => {
+ await new Promise((resolve, reject) => {
+ setTimeout(() => {
+ resolve();
+ throw new Error('test throwing inside an EventEmitter #FAIL004');
+ }, 0);
+ });
+ });
+
+ test('test throwing inside an async setTimeout', async () => {
+ await new Promise((resolve, reject) => {
+ setTimeout(async () => {
+ await 1;
+ resolve();
+ throw new Error('test throwing inside an EventEmitter #FAIL005');
+ }, 0);
+ });
+ });
+
+
+ test('test throwing inside an async setTimeout no await' , async () => {
+ await new Promise((resolve, reject) => {
+ setTimeout(async () => {
+ resolve();
+ throw new Error('test throwing inside an EventEmitter #FAIL006');
+ }, 0);
+ });
+ });
+
+ `;
+
+ rmSync("/tmp/test-throwing-bun/test-throwing-eventemitter.test.js", {
+ force: true,
+ });
+
+ try {
+ mkdirSync("/tmp/test-throwing-bun", { recursive: true });
+ } catch (e) {}
+ writeFileSync("/tmp/test-throwing-bun/test-throwing-eventemitter.test.js", code);
+
+ const { stderr, exitCode } = spawnSync([bunExe(), "wiptest", "test-throwing-eventemitter"], {
+ cwd: realpathSync("/tmp/test-throwing-bun"),
+ env: bunEnv,
+ });
+
+ const str = stderr!.toString();
+ expect(str).toContain("#FAIL001");
+ expect(str).toContain("#FAIL002");
+ expect(str).toContain("#FAIL003");
+ expect(str).toContain("#FAIL004");
+ expect(str).toContain("#FAIL005");
+ expect(str).toContain("#FAIL006");
+ expect(str).toContain("6 fail");
+ expect(str).toContain("0 pass");
+
+ expect(exitCode).toBe(1);
+});
+
+it("should return non-zero exit code for invalid syntax", async () => {
+ const test_dir = realpathSync(await mkdtemp(join(tmpdir(), "test")));
+ try {
+ await writeFile(join(test_dir, "bad.test.js"), "!!!");
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "test", "bad.test.js"],
+ cwd: test_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env: bunEnv,
+ });
+ const err = await new Response(stderr).text();
+ expect(err).toContain("error: Unexpected end of file");
+ expect(err).toContain(" 0 pass");
+ expect(err).toContain(" 1 fail");
+ expect(err).toContain("Ran 1 tests across 1 files");
+ expect(stdout).toBeDefined();
+ expect(await new Response(stdout).text()).toBe("");
+ expect(await exited).toBe(1);
+ } finally {
+ await rm(test_dir, { force: true, recursive: true });
+ }
+});
diff --git a/test/js/bun/util/arraybuffersink.test.ts b/test/js/bun/util/arraybuffersink.test.ts
new file mode 100644
index 000000000..373ac1116
--- /dev/null
+++ b/test/js/bun/util/arraybuffersink.test.ts
@@ -0,0 +1,64 @@
+import { ArrayBufferSink } from "bun";
+import { describe, expect, it } from "bun:test";
+import { withoutAggressiveGC } from "harness";
+
+describe("ArrayBufferSink", () => {
+ const fixtures = [
+ [
+ ["abcdefghijklmnopqrstuvwxyz"],
+ new TextEncoder().encode("abcdefghijklmnopqrstuvwxyz"),
+ "abcdefghijklmnopqrstuvwxyz",
+ ],
+ [
+ ["abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ"],
+ new TextEncoder().encode("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"),
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
+ ],
+ [
+ ["😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"],
+ new TextEncoder().encode("😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"),
+ "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌",
+ ],
+ [
+ ["abcdefghijklmnopqrstuvwxyz", "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"],
+ new TextEncoder().encode("abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"),
+ "abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌",
+ ],
+ [
+ ["abcdefghijklmnopqrstuvwxyz", "😋", " Get Emoji — All Emojis", " to ✂️ Copy and 📋 Paste 👌"],
+ new TextEncoder().encode("abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"),
+ "(rope) " + "abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌",
+ ],
+ [
+ [
+ new TextEncoder().encode("abcdefghijklmnopqrstuvwxyz"),
+ "😋",
+ " Get Emoji — All Emojis",
+ " to ✂️ Copy and 📋 Paste 👌",
+ ],
+ new TextEncoder().encode("abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"),
+ "(array) " + "abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌",
+ ],
+ ] as const;
+
+ for (const [input, expected, label] of fixtures) {
+ it(`${JSON.stringify(label)}`, () => {
+ const sink = new ArrayBufferSink();
+ withoutAggressiveGC(() => {
+ for (let i = 0; i < input.length; i++) {
+ const el = input[i];
+ if (typeof el !== "number") {
+ sink.write(el);
+ }
+ }
+ });
+ const output = new Uint8Array(sink.end());
+ withoutAggressiveGC(() => {
+ for (let i = 0; i < expected.length; i++) {
+ expect(output[i]).toBe(expected[i]);
+ }
+ });
+ expect(output.byteLength).toBe(expected.byteLength);
+ });
+ }
+});
diff --git a/test/js/bun/util/concat.test.js b/test/js/bun/util/concat.test.js
new file mode 100644
index 000000000..0cea303fe
--- /dev/null
+++ b/test/js/bun/util/concat.test.js
@@ -0,0 +1,43 @@
+import { describe, it, expect } from "bun:test";
+import { concatArrayBuffers } from "bun";
+
+describe("concat", () => {
+ function polyfill(chunks) {
+ var size = 0;
+ for (const chunk of chunks) {
+ size += chunk.byteLength;
+ }
+ var buffer = new ArrayBuffer(size);
+ var view = new Uint8Array(buffer);
+ var offset = 0;
+ for (const chunk of chunks) {
+ view.set(chunk, offset);
+ offset += chunk.byteLength;
+ }
+ return buffer;
+ }
+
+ function concatToString(chunks) {
+ return Array.from(new Uint8Array(concatArrayBuffers(chunks))).join("");
+ }
+
+ function polyfillToString(chunks) {
+ return Array.from(new Uint8Array(polyfill(chunks))).join("");
+ }
+
+ it("works with one element", () => {
+ expect(concatToString([new Uint8Array([123])])).toBe(polyfillToString([new Uint8Array([123])]));
+ });
+
+ it("works with two elements", () => {
+ expect(concatToString([Uint8Array.from([123]), Uint8Array.from([456])])).toBe(
+ polyfillToString([Uint8Array.from([123]), Uint8Array.from([456])]),
+ );
+ });
+
+ it("works with mix of ArrayBuffer and TypedArray elements", () => {
+ expect(concatToString([Uint8Array.from([123]).buffer, Uint8Array.from([456])])).toBe(
+ polyfillToString([Uint8Array.from([123]), Uint8Array.from([456])]),
+ );
+ });
+});
diff --git a/test/js/bun/util/empty.js b/test/js/bun/util/empty.js
new file mode 100644
index 000000000..cb0ff5c3b
--- /dev/null
+++ b/test/js/bun/util/empty.js
@@ -0,0 +1 @@
+export {};
diff --git a/test/js/bun/util/escapeHTML.test.js b/test/js/bun/util/escapeHTML.test.js
new file mode 100644
index 000000000..bdddf4a45
--- /dev/null
+++ b/test/js/bun/util/escapeHTML.test.js
@@ -0,0 +1,91 @@
+import { describe, it, expect } from "bun:test";
+import { escapeHTML } from "bun";
+
+describe("escapeHTML", () => {
+ // The matrix of cases we need to test for:
+ // 1. Works with short strings
+ // 2. Works with long strings
+ // 3. Works with latin1 strings
+ // 4. Works with utf16 strings
+ // 5. Works when the text to escape is somewhere in the middle
+ // 6. Works when the text to escape is in the beginning
+ // 7. Works when the text to escape is in the end
+ // 8. Returns the same string when there's no need to escape
+ it("works", () => {
+ expect(escapeHTML("absolutely nothing to do here")).toBe("absolutely nothing to do here");
+ expect(escapeHTML("<script>alert(1)</script>")).toBe("&lt;script&gt;alert(1)&lt;/script&gt;");
+ expect(escapeHTML("<")).toBe("&lt;");
+ expect(escapeHTML(">")).toBe("&gt;");
+ expect(escapeHTML("&")).toBe("&amp;");
+ expect(escapeHTML("'")).toBe("&#x27;");
+ expect(escapeHTML('"')).toBe("&quot;");
+ expect(escapeHTML("\n")).toBe("\n");
+ expect(escapeHTML("\r")).toBe("\r");
+ expect(escapeHTML("\t")).toBe("\t");
+ expect(escapeHTML("\f")).toBe("\f");
+ expect(escapeHTML("\v")).toBe("\v");
+ expect(escapeHTML("\b")).toBe("\b");
+ expect(escapeHTML("\u00A0")).toBe("\u00A0");
+ expect(escapeHTML("<script>ab")).toBe("&lt;script&gt;ab");
+ expect(escapeHTML("<script>")).toBe("&lt;script&gt;");
+ expect(escapeHTML("<script><script>")).toBe("&lt;script&gt;&lt;script&gt;");
+
+ expect(escapeHTML("lalala" + "<script>alert(1)</script>" + "lalala")).toBe(
+ "lalala&lt;script&gt;alert(1)&lt;/script&gt;lalala",
+ );
+
+ expect(escapeHTML("<script>alert(1)</script>" + "lalala")).toBe("&lt;script&gt;alert(1)&lt;/script&gt;lalala");
+ expect(escapeHTML("lalala" + "<script>alert(1)</script>")).toBe("lalala" + "&lt;script&gt;alert(1)&lt;/script&gt;");
+
+ expect(escapeHTML("What does 😊 mean?")).toBe("What does 😊 mean?");
+ const output = escapeHTML("<What does 😊");
+ expect(output).toBe("&lt;What does 😊");
+ expect(escapeHTML("<div>What does 😊 mean in text?")).toBe("&lt;div&gt;What does 😊 mean in text?");
+
+ expect(escapeHTML(("lalala" + "<script>alert(1)</script>" + "lalala").repeat(900))).toBe(
+ "lalala&lt;script&gt;alert(1)&lt;/script&gt;lalala".repeat(900),
+ );
+ expect(escapeHTML(("<script>alert(1)</script>" + "lalala").repeat(900))).toBe(
+ "&lt;script&gt;alert(1)&lt;/script&gt;lalala".repeat(900),
+ );
+ expect(escapeHTML(("lalala" + "<script>alert(1)</script>").repeat(900))).toBe(
+ ("lalala" + "&lt;script&gt;alert(1)&lt;/script&gt;").repeat(900),
+ );
+
+ // the positions of the unicode codepoint are important
+ // our simd code for U16 is at 8 bytes, so we need to especially check the boundaries
+ expect(escapeHTML("😊lalala" + "<script>alert(1)</script>" + "lalala")).toBe(
+ "😊lalala&lt;script&gt;alert(1)&lt;/script&gt;lalala",
+ );
+ expect(escapeHTML("<script>😊alert(1)</script>" + "lalala")).toBe("&lt;script&gt;😊alert(1)&lt;/script&gt;lalala");
+ expect(escapeHTML("<script>alert(1)😊</script>" + "lalala")).toBe("&lt;script&gt;alert(1)😊&lt;/script&gt;lalala");
+ expect(escapeHTML("<script>alert(1)</script>" + "😊lalala")).toBe("&lt;script&gt;alert(1)&lt;/script&gt;😊lalala");
+ expect(escapeHTML("<script>alert(1)</script>" + "lal😊ala")).toBe("&lt;script&gt;alert(1)&lt;/script&gt;lal😊ala");
+ expect(escapeHTML("<script>alert(1)</script>" + "lal😊ala".repeat(10))).toBe(
+ "&lt;script&gt;alert(1)&lt;/script&gt;" + "lal😊ala".repeat(10),
+ );
+
+ for (let i = 1; i < 10; i++)
+ expect(escapeHTML("<script>alert(1)</script>" + "la😊".repeat(i))).toBe(
+ "&lt;script&gt;alert(1)&lt;/script&gt;" + "la😊".repeat(i),
+ );
+
+ expect(escapeHTML("la😊" + "<script>alert(1)</script>")).toBe("la😊" + "&lt;script&gt;alert(1)&lt;/script&gt;");
+ expect(escapeHTML(("lalala" + "<script>alert(1)</script>😊").repeat(1))).toBe(
+ ("lalala" + "&lt;script&gt;alert(1)&lt;/script&gt;😊").repeat(1),
+ );
+
+ expect(escapeHTML("😊".repeat(100))).toBe("😊".repeat(100));
+ expect(escapeHTML("😊<".repeat(100))).toBe("😊&lt;".repeat(100));
+ expect(escapeHTML("<😊>".repeat(100))).toBe("&lt;😊&gt;".repeat(100));
+ expect(escapeHTML("😊")).toBe("😊");
+ expect(escapeHTML("😊😊")).toBe("😊😊");
+ expect(escapeHTML("😊lo")).toBe("😊lo");
+ expect(escapeHTML("lo😊")).toBe("lo😊");
+
+ expect(escapeHTML(" ".repeat(32) + "😊")).toBe(" ".repeat(32) + "😊");
+ expect(escapeHTML(" ".repeat(32) + "😊😊")).toBe(" ".repeat(32) + "😊😊");
+ expect(escapeHTML(" ".repeat(32) + "😊lo")).toBe(" ".repeat(32) + "😊lo");
+ expect(escapeHTML(" ".repeat(32) + "lo😊")).toBe(" ".repeat(32) + "lo😊");
+ });
+});
diff --git a/test/js/bun/util/fileUrl.test.js b/test/js/bun/util/fileUrl.test.js
new file mode 100644
index 000000000..ebae570f8
--- /dev/null
+++ b/test/js/bun/util/fileUrl.test.js
@@ -0,0 +1,16 @@
+import { expect, it, describe } from "bun:test";
+import { pathToFileURL, fileURLToPath } from "bun";
+describe("pathToFileURL", () => {
+ it("should convert a path to a file url", () => {
+ expect(pathToFileURL("/path/to/file.js").href).toBe("file:///path/to/file.js");
+ });
+});
+
+describe("fileURLToPath", () => {
+ it("should convert a file url to a path", () => {
+ expect(fileURLToPath("file:///path/to/file.js")).toBe("/path/to/file.js");
+ });
+ it("should convert a URL to a path", () => {
+ expect(fileURLToPath(new URL("file:///path/to/file.js"))).toBe("/path/to/file.js");
+ });
+});
diff --git a/test/js/bun/util/filesink.test.ts b/test/js/bun/util/filesink.test.ts
new file mode 100644
index 000000000..31fd70e54
--- /dev/null
+++ b/test/js/bun/util/filesink.test.ts
@@ -0,0 +1,141 @@
+import { ArrayBufferSink } from "bun";
+import { describe, expect, it } from "bun:test";
+import { mkfifo } from "mkfifo";
+
+describe("FileSink", () => {
+ const fixtures = [
+ [
+ ["abcdefghijklmnopqrstuvwxyz"],
+ new TextEncoder().encode("abcdefghijklmnopqrstuvwxyz"),
+ "abcdefghijklmnopqrstuvwxyz",
+ ],
+ [
+ ["abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ"],
+ new TextEncoder().encode("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"),
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
+ ],
+ [
+ ["😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"],
+ new TextEncoder().encode("😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"),
+ "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌",
+ ],
+ [
+ ["abcdefghijklmnopqrstuvwxyz", "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"],
+ new TextEncoder().encode("abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"),
+ "abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌",
+ ],
+ [
+ ["abcdefghijklmnopqrstuvwxyz", "😋", " Get Emoji — All Emojis", " to ✂️ Copy and 📋 Paste 👌"],
+ new TextEncoder().encode("abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"),
+ "(rope) " + "abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌",
+ ],
+ [
+ [
+ new TextEncoder().encode("abcdefghijklmnopqrstuvwxyz"),
+ "😋",
+ " Get Emoji — All Emojis",
+ " to ✂️ Copy and 📋 Paste 👌",
+ ],
+ new TextEncoder().encode("abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"),
+ "(array) " + "abcdefghijklmnopqrstuvwxyz" + "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌",
+ ],
+ ] as const;
+
+ function getPath(label) {
+ const path = `/tmp/bun-test-${Bun.hash(label).toString(10)}.txt`;
+ try {
+ require("fs").unlinkSync(path);
+ } catch (e) {}
+ return path;
+ }
+
+ var activeFIFO: Promise<string>;
+ var decoder = new TextDecoder();
+
+ function getFd(label) {
+ const path = `/tmp/bun-test-${Bun.hash(label).toString(10)}.txt`;
+ try {
+ require("fs").unlinkSync(path);
+ } catch (e) {}
+ mkfifo(path, 0o666);
+ activeFIFO = (async function (stream: ReadableStream<Uint8Array>) {
+ var chunks: Uint8Array[] = [];
+ for await (const chunk of stream) {
+ chunks.push(chunk);
+ }
+ return Buffer.concat(chunks).toString();
+ // test it on a small chunk size
+ })(Bun.file(path).stream(64));
+ return path;
+ }
+
+ for (let isPipe of [true, false] as const) {
+ describe(isPipe ? "pipe" : "file", () => {
+ for (const [input, expected, label] of fixtures) {
+ var getPathOrFd = () => (isPipe ? getFd(label) : getPath(label));
+
+ it(`${JSON.stringify(label)}`, async () => {
+ const path = getPathOrFd();
+ const sink = Bun.file(path).writer();
+ for (let i = 0; i < input.length; i++) {
+ sink.write(input[i]);
+ }
+ await sink.end();
+
+ if (!isPipe) {
+ const output = new Uint8Array(await Bun.file(path).arrayBuffer());
+ for (let i = 0; i < expected.length; i++) {
+ expect(output[i]).toBe(expected[i]);
+ }
+ expect(output.byteLength).toBe(expected.byteLength);
+ } else {
+ console.log("reading");
+ const output = await activeFIFO;
+ expect(output).toBe(decoder.decode(expected));
+ }
+ });
+
+ it(`flushing -> ${JSON.stringify(label)}`, async () => {
+ const path = getPathOrFd();
+ const sink = Bun.file(path).writer();
+ for (let i = 0; i < input.length; i++) {
+ sink.write(input[i]);
+ await sink.flush();
+ }
+ await sink.end();
+ if (!isPipe) {
+ const output = new Uint8Array(await Bun.file(path).arrayBuffer());
+ for (let i = 0; i < expected.length; i++) {
+ expect(output[i]).toBe(expected[i]);
+ }
+ expect(output.byteLength).toBe(expected.byteLength);
+ } else {
+ const output = await activeFIFO;
+ expect(output).toBe(decoder.decode(expected));
+ }
+ });
+
+ it(`highWaterMark -> ${JSON.stringify(label)}`, async () => {
+ const path = getPathOrFd();
+ const sink = Bun.file(path).writer({ highWaterMark: 1 });
+ for (let i = 0; i < input.length; i++) {
+ sink.write(input[i]);
+ await sink.flush();
+ }
+ await sink.end();
+
+ if (!isPipe) {
+ const output = new Uint8Array(await Bun.file(path).arrayBuffer());
+ for (let i = 0; i < expected.length; i++) {
+ expect(output[i]).toBe(expected[i]);
+ }
+ expect(output.byteLength).toBe(expected.byteLength);
+ } else {
+ const output = await activeFIFO;
+ expect(output).toBe(decoder.decode(expected));
+ }
+ });
+ }
+ });
+ }
+});
diff --git a/test/js/bun/util/filesystem_router.test.ts b/test/js/bun/util/filesystem_router.test.ts
new file mode 100644
index 000000000..b55e716c0
--- /dev/null
+++ b/test/js/bun/util/filesystem_router.test.ts
@@ -0,0 +1,354 @@
+import { FileSystemRouter } from "bun";
+import { it, expect } from "bun:test";
+import path, { dirname, resolve } from "path";
+import fs, { mkdirSync, realpathSync, rmSync } from "fs";
+import { tmpdir } from "os";
+const tempdir = realpathSync(tmpdir()) + "/";
+
+function createTree(basedir, paths) {
+ for (const end of paths) {
+ const abs = path.join(basedir, end);
+ try {
+ const dir = dirname(abs);
+ if (dir.length > 0 && dir !== "/") fs.mkdirSync(dir, { recursive: true });
+ } catch (e) {}
+ fs.writeFileSync(abs, "export default " + JSON.stringify(end) + ";\n");
+ }
+}
+var count = 0;
+function make(files) {
+ const dir = tempdir + `fs-router-test-${count++}`;
+ rmSync(dir, {
+ recursive: true,
+ force: true,
+ });
+
+ createTree(dir, files);
+ if (files.length === 0) mkdirSync(dir, { recursive: true });
+ return {
+ dir,
+ };
+}
+
+it("should find files", () => {
+ const { dir } = make([
+ `index.tsx`,
+ `[id].tsx`,
+ `a.tsx`,
+ `abc/index.tsx`,
+ `abc/[id].tsx`,
+ `abc/def/[id].tsx`,
+ `abc/def/ghi/index.tsx`,
+ `abc/def/ghi/[id].tsx`,
+ `abc/def/ghi/jkl/index.tsx`,
+ `abc/def/ghi/jkl/[id].tsx`,
+ `abc/def/index.tsx`,
+ `b.tsx`,
+ `foo/[id].tsx`,
+ `catch-all/[[...id]].tsx`,
+ ]);
+
+ const router = new FileSystemRouter({
+ dir,
+ fileExtensions: [".tsx"],
+ style: "nextjs",
+ });
+
+ const routes = router.routes;
+ const fixture = {
+ "/": `${dir}/index.tsx`,
+ "/[id]": `${dir}/[id].tsx`,
+ "/a": `${dir}/a.tsx`,
+ "/abc": `${dir}/abc/index.tsx`,
+ "/abc/[id]": `${dir}/abc/[id].tsx`,
+ "/abc/def/[id]": `${dir}/abc/def/[id].tsx`,
+ "/abc/def/ghi": `${dir}/abc/def/ghi/index.tsx`,
+ "/abc/def/ghi/[id]": `${dir}/abc/def/ghi/[id].tsx`,
+ "/abc/def/ghi/jkl": `${dir}/abc/def/ghi/jkl/index.tsx`,
+ "/abc/def/ghi/jkl/[id]": `${dir}/abc/def/ghi/jkl/[id].tsx`,
+ "/abc/def": `${dir}/abc/def/index.tsx`,
+ "/b": `${dir}/b.tsx`,
+ "/foo/[id]": `${dir}/foo/[id].tsx`,
+ "/catch-all/[[...id]]": `${dir}/catch-all/[[...id]].tsx`,
+ };
+
+ for (const route in fixture) {
+ if (!(route in routes)) {
+ throw new Error(`Route ${route} not found`);
+ }
+
+ expect(routes[route]).toBe(fixture[route]);
+ }
+
+ expect(Object.keys(routes).length).toBe(Object.keys(fixture).length);
+ expect(Object.values(routes).length).toBe(Object.values(fixture).length);
+});
+
+it("should handle empty dirs", () => {
+ const { dir } = make([]);
+
+ const router = new FileSystemRouter({
+ dir,
+ fileExtensions: [".tsx"],
+ style: "nextjs",
+ });
+
+ // assert this doesn't crash
+ expect(router.bar).toBeUndefined();
+
+ const routes = router.routes;
+ expect(Object.keys(routes).length).toBe(0);
+ expect(Object.values(routes).length).toBe(0);
+});
+
+it("should match dynamic routes", () => {
+ // set up the test
+ const { dir } = make(["index.tsx", "posts/[id].tsx", "posts.tsx"]);
+
+ const router = new Bun.FileSystemRouter({
+ dir,
+ style: "nextjs",
+ });
+
+ const { name, filePath } = router.match("/posts/hello-world");
+
+ expect(name).toBe("/posts/[id]");
+ expect(filePath).toBe(`${dir}/posts/[id].tsx`);
+});
+
+it(".params works on dynamic routes", () => {
+ // set up the test
+ const { dir } = make(["index.tsx", "posts/[id].tsx", "posts.tsx"]);
+
+ const router = new Bun.FileSystemRouter({
+ dir,
+ style: "nextjs",
+ });
+
+ const {
+ params: { id },
+ } = router.match("/posts/hello-world");
+
+ expect(id).toBe("hello-world");
+});
+
+it("should support static routes", () => {
+ // set up the test
+ const { dir } = make(["index.tsx", "posts/[id].tsx", "posts.tsx", "posts/hey.tsx"]);
+
+ const router = new Bun.FileSystemRouter({
+ dir,
+ style: "nextjs",
+ });
+
+ const { name, params, filePath } = router.match("/posts/hey");
+
+ expect(name).toBe("/posts/hey");
+ expect(filePath).toBe(`${dir}/posts/hey.tsx`);
+});
+
+it("should support optional catch-all routes", () => {
+ // set up the test
+ const { dir } = make(["index.tsx", "posts/[id].tsx", "posts.tsx", "posts/hey.tsx", "posts/[[...id]].tsx"]);
+
+ const router = new Bun.FileSystemRouter({
+ dir,
+ style: "nextjs",
+ });
+
+ for (let fixture of ["/posts/123", "/posts/hey", "/posts/zorp", "/posts", "/index", "/posts/"]) {
+ expect(router.match(fixture)?.name).not.toBe("/posts/[[...id]]");
+ }
+
+ for (let fixture of ["/posts/hey/there", "/posts/hey/there/you", "/posts/zorp/123"]) {
+ const { name, params, filePath } = router.match(fixture);
+
+ expect(name).toBe("/posts/[[...id]]");
+ expect(filePath).toBe(`${dir}/posts/[[...id]].tsx`);
+ expect(params.id).toBe(fixture.split("/").slice(2).join("/"));
+ }
+});
+
+it("should support catch-all routes", () => {
+ // set up the test
+ const { dir } = make([
+ "index.tsx",
+ "posts/[id].tsx",
+ "posts.tsx",
+ "posts/hey.tsx",
+ "posts/[...id].tsx",
+ "posts/wow/[[...id]].tsx",
+ ]);
+
+ const router = new Bun.FileSystemRouter({
+ dir,
+ style: "nextjs",
+ });
+
+ for (let fixture of ["/posts/123", "/posts/hey", "/posts/zorp", "/posts", "/index", "/posts/"]) {
+ expect(router.match(fixture)?.name).not.toBe("/posts/[...id]");
+ }
+
+ for (let fixture of ["/posts/hey/there", "/posts/hey/there/you", "/posts/zorp/123", "/posts/wow/hey/there"]) {
+ const { name, params, filePath } = router.match(fixture);
+
+ expect(name).toBe("/posts/[...id]");
+ expect(filePath).toBe(`${dir}/posts/[...id].tsx`);
+ expect(params.id).toBe(fixture.split("/").slice(2).join("/"));
+ }
+});
+
+it("should support index routes", () => {
+ // set up the test
+ const { dir } = make(["index.tsx", "posts/[id].tsx", "posts.tsx", "posts/hey.tsx"]);
+
+ const router = new Bun.FileSystemRouter({
+ dir,
+ style: "nextjs",
+ });
+
+ for (let route of ["/", "/index"]) {
+ const { name, params, filePath } = router.match(route);
+
+ expect(name).toBe("/");
+ expect(filePath).toBe(`${dir}/index.tsx`);
+ expect(Object.keys(params).length).toBe(0);
+ }
+
+ for (let route of ["/posts", "/posts/index", "/posts/"]) {
+ const { name, params, filePath } = router.match(route);
+
+ expect(name).toBe("/posts");
+ expect(filePath).toBe(`${dir}/posts.tsx`);
+ expect(Object.keys(params).length).toBe(0);
+ }
+});
+
+it("should support Request", async () => {
+ // set up the test
+ const { dir } = make(["index.tsx", "posts/[id].tsx", "posts.tsx"]);
+
+ const router = new Bun.FileSystemRouter({
+ dir,
+ style: "nextjs",
+ });
+
+ for (let current of [
+ new Request({ url: "/posts/hello-world" }),
+ new Request({ url: "http://example.com/posts/hello-world" }),
+ ]) {
+ const {
+ name,
+ params: { id },
+ filePath,
+ } = router.match(current);
+ expect(name).toBe("/posts/[id]");
+ expect(filePath).toBe(`${dir}/posts/[id].tsx`);
+ expect(id).toBe("hello-world");
+ }
+});
+
+it("assetPrefix, src, and origin", async () => {
+ // set up the test
+ const { dir } = make(["index.tsx", "posts/[id].tsx", "posts.tsx"]);
+
+ const router = new Bun.FileSystemRouter({
+ dir,
+ style: "nextjs",
+ assetPrefix: "/_next/static/",
+ origin: "https://nextjs.org",
+ });
+
+ for (let current of [
+ // Reuqest
+ new Request({ url: "/posts/hello-world" }),
+ new Request({ url: "https://nextjs.org/posts/hello-world" }),
+ ]) {
+ const { name, src, filePath, checkThisDoesntCrash } = router.match(current);
+ expect(name).toBe("/posts/[id]");
+
+ // check nothing is weird on the MatchedRoute object
+ expect(checkThisDoesntCrash).toBeUndefined();
+
+ expect(src).toBe("https://nextjs.org/_next/static/posts/[id].tsx");
+ expect(filePath).toBe(`${dir}/posts/[id].tsx`);
+ }
+});
+
+it(".query works", () => {
+ // set up the test
+ const { dir } = make(["posts.tsx"]);
+
+ const router = new Bun.FileSystemRouter({
+ dir,
+ style: "nextjs",
+ assetPrefix: "/_next/static/",
+ origin: "https://nextjs.org",
+ });
+
+ for (let [current, object] of [
+ [new URL("https://example.com/posts?hello=world").href, { hello: "world" }],
+ [new URL("https://example.com/posts?hello=world&second=2").href, { hello: "world", second: "2" }],
+ [
+ new URL("https://example.com/posts?hello=world&second=2&third=3").href,
+ { hello: "world", second: "2", third: "3" },
+ ],
+ [new URL("https://example.com/posts").href, {}],
+ ]) {
+ const { name, src, filePath, checkThisDoesntCrash, query } = router.match(current);
+ expect(name).toBe("/posts");
+
+ // check nothing is weird on the MatchedRoute object
+ expect(checkThisDoesntCrash).toBeUndefined();
+
+ expect(JSON.stringify(query)).toBe(JSON.stringify(object));
+ expect(filePath).toBe(`${dir}/posts.tsx`);
+ }
+});
+
+it("reload() works", () => {
+ // set up the test
+ const { dir } = make(["posts.tsx"]);
+
+ const router = new Bun.FileSystemRouter({
+ dir,
+ style: "nextjs",
+ assetPrefix: "/_next/static/",
+ origin: "https://nextjs.org",
+ });
+
+ expect(router.match("/posts").name).toBe("/posts");
+ router.reload();
+ expect(router.match("/posts").name).toBe("/posts");
+});
+
+it(".query works with dynamic routes, including params", () => {
+ // set up the test
+ const { dir } = make(["posts/[id].tsx"]);
+
+ const router = new Bun.FileSystemRouter({
+ dir,
+ style: "nextjs",
+ assetPrefix: "/_next/static/",
+ origin: "https://nextjs.org",
+ });
+
+ for (let [current, object] of [
+ [new URL("https://example.com/posts/123?hello=world").href, { id: "123", hello: "world" }],
+ [new URL("https://example.com/posts/123?hello=world&second=2").href, { id: "123", hello: "world", second: "2" }],
+ [
+ new URL("https://example.com/posts/123?hello=world&second=2&third=3").href,
+ { id: "123", hello: "world", second: "2", third: "3" },
+ ],
+ [new URL("https://example.com/posts/123").href, { id: "123" }],
+ ]) {
+ const { name, src, filePath, checkThisDoesntCrash, query } = router.match(current);
+ expect(name).toBe("/posts/[id]");
+
+ // check nothing is weird on the MatchedRoute object
+ expect(checkThisDoesntCrash).toBeUndefined();
+
+ expect(JSON.stringify(query)).toBe(JSON.stringify(object));
+ expect(filePath).toBe(`${dir}/posts/[id].tsx`);
+ }
+});
diff --git a/test/js/bun/util/hash.test.js b/test/js/bun/util/hash.test.js
new file mode 100644
index 000000000..87a5a9ce3
--- /dev/null
+++ b/test/js/bun/util/hash.test.js
@@ -0,0 +1,47 @@
+import fs from "fs";
+import { it, expect } from "bun:test";
+import path from "path";
+import { gcTick } from "harness";
+
+it(`Bun.hash()`, () => {
+ gcTick();
+ Bun.hash("hello world");
+ Bun.hash(new TextEncoder().encode("hello world"));
+});
+it(`Bun.hash.wyhash()`, () => {
+ Bun.hash.wyhash("hello world");
+ gcTick();
+ Bun.hash.wyhash(new TextEncoder().encode("hello world"));
+});
+it(`Bun.hash.adler32()`, () => {
+ Bun.hash.adler32("hello world");
+ gcTick();
+ Bun.hash.adler32(new TextEncoder().encode("hello world"));
+});
+it(`Bun.hash.crc32()`, () => {
+ Bun.hash.crc32("hello world");
+ gcTick();
+ Bun.hash.crc32(new TextEncoder().encode("hello world"));
+});
+it(`Bun.hash.cityHash32()`, () => {
+ Bun.hash.cityHash32("hello world");
+ gcTick();
+ Bun.hash.cityHash32(new TextEncoder().encode("hello world"));
+ gcTick();
+});
+it(`Bun.hash.cityHash64()`, () => {
+ Bun.hash.cityHash64("hello world");
+ gcTick();
+ Bun.hash.cityHash64(new TextEncoder().encode("hello world"));
+ gcTick();
+});
+it(`Bun.hash.murmur32v3()`, () => {
+ Bun.hash.murmur32v3("hello world");
+ gcTick();
+ Bun.hash.murmur32v3(new TextEncoder().encode("hello world"));
+});
+it(`Bun.hash.murmur64v2()`, () => {
+ Bun.hash.murmur64v2("hello world");
+ gcTick();
+ Bun.hash.murmur64v2(new TextEncoder().encode("hello world"));
+});
diff --git a/test/js/bun/util/index-of-line.test.ts b/test/js/bun/util/index-of-line.test.ts
new file mode 100644
index 000000000..1c6cbaea2
--- /dev/null
+++ b/test/js/bun/util/index-of-line.test.ts
@@ -0,0 +1,37 @@
+import { expect, test } from "bun:test";
+import { indexOfLine } from "bun";
+
+test("indexOfLine", () => {
+ const source = `
+ const a = 1;
+
+ const b = 2;
+
+ 😋const c = 3; // handles unicode
+
+ 😋 Get Emoji — All Emojis to ✂️
+
+ const b = 2;
+
+ const c = 3;
+`;
+ var i = 0;
+ var j = 0;
+ const buffer = Buffer.from(source);
+ var nonEmptyLineCount = 0;
+ while (i < buffer.length) {
+ const prev = j;
+ j = source.indexOf("\n", j);
+ i = indexOfLine(buffer, i);
+
+ const delta = Buffer.byteLength(source.slice(0, j), "utf8") - j;
+ console.log(source.slice(prev + 1, j));
+ if (i === -1) {
+ expect(j).toBe(-1);
+ expect(nonEmptyLineCount).toBe(6);
+ break;
+ }
+ expect(i++ - delta).toBe(j++);
+ nonEmptyLineCount++;
+ }
+});
diff --git a/test/js/bun/util/inspect.test.js b/test/js/bun/util/inspect.test.js
new file mode 100644
index 000000000..ec2fb0b45
--- /dev/null
+++ b/test/js/bun/util/inspect.test.js
@@ -0,0 +1,315 @@
+import { it, expect, describe } from "bun:test";
+
+it("Timeout", () => {
+ const id = setTimeout(() => {}, 0);
+ expect(Bun.inspect(id)).toBe(`Timeout (#${+id})`);
+
+ const id2 = setInterval(() => {}, 1);
+ id2.unref();
+ expect(Bun.inspect(id2)).toBe(`Timeout (#${+id2}, repeats)`);
+});
+
+it("when prototype defines the same property, don't print the same property twice", () => {
+ var base = {
+ foo: "123",
+ };
+ var obj = Object.create(base);
+ obj.foo = "456";
+ expect(Bun.inspect(obj).trim()).toBe('{\n foo: "456"\n}'.trim());
+});
+
+it("Blob inspect", () => {
+ expect(Bun.inspect(new Blob(["123"]))).toBe(`Blob (3 bytes)`);
+ expect(Bun.inspect(new Blob(["123".repeat(900)]))).toBe(`Blob (2.70 KB)`);
+ expect(Bun.inspect(Bun.file("/tmp/file.txt"))).toBe(`FileRef ("/tmp/file.txt") {
+ type: "text/plain;charset=utf-8"
+}`);
+ expect(Bun.inspect(Bun.file(123))).toBe(`FileRef (fd: 123) {
+ type: "application/octet-stream"
+}`);
+ expect(Bun.inspect(new Response(new Blob()))).toBe(`Response (0 KB) {
+ ok: true,
+ url: "",
+ statusText: "",
+ redirected: false,
+ bodyUsed: false,
+ status: 200,
+ [Blob detached]
+}`);
+ expect(Bun.inspect(new Response("Hello"))).toBe(`Response (5 bytes) {
+ ok: true,
+ url: "",
+ statusText: "",
+ redirected: false,
+ bodyUsed: false,
+ status: 200,
+ Blob (5 bytes)
+}`);
+});
+
+it.skip("utf16 property name", () => {
+ var { Database } = require("bun:sqlite");
+ const db = Database.open(":memory:");
+ expect("笑".codePointAt(0)).toBe(31505);
+
+ // latin1 escaping identifier issue
+ expect(Object.keys({ 笑: "hey" })[0].codePointAt(0)).toBe(31505);
+
+ const output = JSON.stringify(
+ [
+ {
+ 笑: "😀",
+ },
+ ],
+ null,
+ 2,
+ );
+ expect(Bun.inspect(db.prepare("select '😀' as 笑").all())).toBe(output);
+});
+
+it("latin1", () => {
+ expect(Bun.inspect("English")).toBe("English");
+ expect(Bun.inspect("Français")).toBe("Français");
+ expect(Bun.inspect("Ελληνική")).toBe("Ελληνική");
+ expect(Bun.inspect("日本語")).toBe("日本語");
+ expect(Bun.inspect("Emoji😎")).toBe("Emoji😎");
+ expect(Bun.inspect("Français / Ελληνική")).toBe("Français / Ελληνική");
+});
+
+it("Request object", () => {
+ expect(Bun.inspect(new Request({ url: "https://example.com" })).trim()).toBe(
+ `
+Request (0 KB) {
+ method: "GET",
+ url: "https://example.com"
+}`.trim(),
+ );
+});
+
+it("MessageEvent", () => {
+ expect(Bun.inspect(new MessageEvent("message", { data: 123 }))).toBe(
+ `MessageEvent {
+ type: "message",
+ data: 123
+}`,
+ );
+});
+
+// https://github.com/oven-sh/bun/issues/561
+it("TypedArray prints", () => {
+ for (let TypedArray of [
+ Uint8Array,
+ Uint16Array,
+ Uint32Array,
+ Uint8ClampedArray,
+ Int8Array,
+ Int16Array,
+ Int32Array,
+ Float32Array,
+ Float64Array,
+ ]) {
+ const buffer = new TypedArray([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
+ const input = Bun.inspect(buffer);
+
+ expect(input).toBe(`${TypedArray.name}(${buffer.length}) [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]`);
+ for (let i = 1; i < buffer.length + 1; i++) {
+ expect(Bun.inspect(buffer.subarray(i))).toBe(
+ `${TypedArray.name}(${buffer.length - i}) [ ` + [...buffer.subarray(i)].join(", ") + " ]",
+ );
+ }
+ }
+});
+
+it("BigIntArray", () => {
+ for (let TypedArray of [BigInt64Array, BigUint64Array]) {
+ const buffer = new TypedArray([1n, 2n, 3n, 4n, 5n, 6n, 7n, 8n, 9n, 10n]);
+ const input = Bun.inspect(buffer);
+
+ expect(input).toBe(`${TypedArray.name}(${buffer.length}) [ 1n, 2n, 3n, 4n, 5n, 6n, 7n, 8n, 9n, 10n ]`);
+ for (let i = 1; i < buffer.length + 1; i++) {
+ expect(Bun.inspect(buffer.subarray(i))).toBe(
+ `${TypedArray.name}(${buffer.length - i}) [ ` +
+ [...buffer.subarray(i)].map(a => a.toString(10) + "n").join(", ") +
+ " ]",
+ );
+ }
+ }
+});
+
+it("FloatArray", () => {
+ for (let TypedArray of [Float32Array, Float64Array]) {
+ const buffer = new TypedArray([Math.fround(42.68)]);
+ const input = Bun.inspect(buffer);
+
+ expect(input).toBe(`${TypedArray.name}(${buffer.length}) [ ${[Math.fround(42.68)].join(", ")} ]`);
+ for (let i = 1; i < buffer.length + 1; i++) {
+ expect(Bun.inspect(buffer.subarray(i))).toBe(
+ `${TypedArray.name}(${buffer.length - i}) [ ` + [...buffer.subarray(i)].join(", ") + " ]",
+ );
+ }
+ }
+});
+
+it("jsx with two elements", () => {
+ const input = Bun.inspect(
+ <div hello="quoted">
+ <input type="text" value={"123"} />
+ string inside child
+ </div>,
+ );
+
+ const output = `<div hello="quoted">
+ <input type="text" value="123" />
+ string inside child
+</div>`;
+
+ expect(input).toBe(output);
+});
+
+const Foo = () => <div hello="quoted">foo</div>;
+
+it("jsx with anon component", () => {
+ const input = Bun.inspect(<Foo />);
+
+ const output = `<NoName />`;
+
+ expect(input).toBe(output);
+});
+
+it("jsx with fragment", () => {
+ const input = Bun.inspect(<>foo bar</>);
+
+ const output = `<>foo bar</>`;
+
+ expect(input).toBe(output);
+});
+
+it("inspect", () => {
+ expect(Bun.inspect(new TypeError("what")).includes("TypeError: what")).toBe(true);
+ expect("hi").toBe("hi");
+ expect(Bun.inspect(1)).toBe("1");
+ expect(Bun.inspect(NaN)).toBe("NaN");
+ expect(Bun.inspect(Infinity)).toBe("Infinity");
+ expect(Bun.inspect(-Infinity)).toBe("-Infinity");
+ expect(Bun.inspect(1, "hi")).toBe("1 hi");
+ expect(Bun.inspect([])).toBe("[]");
+ expect(Bun.inspect({})).toBe("{}");
+ expect(Bun.inspect({ hello: 1 })).toBe("{\n hello: 1\n}");
+ expect(Bun.inspect({ hello: 1, there: 2 })).toBe("{\n hello: 1,\n there: 2\n}");
+ expect(Bun.inspect({ hello: "1", there: 2 })).toBe('{\n hello: "1",\n there: 2\n}');
+ expect(Bun.inspect({ 'hello-"there': "1", there: 2 })).toBe('{\n "hello-\\"there": "1",\n there: 2\n}');
+ var str = "123";
+ while (str.length < 4096) {
+ str += "123";
+ }
+ expect(Bun.inspect(str)).toBe(str);
+ // expect(Bun.inspect(new Headers())).toBe("Headers (0 KB) {}");
+ expect(Bun.inspect(new Response()).length > 0).toBe(true);
+ // expect(
+ // JSON.stringify(
+ // new Headers({
+ // hi: "ok",
+ // })
+ // )
+ // ).toBe('{"hi":"ok"}');
+ expect(Bun.inspect(new Set())).toBe("Set {}");
+ expect(Bun.inspect(new Map())).toBe("Map {}");
+ expect(Bun.inspect(new Map([["foo", "bar"]]))).toBe('Map(1) {\n "foo": "bar",\n}');
+ expect(Bun.inspect(new Set(["bar"]))).toBe('Set(1) {\n "bar",\n}');
+ expect(Bun.inspect(<div>foo</div>)).toBe("<div>foo</div>");
+ expect(Bun.inspect(<div hello>foo</div>)).toBe("<div hello=true>foo</div>");
+ expect(Bun.inspect(<div hello={1}>foo</div>)).toBe("<div hello=1>foo</div>");
+ expect(Bun.inspect(<div hello={123}>hi</div>)).toBe("<div hello=123>hi</div>");
+ expect(Bun.inspect(<div hello="quoted">quoted</div>)).toBe('<div hello="quoted">quoted</div>');
+ expect(
+ Bun.inspect(
+ <div hello="quoted">
+ <input type="text" value={"123"} />
+ </div>,
+ ),
+ ).toBe(
+ `
+<div hello="quoted">
+ <input type="text" value="123" />
+</div>`.trim(),
+ );
+ expect(Bun.inspect(BigInt(32))).toBe("32n");
+});
+
+describe("latin1 supplemental", () => {
+ const fixture = [
+ [["äbc"], '[ "äbc" ]'],
+ [["cbä"], '[ "cbä" ]'],
+ [["cäb"], '[ "cäb" ]'],
+ [["äbc äbc"], '[ "äbc äbc" ]'],
+ [["cbä cbä"], '[ "cbä cbä" ]'],
+ [["cäb cäb"], '[ "cäb cäb" ]'],
+ ];
+
+ for (let [input, output] of fixture) {
+ it(`latin1 (input) \"${input}\" ${output}`, () => {
+ expect(Bun.inspect(input)).toBe(output);
+ });
+ it(`latin1 (output) \"${output}\"`, () => {
+ expect(Bun.inspect(output)).toBe(output);
+ });
+ // this test is failing:
+ // it(`latin1 (property key)`, () => {
+ // expect(
+ // Object.keys({
+ // ä: 1,
+ // })[0].codePointAt(0),
+ // ).toBe(228);
+ // });
+ }
+});
+
+const fixture = [
+ () => globalThis,
+ () => Bun.file("/tmp/log.txt").stream(),
+ () => Bun.file("/tmp/log.1.txt").stream().getReader(),
+ () => Bun.file("/tmp/log.2.txt").writer(),
+ () =>
+ new WritableStream({
+ write(chunk) {},
+ }),
+ () => require("events"),
+ () => {
+ return new (import.meta.require("events").EventEmitter)();
+ },
+ async () => await import("node:assert"),
+ async () => await import("../../empty.js.js"),
+ () => import.meta.require("./empty.js"),
+ () => new Proxy({ yolo: 1 }, {}),
+ () =>
+ new Proxy(
+ { yolo: 1 },
+ {
+ get(target, prop) {
+ return prop + "!";
+ },
+ has(target, prop) {
+ return true;
+ },
+ ownKeys() {
+ return ["foo"];
+ },
+ },
+ ),
+];
+
+describe("crash testing", () => {
+ for (let input of fixture) {
+ it(`inspecting "${input.toString().slice(0, 20).replaceAll("\n", "\\n")}" doesn't crash`, async () => {
+ try {
+ Bun.inspect(await input());
+ } catch (e) {
+ // this can throw its fine
+ }
+ });
+ }
+});
+
+it("possibly formatted emojis log", () => {
+ expect(Bun.inspect("✔", "hey")).toBe("✔ hey");
+});
diff --git a/test/js/bun/util/mmap.test.js b/test/js/bun/util/mmap.test.js
new file mode 100644
index 000000000..4972359da
--- /dev/null
+++ b/test/js/bun/util/mmap.test.js
@@ -0,0 +1,69 @@
+import { describe, it, expect } from "bun:test";
+import { gcTick } from "harness";
+
+describe("Bun.mmap", async () => {
+ await gcTick();
+ const path = `/tmp/bun-mmap-test_${Math.random()}.txt`;
+ await gcTick();
+ await Bun.write(path, "hello");
+ await gcTick();
+
+ it("mmap finalizer", async () => {
+ let map = Bun.mmap(path);
+ await gcTick();
+ const map2 = Bun.mmap(path);
+
+ map = null;
+ await gcTick();
+ });
+
+ it("mmap passed to other syscalls", async () => {
+ const map = Bun.mmap(path);
+ await gcTick();
+ await Bun.write(path + "1", map);
+ await gcTick();
+ const text = await (await Bun.file(path + "1")).text();
+ await gcTick();
+
+ expect(text).toBe(new TextDecoder().decode(map));
+ });
+
+ it("mmap sync", async () => {
+ const map = Bun.mmap(path);
+ await gcTick();
+ const map2 = Bun.mmap(path);
+ await gcTick();
+
+ const old = map[0];
+ await gcTick();
+ map[0] = 0;
+ await gcTick();
+ expect(map2[0]).toBe(0);
+
+ map2[0] = old;
+ await gcTick();
+ expect(map[0]).toBe(old);
+ await gcTick();
+ await Bun.write(path, "olleh");
+ await gcTick();
+ expect(new TextDecoder().decode(map)).toBe("olleh");
+ await gcTick();
+ });
+
+ it("mmap private", async () => {
+ await gcTick();
+ const map = Bun.mmap(path, { shared: true });
+ await gcTick();
+ const map2 = Bun.mmap(path, { shared: false });
+ await gcTick();
+ const old = map[0];
+
+ await gcTick();
+ map2[0] = 0;
+ await gcTick();
+ expect(map2[0]).toBe(0);
+ await gcTick();
+ expect(map[0]).toBe(old);
+ await gcTick();
+ });
+});
diff --git a/test/js/bun/util/peek.test.ts b/test/js/bun/util/peek.test.ts
new file mode 100644
index 000000000..421c306d8
--- /dev/null
+++ b/test/js/bun/util/peek.test.ts
@@ -0,0 +1,42 @@
+import { peek } from "bun";
+import { expect, test } from "bun:test";
+
+test("peek", () => {
+ const promise = Promise.resolve(true);
+
+ // no await necessary!
+ expect(peek(promise)).toBe(true);
+
+ // if we peek again, it returns the same value
+ const again = peek(promise);
+ expect(again).toBe(true);
+
+ // if we peek a non-promise, it returns the value
+ const value = peek(42);
+ expect(value).toBe(42);
+
+ // if we peek a pending promise, it returns the promise again
+ const pending = new Promise(() => {});
+ expect(peek(pending)).toBe(pending);
+
+ // If we peek a rejected promise, it:
+ // - returns the error
+ // - does not mark the promise as handled
+ const rejected = Promise.reject<Error>(new Error("Succesfully tested promise rejection"));
+ const peeked = peek(rejected);
+ expect(peeked instanceof Error).toBe(true);
+ expect((peeked as Error).message).toBe("Succesfully tested promise rejection");
+ rejected.catch(() => {});
+});
+
+test("peek.status", () => {
+ const promise = Promise.resolve(true);
+ expect(peek.status(promise)).toBe("fulfilled");
+
+ const pending = new Promise(() => {});
+ expect(peek.status(pending)).toBe("pending");
+
+ const rejected = Promise.reject(new Error("oh nooo"));
+ expect(peek.status(rejected)).toBe("rejected");
+ rejected.catch(() => {});
+});
diff --git a/test/js/bun/util/reportError.test.js b/test/js/bun/util/reportError.test.js
new file mode 100644
index 000000000..e51f93309
--- /dev/null
+++ b/test/js/bun/util/reportError.test.js
@@ -0,0 +1,25 @@
+import { it } from "bun:test";
+
+it("reportError", () => {
+ console.log("---BEGIN REPORT ERROR TEST--");
+ // make sure we don't crash when given non-sensical types
+ reportError(new Error("reportError Test!"));
+ reportError(true);
+ reportError(false);
+ reportError(null);
+ reportError(123);
+ reportError(Infinity);
+ reportError(NaN);
+ reportError(-NaN);
+ reportError("");
+ reportError(new Uint8Array(1));
+ reportError(new Uint8Array(0));
+ reportError(new ArrayBuffer(0));
+ reportError(new ArrayBuffer(1));
+ reportError("string");
+ reportError([]);
+ reportError([123, null]);
+ reportError({});
+ reportError([{}]);
+ console.log("---END REPORT ERROR TEST--");
+});
diff --git a/test/js/bun/util/sleep.js b/test/js/bun/util/sleep.js
new file mode 100644
index 000000000..1ec79e79d
--- /dev/null
+++ b/test/js/bun/util/sleep.js
@@ -0,0 +1,10 @@
+const interval = 10;
+const now = performance.now();
+console.time("Slept");
+Bun.sleepSync(interval);
+const elapsed = performance.now() - now;
+if (elapsed < interval) {
+ throw new Error("Didn't sleep");
+}
+
+console.timeEnd("Slept");
diff --git a/test/js/bun/util/sleepSync.test.ts b/test/js/bun/util/sleepSync.test.ts
new file mode 100644
index 000000000..dd2e8818a
--- /dev/null
+++ b/test/js/bun/util/sleepSync.test.ts
@@ -0,0 +1,32 @@
+import { it, expect } from "bun:test";
+import { sleepSync } from "bun";
+
+it("sleepSync uses milliseconds", async () => {
+ const start = Date.now();
+ sleepSync(5);
+ const end = Date.now();
+ expect(end - start).toBeGreaterThanOrEqual(5);
+ expect(end - start).toBeLessThan(10);
+});
+
+it("sleepSync with no arguments throws", async () => {
+ expect(() => sleepSync()).toThrow();
+});
+
+it("sleepSync with non-numbers throws", async () => {
+ expect(() => sleepSync(true)).toThrow();
+ expect(() => sleepSync(false)).toThrow();
+ expect(() => sleepSync("hi")).toThrow();
+ expect(() => sleepSync({})).toThrow();
+ expect(() => sleepSync([])).toThrow();
+ expect(() => sleepSync(undefined)).toThrow();
+ expect(() => sleepSync(null)).toThrow();
+});
+
+it("sleepSync with negative number throws", async () => {
+ expect(() => sleepSync(-10)).toThrow();
+});
+
+it("can map with sleepSync", async () => {
+ [1, 2, 3].map(sleepSync);
+});
diff --git a/test/js/bun/util/unsafe.test.js b/test/js/bun/util/unsafe.test.js
new file mode 100644
index 000000000..38830fa36
--- /dev/null
+++ b/test/js/bun/util/unsafe.test.js
@@ -0,0 +1,51 @@
+import { test, expect, it, describe } from "bun:test";
+import { gc } from "harness";
+
+it("arrayBufferToString u8", async () => {
+ var encoder = new TextEncoder();
+ const bytes = encoder.encode("hello world");
+ gc(true);
+ expect(Bun.unsafe.arrayBufferToString(bytes)).toBe("hello world");
+ gc(true);
+ await new Promise(resolve => setTimeout(resolve, 0));
+ gc(true);
+});
+
+it("arrayBufferToString ArrayBuffer", async () => {
+ var encoder = new TextEncoder();
+ var bytes = encoder.encode("hello world");
+ gc(true);
+ const out = Bun.unsafe.arrayBufferToString(bytes.buffer);
+ expect(out).toBe("hello world");
+ gc(true);
+ await new Promise(resolve => setTimeout(resolve, 0));
+ globalThis.bytes = bytes;
+ gc(true);
+ expect(out).toBe("hello world");
+});
+
+it("arrayBufferToString u16", () => {
+ var encoder = new TextEncoder();
+ const bytes = encoder.encode("hello world");
+ var uint16 = new Uint16Array(bytes.byteLength);
+ uint16.set(bytes);
+ const charCodes = Bun.unsafe
+ .arrayBufferToString(uint16)
+ .split("")
+ .map(a => a.charCodeAt(0));
+ gc(true);
+ for (let i = 0; i < charCodes.length; i++) {
+ expect("hello world"[i]).toBe(String.fromCharCode(charCodes[i]));
+ }
+ gc(true);
+ expect(charCodes.length).toBe("hello world".length);
+ gc(true);
+});
+
+it("Bun.allocUnsafe", () => {
+ var buffer = Bun.allocUnsafe(1024);
+ expect(buffer instanceof Uint8Array).toBe(true);
+ expect(buffer.length).toBe(1024);
+ buffer[0] = 0;
+ expect(buffer[0]).toBe(0);
+});
diff --git a/test/js/bun/util/which.test.ts b/test/js/bun/util/which.test.ts
new file mode 100644
index 000000000..e142e398c
--- /dev/null
+++ b/test/js/bun/util/which.test.ts
@@ -0,0 +1,65 @@
+import { test, expect } from "bun:test";
+
+import { which } from "bun";
+import { chmodSync, mkdirSync, unlinkSync } from "node:fs";
+
+test("which", () => {
+ writeFixture("/tmp/myscript.sh");
+
+ // Our cwd is not /tmp
+ expect(which("myscript.sh")).toBe(null);
+
+ try {
+ mkdirSync("myscript.sh");
+ chmodSync("myscript.sh", "755");
+ } catch (e) {}
+
+ // directories should not be returned
+ expect(which("myscript.sh")).toBe(null);
+
+ // "bun" is in our PATH
+ expect(which("bun")?.length > 0).toBe(true);
+
+ expect(
+ // You can override PATH
+ which("myscript.sh", {
+ PATH: "/tmp",
+ }),
+ ).toBe("/tmp/myscript.sh");
+
+ expect(
+ which("myscript.sh", {
+ PATH: "/not-tmp",
+ }),
+ ).toBe(null);
+
+ expect(
+ // PATH works like the $PATH environment variable, respecting colons
+ which("myscript.sh", {
+ PATH: "/not-tmp:/tmp",
+ }),
+ ).toBe("/tmp/myscript.sh");
+
+ expect(
+ // cwd is checked first
+ which("myscript.sh", {
+ cwd: "/tmp",
+ }),
+ ).toBe("/tmp/myscript.sh");
+
+ try {
+ unlinkSync("myscript.sh");
+ } catch (e) {}
+});
+
+function writeFixture(path) {
+ var fs = require("fs");
+ try {
+ fs.unlinkSync(path);
+ } catch (e) {}
+
+ var script_name = path;
+ var script_content = "echo Hello world!";
+ fs.writeFileSync(script_name, script_content);
+ fs.chmodSync(script_name, "755");
+}
diff --git a/test/js/bun/wasm/hello-wasi.wasm b/test/js/bun/wasm/hello-wasi.wasm
new file mode 100755
index 000000000..e134bcee4
--- /dev/null
+++ b/test/js/bun/wasm/hello-wasi.wasm
Binary files differ
diff --git a/test/js/bun/wasm/wasi.test.js b/test/js/bun/wasm/wasi.test.js
new file mode 100644
index 000000000..ac1d986d4
--- /dev/null
+++ b/test/js/bun/wasm/wasi.test.js
@@ -0,0 +1,14 @@
+import { spawnSync } from "bun";
+import { expect, it } from "bun:test";
+import { bunExe, bunEnv } from "harness";
+
+it("Should support printing 'hello world'", () => {
+ const { stdout, exitCode } = spawnSync({
+ cmd: [bunExe(), import.meta.dir + "/hello-wasi.wasm"],
+ stdout: "pipe",
+ env: bunEnv,
+ });
+
+ expect(stdout.toString()).toEqual("hello world\n");
+ expect(exitCode).toBe(0);
+});
diff --git a/test/js/bun/wasm/wasm-return-1-test.zig b/test/js/bun/wasm/wasm-return-1-test.zig
new file mode 100644
index 000000000..d46bdae92
--- /dev/null
+++ b/test/js/bun/wasm/wasm-return-1-test.zig
@@ -0,0 +1,5 @@
+export fn hello() i32 {
+ return 1;
+}
+
+pub fn main() void {}
diff --git a/test/js/bun/wasm/wasm.js b/test/js/bun/wasm/wasm.js
new file mode 100644
index 000000000..a4daaaffe
--- /dev/null
+++ b/test/js/bun/wasm/wasm.js
@@ -0,0 +1 @@
+import * as wasm from "./wasm-return-1-test.wasm";
diff --git a/test/js/bun/websocket/websocket-server.test.ts b/test/js/bun/websocket/websocket-server.test.ts
new file mode 100644
index 000000000..b3b2e57b5
--- /dev/null
+++ b/test/js/bun/websocket/websocket-server.test.ts
@@ -0,0 +1,954 @@
+import { describe, expect, it } from "bun:test";
+import { gcTick } from "harness";
+import { serve } from "bun";
+
+describe("websocket server", () => {
+ it("can do publish()", async done => {
+ var server = serve({
+ port: 0,
+ websocket: {
+ open(ws) {
+ ws.subscribe("all");
+ },
+ message(ws, msg) {},
+ close(ws) {},
+ },
+ fetch(req, server) {
+ if (server.upgrade(req)) {
+ return;
+ }
+
+ return new Response("success");
+ },
+ });
+
+ await new Promise<void>((resolve2, reject2) => {
+ var socket = new WebSocket(`ws://${server.hostname}:${server.port}`);
+ var clientCounter = 0;
+
+ socket.onmessage = e => {
+ expect(e.data).toBe("hello");
+ resolve2();
+ };
+ socket.onopen = () => {
+ queueMicrotask(() => {
+ server.publish("all", "hello");
+ });
+ };
+ });
+ server.stop(true);
+ done();
+ });
+
+ it("can do publish() with publishToSelf: false", async done => {
+ var server = serve({
+ port: 0,
+ websocket: {
+ open(ws) {
+ ws.subscribe("all");
+ ws.publish("all", "hey");
+ server.publish("all", "hello");
+ },
+ message(ws, msg) {
+ if (new TextDecoder().decode(msg) !== "hello") {
+ done(new Error("unexpected message"));
+ }
+ },
+ close(ws) {},
+ publishToSelf: false,
+ },
+ fetch(req, server) {
+ if (server.upgrade(req)) {
+ return;
+ }
+
+ return new Response("success");
+ },
+ });
+
+ await new Promise<void>((resolve2, reject2) => {
+ var socket = new WebSocket(`ws://${server.hostname}:${server.port}`);
+
+ socket.onmessage = e => {
+ expect(e.data).toBe("hello");
+ resolve2();
+ };
+ });
+ server.stop(true);
+ done();
+ });
+
+ for (let method of ["publish", "publishText", "publishBinary"]) {
+ describe(method, () => {
+ it("in close() should work", async () => {
+ var count = 0;
+ var server = serve({
+ port: 0,
+ websocket: {
+ open(ws) {
+ ws.subscribe("all");
+ },
+ message(ws, msg) {},
+ close(ws) {
+ ws[method]("all", method === "publishBinary" ? Buffer.from("bye!") : "bye!");
+ count++;
+
+ if (count >= 2) {
+ server.stop(true);
+ }
+ },
+ },
+ fetch(req, server) {
+ if (server.upgrade(req)) {
+ return;
+ }
+
+ return new Response("success");
+ },
+ });
+
+ try {
+ const first = await new Promise<WebSocket>((resolve2, reject2) => {
+ var socket = new WebSocket(`ws://${server.hostname}:${server.port}`);
+ socket.onopen = () => resolve2(socket);
+ });
+
+ await new Promise<WebSocket>((resolve2, reject2) => {
+ var socket = new WebSocket(`ws://${server.hostname}:${server.port}`);
+ socket.onopen = () => {
+ queueMicrotask(() => first.close());
+ };
+ socket.onmessage = ev => {
+ var msg = ev.data;
+ if (typeof msg !== "string") {
+ msg = new TextDecoder().decode(msg);
+ }
+
+ if (msg === "bye!") {
+ socket.close(0);
+ resolve2(socket);
+ } else {
+ reject2(msg);
+ }
+ };
+ });
+ } finally {
+ server.stop(true);
+ }
+ });
+ });
+ }
+
+ it("close inside open", async () => {
+ var resolve;
+ console.trace("here");
+ var server = serve({
+ port: 0,
+ websocket: {
+ open(ws) {},
+ message(ws, msg) {},
+ close() {
+ resolve();
+ server.stop(true);
+ },
+ },
+ fetch(req, server) {
+ if (
+ server.upgrade(req, {
+ data: "hello world",
+
+ // check that headers works
+ headers: {
+ "x-a": "text/plain",
+ },
+ })
+ ) {
+ if (server.upgrade(req)) {
+ throw new Error("should not upgrade twice");
+ }
+ return;
+ }
+
+ return new Response("noooooo hello world");
+ },
+ });
+
+ await new Promise<void>((resolve_, reject) => {
+ resolve = resolve_;
+ const websocket = new WebSocket(`ws://${server.hostname}:${server.port}`);
+ websocket.onopen = () => {
+ websocket.close();
+ };
+ websocket.onmessage = e => {};
+ websocket.onerror = e => {};
+ });
+ });
+
+ it("headers error doesn't crash", async () => {
+ await new Promise<void>((resolve, reject) => {
+ const server = serve({
+ port: 0,
+ websocket: {
+ open(ws) {
+ ws.close();
+ },
+ message(ws, msg) {},
+ close() {
+ resolve();
+ server.stop(true);
+ },
+ },
+ error(err) {
+ resolve();
+ server.stop(true);
+ },
+ fetch(req, server) {
+ expect(() => {
+ if (
+ server.upgrade(req, {
+ data: "hello world",
+ headers: 1238 as any,
+ })
+ ) {
+ reject(new Error("should not upgrade"));
+ return new Response("should not upgrade");
+ }
+ }).toThrow("upgrade options.headers must be a Headers or an object");
+ resolve();
+ return new Response("success");
+ },
+ });
+
+ const websocket = new WebSocket(`ws://${server.hostname}:${server.port}`);
+ websocket.onopen = () => websocket.close();
+ websocket.onmessage = e => {};
+ websocket.onerror = e => {};
+ });
+ });
+ it("can do hello world", async () => {
+ const server = serve({
+ port: 0,
+ websocket: {
+ open(ws) {
+ server.stop();
+ },
+ message(ws, msg) {
+ ws.send("hello world");
+ },
+ },
+ fetch(req, server) {
+ server.stop();
+ if (
+ server.upgrade(req, {
+ data: "hello world",
+
+ // check that headers works
+ headers: {
+ "x-a": "text/plain",
+ },
+ })
+ ) {
+ if (server.upgrade(req)) {
+ throw new Error("should not upgrade twice");
+ }
+ return;
+ }
+
+ return new Response("noooooo hello world");
+ },
+ });
+
+ await new Promise<void>((resolve, reject) => {
+ const websocket = new WebSocket(`ws://${server.hostname}:${server.port}`);
+ websocket.onopen = () => {
+ websocket.send("hello world");
+ };
+ websocket.onmessage = e => {
+ try {
+ expect(e.data).toBe("hello world");
+ resolve();
+ } catch (r) {
+ reject(r);
+ } finally {
+ websocket.close();
+ }
+ };
+ websocket.onerror = e => {
+ reject(e);
+ };
+ });
+ });
+
+ it("fetch() allows a Response object to be returned for an upgraded ServerWebSocket", () => {
+ const server = serve({
+ port: 0,
+ websocket: {
+ open(ws) {
+ server.stop();
+ },
+ message(ws, msg) {
+ ws.send("hello world");
+ },
+ },
+ error(err) {
+ console.error(err);
+ },
+ fetch(req, server) {
+ server.stop();
+ if (
+ server.upgrade(req, {
+ data: "hello world",
+
+ // check that headers works
+ headers: {
+ "x-a": "text/plain",
+ },
+ })
+ ) {
+ if (server.upgrade(req)) {
+ throw new Error("should not upgrade twice");
+ }
+ return new Response("lol!", {
+ status: 101,
+ });
+ }
+
+ return new Response("noooooo hello world");
+ },
+ });
+
+ return new Promise<void>((resolve, reject) => {
+ const websocket = new WebSocket(`ws://${server.hostname}:${server.port}`);
+ websocket.onopen = () => {
+ websocket.send("hello world");
+ };
+ websocket.onmessage = e => {
+ try {
+ expect(e.data).toBe("hello world");
+ resolve();
+ } catch (r) {
+ reject(r);
+ } finally {
+ websocket.close();
+ }
+ };
+ websocket.onerror = e => {
+ reject(e);
+ };
+ });
+ });
+
+ it("fetch() allows a Promise<Response> object to be returned for an upgraded ServerWebSocket", () => {
+ const server = serve({
+ port: 0,
+ websocket: {
+ async open(ws) {
+ server.stop();
+ },
+ async message(ws, msg) {
+ await 1;
+ ws.send("hello world");
+ },
+ },
+ error(err) {
+ console.error(err);
+ },
+ async fetch(req, server) {
+ server.stop();
+ await 1;
+ if (
+ server.upgrade(req, {
+ data: "hello world",
+
+ // check that headers works
+ headers: {
+ "x-a": "text/plain",
+ },
+ })
+ ) {
+ if (server.upgrade(req)) {
+ throw new Error("should not upgrade twice");
+ }
+ return new Response("lol!", {
+ status: 101,
+ });
+ }
+
+ return new Response("noooooo hello world");
+ },
+ });
+ return new Promise<void>((resolve, reject) => {
+ const websocket = new WebSocket(`ws://${server.hostname}:${server.port}`);
+ websocket.onopen = () => {
+ websocket.send("hello world");
+ };
+ websocket.onmessage = e => {
+ try {
+ expect(e.data).toBe("hello world");
+ resolve();
+ } catch (r) {
+ reject(r);
+ } finally {
+ websocket.close();
+ }
+ };
+ websocket.onerror = e => {
+ reject(e);
+ };
+ });
+ });
+ it("binaryType works", async () => {
+ var done = false;
+ const server = serve({
+ port: 0,
+ websocket: {
+ open(ws) {
+ server.stop();
+ },
+ message(ws, msg) {
+ if (ws.binaryType === "uint8array") {
+ expect(ws.binaryType).toBe("uint8array");
+ ws.binaryType = "arraybuffer";
+ expect(ws.binaryType).toBe("arraybuffer");
+ expect(msg instanceof Uint8Array).toBe(true);
+ } else {
+ expect(ws.binaryType).toBe("arraybuffer");
+ expect(msg instanceof ArrayBuffer).toBe(true);
+ done = true;
+ }
+
+ ws.send("hello world");
+ },
+ },
+ fetch(req, server) {
+ server.stop();
+ if (server.upgrade(req, { data: "hello world" })) {
+ if (server.upgrade(req)) {
+ throw new Error("should not upgrade twice");
+ }
+ return;
+ }
+
+ return new Response("noooooo hello world");
+ },
+ });
+
+ await new Promise<boolean>((resolve, reject) => {
+ var counter = 0;
+ const websocket = new WebSocket(`ws://${server.hostname}:${server.port}`);
+ websocket.onopen = () => {
+ websocket.send(Buffer.from("hello world"));
+ };
+ websocket.onmessage = e => {
+ try {
+ expect(e.data).toBe("hello world");
+
+ if (counter++ > 0) {
+ websocket.close();
+ resolve(done);
+ }
+ websocket.send(Buffer.from("oaksd"));
+ } catch (r) {
+ websocket.close();
+ reject(r);
+ }
+ };
+ websocket.onerror = e => {
+ reject(e);
+ };
+ });
+ });
+
+ it("does not upgrade for non-websocket connections", async () => {
+ await new Promise<void>(async (resolve, reject) => {
+ var server = serve({
+ port: 0,
+ websocket: {
+ open(ws) {
+ ws.send("hello world");
+ },
+ message(ws, msg) {},
+ },
+ fetch(req, server) {
+ if (server.upgrade(req)) {
+ reject(new Error("should not upgrade"));
+ }
+
+ return new Response("success");
+ },
+ });
+
+ const response = await fetch(`http://${server.hostname}:${server.port}`);
+ expect(await response.text()).toBe("success");
+ resolve();
+ server.stop(true);
+ });
+ });
+
+ it("does not upgrade for non-websocket servers", async () => {
+ await new Promise<void>(async (resolve, reject) => {
+ const server = serve({
+ port: 0,
+ fetch(req, server) {
+ server.stop();
+ expect(() => {
+ server.upgrade(req);
+ }).toThrow('To enable websocket support, set the "websocket" object in Bun.serve({})');
+ return new Response("success");
+ },
+ });
+
+ const response = await fetch(`http://${server.hostname}:${server.port}`);
+ expect(await response.text()).toBe("success");
+ resolve();
+ });
+ });
+
+ it("async can do hello world", async () => {
+ const server = serve({
+ port: 0,
+ websocket: {
+ async open(ws) {
+ server.stop(true);
+ ws.send("hello world");
+ },
+ message(ws, msg) {},
+ },
+ async fetch(req, server) {
+ server.stop();
+ await 1;
+ if (server.upgrade(req)) return;
+
+ return new Response("noooooo hello world");
+ },
+ });
+
+ await new Promise<void>((resolve, reject) => {
+ const websocket = new WebSocket(`ws://${server.hostname}:${server.port}`);
+
+ websocket.onmessage = e => {
+ try {
+ expect(e.data).toBe("hello world");
+ resolve();
+ } catch (r) {
+ reject(r);
+ } finally {
+ websocket.close();
+ }
+ };
+ websocket.onerror = e => {
+ reject(e);
+ };
+ });
+ });
+
+ it("publishText()", async () => {
+ await new Promise<void>((resolve, reject) => {
+ var websocket;
+ var server = serve({
+ port: 0,
+ websocket: {
+ async open(ws) {
+ // we don't care about the data
+ // we just want to make sure the DOMJIT call doesn't crash
+ for (let i = 0; i < 40_000; i++) ws.publishText("hello", "world");
+ websocket.close();
+ server.stop(true);
+ resolve();
+ },
+ message(ws, msg) {},
+ },
+ async fetch(req, server) {
+ await 1;
+ if (server.upgrade(req)) return;
+
+ return new Response("noooooo hello world");
+ },
+ });
+
+ websocket = new WebSocket(`ws://${server.hostname}:${server.port}`);
+ });
+ });
+
+ it("publishBinary()", async () => {
+ const bytes = Buffer.from("hello");
+
+ await new Promise<void>((resolve, reject) => {
+ var websocket;
+ var server = serve({
+ port: 0,
+ websocket: {
+ async open(ws) {
+ // we don't care about the data
+ // we just want to make sure the DOMJIT call doesn't crash
+ for (let i = 0; i < 40_000; i++) ws.publishBinary("hello", bytes);
+ websocket.close();
+ server.stop(true);
+ resolve();
+ },
+ message(ws, msg) {},
+ },
+ async fetch(req, server) {
+ await 1;
+ if (server.upgrade(req)) return;
+
+ return new Response("noooooo hello world");
+ },
+ });
+
+ websocket = new WebSocket(`ws://${server.hostname}:${server.port}`);
+ });
+ });
+
+ it("sendText()", async () => {
+ await new Promise<void>((resolve, reject) => {
+ var websocket;
+ var server = serve({
+ port: 0,
+ websocket: {
+ async open(ws) {
+ // we don't care about the data
+ // we just want to make sure the DOMJIT call doesn't crash
+ for (let i = 0; i < 40_000; i++) ws.sendText("hello world", true);
+ resolve();
+ websocket.close();
+ server.stop(true);
+ },
+ message(ws, msg) {},
+ },
+ async fetch(req, server) {
+ await 1;
+ if (server.upgrade(req)) return;
+
+ return new Response("noooooo hello world");
+ },
+ });
+ websocket = new WebSocket(`ws://${server.hostname}:${server.port}`);
+ });
+ });
+
+ it("sendBinary()", async () => {
+ const bytes = Buffer.from("hello");
+ await new Promise<void>((resolve, reject) => {
+ var websocket;
+ var server = serve({
+ port: 0,
+ websocket: {
+ async open(ws) {
+ // we don't care about the data
+ // we just want to make sure the DOMJIT call doesn't crash
+ for (let i = 0; i < 40_000; i++) ws.sendBinary(bytes, true);
+ websocket.close();
+ server.stop(true);
+ resolve();
+ },
+ message(ws, msg) {},
+ },
+ async fetch(req, server) {
+ await 1;
+ if (server.upgrade(req)) return;
+
+ return new Response("noooooo hello world");
+ },
+ });
+
+ websocket = new WebSocket(`ws://${server.hostname}:${server.port}`);
+ });
+ });
+
+ it("can do hello world corked", async () => {
+ const server = serve({
+ port: 0,
+ websocket: {
+ open(ws) {
+ server.stop();
+ ws.send("hello world");
+ },
+ message(ws, msg) {
+ ws.cork(() => {
+ ws.send("hello world");
+ });
+ },
+ },
+ fetch(req, server) {
+ server.stop();
+ if (server.upgrade(req)) return;
+ return new Response("noooooo hello world");
+ },
+ });
+
+ await new Promise<void>((resolve, reject) => {
+ const websocket = new WebSocket(`ws://${server.hostname}:${server.port}`);
+
+ websocket.onmessage = e => {
+ try {
+ expect(e.data).toBe("hello world");
+ resolve();
+ } catch (r) {
+ reject(r);
+ } finally {
+ websocket.close();
+ }
+ };
+ websocket.onerror = e => {
+ reject(e);
+ };
+ });
+ server.stop(true);
+ });
+
+ it("can do some back and forth", async () => {
+ var dataCount = 0;
+ const server = serve({
+ port: 0,
+ websocket: {
+ open(ws) {
+ server.stop();
+ },
+ message(ws, msg) {
+ if (msg === "first") {
+ ws.send("first");
+ return;
+ }
+ ws.send(`counter: ${dataCount++}`);
+ },
+ },
+ fetch(req, server) {
+ server.stop();
+ if (
+ server.upgrade(req, {
+ data: { count: 0 },
+ })
+ )
+ return new Response("noooooo hello world");
+ },
+ });
+
+ await new Promise<void>((resolve, reject) => {
+ const websocket = new WebSocket(`ws://${server.hostname}:${server.port}`);
+ websocket.onerror = e => {
+ reject(e);
+ };
+
+ var counter = 0;
+ websocket.onopen = () => websocket.send("first");
+ websocket.onmessage = e => {
+ try {
+ switch (counter++) {
+ case 0: {
+ expect(e.data).toBe("first");
+ websocket.send("where are the loops");
+ break;
+ }
+ case 1: {
+ expect(e.data).toBe("counter: 0");
+ websocket.send("br0ther may i have some loops");
+ break;
+ }
+ case 2: {
+ expect(e.data).toBe("counter: 1");
+ websocket.send("br0ther may i have some loops");
+ break;
+ }
+ case 3: {
+ expect(e.data).toBe("counter: 2");
+ resolve();
+ break;
+ }
+ }
+ } catch (r) {
+ reject(r);
+ websocket.close();
+ }
+ };
+ });
+ server.stop(true);
+ });
+
+ it("send rope strings", async () => {
+ var ropey = "hello world".repeat(10);
+ var sendQueue: any[] = [];
+ for (var i = 0; i < 100; i++) {
+ sendQueue.push(ropey + " " + i);
+ }
+
+ var serverCounter = 0;
+ var clientCounter = 0;
+
+ const server = serve({
+ port: 0,
+ websocket: {
+ open(ws) {
+ server.stop();
+ },
+ message(ws, msg) {
+ ws.send(sendQueue[serverCounter++] + " ");
+ gcTick();
+ },
+ },
+ fetch(req, server) {
+ server.stop();
+ if (
+ server.upgrade(req, {
+ data: { count: 0 },
+ })
+ )
+ return;
+
+ return new Response("noooooo hello world");
+ },
+ });
+
+ await new Promise<void>((resolve, reject) => {
+ const websocket = new WebSocket(`ws://${server.hostname}:${server.port}`);
+ websocket.onerror = e => {
+ reject(e);
+ };
+
+ var counter = 0;
+ websocket.onopen = () => websocket.send("first");
+ websocket.onmessage = e => {
+ try {
+ const expected = sendQueue[clientCounter++] + " ";
+ expect(e.data).toBe(expected);
+ websocket.send("next");
+ if (clientCounter === sendQueue.length) {
+ websocket.close();
+ resolve();
+ }
+ } catch (r) {
+ reject(r);
+ console.error(r);
+ websocket.close();
+ }
+ };
+ });
+ server.stop(true);
+ });
+
+ // this test sends 100 messages to 10 connected clients via pubsub
+ it("pub/sub", async () => {
+ var ropey = "hello world".repeat(10);
+ var sendQueue: any[] = [];
+ for (var i = 0; i < 100; i++) {
+ sendQueue.push(ropey + " " + i);
+ gcTick();
+ }
+ var serverCounter = 0;
+ var clientCount = 0;
+ const server = serve({
+ port: 0,
+ websocket: {
+ open(ws) {
+ server.stop();
+ ws.subscribe("test");
+ gcTick();
+ if (!ws.isSubscribed("test")) {
+ throw new Error("not subscribed");
+ }
+ ws.unsubscribe("test");
+ if (ws.isSubscribed("test")) {
+ throw new Error("subscribed");
+ }
+ ws.subscribe("test");
+ clientCount++;
+ if (clientCount === 10) setTimeout(() => ws.publish("test", "hello world"), 1);
+ },
+ message(ws, msg) {
+ if (serverCounter < sendQueue.length) ws.publish("test", sendQueue[serverCounter++] + " ");
+ },
+ },
+ fetch(req) {
+ gcTick();
+ server.stop();
+ if (
+ server.upgrade(req, {
+ data: { count: 0 },
+ })
+ )
+ return;
+ return new Response("noooooo hello world");
+ },
+ });
+
+ const connections = new Array(10);
+ const websockets = new Array(connections.length);
+ var doneCounter = 0;
+ await new Promise<void>(done => {
+ for (var i = 0; i < connections.length; i++) {
+ var j = i;
+ var resolve, reject, resolveConnection, rejectConnection;
+ connections[j] = new Promise((res, rej) => {
+ resolveConnection = res;
+ rejectConnection = rej;
+ });
+ websockets[j] = new Promise((res, rej) => {
+ resolve = res;
+ reject = rej;
+ });
+ gcTick();
+ const websocket = new WebSocket(`ws://${server.hostname}:${server.port}`);
+ websocket.onerror = e => {
+ reject(e);
+ };
+ websocket.onclose = () => {
+ doneCounter++;
+ if (doneCounter === connections.length) {
+ done();
+ }
+ };
+ var hasOpened = false;
+ websocket.onopen = () => {
+ if (!hasOpened) {
+ hasOpened = true;
+ resolve(websocket);
+ }
+ };
+
+ let clientCounter = -1;
+ var hasSentThisTick = false;
+
+ websocket.onmessage = e => {
+ gcTick();
+
+ if (!hasOpened) {
+ hasOpened = true;
+ resolve(websocket);
+ }
+
+ if (e.data === "hello world") {
+ clientCounter = 0;
+ websocket.send("first");
+ return;
+ }
+
+ try {
+ expect(!!sendQueue.find(a => a + " " === e.data)).toBe(true);
+
+ if (!hasSentThisTick) {
+ websocket.send("second");
+ hasSentThisTick = true;
+ queueMicrotask(() => {
+ hasSentThisTick = false;
+ });
+ }
+
+ gcTick();
+
+ if (clientCounter++ === sendQueue.length - 1) {
+ websocket.close();
+ resolveConnection();
+ }
+ } catch (r) {
+ console.error(r);
+ websocket.close();
+ rejectConnection(r);
+ gcTick();
+ }
+ };
+ }
+ });
+ expect(serverCounter).toBe(sendQueue.length);
+ server.stop(true);
+ });
+});
diff --git a/test/js/first_party/undici/undici.test.ts b/test/js/first_party/undici/undici.test.ts
new file mode 100644
index 000000000..603b7a03d
--- /dev/null
+++ b/test/js/first_party/undici/undici.test.ts
@@ -0,0 +1,140 @@
+import { describe, it, expect } from "bun:test";
+import { request } from "undici";
+
+describe("undici", () => {
+ describe("request", () => {
+ it("should make a GET request when passed a URL string", async () => {
+ const { body } = await request("https://httpbin.org/get");
+ expect(body).toBeDefined();
+ const json = (await body.json()) as { url: string };
+ expect(json.url).toBe("https://httpbin.org/get");
+ });
+
+ it("should error when body has already been consumed", async () => {
+ const { body } = await request("https://httpbin.org/get");
+ await body.json();
+ expect(body.bodyUsed).toBe(true);
+ try {
+ await body.json();
+ throw new Error("Should have errored");
+ } catch (e) {
+ expect((e as Error).message).toBe("unusable");
+ }
+ });
+
+ it("should make a POST request when provided a body and POST method", async () => {
+ const { body } = await request("https://httpbin.org/post", {
+ method: "POST",
+ body: "Hello world",
+ });
+ expect(body).toBeDefined();
+ const json = (await body.json()) as { data: string };
+ expect(json.data).toBe("Hello world");
+ });
+
+ it("should accept a URL class object", async () => {
+ const { body } = await request(new URL("https://httpbin.org/get"));
+ expect(body).toBeDefined();
+ const json = (await body.json()) as { url: string };
+ expect(json.url).toBe("https://httpbin.org/get");
+ });
+
+ // it("should accept an undici UrlObject", async () => {
+ // // @ts-ignore
+ // const { body } = await request({ protocol: "https:", hostname: "httpbin.org", path: "/get" });
+ // expect(body).toBeDefined();
+ // const json = (await body.json()) as { url: string };
+ // expect(json.url).toBe("https://httpbin.org/get");
+ // });
+
+ it("should prevent body from being attached to GET or HEAD requests", async () => {
+ try {
+ await request("https://httpbin.org/get", {
+ method: "GET",
+ body: "Hello world",
+ });
+ throw new Error("Should have errored");
+ } catch (e) {
+ expect((e as Error).message).toBe("Body not allowed for GET or HEAD requests");
+ }
+
+ try {
+ await request("https://httpbin.org/head", {
+ method: "HEAD",
+ body: "Hello world",
+ });
+ throw new Error("Should have errored");
+ } catch (e) {
+ expect((e as Error).message).toBe("Body not allowed for GET or HEAD requests");
+ }
+ });
+
+ it("should allow a query string to be passed", async () => {
+ const { body } = await request("https://httpbin.org/get?foo=bar");
+ expect(body).toBeDefined();
+ const json = (await body.json()) as { args: { foo: string } };
+ expect(json.args.foo).toBe("bar");
+
+ const { body: body2 } = await request("https://httpbin.org/get", {
+ query: { foo: "bar" },
+ });
+ expect(body2).toBeDefined();
+ const json2 = (await body2.json()) as { args: { foo: string } };
+ expect(json2.args.foo).toBe("bar");
+ });
+
+ it("should throw on HTTP 4xx or 5xx error when throwOnError is true", async () => {
+ try {
+ await request("https://httpbin.org/status/404", { throwOnError: true });
+ throw new Error("Should have errored");
+ } catch (e) {
+ expect((e as Error).message).toBe("Request failed with status code 404");
+ }
+
+ try {
+ await request("https://httpbin.org/status/500", { throwOnError: true });
+ throw new Error("Should have errored");
+ } catch (e) {
+ expect((e as Error).message).toBe("Request failed with status code 500");
+ }
+ });
+
+ it("should allow us to abort the request with a signal", async () => {
+ const controller = new AbortController();
+ try {
+ setTimeout(() => controller.abort(), 1000);
+ const req = await request("https://httpbin.org/delay/5", {
+ signal: controller.signal,
+ });
+ await req.body.json();
+ throw new Error("Should have errored");
+ } catch (e) {
+ expect((e as Error).message).toBe("The operation was aborted.");
+ }
+ });
+
+ it("should properly append headers to the request", async () => {
+ const { body } = await request("https://httpbin.org/headers", {
+ headers: {
+ "x-foo": "bar",
+ },
+ });
+ expect(body).toBeDefined();
+ const json = (await body.json()) as { headers: { "X-Foo": string } };
+ expect(json.headers["X-Foo"]).toBe("bar");
+ });
+
+ // it("should allow the use of FormData", async () => {
+ // const form = new FormData();
+ // form.append("foo", "bar");
+ // const { body } = await request("https://httpbin.org/post", {
+ // method: "POST",
+ // body: form,
+ // });
+
+ // expect(body).toBeDefined();
+ // const json = (await body.json()) as { form: { foo: string } };
+ // expect(json.form.foo).toBe("bar");
+ // });
+ });
+});
diff --git a/test/js/node/assert/assert-test.test.ts b/test/js/node/assert/assert-test.test.ts
new file mode 100644
index 000000000..1723b7d47
--- /dev/null
+++ b/test/js/node/assert/assert-test.test.ts
@@ -0,0 +1,11 @@
+import assert from "assert";
+import { expect, test } from "bun:test";
+
+// https://github.com/oven-sh/bun/issues/941
+test("assert as a function does not throw", () => assert(true));
+test("assert as a function does throw", () => {
+ try {
+ assert(false);
+ expect(false).toBe(true);
+ } catch (e) {}
+});
diff --git a/test/js/node/buffer.test.js b/test/js/node/buffer.test.js
new file mode 100644
index 000000000..568bf8d44
--- /dev/null
+++ b/test/js/node/buffer.test.js
@@ -0,0 +1,2563 @@
+import { describe, it, expect, beforeEach, afterEach, test } from "bun:test";
+import { gc } from "harness";
+
+const BufferModule = await import("buffer");
+
+beforeEach(() => gc());
+afterEach(() => gc());
+
+function assert(a) {
+ expect(a).toBeTruthy();
+}
+
+Object.assign(assert, {
+ ok(a) {
+ expect(a).toBeTruthy();
+ },
+ deepStrictEqual(a, b) {
+ expect(b).toStrictEqual(a);
+ },
+ strictEqual(a, b) {
+ expect(a).toBe(b);
+ },
+ throws(a, b) {
+ expect(a).toThrow();
+ },
+});
+
+// https://github.com/oven-sh/bun/issues/2052
+it("Buffer global is settable", () => {
+ var prevBuffer = globalThis.Buffer;
+ globalThis.Buffer = 42;
+ expect(globalThis.Buffer).toBe(42);
+ globalThis.Buffer = prevBuffer;
+ expect(globalThis.Buffer).toBe(BufferModule.Buffer);
+ expect(globalThis.Buffer).toBe(prevBuffer);
+});
+
+it("Buffer.alloc", () => {
+ // Verify the maximum Uint8Array size. There is no concrete limit by spec. The
+ // internal limits should be updated if this fails.
+ assert.throws(() => new Uint8Array(2 ** 32 + 1), {
+ message: "Invalid typed array length: 4294967297",
+ });
+
+ const b = Buffer.allocUnsafe(1024);
+ assert.strictEqual(b.length, 1024);
+
+ b[0] = -1;
+ assert.strictEqual(b[0], 255);
+
+ for (let i = 0; i < 1024; i++) {
+ b[i] = i % 256;
+ }
+
+ for (let i = 0; i < 1024; i++) {
+ assert.strictEqual(i % 256, b[i]);
+ }
+
+ const c = Buffer.allocUnsafe(512);
+ assert.strictEqual(c.length, 512);
+
+ const d = Buffer.from([]);
+ assert.strictEqual(d.length, 0);
+
+ // Test offset properties
+ {
+ const b = Buffer.alloc(128);
+ assert.strictEqual(b.length, 128);
+ assert.strictEqual(b.byteOffset, 0);
+ assert.strictEqual(b.offset, 0);
+ }
+
+ // Test creating a Buffer from a Uint32Array
+ {
+ const ui32 = new Uint32Array(4).fill(42);
+ const e = Buffer.from(ui32);
+ for (const [index, value] of e.entries()) {
+ assert.strictEqual(value, ui32[index]);
+ }
+ }
+ // Test creating a Buffer from a Uint32Array (old constructor)
+ {
+ const ui32 = new Uint32Array(4).fill(42);
+ const e = Buffer(ui32);
+ for (const [key, value] of e.entries()) {
+ assert.deepStrictEqual(value, ui32[key]);
+ }
+ }
+
+ // Test invalid encoding for Buffer.toString
+ assert.throws(() => b.toString("invalid"), /Unknown encoding: invalid/);
+ // Invalid encoding for Buffer.write
+ assert.throws(() => b.write("test string", 0, 5, "invalid"), /Unknown encoding: invalid/);
+ // Unsupported arguments for Buffer.write
+ // assert.throws(() => b.write("test", "utf8", 0), {
+ // code: "ERR_INVALID_ARG_TYPE",
+ // });
+
+ // Try to create 0-length buffers. Should not throw.
+ Buffer.from("");
+ Buffer.from("", "ascii");
+ Buffer.from("", "latin1");
+ Buffer.alloc(0);
+ Buffer.allocUnsafe(0);
+ new Buffer("");
+ new Buffer("", "ascii");
+ new Buffer("", "latin1");
+ new Buffer("", "binary");
+ Buffer(0);
+
+ const outOfRangeError = {
+ code: "ERR_OUT_OF_RANGE",
+ name: "RangeError",
+ };
+
+ // Try to write a 0-length string beyond the end of b
+ // assert.throws(() => b.write("", 2048), outOfRangeError);
+
+ // // Throw when writing to negative offset
+ // assert.throws(() => b.write("a", -1), outOfRangeError);
+
+ // // Throw when writing past bounds from the pool
+ // assert.throws(() => b.write("a", 2048), outOfRangeError);
+
+ // // Throw when writing to negative offset
+ // assert.throws(() => b.write("a", -1), outOfRangeError);
+
+ // Try to copy 0 bytes worth of data into an empty buffer
+ b.copy(Buffer.alloc(0), 0, 0, 0);
+
+ // Try to copy 0 bytes past the end of the target buffer
+ b.copy(Buffer.alloc(0), 1, 1, 1);
+ b.copy(Buffer.alloc(1), 1, 1, 1);
+
+ // Try to copy 0 bytes from past the end of the source buffer
+ b.copy(Buffer.alloc(1), 0, 2048, 2048);
+
+ // Testing for smart defaults and ability to pass string values as offset
+ {
+ const writeTest = Buffer.from("abcdes");
+ writeTest.write("n", "ascii");
+ assert.throws(() => writeTest.write("o", "1", "ascii"), {
+ code: "ERR_INVALID_ARG_TYPE",
+ });
+ writeTest.write("o", 1, "ascii");
+ writeTest.write("d", 2, "ascii");
+ writeTest.write("e", 3, "ascii");
+ writeTest.write("j", 4, "ascii");
+ assert.strictEqual(writeTest.toString(), "nodejs");
+ }
+
+ // Offset points to the end of the buffer and does not throw.
+ // (see https://github.com/nodejs/node/issues/8127).
+ Buffer.alloc(1).write("", 1, 0);
+
+ // ASCII slice test
+ {
+ const asciiString = "hello world";
+
+ for (let i = 0; i < asciiString.length; i++) {
+ b[i] = asciiString.charCodeAt(i);
+ }
+ const asciiSlice = b.toString("ascii", 0, asciiString.length);
+ assert.strictEqual(asciiString, asciiSlice);
+ }
+
+ {
+ const asciiString = "hello world";
+ const offset = 100;
+
+ assert.strictEqual(asciiString.length, b.write(asciiString, offset, "ascii"));
+ const asciiSlice = b.toString("ascii", offset, offset + asciiString.length);
+ assert.strictEqual(asciiString, asciiSlice);
+ }
+
+ {
+ const asciiString = "hello world";
+ const offset = 100;
+
+ const sliceA = b.slice(offset, offset + asciiString.length);
+ const sliceB = b.slice(offset, offset + asciiString.length);
+ for (let i = 0; i < asciiString.length; i++) {
+ assert.strictEqual(sliceA[i], sliceB[i]);
+ }
+ }
+
+ // UTF-8 slice test
+ {
+ const utf8String = "¡hέlló wôrld!";
+ const offset = 100;
+
+ b.write(utf8String, 0, Buffer.byteLength(utf8String), "utf8");
+ let utf8Slice = b.toString("utf8", 0, Buffer.byteLength(utf8String));
+ assert.strictEqual(utf8String, utf8Slice);
+
+ assert.strictEqual(Buffer.byteLength(utf8String), b.write(utf8String, offset, "utf8"));
+ utf8Slice = b.toString("utf8", offset, offset + Buffer.byteLength(utf8String));
+ assert.strictEqual(utf8String, utf8Slice);
+
+ const sliceA = b.slice(offset, offset + Buffer.byteLength(utf8String));
+ const sliceB = b.slice(offset, offset + Buffer.byteLength(utf8String));
+ for (let i = 0; i < Buffer.byteLength(utf8String); i++) {
+ assert.strictEqual(sliceA[i], sliceB[i]);
+ }
+ }
+
+ {
+ const slice = b.slice(100, 150);
+ assert.strictEqual(slice.length, 50);
+ for (let i = 0; i < 50; i++) {
+ assert.strictEqual(b[100 + i], slice[i]);
+ }
+ }
+
+ {
+ // Make sure only top level parent propagates from allocPool
+ const b = Buffer.allocUnsafe(5);
+ const c = b.slice(0, 4);
+ const d = c.slice(0, 2);
+ assert.strictEqual(b.parent, c.parent);
+ assert.strictEqual(b.parent, d.parent);
+ }
+
+ {
+ // Also from a non-pooled instance
+ const b = Buffer.allocUnsafeSlow(5);
+ const c = b.slice(0, 4);
+ const d = c.slice(0, 2);
+ assert.strictEqual(c.parent, d.parent);
+ }
+
+ {
+ // Bug regression test
+ const testValue = "\u00F6\u65E5\u672C\u8A9E"; // ö日本語
+ const buffer = Buffer.allocUnsafe(32);
+ const size = buffer.write(testValue, 0, "utf8");
+ const slice = buffer.toString("utf8", 0, size);
+ assert.strictEqual(slice, testValue);
+ }
+
+ {
+ // Test triple slice
+ const a = Buffer.allocUnsafe(8);
+ for (let i = 0; i < 8; i++) a[i] = i;
+ const b = a.slice(4, 8);
+ assert.strictEqual(b[0], 4);
+ assert.strictEqual(b[1], 5);
+ assert.strictEqual(b[2], 6);
+ assert.strictEqual(b[3], 7);
+ const c = b.slice(2, 4);
+ assert.strictEqual(c[0], 6);
+ assert.strictEqual(c[1], 7);
+ }
+
+ {
+ const d = Buffer.from([23, 42, 255]);
+ assert.strictEqual(d.length, 3);
+ assert.strictEqual(d[0], 23);
+ assert.strictEqual(d[1], 42);
+ assert.strictEqual(d[2], 255);
+ assert.deepStrictEqual(d, Buffer.from(d));
+ }
+
+ {
+ // Test for proper UTF-8 Encoding
+ const e = Buffer.from("über");
+ assert.deepStrictEqual(e, Buffer.from([195, 188, 98, 101, 114]));
+ }
+
+ {
+ // Test for proper ascii Encoding, length should be 4
+ const f = Buffer.from("über", "ascii");
+ assert.deepStrictEqual(f, Buffer.from([252, 98, 101, 114]));
+ }
+
+ ["ucs2", "ucs-2", "utf16le", "utf-16le"].forEach(encoding => {
+ {
+ // Test for proper UTF16LE encoding, length should be 8
+ const f = Buffer.from("über", encoding);
+ assert.deepStrictEqual(f, Buffer.from([252, 0, 98, 0, 101, 0, 114, 0]));
+ }
+
+ {
+ // Length should be 12
+ const f = Buffer.from("привет", encoding);
+ assert.deepStrictEqual(f, Buffer.from([63, 4, 64, 4, 56, 4, 50, 4, 53, 4, 66, 4]));
+ assert.strictEqual(f.toString(encoding), "привет");
+ }
+
+ {
+ const f = Buffer.from([0, 0, 0, 0, 0]);
+ assert.strictEqual(f.length, 5);
+ const size = f.write("あいうえお", encoding);
+ assert.strictEqual(size, 4);
+ assert.deepStrictEqual(f, Buffer.from([0x42, 0x30, 0x44, 0x30, 0x00]));
+ }
+ });
+
+ {
+ const f = Buffer.from("\uD83D\uDC4D", "utf-16le"); // THUMBS UP SIGN (U+1F44D)
+ assert.strictEqual(f.length, 4);
+ assert.deepStrictEqual(f, Buffer.from("3DD84DDC", "hex"));
+ }
+
+ // Test construction from arrayish object
+ {
+ const arrayIsh = { 0: 0, 1: 1, 2: 2, 3: 3, length: 4 };
+ let g = Buffer.from(arrayIsh);
+ assert.deepStrictEqual(g, Buffer.from([0, 1, 2, 3]));
+ const strArrayIsh = { 0: "0", 1: "1", 2: "2", 3: "3", length: 4 };
+ g = Buffer.from(strArrayIsh);
+ assert.deepStrictEqual(g, Buffer.from([0, 1, 2, 3]));
+ }
+
+ //
+ // Test toString('base64')
+ //
+ assert.strictEqual(Buffer.from("Man").toString("base64"), "TWFu");
+ assert.strictEqual(Buffer.from("Woman").toString("base64"), "V29tYW4=");
+
+ //
+ // Test toString('base64url')
+ //
+ assert.strictEqual(Buffer.from("Man").toString("base64url"), "TWFu");
+ assert.strictEqual(Buffer.from("Woman").toString("base64url"), "V29tYW4");
+
+ {
+ // Test that regular and URL-safe base64 both work both ways
+ const expected = [0xff, 0xff, 0xbe, 0xff, 0xef, 0xbf, 0xfb, 0xef, 0xff];
+ assert.deepStrictEqual(Buffer.from("//++/++/++//", "base64"), Buffer.from(expected));
+ assert.deepStrictEqual(Buffer.from("__--_--_--__", "base64"), Buffer.from(expected));
+ assert.deepStrictEqual(Buffer.from("//++/++/++//", "base64url"), Buffer.from(expected));
+ assert.deepStrictEqual(Buffer.from("__--_--_--__", "base64url"), Buffer.from(expected));
+ }
+
+ const base64flavors = ["base64", "base64url"];
+
+ {
+ // Test that regular and URL-safe base64 both work both ways with padding
+ const expected = [0xff, 0xff, 0xbe, 0xff, 0xef, 0xbf, 0xfb, 0xef, 0xff, 0xfb];
+ assert.deepStrictEqual(Buffer.from("//++/++/++//+w==", "base64"), Buffer.from(expected));
+ assert.deepStrictEqual(Buffer.from("//++/++/++//+w==", "base64"), Buffer.from(expected));
+ assert.deepStrictEqual(Buffer.from("//++/++/++//+w==", "base64url"), Buffer.from(expected));
+ assert.deepStrictEqual(Buffer.from("//++/++/++//+w==", "base64url"), Buffer.from(expected));
+ }
+
+ {
+ // big example
+ const quote =
+ "Man is distinguished, not only by his reason, but by this " +
+ "singular passion from other animals, which is a lust " +
+ "of the mind, that by a perseverance of delight in the " +
+ "continued and indefatigable generation of knowledge, " +
+ "exceeds the short vehemence of any carnal pleasure.";
+ const expected =
+ "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb" +
+ "24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlci" +
+ "BhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQ" +
+ "gYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu" +
+ "dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZ" +
+ "GdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm" +
+ "5hbCBwbGVhc3VyZS4=";
+ assert.strictEqual(Buffer.from(quote).toString("base64"), expected);
+ assert.strictEqual(
+ Buffer.from(quote).toString("base64url"),
+ expected.replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", ""),
+ );
+
+ base64flavors.forEach(encoding => {
+ let b = Buffer.allocUnsafe(1024);
+ let bytesWritten = b.write(expected, 0, encoding);
+ assert.strictEqual(quote.length, bytesWritten);
+ assert.strictEqual(quote, b.toString("ascii", 0, quote.length));
+
+ // Check that the base64 decoder ignores whitespace
+ const expectedWhite =
+ `${expected.slice(0, 60)} \n` +
+ `${expected.slice(60, 120)} \n` +
+ `${expected.slice(120, 180)} \n` +
+ `${expected.slice(180, 240)} \n` +
+ `${expected.slice(240, 300)}\n` +
+ `${expected.slice(300, 360)}\n`;
+ b = Buffer.allocUnsafe(1024);
+ bytesWritten = b.write(expectedWhite, 0, encoding);
+ assert.strictEqual(quote.length, bytesWritten);
+ assert.strictEqual(quote, b.toString("ascii", 0, quote.length));
+
+ // Check that the base64 decoder on the constructor works
+ // even in the presence of whitespace.
+ b = Buffer.from(expectedWhite, encoding);
+ assert.strictEqual(quote.length, b.length);
+ assert.strictEqual(quote, b.toString("ascii", 0, quote.length));
+
+ // Check that the base64 decoder ignores illegal chars
+ const expectedIllegal =
+ expected.slice(0, 60) +
+ " \x80" +
+ expected.slice(60, 120) +
+ " \xff" +
+ expected.slice(120, 180) +
+ " \x00" +
+ expected.slice(180, 240) +
+ " \x98" +
+ expected.slice(240, 300) +
+ "\x03" +
+ expected.slice(300, 360);
+ b = Buffer.from(expectedIllegal, encoding);
+ assert.strictEqual(quote.length, b.length);
+ assert.strictEqual(quote, b.toString("ascii", 0, quote.length));
+ });
+ }
+
+ base64flavors.forEach(encoding => {
+ assert.strictEqual(Buffer.from("", encoding).toString(), "");
+ assert.strictEqual(Buffer.from("K", encoding).toString(), "");
+
+ // multiple-of-4 with padding
+ assert.strictEqual(Buffer.from("Kg==", encoding).toString(), "*");
+ assert.strictEqual(Buffer.from("Kio=", encoding).toString(), "*".repeat(2));
+ assert.strictEqual(Buffer.from("Kioq", encoding).toString(), "*".repeat(3));
+ assert.strictEqual(Buffer.from("KioqKg==", encoding).toString(), "*".repeat(4));
+ assert.strictEqual(Buffer.from("KioqKio=", encoding).toString(), "*".repeat(5));
+ assert.strictEqual(Buffer.from("KioqKioq", encoding).toString(), "*".repeat(6));
+ assert.strictEqual(Buffer.from("KioqKioqKg==", encoding).toString(), "*".repeat(7));
+ assert.strictEqual(Buffer.from("KioqKioqKio=", encoding).toString(), "*".repeat(8));
+ assert.strictEqual(Buffer.from("KioqKioqKioq", encoding).toString(), "*".repeat(9));
+ assert.strictEqual(Buffer.from("KioqKioqKioqKg==", encoding).toString(), "*".repeat(10));
+ assert.strictEqual(Buffer.from("KioqKioqKioqKio=", encoding).toString(), "*".repeat(11));
+ assert.strictEqual(Buffer.from("KioqKioqKioqKioq", encoding).toString(), "*".repeat(12));
+ assert.strictEqual(Buffer.from("KioqKioqKioqKioqKg==", encoding).toString(), "*".repeat(13));
+ assert.strictEqual(Buffer.from("KioqKioqKioqKioqKio=", encoding).toString(), "*".repeat(14));
+ assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioq", encoding).toString(), "*".repeat(15));
+ assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKg==", encoding).toString(), "*".repeat(16));
+ assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKio=", encoding).toString(), "*".repeat(17));
+ assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKioq", encoding).toString(), "*".repeat(18));
+ assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKioqKg==", encoding).toString(), "*".repeat(19));
+ assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKioqKio=", encoding).toString(), "*".repeat(20));
+
+ // No padding, not a multiple of 4
+ assert.strictEqual(Buffer.from("Kg", encoding).toString(), "*");
+ assert.strictEqual(Buffer.from("Kio", encoding).toString(), "*".repeat(2));
+ assert.strictEqual(Buffer.from("KioqKg", encoding).toString(), "*".repeat(4));
+ assert.strictEqual(Buffer.from("KioqKio", encoding).toString(), "*".repeat(5));
+ assert.strictEqual(Buffer.from("KioqKioqKg", encoding).toString(), "*".repeat(7));
+ assert.strictEqual(Buffer.from("KioqKioqKio", encoding).toString(), "*".repeat(8));
+ assert.strictEqual(Buffer.from("KioqKioqKioqKg", encoding).toString(), "*".repeat(10));
+ assert.strictEqual(Buffer.from("KioqKioqKioqKio", encoding).toString(), "*".repeat(11));
+ assert.strictEqual(Buffer.from("KioqKioqKioqKioqKg", encoding).toString(), "*".repeat(13));
+ assert.strictEqual(Buffer.from("KioqKioqKioqKioqKio", encoding).toString(), "*".repeat(14));
+ assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKg", encoding).toString(), "*".repeat(16));
+ assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKio", encoding).toString(), "*".repeat(17));
+ assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKioqKg", encoding).toString(), "*".repeat(19));
+ assert.strictEqual(Buffer.from("KioqKioqKioqKioqKioqKioqKio", encoding).toString(), "*".repeat(20));
+ });
+
+ // Handle padding graciously, multiple-of-4 or not
+ assert.strictEqual(Buffer.from("72INjkR5fchcxk9+VgdGPFJDxUBFR5/rMFsghgxADiw==", "base64").length, 32);
+ assert.strictEqual(Buffer.from("72INjkR5fchcxk9-VgdGPFJDxUBFR5_rMFsghgxADiw==", "base64url").length, 32);
+ assert.strictEqual(Buffer.from("72INjkR5fchcxk9+VgdGPFJDxUBFR5/rMFsghgxADiw=", "base64").length, 32);
+ assert.strictEqual(Buffer.from("72INjkR5fchcxk9-VgdGPFJDxUBFR5_rMFsghgxADiw=", "base64url").length, 32);
+ assert.strictEqual(Buffer.from("72INjkR5fchcxk9+VgdGPFJDxUBFR5/rMFsghgxADiw", "base64").length, 32);
+ assert.strictEqual(Buffer.from("72INjkR5fchcxk9-VgdGPFJDxUBFR5_rMFsghgxADiw", "base64url").length, 32);
+ assert.strictEqual(Buffer.from("w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg==", "base64").length, 31);
+ assert.strictEqual(Buffer.from("w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg==", "base64url").length, 31);
+ assert.strictEqual(Buffer.from("w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg=", "base64").length, 31);
+ assert.strictEqual(Buffer.from("w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg=", "base64url").length, 31);
+ assert.strictEqual(Buffer.from("w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg", "base64").length, 31);
+ assert.strictEqual(Buffer.from("w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg", "base64url").length, 31);
+
+ {
+ // This string encodes single '.' character in UTF-16
+ const dot = Buffer.from("//4uAA==", "base64");
+ assert.strictEqual(dot[0], 0xff);
+ assert.strictEqual(dot[1], 0xfe);
+ assert.strictEqual(dot[2], 0x2e);
+ assert.strictEqual(dot[3], 0x00);
+ assert.strictEqual(dot.toString("base64"), "//4uAA==");
+ }
+
+ {
+ // This string encodes single '.' character in UTF-16
+ const dot = Buffer.from("//4uAA", "base64url");
+ assert.strictEqual(dot[0], 0xff);
+ assert.strictEqual(dot[1], 0xfe);
+ assert.strictEqual(dot[2], 0x2e);
+ assert.strictEqual(dot[3], 0x00);
+ assert.strictEqual(dot.toString("base64url"), "__4uAA");
+ }
+
+ {
+ // Writing base64 at a position > 0 should not mangle the result.
+ //
+ // https://github.com/joyent/node/issues/402
+ const segments = ["TWFkbmVzcz8h", "IFRoaXM=", "IGlz", "IG5vZGUuanMh"];
+ const b = Buffer.allocUnsafe(64);
+ let pos = 0;
+
+ for (let i = 0; i < segments.length; ++i) {
+ pos += b.write(segments[i], pos, "base64");
+ }
+ assert.strictEqual(b.toString("latin1", 0, pos), "Madness?! This is node.js!");
+ }
+
+ {
+ // Writing base64url at a position > 0 should not mangle the result.
+ //
+ // https://github.com/joyent/node/issues/402
+ const segments = ["TWFkbmVzcz8h", "IFRoaXM", "IGlz", "IG5vZGUuanMh"];
+ const b = Buffer.allocUnsafe(64);
+ let pos = 0;
+
+ for (let i = 0; i < segments.length; ++i) {
+ pos += b.write(segments[i], pos, "base64url");
+ }
+ assert.strictEqual(b.toString("latin1", 0, pos), "Madness?! This is node.js!");
+ }
+
+ // Regression test for https://github.com/nodejs/node/issues/3496.
+ assert.strictEqual(Buffer.from("=bad".repeat(1e4), "base64").length, 0);
+
+ // Regression test for https://github.com/nodejs/node/issues/11987.
+ assert.deepStrictEqual(Buffer.from("w0 ", "base64"), Buffer.from("w0", "base64"));
+
+ // Regression test for https://github.com/nodejs/node/issues/13657.
+ assert.deepStrictEqual(Buffer.from(" YWJvcnVtLg", "base64"), Buffer.from("YWJvcnVtLg", "base64"));
+
+ {
+ // Creating buffers larger than pool size.
+ const l = Buffer.poolSize + 5;
+ const s = "h".repeat(l);
+ const b = Buffer.from(s);
+
+ for (let i = 0; i < l; i++) {
+ assert.strictEqual(b[i], "h".charCodeAt(0));
+ }
+
+ const sb = b.toString();
+ assert.strictEqual(sb.length, s.length);
+ assert.strictEqual(sb, s);
+ }
+
+ {
+ // test hex toString
+ const hexb = Buffer.allocUnsafe(256);
+ for (let i = 0; i < 256; i++) {
+ hexb[i] = i;
+ }
+ const hexStr = hexb.toString("hex");
+ assert.strictEqual(
+ hexStr,
+ "000102030405060708090a0b0c0d0e0f" +
+ "101112131415161718191a1b1c1d1e1f" +
+ "202122232425262728292a2b2c2d2e2f" +
+ "303132333435363738393a3b3c3d3e3f" +
+ "404142434445464748494a4b4c4d4e4f" +
+ "505152535455565758595a5b5c5d5e5f" +
+ "606162636465666768696a6b6c6d6e6f" +
+ "707172737475767778797a7b7c7d7e7f" +
+ "808182838485868788898a8b8c8d8e8f" +
+ "909192939495969798999a9b9c9d9e9f" +
+ "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf" +
+ "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" +
+ "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" +
+ "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" +
+ "e0e1e2e3e4e5e6e7e8e9eaebecedeeef" +
+ "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+ );
+
+ const hexb2 = Buffer.from(hexStr, "hex");
+ for (let i = 0; i < 256; i++) {
+ assert.strictEqual(hexb2[i], hexb[i]);
+ }
+ }
+
+ // Test single hex character is discarded.
+ assert.strictEqual(Buffer.from("A", "hex").length, 0);
+
+ // Test that if a trailing character is discarded, rest of string is processed.
+ assert.deepStrictEqual(Buffer.from("Abx", "hex"), Buffer.from("Ab", "hex"));
+
+ // Test single base64 char encodes as 0.
+ assert.strictEqual(Buffer.from("A", "base64").length, 0);
+
+ {
+ // Test an invalid slice end.
+ const b = Buffer.from([1, 2, 3, 4, 5]);
+ const b2 = b.toString("hex", 1, 10000);
+ const b3 = b.toString("hex", 1, 5);
+ const b4 = b.toString("hex", 1);
+ assert.strictEqual(b2, b3);
+ assert.strictEqual(b2, b4);
+ }
+
+ function buildBuffer(data) {
+ if (Array.isArray(data)) {
+ const buffer = Buffer.allocUnsafe(data.length);
+ data.forEach((v, k) => (buffer[k] = v));
+ return buffer;
+ }
+ return null;
+ }
+
+ const x = buildBuffer([0x81, 0xa3, 0x66, 0x6f, 0x6f, 0xa3, 0x62, 0x61, 0x72]);
+
+ // assert.strictEqual(x.inspect(), "<Buffer 81 a3 66 6f 6f a3 62 61 72>");
+
+ {
+ const z = x.slice(4);
+ assert.strictEqual(z.length, 5);
+ assert.strictEqual(z[0], 0x6f);
+ assert.strictEqual(z[1], 0xa3);
+ assert.strictEqual(z[2], 0x62);
+ assert.strictEqual(z[3], 0x61);
+ assert.strictEqual(z[4], 0x72);
+ }
+
+ {
+ const z = x.slice(0);
+ assert.strictEqual(z.length, x.length);
+ }
+
+ {
+ const z = x.slice(0, 4);
+ assert.strictEqual(z.length, 4);
+ assert.strictEqual(z[0], 0x81);
+ assert.strictEqual(z[1], 0xa3);
+ }
+
+ {
+ const z = x.slice(0, 9);
+ assert.strictEqual(z.length, 9);
+ }
+
+ {
+ const z = x.slice(1, 4);
+ assert.strictEqual(z.length, 3);
+ assert.strictEqual(z[0], 0xa3);
+ }
+
+ {
+ const z = x.slice(2, 4);
+ assert.strictEqual(z.length, 2);
+ assert.strictEqual(z[0], 0x66);
+ assert.strictEqual(z[1], 0x6f);
+ }
+
+ ["ucs2", "ucs-2", "utf16le", "utf-16le"].forEach(encoding => {
+ const b = Buffer.allocUnsafe(10);
+ b.write("あいうえお", encoding);
+ assert.strictEqual(b.toString(encoding), "あいうえお");
+ });
+
+ ["ucs2", "ucs-2", "utf16le", "utf-16le"].forEach(encoding => {
+ const b = Buffer.allocUnsafe(11);
+ b.write("あいうえお", 1, encoding);
+ assert.strictEqual(b.toString(encoding, 1), "あいうえお");
+ });
+
+ {
+ // latin1 encoding should write only one byte per character.
+ const b = Buffer.from([0xde, 0xad, 0xbe, 0xef]);
+ let s = String.fromCharCode(0xffff);
+ b.write(s, 0, "latin1");
+ assert.strictEqual(b[0], 0xff);
+ assert.strictEqual(b[1], 0xad);
+ assert.strictEqual(b[2], 0xbe);
+ assert.strictEqual(b[3], 0xef);
+ s = String.fromCharCode(0xaaee);
+ b.write(s, 0, "latin1");
+ assert.strictEqual(b[0], 0xee);
+ assert.strictEqual(b[1], 0xad);
+ assert.strictEqual(b[2], 0xbe);
+ assert.strictEqual(b[3], 0xef);
+ }
+
+ {
+ // Binary encoding should write only one byte per character.
+ const b = Buffer.from([0xde, 0xad, 0xbe, 0xef]);
+ let s = String.fromCharCode(0xffff);
+ b.write(s, 0, "latin1");
+ assert.strictEqual(b[0], 0xff);
+ assert.strictEqual(b[1], 0xad);
+ assert.strictEqual(b[2], 0xbe);
+ assert.strictEqual(b[3], 0xef);
+ s = String.fromCharCode(0xaaee);
+ b.write(s, 0, "latin1");
+ assert.strictEqual(b[0], 0xee);
+ assert.strictEqual(b[1], 0xad);
+ assert.strictEqual(b[2], 0xbe);
+ assert.strictEqual(b[3], 0xef);
+ }
+
+ {
+ // https://github.com/nodejs/node-v0.x-archive/pull/1210
+ // Test UTF-8 string includes null character
+ let buf = Buffer.from("\0");
+ assert.strictEqual(buf.length, 1);
+ buf = Buffer.from("\0\0");
+ assert.strictEqual(buf.length, 2);
+ }
+
+ {
+ const buf = Buffer.allocUnsafe(2);
+ assert.strictEqual(buf.write(""), 0); // 0bytes
+ assert.strictEqual(buf.write("\0"), 1); // 1byte (v8 adds null terminator)
+ assert.strictEqual(buf.write("a\0"), 2); // 1byte * 2
+ assert.strictEqual(buf.write("あ"), 0); // 3bytes
+ assert.strictEqual(buf.write("\0あ"), 1); // 1byte + 3bytes
+ assert.strictEqual(buf.write("\0\0あ"), 2); // 1byte * 2 + 3bytes
+ }
+
+ {
+ const buf = Buffer.allocUnsafe(10);
+ assert.strictEqual(buf.write("あいう"), 9); // 3bytes * 3 (v8 adds null term.)
+ assert.strictEqual(buf.write("あいう\0"), 10); // 3bytes * 3 + 1byte
+ }
+
+ {
+ // https://github.com/nodejs/node-v0.x-archive/issues/243
+ // Test write() with maxLength
+ const buf = Buffer.allocUnsafe(4);
+ buf.fill(0xff);
+ assert.strictEqual(buf.write("abcd", 1, 2, "utf8"), 2);
+ assert.strictEqual(buf[0], 0xff);
+ assert.strictEqual(buf[1], 0x61);
+ assert.strictEqual(buf[2], 0x62);
+ assert.strictEqual(buf[3], 0xff);
+
+ buf.fill(0xff);
+ assert.strictEqual(buf.write("abcd", 1, 4), 3);
+ assert.strictEqual(buf[0], 0xff);
+ assert.strictEqual(buf[1], 0x61);
+ assert.strictEqual(buf[2], 0x62);
+ assert.strictEqual(buf[3], 0x63);
+
+ buf.fill(0xff);
+ assert.strictEqual(buf.write("abcd", 1, 2, "utf8"), 2);
+ assert.strictEqual(buf[0], 0xff);
+ assert.strictEqual(buf[1], 0x61);
+ assert.strictEqual(buf[2], 0x62);
+ assert.strictEqual(buf[3], 0xff);
+
+ buf.fill(0xff);
+ assert.strictEqual(buf.write("abcdef", 1, 2, "hex"), 2);
+ assert.strictEqual(buf[0], 0xff);
+ assert.strictEqual(buf[1], 0xab);
+ assert.strictEqual(buf[2], 0xcd);
+ assert.strictEqual(buf[3], 0xff);
+
+ ["ucs2", "ucs-2", "utf16le", "utf-16le"].forEach(encoding => {
+ buf.fill(0xff);
+ assert.strictEqual(buf.write("abcd", 0, 2, encoding), 2);
+ assert.strictEqual(buf[0], 0x61);
+ assert.strictEqual(buf[1], 0x00);
+ assert.strictEqual(buf[2], 0xff);
+ assert.strictEqual(buf[3], 0xff);
+ });
+ }
+
+ {
+ // Test offset returns are correct
+ const b = Buffer.allocUnsafe(16);
+ assert.strictEqual(b.writeUInt32LE(0, 0), 4);
+ assert.strictEqual(b.writeUInt16LE(0, 4), 6);
+ assert.strictEqual(b.writeUInt8(0, 6), 7);
+ assert.strictEqual(b.writeInt8(0, 7), 8);
+ assert.strictEqual(b.writeDoubleLE(0, 8), 16);
+ }
+
+ {
+ // Test unmatched surrogates not producing invalid utf8 output
+ // ef bf bd = utf-8 representation of unicode replacement character
+ // see https://codereview.chromium.org/121173009/
+ let buf = Buffer.from("ab\ud800cd", "utf8");
+ assert.strictEqual(buf[0], 0x61);
+ assert.strictEqual(buf[1], 0x62);
+ assert.strictEqual(buf[2], 0xef);
+ assert.strictEqual(buf[3], 0xbf);
+ assert.strictEqual(buf[4], 0xbd);
+ assert.strictEqual(buf[5], 0x63);
+ assert.strictEqual(buf[6], 0x64);
+
+ buf = Buffer.from("abcd\ud800", "utf8");
+ expect(buf[0]).toBe(0x61);
+ expect(buf[1]).toBe(0x62);
+ expect(buf[2]).toBe(0x63);
+ expect(buf[3]).toBe(0x64);
+ expect(buf[4]).toBe(0xef);
+ expect(buf[5]).toBe(0xbf);
+ expect(buf[6]).toBe(0xbd);
+
+ buf = Buffer.from("\ud800abcd", "utf8");
+ expect(buf[0]).toBe(0xef);
+ expect(buf[1]).toBe(0xbf);
+ expect(buf[2]).toBe(0xbd);
+ expect(buf[3]).toBe(0x61);
+ expect(buf[4]).toBe(0x62);
+ expect(buf[5]).toBe(0x63);
+ expect(buf[6]).toBe(0x64);
+ }
+
+ {
+ // Test for buffer overrun
+ const buf = Buffer.from([0, 0, 0, 0, 0]); // length: 5
+ const sub = buf.slice(0, 4); // length: 4
+ assert.strictEqual(sub.write("12345", "latin1"), 4);
+ assert.strictEqual(buf[4], 0);
+ assert.strictEqual(sub.write("12345", "binary"), 4);
+ assert.strictEqual(buf[4], 0);
+ }
+
+ {
+ // Test alloc with fill option
+ const buf = Buffer.alloc(5, "800A", "hex");
+ assert.strictEqual(buf[0], 128);
+ assert.strictEqual(buf[1], 10);
+ assert.strictEqual(buf[2], 128);
+ assert.strictEqual(buf[3], 10);
+ assert.strictEqual(buf[4], 128);
+ }
+
+ // Check for fractional length args, junk length args, etc.
+ // https://github.com/joyent/node/issues/1758
+
+ // Call .fill() first, stops valgrind warning about uninitialized memory reads.
+ Buffer.allocUnsafe(3.3).fill().toString();
+ // Throws bad argument error in commit 43cb4ec
+ Buffer.alloc(3.3).fill().toString();
+ assert.strictEqual(Buffer.allocUnsafe(3.3).length, 3);
+ assert.strictEqual(Buffer.from({ length: 3.3 }).length, 3);
+ assert.strictEqual(Buffer.from({ length: "BAM" }).length, 0);
+
+ // Make sure that strings are not coerced to numbers.
+ assert.strictEqual(Buffer.from("99").length, 2);
+ assert.strictEqual(Buffer.from("13.37").length, 5);
+
+ // Ensure that the length argument is respected.
+ ["ascii", "utf8", "hex", "base64", "latin1", "binary"].forEach(enc => {
+ assert.strictEqual(Buffer.allocUnsafe(1).write("aaaaaa", 0, 1, enc), 1);
+ });
+
+ {
+ // Regression test, guard against buffer overrun in the base64 decoder.
+ const a = Buffer.allocUnsafe(3);
+ const b = Buffer.from("xxx");
+ a.write("aaaaaaaa", "base64");
+ assert.strictEqual(b.toString(), "xxx");
+ }
+
+ // issue GH-3416
+ Buffer.from(Buffer.allocUnsafe(0), 0, 0);
+
+ // issue GH-5587
+ assert.throws(() => Buffer.alloc(8).writeFloatLE(0, 5), outOfRangeError);
+ assert.throws(() => Buffer.alloc(16).writeDoubleLE(0, 9), outOfRangeError);
+
+ // Attempt to overflow buffers, similar to previous bug in array buffers
+ assert.throws(() => Buffer.allocUnsafe(8).writeFloatLE(0.0, 0xffffffff), outOfRangeError);
+ assert.throws(() => Buffer.allocUnsafe(8).writeFloatLE(0.0, 0xffffffff), outOfRangeError);
+
+ // Ensure negative values can't get past offset
+ assert.throws(() => Buffer.allocUnsafe(8).writeFloatLE(0.0, -1), outOfRangeError);
+ assert.throws(() => Buffer.allocUnsafe(8).writeFloatLE(0.0, -1), outOfRangeError);
+
+ // Test for common write(U)IntLE/BE
+ {
+ let buf = Buffer.allocUnsafe(3);
+ buf.writeUIntLE(0x123456, 0, 3);
+ assert.deepStrictEqual(buf.toJSON().data, [0x56, 0x34, 0x12]);
+ assert.strictEqual(buf.readUIntLE(0, 3), 0x123456);
+
+ buf.fill(0xff);
+ buf.writeUIntBE(0x123456, 0, 3);
+ assert.deepStrictEqual(buf.toJSON().data, [0x12, 0x34, 0x56]);
+ assert.strictEqual(buf.readUIntBE(0, 3), 0x123456);
+
+ buf.fill(0xff);
+ buf.writeIntLE(0x123456, 0, 3);
+ assert.deepStrictEqual(buf.toJSON().data, [0x56, 0x34, 0x12]);
+ assert.strictEqual(buf.readIntLE(0, 3), 0x123456);
+
+ buf.fill(0xff);
+ buf.writeIntBE(0x123456, 0, 3);
+ assert.deepStrictEqual(buf.toJSON().data, [0x12, 0x34, 0x56]);
+ assert.strictEqual(buf.readIntBE(0, 3), 0x123456);
+
+ buf.fill(0xff);
+ buf.writeIntLE(-0x123456, 0, 3);
+ assert.deepStrictEqual(buf.toJSON().data, [0xaa, 0xcb, 0xed]);
+ assert.strictEqual(buf.readIntLE(0, 3), -0x123456);
+
+ buf.fill(0xff);
+ buf.writeIntBE(-0x123456, 0, 3);
+ assert.deepStrictEqual(buf.toJSON().data, [0xed, 0xcb, 0xaa]);
+ assert.strictEqual(buf.readIntBE(0, 3), -0x123456);
+
+ buf.fill(0xff);
+ buf.writeIntLE(-0x123400, 0, 3);
+ assert.deepStrictEqual(buf.toJSON().data, [0x00, 0xcc, 0xed]);
+ assert.strictEqual(buf.readIntLE(0, 3), -0x123400);
+
+ buf.fill(0xff);
+ buf.writeIntBE(-0x123400, 0, 3);
+ assert.deepStrictEqual(buf.toJSON().data, [0xed, 0xcc, 0x00]);
+ assert.strictEqual(buf.readIntBE(0, 3), -0x123400);
+
+ buf.fill(0xff);
+ buf.writeIntLE(-0x120000, 0, 3);
+ assert.deepStrictEqual(buf.toJSON().data, [0x00, 0x00, 0xee]);
+ assert.strictEqual(buf.readIntLE(0, 3), -0x120000);
+
+ buf.fill(0xff);
+ buf.writeIntBE(-0x120000, 0, 3);
+ assert.deepStrictEqual(buf.toJSON().data, [0xee, 0x00, 0x00]);
+ assert.strictEqual(buf.readIntBE(0, 3), -0x120000);
+
+ buf = Buffer.allocUnsafe(5);
+ buf.writeUIntLE(0x1234567890, 0, 5);
+ assert.deepStrictEqual(buf.toJSON().data, [0x90, 0x78, 0x56, 0x34, 0x12]);
+ assert.strictEqual(buf.readUIntLE(0, 5), 0x1234567890);
+
+ buf.fill(0xff);
+ buf.writeUIntBE(0x1234567890, 0, 5);
+ assert.deepStrictEqual(buf.toJSON().data, [0x12, 0x34, 0x56, 0x78, 0x90]);
+ assert.strictEqual(buf.readUIntBE(0, 5), 0x1234567890);
+
+ buf.fill(0xff);
+ buf.writeIntLE(0x1234567890, 0, 5);
+ assert.deepStrictEqual(buf.toJSON().data, [0x90, 0x78, 0x56, 0x34, 0x12]);
+ assert.strictEqual(buf.readIntLE(0, 5), 0x1234567890);
+
+ buf.fill(0xff);
+ buf.writeIntBE(0x1234567890, 0, 5);
+ assert.deepStrictEqual(buf.toJSON().data, [0x12, 0x34, 0x56, 0x78, 0x90]);
+ assert.strictEqual(buf.readIntBE(0, 5), 0x1234567890);
+
+ buf.fill(0xff);
+ buf.writeIntLE(-0x1234567890, 0, 5);
+ assert.deepStrictEqual(buf.toJSON().data, [0x70, 0x87, 0xa9, 0xcb, 0xed]);
+ assert.strictEqual(buf.readIntLE(0, 5), -0x1234567890);
+
+ buf.fill(0xff);
+ buf.writeIntBE(-0x1234567890, 0, 5);
+ assert.deepStrictEqual(buf.toJSON().data, [0xed, 0xcb, 0xa9, 0x87, 0x70]);
+ assert.strictEqual(buf.readIntBE(0, 5), -0x1234567890);
+
+ buf.fill(0xff);
+ buf.writeIntLE(-0x0012000000, 0, 5);
+ assert.deepStrictEqual(buf.toJSON().data, [0x00, 0x00, 0x00, 0xee, 0xff]);
+ assert.strictEqual(buf.readIntLE(0, 5), -0x0012000000);
+
+ buf.fill(0xff);
+ buf.writeIntBE(-0x0012000000, 0, 5);
+ assert.deepStrictEqual(buf.toJSON().data, [0xff, 0xee, 0x00, 0x00, 0x00]);
+ assert.strictEqual(buf.readIntBE(0, 5), -0x0012000000);
+ }
+
+ // Regression test for https://github.com/nodejs/node-v0.x-archive/issues/5482:
+ // should throw but not assert in C++ land.
+ assert.throws(() => Buffer.from("", "buffer"), {
+ code: "ERR_UNKNOWN_ENCODING",
+ name: "TypeError",
+ message: "Unknown encoding: buffer",
+ });
+
+ // Regression test for https://github.com/nodejs/node-v0.x-archive/issues/6111.
+ // Constructing a buffer from another buffer should a) work, and b) not corrupt
+ // the source buffer.
+ {
+ const a = [...Array(128).keys()]; // [0, 1, 2, 3, ... 126, 127]
+ const b = Buffer.from(a);
+ const c = Buffer.from(b);
+ assert.strictEqual(b.length, a.length);
+ assert.strictEqual(c.length, a.length);
+ for (let i = 0, k = a.length; i < k; ++i) {
+ assert.strictEqual(a[i], i);
+ assert.strictEqual(b[i], i);
+ assert.strictEqual(c[i], i);
+ }
+ }
+
+ // if (common.hasCrypto) {
+ // eslint-disable-line node-core/crypto-check
+ // Test truncation after decode
+ const crypto = require("crypto");
+
+ const b1 = Buffer.from("YW55=======", "base64");
+ const b2 = Buffer.from("YW55", "base64");
+
+ assert.strictEqual(
+ crypto.createHash("sha1").update(b1).digest("hex"),
+ crypto.createHash("sha1").update(b2).digest("hex"),
+ );
+ // } else {
+ // common.printSkipMessage("missing crypto");
+ // }
+
+ const ps = Buffer.poolSize;
+ Buffer.poolSize = 0;
+ assert(Buffer.allocUnsafe(1).parent instanceof ArrayBuffer);
+ Buffer.poolSize = ps;
+
+ assert.throws(() => Buffer.allocUnsafe(10).copy(), {
+ code: "ERR_INVALID_ARG_TYPE",
+ name: "TypeError",
+ message: 'The "target" argument must be an instance of Buffer or ' + "Uint8Array. Received undefined",
+ });
+
+ assert.throws(() => Buffer.from(), {
+ name: "TypeError",
+ message:
+ "The first argument must be of type string or an instance of " +
+ "Buffer, ArrayBuffer, or Array or an Array-like Object. Received undefined",
+ });
+ assert.throws(() => Buffer.from(null), {
+ name: "TypeError",
+ message:
+ "The first argument must be of type string or an instance of " +
+ "Buffer, ArrayBuffer, or Array or an Array-like Object. Received null",
+ });
+
+ // Test prototype getters don't throw
+ assert.strictEqual(Buffer.prototype.parent, undefined);
+ assert.strictEqual(Buffer.prototype.offset, undefined);
+ assert.strictEqual(SlowBuffer.prototype.parent, undefined);
+ assert.strictEqual(SlowBuffer.prototype.offset, undefined);
+
+ {
+ // Test that large negative Buffer length inputs don't affect the pool offset.
+ // Use the fromArrayLike() variant here because it's more lenient
+ // about its input and passes the length directly to allocate().
+ assert.deepStrictEqual(Buffer.from({ length: -Buffer.poolSize }), Buffer.from(""));
+ assert.deepStrictEqual(Buffer.from({ length: -100 }), Buffer.from(""));
+
+ // Check pool offset after that by trying to write string into the pool.
+ Buffer.from("abc");
+ }
+
+ // Test that ParseArrayIndex handles full uint32
+ {
+ const errMsg = common.expectsError({
+ code: "ERR_BUFFER_OUT_OF_BOUNDS",
+ name: "RangeError",
+ message: '"offset" is outside of buffer bounds',
+ });
+ assert.throws(() => Buffer.from(new ArrayBuffer(0), -1 >>> 0), errMsg);
+ }
+
+ // ParseArrayIndex() should reject values that don't fit in a 32 bits size_t.
+ assert.throws(() => {
+ const a = Buffer.alloc(1);
+ const b = Buffer.alloc(1);
+ a.copy(b, 0, 0x100000000, 0x100000001);
+ }, outOfRangeError);
+
+ // Unpooled buffer (replaces SlowBuffer)
+ {
+ const ubuf = Buffer.allocUnsafeSlow(10);
+ assert(ubuf);
+ assert(ubuf.buffer);
+ assert.strictEqual(ubuf.buffer.byteLength, 10);
+ }
+
+ // Regression test to verify that an empty ArrayBuffer does not throw.
+ Buffer.from(new ArrayBuffer());
+
+ // Test that ArrayBuffer from a different context is detected correctly.
+ // const arrayBuf = vm.runInNewContext("new ArrayBuffer()");
+ // Buffer.from(arrayBuf);
+ // Buffer.from({ buffer: arrayBuf });
+
+ assert.throws(() => Buffer.alloc({ valueOf: () => 1 }), /"size" argument must be of type number/);
+ assert.throws(() => Buffer.alloc({ valueOf: () => -1 }), /"size" argument must be of type number/);
+
+ assert.strictEqual(Buffer.prototype.toLocaleString, Buffer.prototype.toString);
+ {
+ const buf = Buffer.from("test");
+ assert.strictEqual(buf.toLocaleString(), buf.toString());
+ }
+
+ assert.throws(
+ () => {
+ Buffer.alloc(0x1000, "This is not correctly encoded", "hex");
+ },
+ {
+ code: "ERR_INVALID_ARG_VALUE",
+ name: "TypeError",
+ },
+ );
+
+ assert.throws(
+ () => {
+ Buffer.alloc(0x1000, "c", "hex");
+ },
+ {
+ code: "ERR_INVALID_ARG_VALUE",
+ name: "TypeError",
+ },
+ );
+
+ assert.throws(
+ () => {
+ Buffer.alloc(1, Buffer.alloc(0));
+ },
+ {
+ code: "ERR_INVALID_ARG_VALUE",
+ name: "TypeError",
+ },
+ );
+
+ assert.throws(
+ () => {
+ Buffer.alloc(40, "x", 20);
+ },
+ {
+ code: "ERR_INVALID_ARG_TYPE",
+ name: "TypeError",
+ },
+ );
+});
+
+it("Buffer.toJSON()", () => {
+ expect(JSON.stringify(Buffer.from("hello"))).toBe(
+ JSON.stringify({
+ type: "Buffer",
+ data: [104, 101, 108, 108, 111],
+ }),
+ );
+});
+
+it("buffer", () => {
+ var buf = new Buffer(20);
+ gc();
+ // if this fails or infinitely loops, it means there is a memory issue with the JSC::Structure object
+ expect(Object.keys(buf).length > 0).toBe(true);
+ gc();
+ expect(buf.write("hello world ")).toBe(12);
+ expect(buf.write("hello world ", "utf8")).toBe(12);
+
+ gc();
+ expect(buf.toString("utf8", 0, "hello world ".length)).toBe("hello world ");
+ gc();
+ expect(buf.toString("base64url", 0, "hello world ".length)).toBe(btoa("hello world "));
+ gc();
+ expect(buf instanceof Uint8Array).toBe(true);
+ gc();
+ expect(buf instanceof Buffer).toBe(true);
+ gc();
+ expect(buf.slice() instanceof Uint8Array).toBe(true);
+ gc();
+ expect(buf.slice(0, 1) instanceof Buffer).toBe(true);
+ gc();
+ expect(buf.slice(0, 1) instanceof Uint8Array).toBe(true);
+ gc();
+ expect(buf.slice(0, 1) instanceof Buffer).toBe(true);
+ gc();
+ expect(buf.slice(0, 0).length).toBe(0);
+});
+
+it("Buffer", () => {
+ var inputs = ["hello world", "hello world".repeat(100), `😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌`];
+ var good = inputs.map(a => new TextEncoder().encode(a));
+ for (let i = 0; i < inputs.length; i++) {
+ var input = inputs[i];
+ expect(new Buffer(input).toString("utf8")).toBe(inputs[i]);
+ gc();
+ expect(Array.from(new Buffer(input)).join(",")).toBe(good[i].join(","));
+ gc();
+ expect(Buffer.byteLength(input)).toBe(good[i].length);
+ gc();
+ expect(Buffer.from(input).byteLength).toBe(Buffer.byteLength(input));
+ }
+});
+
+it("Buffer.byteLength", () => {
+ expect(Buffer.byteLength("😀😃😄😁😆😅😂🤣☺️😊😊😇")).toBe(
+ new TextEncoder().encode("😀😃😄😁😆😅😂🤣☺️😊😊😇").byteLength,
+ );
+});
+
+it("Buffer.isBuffer", () => {
+ expect(Buffer.isBuffer(new Buffer(1))).toBe(true);
+ gc();
+ expect(Buffer.isBuffer(new Buffer(0))).toBe(true);
+ gc();
+ expect(Buffer.isBuffer(new Uint8Array(0))).toBe(false);
+ gc();
+ expect(Buffer.isBuffer(new Uint8Array(1))).toBe(false);
+ gc();
+ var a = new Uint8Array(1);
+ gc();
+ expect(Buffer.isBuffer(a)).toBe(false);
+ gc();
+ a = new Buffer(a.buffer);
+ gc();
+ expect(Buffer.isBuffer(a)).toBe(true);
+ gc();
+ expect(a instanceof Buffer).toBe(true);
+ expect(a instanceof Uint8Array).toBe(true);
+ expect(new Uint8Array(0) instanceof Buffer).toBe(false);
+
+ // DOMJIT
+ for (let i = 0; i < 9000; i++) {
+ if (!Buffer.isBuffer(a)) {
+ throw new Error("Buffer.isBuffer failed");
+ }
+
+ if (Buffer.isBuffer("wat")) {
+ throw new Error("Buffer.isBuffer failed");
+ }
+ }
+});
+
+it("writeInt", () => {
+ var buf = new Buffer(1024);
+ var data = new DataView(buf.buffer);
+ buf.writeInt32BE(100);
+ expect(data.getInt32(0, false)).toBe(100);
+ buf.writeInt32BE(100);
+ expect(data.getInt32(0, false)).toBe(100);
+ var childBuf = buf.subarray(0, 4);
+ expect(data.getInt32(0, false)).toBe(100);
+ expect(childBuf.readInt32BE(0, false)).toBe(100);
+});
+
+it("Buffer.from", () => {
+ expect(Buffer.from("hello world").toString("utf8")).toBe("hello world");
+ expect(Buffer.from("hello world", "ascii").toString("utf8")).toBe("hello world");
+ expect(Buffer.from("hello world", "latin1").toString("utf8")).toBe("hello world");
+ gc();
+ expect(Buffer.from([254]).join(",")).toBe("254");
+
+ expect(Buffer.from([254], "utf8").join(",")).toBe("254");
+ expect(Buffer.from([254], "utf-8").join(",")).toBe("254");
+ expect(Buffer.from([254], "latin").join(",")).toBe("254");
+ expect(Buffer.from([254], "uc2").join(",")).toBe("254");
+ expect(Buffer.from([254], "utf16").join(",")).toBe("254");
+ expect(Buffer.isBuffer(Buffer.from([254], "utf16"))).toBe(true);
+
+ expect(() => Buffer.from(123).join(",")).toThrow();
+
+ expect(Buffer.from({ length: 124 }).join(",")).toBe(Uint8Array.from({ length: 124 }).join(","));
+
+ expect(Buffer.from(new ArrayBuffer(1024), 0, 512).join(",")).toBe(new Uint8Array(512).join(","));
+
+ expect(Buffer.from(new Buffer(new ArrayBuffer(1024), 0, 512)).join(",")).toBe(new Uint8Array(512).join(","));
+ gc();
+});
+
+it("Buffer.from latin1 vs ascii", () => {
+ const simpleBuffer = Buffer.from("\xa4", "binary");
+ expect(simpleBuffer.toString("latin1")).toBe("¤");
+ expect(simpleBuffer.toString("ascii")).toBe("$");
+ gc();
+ const asciiBuffer = Buffer.from("\xa4", "ascii");
+ expect(asciiBuffer.toString("latin1")).toBe("¤");
+ expect(asciiBuffer.toString("ascii")).toBe("$");
+ gc();
+});
+
+it("Buffer.equals", () => {
+ var a = new Uint8Array(10);
+ a[2] = 1;
+ var b = new Uint8Array(10);
+ b[2] = 1;
+ a = new Buffer(a.buffer);
+ b = new Buffer(b.buffer);
+ expect(a.equals(b)).toBe(true);
+ b[2] = 0;
+ expect(a.equals(b)).toBe(false);
+});
+
+it("Buffer.compare", () => {
+ var a = new Uint8Array(10);
+ a[2] = 1;
+ var b = new Uint8Array(10);
+ b[2] = 1;
+ a = new Buffer(a.buffer);
+ b = new Buffer(b.buffer);
+ expect(a.compare(b)).toBe(0);
+ b[2] = 0;
+ expect(a.compare(b)).toBe(1);
+ expect(b.compare(a)).toBe(-1);
+
+ const buf = Buffer.from("0123456789", "utf8");
+ const expectedSameBufs = [
+ [buf.slice(-10, 10), Buffer.from("0123456789", "utf8")],
+ [buf.slice(-20, 10), Buffer.from("0123456789", "utf8")],
+ [buf.slice(-20, -10), Buffer.from("", "utf8")],
+ [buf.slice(), Buffer.from("0123456789", "utf8")],
+ [buf.slice(0), Buffer.from("0123456789", "utf8")],
+ [buf.slice(0, 0), Buffer.from("", "utf8")],
+ [buf.slice(undefined), Buffer.from("0123456789", "utf8")],
+ [buf.slice("foobar"), Buffer.from("0123456789", "utf8")],
+ [buf.slice(undefined, undefined), Buffer.from("0123456789", "utf8")],
+ [buf.slice(2), Buffer.from("23456789", "utf8")],
+ [buf.slice(5), Buffer.from("56789", "utf8")],
+ [buf.slice(10), Buffer.from("", "utf8")],
+ [buf.slice(5, 8), Buffer.from("567", "utf8")],
+ [buf.slice(8, -1), Buffer.from("8", "utf8")],
+ [buf.slice(-10), Buffer.from("0123456789", "utf8")],
+ [buf.slice(0, -9), Buffer.from("0", "utf8")],
+ [buf.slice(0, -10), Buffer.from("", "utf8")],
+ [buf.slice(0, -1), Buffer.from("012345678", "utf8")],
+ [buf.slice(2, -2), Buffer.from("234567", "utf8")],
+ [buf.slice(0, 65536), Buffer.from("0123456789", "utf8")],
+ [buf.slice(65536, 0), Buffer.from("", "utf8")],
+ [buf.slice(-5, -8), Buffer.from("", "utf8")],
+ [buf.slice(-5, -3), Buffer.from("56", "utf8")],
+ [buf.slice(-10, 10), Buffer.from("0123456789", "utf8")],
+ [buf.slice("0", "1"), Buffer.from("0", "utf8")],
+ [buf.slice("-5", "10"), Buffer.from("56789", "utf8")],
+ [buf.slice("-10", "10"), Buffer.from("0123456789", "utf8")],
+ [buf.slice("-10", "-5"), Buffer.from("01234", "utf8")],
+ [buf.slice("-10", "-0"), Buffer.from("", "utf8")],
+ [buf.slice("111"), Buffer.from("", "utf8")],
+ [buf.slice("0", "-111"), Buffer.from("", "utf8")],
+ ];
+
+ for (let i = 0, s = buf.toString(); i < buf.length; ++i) {
+ expectedSameBufs.push(
+ [buf.slice(i), Buffer.from(s.slice(i))],
+ [buf.slice(0, i), Buffer.from(s.slice(0, i))],
+ [buf.slice(-i), Buffer.from(s.slice(-i))],
+ [buf.slice(0, -i), Buffer.from(s.slice(0, -i))],
+ );
+ }
+
+ expectedSameBufs.forEach(([buf1, buf2]) => {
+ expect(Buffer.compare(buf1, buf2)).toBe(0);
+ });
+
+ {
+ const buf = Buffer.from([
+ 1, 29, 0, 0, 1, 143, 216, 162, 92, 254, 248, 63, 0, 0, 0, 18, 184, 6, 0, 175, 29, 0, 8, 11, 1, 0, 0,
+ ]);
+ const chunk1 = Buffer.from([1, 29, 0, 0, 1, 143, 216, 162, 92, 254, 248, 63, 0]);
+ const chunk2 = Buffer.from([0, 0, 18, 184, 6, 0, 175, 29, 0, 8, 11, 1, 0, 0]);
+ const middle = buf.length / 2;
+
+ expect(JSON.stringify(buf.slice(0, middle))).toBe(JSON.stringify(chunk1));
+ expect(JSON.stringify(buf.slice(middle))).toBe(JSON.stringify(chunk2));
+ }
+});
+
+it("Buffer.copy", () => {
+ var array1 = new Uint8Array(128);
+ array1.fill(100);
+ array1 = new Buffer(array1.buffer);
+ var array2 = new Uint8Array(128);
+ array2.fill(200);
+ array2 = new Buffer(array2.buffer);
+ var array3 = new Uint8Array(128);
+ array3 = new Buffer(array3.buffer);
+ gc();
+ expect(array1.copy(array2)).toBe(128);
+ expect(array1.join("")).toBe(array2.join(""));
+
+ {
+ // Create two `Buffer` instances.
+ const buf1 = Buffer.allocUnsafe(26);
+ const buf2 = Buffer.allocUnsafe(26).fill("!");
+
+ for (let i = 0; i < 26; i++) {
+ // 97 is the decimal ASCII value for 'a'.
+ buf1[i] = i + 97;
+ }
+
+ // Copy `buf1` bytes 16 through 19 into `buf2` starting at byte 8 of `buf2`.
+ buf1.copy(buf2, 8, 16, 20);
+ expect(buf2.toString("ascii", 0, 25)).toBe("!!!!!!!!qrst!!!!!!!!!!!!!");
+ }
+
+ {
+ const buf = Buffer.allocUnsafe(26);
+
+ for (let i = 0; i < 26; i++) {
+ // 97 is the decimal ASCII value for 'a'.
+ buf[i] = i + 97;
+ }
+
+ buf.copy(buf, 0, 4, 10);
+ expect(buf.toString()).toBe("efghijghijklmnopqrstuvwxyz");
+ }
+});
+
+export function fillRepeating(dstBuffer, start, end) {
+ let len = dstBuffer.length, // important: use indices length, not byte-length
+ sLen = end - start,
+ p = sLen; // set initial position = source sequence length
+
+ // step 2: copy existing data doubling segment length per iteration
+ while (p < len) {
+ if (p + sLen > len) sLen = len - p; // if not power of 2, truncate last segment
+ dstBuffer.copyWithin(p, start, sLen); // internal copy
+ p += sLen; // add current length to offset
+ sLen <<= 1; // double length for next segment
+ }
+}
+
+describe("Buffer.fill string", () => {
+ for (let text of ["hello world", "1234567890", "\uD83D\uDE00", "😀😃😄😁😆😅😂🤣☺️😊😊😇"]) {
+ it(text, () => {
+ var input = new Buffer(1024);
+ input.fill(text);
+ var demo = new Uint8Array(1024);
+ var encoded = new TextEncoder().encode(text);
+
+ demo.set(encoded);
+ fillRepeating(demo, 0, encoded.length);
+ expect(input.join("")).toBe(demo.join(""));
+ });
+ }
+});
+
+it("Buffer.fill 1 char string", () => {
+ var input = new Buffer(1024);
+ input.fill("h");
+ var demo = new Uint8Array(1024);
+ var encoded = new TextEncoder().encode("h");
+
+ demo.set(encoded);
+ fillRepeating(demo, 0, encoded.length);
+ expect(input.join("")).toBe(demo.join(""));
+});
+
+it("Buffer.concat", () => {
+ var array1 = new Uint8Array(128);
+ array1.fill(100);
+ var array2 = new Uint8Array(128);
+ array2.fill(200);
+ var array3 = new Uint8Array(128);
+ array3.fill(300);
+ gc();
+ expect(Buffer.concat([array1, array2, array3]).join("")).toBe(array1.join("") + array2.join("") + array3.join(""));
+ expect(Buffer.concat([array1, array2, array3], 222).length).toBe(222);
+ expect(Buffer.concat([array1, array2, array3], 222).subarray(0, 128).join("")).toBe("100".repeat(128));
+ expect(Buffer.concat([array1, array2, array3], 222).subarray(129, 222).join("")).toBe("200".repeat(222 - 129));
+});
+
+it("read", () => {
+ var buf = new Buffer(1024);
+ var data = new DataView(buf.buffer);
+ function reset() {
+ new Uint8Array(buf.buffer).fill(0);
+ }
+ data.setBigInt64(0, BigInt(1000), false);
+ expect(buf.readBigInt64BE(0)).toBe(BigInt(1000));
+ reset();
+
+ data.setBigInt64(0, BigInt(1000), true);
+ expect(buf.readBigInt64LE(0)).toBe(BigInt(1000));
+ reset();
+
+ data.setBigUint64(0, BigInt(1000), false);
+ expect(buf.readBigUInt64BE(0)).toBe(BigInt(1000));
+ reset();
+
+ data.setBigUint64(0, BigInt(1000), true);
+ expect(buf.readBigUInt64LE(0)).toBe(BigInt(1000));
+ reset();
+
+ data.setFloat64(0, 1000, false);
+ expect(buf.readDoubleBE(0)).toBe(1000);
+ reset();
+
+ data.setFloat64(0, 1000, true);
+ expect(buf.readDoubleLE(0)).toBe(1000);
+ reset();
+
+ data.setFloat32(0, 1000, false);
+ expect(buf.readFloatBE(0)).toBe(1000);
+ reset();
+
+ data.setFloat32(0, 1000, true);
+ expect(buf.readFloatLE(0)).toBe(1000);
+ reset();
+
+ data.setInt16(0, 1000, false);
+ expect(buf.readInt16BE(0)).toBe(1000);
+ reset();
+
+ data.setInt16(0, 1000, true);
+ expect(buf.readInt16LE(0)).toBe(1000);
+ reset();
+
+ data.setInt32(0, 1000, false);
+ expect(buf.readInt32BE(0)).toBe(1000);
+ reset();
+
+ data.setInt32(0, 1000, true);
+ expect(buf.readInt32LE(0)).toBe(1000);
+ reset();
+
+ data.setInt8(0, 100, false);
+ expect(buf.readInt8(0)).toBe(100);
+ reset();
+
+ data.setUint16(0, 1000, false);
+ expect(buf.readUInt16BE(0)).toBe(1000);
+ reset();
+
+ data.setUint16(0, 1000, true);
+ expect(buf.readUInt16LE(0)).toBe(1000);
+ reset();
+
+ data.setUint32(0, 1000, false);
+ expect(buf.readUInt32BE(0)).toBe(1000);
+ reset();
+
+ data.setUint32(0, 1000, true);
+ expect(buf.readUInt32LE(0)).toBe(1000);
+ reset();
+
+ data.setUint8(0, 255, false);
+ expect(buf.readUInt8(0)).toBe(255);
+ reset();
+
+ data.setUint8(0, 255, false);
+ expect(buf.readUInt8(0)).toBe(255);
+ reset();
+});
+
+// this is for checking the simd code path
+it("write long utf16 string works", () => {
+ const long = "😀😃😄😁😆😅😂🤣☺️😊😊😇".repeat(200);
+ const buf = Buffer.alloc(long.length * 2);
+ buf.write(long, 0, "utf16le");
+ expect(buf.toString("utf16le")).toBe(long);
+ for (let offset = 0; offset < long.length; offset += 48) {
+ expect(buf.toString("utf16le", offset, offset + 4)).toBe("😀");
+ expect(buf.toString("utf16le", offset, offset + 8)).toBe("😀😃");
+ expect(buf.toString("utf16le", offset, offset + 12)).toBe("😀😃😄");
+ expect(buf.toString("utf16le", offset, offset + 16)).toBe("😀😃😄😁");
+ expect(buf.toString("utf16le", offset, offset + 20)).toBe("😀😃😄😁😆");
+ expect(buf.toString("utf16le", offset, offset + 24)).toBe("😀😃😄😁😆😅");
+ expect(buf.toString("utf16le", offset, offset + 28)).toBe("😀😃😄😁😆😅😂");
+ expect(buf.toString("utf16le", offset, offset + 32)).toBe("😀😃😄😁😆😅😂🤣");
+ expect(buf.toString("utf16le", offset, offset + 36)).toBe("😀😃😄😁😆😅😂🤣☺️");
+ expect(buf.toString("utf16le", offset, offset + 40)).toBe("😀😃😄😁😆😅😂🤣☺️😊");
+ expect(buf.toString("utf16le", offset, offset + 44)).toBe("😀😃😄😁😆😅😂🤣☺️😊😊");
+ expect(buf.toString("utf16le", offset, offset + 48)).toBe("😀😃😄😁😆😅😂🤣☺️😊😊😇");
+ }
+});
+
+it("write", () => {
+ const resultMap = new Map([
+ ["utf8", Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])],
+ ["ucs2", Buffer.from([102, 0, 111, 0, 111, 0, 0, 0, 0])],
+ ["ascii", Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])],
+ ["latin1", Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])],
+ ["binary", Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])],
+ ["utf16le", Buffer.from([102, 0, 111, 0, 111, 0, 0, 0, 0])],
+ ["base64", Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])],
+ ["base64url", Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])],
+ ["hex", Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])],
+ ]);
+
+ let buf = Buffer.alloc(9);
+ function reset() {
+ new Uint8Array(buf.buffer).fill(0);
+ }
+
+ // utf8, ucs2, ascii, latin1, utf16le
+ const encodings = ["utf8", "utf-8", "ucs2", "ucs-2", "ascii", "latin1", "binary", "utf16le", "utf-16le"];
+
+ encodings
+ .reduce((es, e) => es.concat(e, e.toUpperCase()), [])
+ .forEach(encoding => {
+ reset();
+
+ const len = Buffer.byteLength("foo", encoding);
+ expect(buf.write("foo", 0, len, encoding)).toBe(len);
+
+ if (encoding.includes("-")) encoding = encoding.replace("-", "");
+
+ expect(buf).toStrictEqual(resultMap.get(encoding.toLowerCase()));
+ });
+
+ // base64
+ ["base64", "BASE64", "base64url", "BASE64URL"].forEach(encoding => {
+ reset();
+
+ const len = Buffer.byteLength("Zm9v", encoding);
+
+ expect(buf.write("Zm9v", 0, len, encoding)).toBe(len);
+ expect(buf).toStrictEqual(resultMap.get(encoding.toLowerCase()));
+ });
+
+ // hex
+ ["hex", "HEX"].forEach(encoding => {
+ reset();
+ const len = Buffer.byteLength("666f6f", encoding);
+
+ expect(buf.write("666f6f", 0, len, encoding)).toBe(len);
+ expect(buf).toStrictEqual(resultMap.get(encoding.toLowerCase()));
+ });
+
+ // UCS-2 overflow CVE-2018-12115
+ for (let i = 1; i < 4; i++) {
+ // Allocate two Buffers sequentially off the pool. Run more than once in case
+ // we hit the end of the pool and don't get sequential allocations
+ const x = Buffer.allocUnsafe(4).fill(0);
+ const y = Buffer.allocUnsafe(4).fill(1);
+ // Should not write anything, pos 3 doesn't have enough room for a 16-bit char
+ expect(x.write("ыыыыыы", 3, "ucs2")).toBe(0);
+ // CVE-2018-12115 experienced via buffer overrun to next block in the pool
+ expect(Buffer.compare(y, Buffer.alloc(4, 1))).toBe(0);
+ }
+
+ // // Should not write any data when there is no space for 16-bit chars
+ const z = Buffer.alloc(4, 0);
+ expect(z.write("\u0001", 3, "ucs2")).toBe(0);
+ expect(Buffer.compare(z, Buffer.alloc(4, 0))).toBe(0);
+ // Make sure longer strings are written up to the buffer end.
+ expect(z.write("abcd", 2)).toBe(2);
+ expect([...z]).toStrictEqual([0, 0, 0x61, 0x62]);
+
+ //Large overrun could corrupt the process with utf8
+ expect(Buffer.alloc(4).write("a".repeat(100), 3, "utf8")).toBe(1);
+
+ // Large overrun could corrupt the process
+ expect(Buffer.alloc(4).write("ыыыыыы".repeat(100), 3, "utf16le")).toBe(0);
+
+ {
+ // .write() does not affect the byte after the written-to slice of the Buffer.
+ // Refs: https://github.com/nodejs/node/issues/26422
+ const buf = Buffer.alloc(8);
+ expect(buf.write("ыы", 1, "utf16le")).toBe(4);
+ expect([...buf]).toStrictEqual([0, 0x4b, 0x04, 0x4b, 0x04, 0, 0, 0]);
+ }
+});
+
+it("includes", () => {
+ const buf = Buffer.from("this is a buffer");
+
+ expect(buf.includes("this")).toBe(true);
+ expect(buf.includes("is")).toBe(true);
+ expect(buf.includes(Buffer.from("a buffer"))).toBe(true);
+ expect(buf.includes(97)).toBe(true);
+ expect(buf.includes(Buffer.from("a buffer example"))).toBe(false);
+ expect(buf.includes(Buffer.from("a buffer example").slice(0, 8))).toBe(true);
+ expect(buf.includes("this", 4)).toBe(false);
+});
+
+it("indexOf", () => {
+ const buf = Buffer.from("this is a buffer");
+
+ expect(buf.indexOf("this")).toBe(0);
+ expect(buf.indexOf("is")).toBe(2);
+ expect(buf.indexOf(Buffer.from("a buffer"))).toBe(8);
+ expect(buf.indexOf(97)).toBe(8);
+ expect(buf.indexOf(Buffer.from("a buffer example"))).toBe(-1);
+ expect(buf.indexOf(Buffer.from("a buffer example").slice(0, 8))).toBe(8);
+
+ const utf16Buffer = Buffer.from("\u039a\u0391\u03a3\u03a3\u0395", "utf16le");
+
+ expect(utf16Buffer.indexOf("\u03a3", 0, "utf16le")).toBe(4);
+ expect(utf16Buffer.indexOf("\u03a3", -4, "utf16le")).toBe(6);
+
+ const b = Buffer.from("abcdef");
+
+ // Passing a value that's a number, but not a valid byte.
+ // Prints: 2, equivalent to searching for 99 or 'c'.
+ expect(b.indexOf(99.9)).toBe(2);
+ expect(b.indexOf(256 + 99)).toBe(2);
+
+ // Passing a byteOffset that coerces to NaN or 0.
+ // Prints: 1, searching the whole buffer.
+ expect(b.indexOf("b", undefined)).toBe(1);
+ expect(b.indexOf("b", {})).toBe(1);
+ expect(b.indexOf("b", null)).toBe(1);
+ expect(b.indexOf("b", [])).toBe(1);
+});
+
+it("lastIndexOf", () => {
+ const buf = Buffer.from("this buffer is a buffer");
+
+ expect(buf.lastIndexOf("this")).toBe(0);
+ expect(buf.lastIndexOf("this", 0)).toBe(0);
+ expect(buf.lastIndexOf("this", -1000)).toBe(-1);
+ expect(buf.lastIndexOf("buffer")).toBe(17);
+ expect(buf.lastIndexOf(Buffer.from("buffer"))).toBe(17);
+ expect(buf.lastIndexOf(97)).toBe(15);
+ expect(buf.lastIndexOf(Buffer.from("yolo"))).toBe(-1);
+ expect(buf.lastIndexOf("buffer", 5)).toBe(5);
+ expect(buf.lastIndexOf("buffer", 4)).toBe(-1);
+
+ const utf16Buffer = Buffer.from("\u039a\u0391\u03a3\u03a3\u0395", "utf16le");
+
+ expect(utf16Buffer.lastIndexOf("\u03a3", undefined, "utf16le")).toBe(6);
+ expect(utf16Buffer.lastIndexOf("\u03a3", -5, "utf16le")).toBe(4);
+
+ const b = Buffer.from("abcdef");
+
+ // Passing a value that's a number, but not a valid byte.
+ // Prints: 2, equivalent to searching for 99 or 'c'.
+ expect(b.lastIndexOf(99.9)).toBe(2);
+ expect(b.lastIndexOf(256 + 99)).toBe(2);
+
+ // Passing a byteOffset that coerces to NaN or 0.
+ // Prints: 1, searching the whole buffer.
+ expect(b.lastIndexOf("b", undefined)).toBe(1);
+ expect(b.lastIndexOf("b", {})).toBe(1);
+
+ // Passing a byteOffset that coerces to 0.
+ // Prints: -1, equivalent to passing 0.
+ expect(b.lastIndexOf("b", null)).toBe(-1);
+ expect(b.lastIndexOf("b", [])).toBe(-1);
+});
+
+for (let fn of [Buffer.prototype.slice, Buffer.prototype.subarray]) {
+ it(`Buffer.${fn.name}`, () => {
+ const buf = new Buffer("buffer");
+ const slice = fn.call(buf, 1, 3);
+ expect(slice.toString()).toBe("uf");
+ const slice2 = fn.call(slice, 100);
+ expect(slice2.toString()).toBe("");
+
+ const slice3 = fn.call(slice, -1);
+ expect(slice3.toString()).toBe("f");
+ });
+}
+
+it("Buffer.from(base64)", () => {
+ const buf = Buffer.from("aGVsbG8gd29ybGQ=", "base64");
+ expect(buf.toString()).toBe("hello world");
+
+ expect(Buffer.from(btoa('console.log("hello world")\n'), "base64").toString()).toBe('console.log("hello world")\n');
+});
+
+it("Buffer.swap16", () => {
+ const examples = [
+ ["", ""],
+ ["a1", "1a"],
+ ["a1b2", "1a2b"],
+ ];
+
+ for (let i = 0; i < examples.length; i++) {
+ const input = examples[i][0];
+ const output = examples[i][1];
+ const buf = Buffer.from(input, "utf-8");
+
+ const ref = buf.swap16();
+ expect(ref instanceof Buffer).toBe(true);
+ expect(buf.toString()).toBe(output);
+ }
+
+ const buf = Buffer.from("123", "utf-8");
+ try {
+ buf.swap16();
+ expect(false).toBe(true);
+ } catch (exception) {
+ expect(exception.message).toBe("Buffer size must be a multiple of 16-bits");
+ }
+});
+
+it("Buffer.swap32", () => {
+ const examples = [
+ ["", ""],
+ ["a1b2", "2b1a"],
+ ["a1b2c3d4", "2b1a4d3c"],
+ ];
+
+ for (let i = 0; i < examples.length; i++) {
+ const input = examples[i][0];
+ const output = examples[i][1];
+ const buf = Buffer.from(input, "utf-8");
+
+ const ref = buf.swap32();
+ expect(ref instanceof Buffer).toBe(true);
+ expect(buf.toString()).toBe(output);
+ }
+
+ const buf = Buffer.from("12345", "utf-8");
+ try {
+ buf.swap32();
+ expect(false).toBe(true);
+ } catch (exception) {
+ expect(exception.message).toBe("Buffer size must be a multiple of 32-bits");
+ }
+});
+
+it("Buffer.swap64", () => {
+ const examples = [
+ ["", ""],
+ ["a1b2c3d4", "4d3c2b1a"],
+ ["a1b2c3d4e5f6g7h8", "4d3c2b1a8h7g6f5e"],
+ ];
+
+ for (let i = 0; i < examples.length; i++) {
+ const input = examples[i][0];
+ const output = examples[i][1];
+ const buf = Buffer.from(input, "utf-8");
+
+ const ref = buf.swap64();
+ expect(ref instanceof Buffer).toBe(true);
+ expect(buf.toString()).toBe(output);
+ }
+
+ const buf = Buffer.from("123456789", "utf-8");
+ try {
+ buf.swap64();
+ expect(false).toBe(true);
+ } catch (exception) {
+ expect(exception.message).toBe("Buffer size must be a multiple of 64-bits");
+ }
+});
+
+it("Buffer.toString regessions", () => {
+ expect(
+ Buffer.from([65, 0])
+ .toString("utf16le")
+ .split("")
+ .map(x => x.charCodeAt(0)),
+ ).toEqual([65]);
+ expect(Buffer.from([65, 0]).toString("base64")).toBe("QQA=");
+ expect(Buffer.from('{"alg":"RS256","typ":"JWT"}', "latin1").toString("latin1")).toBe('{"alg":"RS256","typ":"JWT"}');
+ expect(Buffer.from('{"alg":"RS256","typ":"JWT"}', "utf8").toString("utf8")).toBe('{"alg":"RS256","typ":"JWT"}');
+});
+
+it("Buffer.toString(utf16le)", () => {
+ const buf = Buffer.from("hello world", "utf16le");
+ expect(buf.toString("utf16le")).toBe("hello world");
+ expect(buf.toString("utf16le", 0, 5)).toBe("he");
+});
+
+it("Buffer.toString(binary)", () => {
+ var x = Buffer.from("<?xm", "binary");
+ expect(x.toString("binary")).toBe("<?xm");
+});
+
+it("Buffer.toString(base64)", () => {
+ {
+ const buf = Buffer.from("hello world");
+ expect(buf.toString("base64")).toBe("aGVsbG8gd29ybGQ=");
+ }
+
+ {
+ expect(Buffer.from(`console.log("hello world")\n`).toString("base64")).toBe(btoa('console.log("hello world")\n'));
+ }
+});
+
+it("Buffer can be mocked", () => {
+ function MockBuffer() {
+ const noop = function () {};
+ const res = Buffer.alloc(0);
+ for (const op in Buffer.prototype) {
+ if (typeof res[op] === "function") {
+ res[op] = noop;
+ }
+ }
+ return res;
+ }
+
+ const buf = MockBuffer();
+
+ expect(() => {
+ buf.write("hello world");
+ buf.writeUint16BE(0);
+ buf.writeUint32BE(0);
+ buf.writeBigInt64BE(0);
+ buf.writeBigUInt64BE(0);
+ buf.writeBigInt64LE(0);
+ buf.writeBigUInt64LE(0);
+ }).not.toThrow();
+});
+
+it("constants", () => {
+ expect(BufferModule.constants.MAX_LENGTH).toBe(4294967296);
+ expect(BufferModule.constants.MAX_STRING_LENGTH).toBe(536870888);
+ expect(BufferModule.default.constants.MAX_LENGTH).toBe(4294967296);
+ expect(BufferModule.default.constants.MAX_STRING_LENGTH).toBe(536870888);
+});
+
+it("File", () => {
+ expect(BufferModule.File).toBe(Blob);
+});
+
+it("transcode", () => {
+ expect(typeof BufferModule.transcode).toBe("undefined");
+
+ // This is a masqueradesAsUndefined function
+ expect(() => BufferModule.transcode()).toThrow("Not implemented");
+});
+
+it("Buffer.from (Node.js test/test-buffer-from.js)", () => {
+ const checkString = "test";
+
+ const check = Buffer.from(checkString);
+
+ class MyString extends String {
+ constructor() {
+ super(checkString);
+ }
+ }
+
+ class MyPrimitive {
+ [Symbol.toPrimitive]() {
+ return checkString;
+ }
+ }
+
+ class MyBadPrimitive {
+ [Symbol.toPrimitive]() {
+ return 1;
+ }
+ }
+
+ expect(Buffer.from(new String(checkString))).toStrictEqual(check);
+ expect(Buffer.from(new MyString())).toStrictEqual(check);
+ expect(Buffer.from(new MyPrimitive())).toStrictEqual(check);
+
+ [
+ {},
+ new Boolean(true),
+ {
+ valueOf() {
+ return null;
+ },
+ },
+ {
+ valueOf() {
+ return undefined;
+ },
+ },
+ { valueOf: null },
+ Object.create(null),
+ new Number(true),
+ new MyBadPrimitive(),
+ Symbol(),
+ 5n,
+ (one, two, three) => {},
+ undefined,
+ null,
+ ].forEach(input => {
+ expect(() => Buffer.from(input)).toThrow();
+ expect(() => Buffer.from(input, "hex")).toThrow();
+ });
+
+ expect(() => Buffer.allocUnsafe(10)).not.toThrow(); // Should not throw.
+ expect(() => Buffer.from("deadbeaf", "hex")).not.toThrow(); // Should not throw.
+});
+
+it("new Buffer() (Node.js test/test-buffer-new.js)", () => {
+ const LENGTH = 16;
+
+ const ab = new ArrayBuffer(LENGTH);
+ const dv = new DataView(ab);
+ const ui = new Uint8Array(ab);
+ const buf = Buffer.from(ab);
+
+ expect(buf instanceof Buffer).toBe(true);
+ // expect(buf.parent, buf.buffer);
+ expect(buf.buffer).toBe(ab);
+ expect(buf.length).toBe(ab.byteLength);
+
+ buf.fill(0xc);
+ for (let i = 0; i < LENGTH; i++) {
+ expect(ui[i]).toBe(0xc);
+ ui[i] = 0xf;
+ expect(buf[i]).toBe(0xf);
+ }
+
+ buf.writeUInt32LE(0xf00, 0);
+ buf.writeUInt32BE(0xb47, 4);
+ buf.writeDoubleLE(3.1415, 8);
+ expect(dv.getUint32(0, true)).toBe(0xf00);
+ expect(dv.getUint32(4)).toBe(0xb47);
+ expect(dv.getFloat64(8, true)).toBe(3.1415);
+
+ // Now test protecting users from doing stupid things
+
+ // expect(function () {
+ // function AB() {}
+ // Object.setPrototypeOf(AB, ArrayBuffer);
+ // Object.setPrototypeOf(AB.prototype, ArrayBuffer.prototype);
+ // // Buffer.from(new AB());
+ // }).toThrow();
+ // console.log(origAB !== ab);
+
+ // Test the byteOffset and length arguments
+ {
+ const ab = new Uint8Array(5);
+ ab[0] = 1;
+ ab[1] = 2;
+ ab[2] = 3;
+ ab[3] = 4;
+ ab[4] = 5;
+ const buf = Buffer.from(ab.buffer, 1, 3);
+ expect(buf.length).toBe(3);
+ expect(buf[0]).toBe(2);
+ expect(buf[1]).toBe(3);
+ expect(buf[2]).toBe(4);
+ buf[0] = 9;
+ expect(ab[1]).toBe(9);
+
+ expect(() => Buffer.from(ab.buffer, 6)).toThrow();
+ expect(() => Buffer.from(ab.buffer, 3, 6)).toThrow();
+ }
+
+ // Test the deprecated Buffer() version also
+ {
+ const ab = new Uint8Array(5);
+ ab[0] = 1;
+ ab[1] = 2;
+ ab[2] = 3;
+ ab[3] = 4;
+ ab[4] = 5;
+ const buf = Buffer(ab.buffer, 1, 3);
+ expect(buf.length).toBe(3);
+ expect(buf[0]).toBe(2);
+ expect(buf[1]).toBe(3);
+ expect(buf[2]).toBe(4);
+ buf[0] = 9;
+ expect(ab[1]).toBe(9);
+
+ expect(() => Buffer(ab.buffer, 6)).toThrow();
+ expect(() => Buffer(ab.buffer, 3, 6)).toThrow();
+ }
+
+ {
+ // If byteOffset is not numeric, it defaults to 0.
+ const ab = new ArrayBuffer(10);
+ const expected = Buffer.from(ab, 0);
+ expect(Buffer.from(ab, "fhqwhgads")).toStrictEqual(expected);
+ expect(Buffer.from(ab, NaN)).toStrictEqual(expected);
+ expect(Buffer.from(ab, {})).toStrictEqual(expected);
+ expect(Buffer.from(ab, [])).toStrictEqual(expected);
+
+ // If byteOffset can be converted to a number, it will be.
+ expect(Buffer.from(ab, [1])).toStrictEqual(Buffer.from(ab, 1));
+
+ // If byteOffset is Infinity, throw.
+ expect(() => {
+ Buffer.from(ab, Infinity);
+ }).toThrow();
+ }
+
+ {
+ // If length is not numeric, it defaults to 0.
+ const ab = new ArrayBuffer(10);
+ const expected = Buffer.from(ab, 0, 0);
+ expect(Buffer.from(ab, 0, "fhqwhgads")).toStrictEqual(expected);
+ expect(Buffer.from(ab, 0, NaN)).toStrictEqual(expected);
+ expect(Buffer.from(ab, 0, {})).toStrictEqual(expected);
+ expect(Buffer.from(ab, 0, [])).toStrictEqual(expected);
+
+ // If length can be converted to a number, it will be.
+ expect(Buffer.from(ab, 0, [1])).toStrictEqual(Buffer.from(ab, 0, 1));
+
+ // If length is Infinity, throw.
+ expect(() => Buffer.from(ab, 0, Infinity)).toThrow();
+ }
+
+ // Test an array like entry with the length set to NaN.
+ expect(Buffer.from({ length: NaN })).toStrictEqual(Buffer.alloc(0));
+});
+
+it("Buffer.fill (Node.js tests)", () => {
+ "use strict";
+ const SIZE = 28;
+
+ const buf1 = Buffer.allocUnsafe(SIZE);
+ const buf2 = Buffer.allocUnsafe(SIZE);
+
+ function assertEqual(a, b) {
+ expect(a).toEqual(b);
+ }
+
+ // Default encoding
+ testBufs("abc");
+ testBufs("\u0222aa");
+ testBufs("a\u0234b\u0235c\u0236");
+ testBufs("abc", 4);
+ testBufs("abc", 5);
+ testBufs("abc", SIZE);
+ testBufs("\u0222aa", 2);
+ testBufs("\u0222aa", 8);
+ testBufs("a\u0234b\u0235c\u0236", 4);
+ testBufs("a\u0234b\u0235c\u0236", 12);
+ testBufs("abc", 4, 1);
+ testBufs("abc", 5, 1);
+ testBufs("\u0222aa", 8, 1);
+ testBufs("a\u0234b\u0235c\u0236", 4, 1);
+ testBufs("a\u0234b\u0235c\u0236", 12, 1);
+
+ // UTF8
+ testBufs("abc", "utf8");
+ testBufs("\u0222aa", "utf8");
+ testBufs("a\u0234b\u0235c\u0236", "utf8");
+ testBufs("abc", 4, "utf8");
+ testBufs("abc", 5, "utf8");
+ testBufs("abc", SIZE, "utf8");
+ testBufs("\u0222aa", 2, "utf8");
+ testBufs("\u0222aa", 8, "utf8");
+ testBufs("a\u0234b\u0235c\u0236", 4, "utf8");
+ testBufs("a\u0234b\u0235c\u0236", 12, "utf8");
+ testBufs("abc", 4, 1, "utf8");
+ testBufs("abc", 5, 1, "utf8");
+ testBufs("\u0222aa", 8, 1, "utf8");
+ testBufs("a\u0234b\u0235c\u0236", 4, 1, "utf8");
+ testBufs("a\u0234b\u0235c\u0236", 12, 1, "utf8");
+ assertEqual(Buffer.allocUnsafe(1).fill(0).fill("\u0222")[0], 0xc8);
+
+ // BINARY
+ testBufs("abc", "binary");
+ testBufs("\u0222aa", "binary");
+ testBufs("a\u0234b\u0235c\u0236", "binary");
+ testBufs("abc", 4, "binary");
+ testBufs("abc", 5, "binary");
+ testBufs("abc", SIZE, "binary");
+ testBufs("\u0222aa", 2, "binary");
+ testBufs("\u0222aa", 8, "binary");
+ testBufs("a\u0234b\u0235c\u0236", 4, "binary");
+ testBufs("a\u0234b\u0235c\u0236", 12, "binary");
+ testBufs("abc", 4, 1, "binary");
+ testBufs("abc", 5, 1, "binary");
+ testBufs("\u0222aa", 8, 1, "binary");
+ testBufs("a\u0234b\u0235c\u0236", 4, 1, "binary");
+ testBufs("a\u0234b\u0235c\u0236", 12, 1, "binary");
+
+ // LATIN1
+ testBufs("abc", "latin1");
+ testBufs("\u0222aa", "latin1");
+ testBufs("a\u0234b\u0235c\u0236", "latin1");
+ testBufs("abc", 4, "latin1");
+ testBufs("abc", 5, "latin1");
+ testBufs("abc", SIZE, "latin1");
+ testBufs("\u0222aa", 2, "latin1");
+ testBufs("\u0222aa", 8, "latin1");
+ testBufs("a\u0234b\u0235c\u0236", 4, "latin1");
+ testBufs("a\u0234b\u0235c\u0236", 12, "latin1");
+ testBufs("abc", 4, 1, "latin1");
+ testBufs("abc", 5, 1, "latin1");
+ testBufs("\u0222aa", 8, 1, "latin1");
+ testBufs("a\u0234b\u0235c\u0236", 4, 1, "latin1");
+ testBufs("a\u0234b\u0235c\u0236", 12, 1, "latin1");
+
+ // UCS2
+ testBufs("abc", "ucs2");
+ testBufs("\u0222aa", "ucs2");
+ testBufs("a\u0234b\u0235c\u0236", "ucs2");
+ testBufs("abc", 4, "ucs2");
+ testBufs("abc", SIZE, "ucs2");
+ testBufs("\u0222aa", 2, "ucs2");
+ testBufs("\u0222aa", 8, "ucs2");
+ testBufs("a\u0234b\u0235c\u0236", 4, "ucs2");
+ testBufs("a\u0234b\u0235c\u0236", 12, "ucs2");
+ testBufs("abc", 4, 1, "ucs2");
+ testBufs("abc", 5, 1, "ucs2");
+ testBufs("\u0222aa", 8, 1, "ucs2");
+ testBufs("a\u0234b\u0235c\u0236", 4, 1, "ucs2");
+ testBufs("a\u0234b\u0235c\u0236", 12, 1, "ucs2");
+ assertEqual(Buffer.allocUnsafe(1).fill("\u0222", "ucs2")[0], 0x22);
+
+ // HEX
+ testBufs("616263", "hex");
+ testBufs("c8a26161", "hex");
+ testBufs("61c8b462c8b563c8b6", "hex");
+ testBufs("616263", 4, "hex");
+ testBufs("616263", 5, "hex");
+ testBufs("616263", SIZE, "hex");
+ testBufs("c8a26161", 2, "hex");
+ testBufs("c8a26161", 8, "hex");
+ testBufs("61c8b462c8b563c8b6", 4, "hex");
+ testBufs("61c8b462c8b563c8b6", 12, "hex");
+ testBufs("616263", 4, 1, "hex");
+ testBufs("616263", 5, 1, "hex");
+ testBufs("c8a26161", 8, 1, "hex");
+ testBufs("61c8b462c8b563c8b6", 4, 1, "hex");
+ testBufs("61c8b462c8b563c8b6", 12, 1, "hex");
+
+ expect(() => {
+ const buf = Buffer.allocUnsafe(SIZE);
+
+ buf.fill("yKJh", "hex");
+ }).toThrow();
+
+ expect(() => {
+ const buf = Buffer.allocUnsafe(SIZE);
+
+ buf.fill("\u0222", "hex");
+ }).toThrow();
+
+ // BASE64
+ testBufs("YWJj", "base64");
+ testBufs("yKJhYQ==", "base64");
+ testBufs("Yci0Ysi1Y8i2", "base64");
+ testBufs("YWJj", 4, "base64");
+ testBufs("YWJj", SIZE, "base64");
+ testBufs("yKJhYQ==", 2, "base64");
+ testBufs("yKJhYQ==", 8, "base64");
+ testBufs("Yci0Ysi1Y8i2", 4, "base64");
+ testBufs("Yci0Ysi1Y8i2", 12, "base64");
+ testBufs("YWJj", 4, 1, "base64");
+ testBufs("YWJj", 5, 1, "base64");
+ testBufs("yKJhYQ==", 8, 1, "base64");
+ testBufs("Yci0Ysi1Y8i2", 4, 1, "base64");
+ testBufs("Yci0Ysi1Y8i2", 12, 1, "base64");
+
+ // BASE64URL
+ testBufs("YWJj", "base64url");
+ testBufs("yKJhYQ", "base64url");
+ testBufs("Yci0Ysi1Y8i2", "base64url");
+ testBufs("YWJj", 4, "base64url");
+ testBufs("YWJj", SIZE, "base64url");
+ testBufs("yKJhYQ", 2, "base64url");
+ testBufs("yKJhYQ", 8, "base64url");
+ testBufs("Yci0Ysi1Y8i2", 4, "base64url");
+ testBufs("Yci0Ysi1Y8i2", 12, "base64url");
+ testBufs("YWJj", 4, 1, "base64url");
+ testBufs("YWJj", 5, 1, "base64url");
+ testBufs("yKJhYQ", 8, 1, "base64url");
+ testBufs("Yci0Ysi1Y8i2", 4, 1, "base64url");
+ testBufs("Yci0Ysi1Y8i2", 12, 1, "base64url");
+
+ // Buffer
+ function deepStrictEqualValues(buf, arr) {
+ for (const [index, value] of buf.entries()) {
+ expect(value).toStrictEqual(arr[index]);
+ }
+ }
+
+ const buf2Fill = Buffer.allocUnsafe(1).fill(2);
+ deepStrictEqualValues(genBuffer(4, [buf2Fill]), [2, 2, 2, 2]);
+ deepStrictEqualValues(genBuffer(4, [buf2Fill, 1]), [0, 2, 2, 2]);
+ deepStrictEqualValues(genBuffer(4, [buf2Fill, 1, 3]), [0, 2, 2, 0]);
+ deepStrictEqualValues(genBuffer(4, [buf2Fill, 1, 1]), [0, 0, 0, 0]);
+ const hexBufFill = Buffer.allocUnsafe(2).fill(0).fill("0102", "hex");
+ deepStrictEqualValues(genBuffer(4, [hexBufFill]), [1, 2, 1, 2]);
+ deepStrictEqualValues(genBuffer(4, [hexBufFill, 1]), [0, 1, 2, 1]);
+ deepStrictEqualValues(genBuffer(4, [hexBufFill, 1, 3]), [0, 1, 2, 0]);
+ deepStrictEqualValues(genBuffer(4, [hexBufFill, 1, 1]), [0, 0, 0, 0]);
+
+ // Check exceptions
+ [
+ [0, -1],
+ [0, 0, buf1.length + 1],
+ ["", -1],
+ ["", 0, buf1.length + 1],
+ ["", 1, -1],
+ ].forEach(args => {
+ expect(() => buf1.fill(...args)).toThrow();
+ });
+
+ expect(() => buf1.fill("a", 0, buf1.length, "node rocks!")).toThrow();
+
+ [
+ ["a", 0, 0, NaN],
+ ["a", 0, 0, false],
+ ].forEach(args => {
+ expect(() => buf1.fill(...args)).toThrow();
+ });
+
+ expect(() => buf1.fill("a", 0, 0, "foo")).toThrow();
+
+ function genBuffer(size, args) {
+ const b = Buffer.allocUnsafe(size);
+ return b.fill(0).fill.apply(b, args);
+ }
+
+ function bufReset() {
+ buf1.fill(0);
+ buf2.fill(0);
+ }
+
+ // This is mostly accurate. Except write() won't write partial bytes to the
+ // string while fill() blindly copies bytes into memory. To account for that an
+ // error will be thrown if not all the data can be written, and the SIZE has
+ // been massaged to work with the input characters.
+ function writeToFill(string, offset, end, encoding) {
+ if (typeof offset === "string") {
+ encoding = offset;
+ offset = 0;
+ end = buf2.length;
+ } else if (typeof end === "string") {
+ encoding = end;
+ end = buf2.length;
+ } else if (end === undefined) {
+ end = buf2.length;
+ }
+
+ // Should never be reached.
+ if (offset < 0 || end > buf2.length) throw new ERR_OUT_OF_RANGE();
+
+ if (end <= offset) return buf2;
+
+ offset >>>= 0;
+ end >>>= 0;
+ expect(offset <= buf2.length).toBe(true);
+
+ // Convert "end" to "length" (which write understands).
+ const length = end - offset < 0 ? 0 : end - offset;
+
+ let wasZero = false;
+ do {
+ const written = buf2.write(string, offset, length, encoding);
+ offset += written;
+ // Safety check in case write falls into infinite loop.
+ if (written === 0) {
+ if (wasZero) throw new Error("Could not write all data to Buffer at " + offset);
+ else wasZero = true;
+ }
+ } while (offset < buf2.length);
+
+ return buf2;
+ }
+
+ function testBufs(string, offset, length, encoding) {
+ bufReset();
+ buf1.fill.apply(buf1, arguments);
+ // Swap bytes on BE archs for ucs2 encoding.
+ expect(buf1.fill.apply(buf1, arguments)).toStrictEqual(writeToFill.apply(null, arguments));
+ }
+
+ // Make sure these throw.
+ expect(() => Buffer.allocUnsafe(8).fill("a", -1)).toThrow();
+ expect(() => Buffer.allocUnsafe(8).fill("a", 0, 9)).toThrow();
+
+ // Make sure this doesn't hang indefinitely.
+ Buffer.allocUnsafe(8).fill("");
+ Buffer.alloc(8, "");
+
+ {
+ const buf = Buffer.alloc(64, 10);
+ for (let i = 0; i < buf.length; i++) assertEqual(buf[i], 10);
+
+ buf.fill(11, 0, buf.length >> 1);
+ for (let i = 0; i < buf.length >> 1; i++) assertEqual(buf[i], 11);
+ for (let i = (buf.length >> 1) + 1; i < buf.length; i++) assertEqual(buf[i], 10);
+
+ buf.fill("h");
+ for (let i = 0; i < buf.length; i++) assertEqual(buf[i], "h".charCodeAt(0));
+
+ buf.fill(0);
+ for (let i = 0; i < buf.length; i++) assertEqual(buf[i], 0);
+
+ buf.fill(null);
+ for (let i = 0; i < buf.length; i++) assertEqual(buf[i], 0);
+
+ buf.fill(1, 16, 32);
+ for (let i = 0; i < 16; i++) assertEqual(buf[i], 0);
+ for (let i = 16; i < 32; i++) assertEqual(buf[i], 1);
+ for (let i = 32; i < buf.length; i++) assertEqual(buf[i], 0);
+ }
+
+ {
+ const buf = Buffer.alloc(10, "abc");
+ assertEqual(buf.toString(), "abcabcabca");
+ buf.fill("է");
+ assertEqual(buf.toString(), "էէէէէ");
+ }
+
+ // // Testing process.binding. Make sure "start" is properly checked for range
+ // // errors.
+ // assert.throws(
+ // () => {
+ // internalBinding("buffer").fill(Buffer.alloc(1), 1, -1, 0, 1);
+ // },
+ // { code: "ERR_OUT_OF_RANGE" },
+ // );
+
+ // Make sure "end" is properly checked, even if it's magically mangled using
+ // Symbol.toPrimitive.
+ {
+ expect(() => {
+ const end = {
+ [Symbol.toPrimitive]() {
+ return 1;
+ },
+ };
+ Buffer.alloc(1).fill(Buffer.alloc(1), 0, end);
+ }).toThrow();
+ }
+
+ // Testing process.binding. Make sure "end" is properly checked for range
+ // errors.
+ // assert.throws(
+ // () => {
+ // internalBinding("buffer").fill(Buffer.alloc(1), 1, 1, -2, 1);
+ // },
+ // { code: "ERR_OUT_OF_RANGE" },
+ // );
+
+ // Test that bypassing 'length' won't cause an abort.
+ expect(() => {
+ const buf = Buffer.from("w00t");
+ Object.defineProperty(buf, "length", {
+ value: 1337,
+ enumerable: true,
+ });
+ buf.fill("");
+ }).toThrow();
+
+ assertEqual(Buffer.allocUnsafeSlow(16).fill("ab", "utf16le"), Buffer.from("61006200610062006100620061006200", "hex"));
+
+ assertEqual(Buffer.allocUnsafeSlow(15).fill("ab", "utf16le"), Buffer.from("610062006100620061006200610062", "hex"));
+
+ assertEqual(Buffer.allocUnsafeSlow(16).fill("ab", "utf16le"), Buffer.from("61006200610062006100620061006200", "hex"));
+ assertEqual(Buffer.allocUnsafeSlow(16).fill("a", "utf16le"), Buffer.from("61006100610061006100610061006100", "hex"));
+
+ assertEqual(Buffer.allocUnsafeSlow(16).fill("a", "utf16le").toString("utf16le"), "a".repeat(8));
+ assertEqual(Buffer.allocUnsafeSlow(16).fill("a", "latin1").toString("latin1"), "a".repeat(16));
+ assertEqual(Buffer.allocUnsafeSlow(16).fill("a", "utf8").toString("utf8"), "a".repeat(16));
+
+ assertEqual(Buffer.allocUnsafeSlow(16).fill("Љ", "utf16le").toString("utf16le"), "Љ".repeat(8));
+ assertEqual(Buffer.allocUnsafeSlow(16).fill("Љ", "latin1").toString("latin1"), "\t".repeat(16));
+ assertEqual(Buffer.allocUnsafeSlow(16).fill("Љ", "utf8").toString("utf8"), "Љ".repeat(8));
+
+ expect(() => {
+ const buf = Buffer.from("a".repeat(1000));
+
+ buf.fill("This is not correctly encoded", "hex");
+ }).toThrow();
+});
+
+test("Buffer.byteLength", () => {
+ const SlowBuffer = require("buffer").SlowBuffer;
+
+ [[32, "latin1"], [NaN, "utf8"], [{}, "latin1"], []].forEach(args => {
+ assert.throws(() => Buffer.byteLength(...args));
+ });
+
+ assert.strictEqual(Buffer.byteLength("", undefined, true), 0);
+
+ assert(ArrayBuffer.isView(new Buffer(10)));
+ assert(ArrayBuffer.isView(new SlowBuffer(10)));
+ assert(ArrayBuffer.isView(Buffer.alloc(10)));
+ assert(ArrayBuffer.isView(Buffer.allocUnsafe(10)));
+ assert(ArrayBuffer.isView(Buffer.allocUnsafeSlow(10)));
+ assert(ArrayBuffer.isView(Buffer.from("")));
+
+ // buffer
+ const incomplete = Buffer.from([0xe4, 0xb8, 0xad, 0xe6, 0x96]);
+ assert.strictEqual(Buffer.byteLength(incomplete), 5);
+ const ascii = Buffer.from("abc");
+ assert.strictEqual(Buffer.byteLength(ascii), 3);
+
+ // ArrayBuffer
+ const buffer = new ArrayBuffer(8);
+ assert.strictEqual(Buffer.byteLength(buffer), 8);
+
+ // TypedArray
+ const int8 = new Int8Array(8);
+ assert.strictEqual(Buffer.byteLength(int8), 8);
+ const uint8 = new Uint8Array(8);
+ assert.strictEqual(Buffer.byteLength(uint8), 8);
+ const uintc8 = new Uint8ClampedArray(2);
+ assert.strictEqual(Buffer.byteLength(uintc8), 2);
+ const int16 = new Int16Array(8);
+ assert.strictEqual(Buffer.byteLength(int16), 16);
+ const uint16 = new Uint16Array(8);
+ assert.strictEqual(Buffer.byteLength(uint16), 16);
+ const int32 = new Int32Array(8);
+ assert.strictEqual(Buffer.byteLength(int32), 32);
+ const uint32 = new Uint32Array(8);
+ assert.strictEqual(Buffer.byteLength(uint32), 32);
+ const float32 = new Float32Array(8);
+ assert.strictEqual(Buffer.byteLength(float32), 32);
+ const float64 = new Float64Array(8);
+ assert.strictEqual(Buffer.byteLength(float64), 64);
+
+ // DataView
+ const dv = new DataView(new ArrayBuffer(2));
+ assert.strictEqual(Buffer.byteLength(dv), 2);
+
+ // Special case: zero length string
+ assert.strictEqual(Buffer.byteLength("", "ascii"), 0);
+ assert.strictEqual(Buffer.byteLength("", "HeX"), 0);
+
+ // utf8
+ assert.strictEqual(Buffer.byteLength("∑éllö wørl∂!", "utf-8"), 19);
+ assert.strictEqual(Buffer.byteLength("κλμνξο", "utf8"), 12);
+ assert.strictEqual(Buffer.byteLength("挵挶挷挸挹", "utf-8"), 15);
+ assert.strictEqual(Buffer.byteLength("𠝹𠱓𠱸", "UTF8"), 12);
+ // Without an encoding, utf8 should be assumed
+ assert.strictEqual(Buffer.byteLength("hey there"), 9);
+ assert.strictEqual(Buffer.byteLength("𠱸挶νξ#xx :)"), 17);
+ assert.strictEqual(Buffer.byteLength("hello world", ""), 11);
+ // It should also be assumed with unrecognized encoding
+ assert.strictEqual(Buffer.byteLength("hello world", "abc"), 11);
+ assert.strictEqual(Buffer.byteLength("ßœ∑≈", "unkn0wn enc0ding"), 10);
+
+ // base64
+ assert.strictEqual(Buffer.byteLength("aGVsbG8gd29ybGQ=", "base64"), 11);
+ assert.strictEqual(Buffer.byteLength("aGVsbG8gd29ybGQ=", "BASE64"), 11);
+ assert.strictEqual(Buffer.byteLength("bm9kZS5qcyByb2NrcyE=", "base64"), 14);
+ assert.strictEqual(Buffer.byteLength("aGkk", "base64"), 3);
+ assert.strictEqual(Buffer.byteLength("bHNrZGZsa3NqZmtsc2xrZmFqc2RsZmtqcw==", "base64"), 25);
+ // base64url
+ assert.strictEqual(Buffer.byteLength("aGVsbG8gd29ybGQ", "base64url"), 11);
+ assert.strictEqual(Buffer.byteLength("aGVsbG8gd29ybGQ", "BASE64URL"), 11);
+ assert.strictEqual(Buffer.byteLength("bm9kZS5qcyByb2NrcyE", "base64url"), 14);
+ assert.strictEqual(Buffer.byteLength("aGkk", "base64url"), 3);
+ assert.strictEqual(Buffer.byteLength("bHNrZGZsa3NqZmtsc2xrZmFqc2RsZmtqcw", "base64url"), 25);
+ // special padding
+ assert.strictEqual(Buffer.byteLength("aaa=", "base64"), 2);
+ assert.strictEqual(Buffer.byteLength("aaaa==", "base64"), 3);
+ assert.strictEqual(Buffer.byteLength("aaa=", "base64url"), 2);
+ assert.strictEqual(Buffer.byteLength("aaaa==", "base64url"), 3);
+ assert.strictEqual(Buffer.byteLength("Il était tué", "utf8"), 14);
+ assert.strictEqual(Buffer.byteLength("Il était tué"), 14);
+
+ ["ascii", "latin1", "binary"]
+ .reduce((es, e) => es.concat(e, e.toUpperCase()), [])
+ .forEach(encoding => {
+ assert.strictEqual(Buffer.byteLength("Il était tué", encoding), 12);
+ });
+
+ ["ucs2", "ucs-2", "utf16le", "utf-16le"]
+ .reduce((es, e) => es.concat(e, e.toUpperCase()), [])
+ .forEach(encoding => {
+ assert.strictEqual(Buffer.byteLength("Il était tué", encoding), 24);
+ });
+
+ // Test that ArrayBuffer from a different context is detected correctly
+ // const arrayBuf = vm.runInNewContext("new ArrayBuffer()");
+ // assert.strictEqual(Buffer.byteLength(arrayBuf), 0);
+
+ // Verify that invalid encodings are treated as utf8
+ for (let i = 1; i < 10; i++) {
+ const encoding = String(i).repeat(i);
+
+ assert.ok(!Buffer.isEncoding(encoding));
+ assert.strictEqual(Buffer.byteLength("foo", encoding), Buffer.byteLength("foo", "utf8"));
+ }
+});
+
+it("should not crash on invalid UTF-8 byte sequence", () => {
+ const buf = Buffer.from([0xc0, 0xfd]);
+ expect(buf.length).toBe(2);
+ const str = buf.toString();
+ expect(str.length).toBe(2);
+ expect(str).toBe("\uFFFD\uFFFD");
+});
+
+it("should not crash on invalid UTF-8 byte sequence with ASCII head", () => {
+ const buf = Buffer.from([0x42, 0xc0, 0xfd]);
+ expect(buf.length).toBe(3);
+ const str = buf.toString();
+ expect(str.length).toBe(3);
+ expect(str).toBe("B\uFFFD\uFFFD");
+});
+
+it("should not perform out-of-bound access on invalid UTF-8 byte sequence", () => {
+ const buf = Buffer.from([0x01, 0x9a, 0x84, 0x13, 0x12, 0x11, 0x10, 0x09]).subarray(2);
+ expect(buf.length).toBe(6);
+ const str = buf.toString();
+ expect(str.length).toBe(6);
+ expect(str).toBe("\uFFFD\x13\x12\x11\x10\x09");
+});
+
+it("repro #2063", () => {
+ const buf = Buffer.from(
+ "eyJlbWFpbCI6Ijg3MTg4NDYxN0BxcS5jb20iLCJpZCI6OCwicm9sZSI6Im5vcm1hbCIsImlhdCI6MTY3NjI4NDQyMSwiZXhwIjoxNjc2ODg5MjIxfQ",
+ "base64",
+ );
+ expect(buf.length).toBe(85);
+ expect(buf[82]).toBe(50);
+ expect(buf[83]).toBe(49);
+ expect(buf[84]).toBe(125);
+});
diff --git a/test/js/node/child_process/child-process-stdio.test.js b/test/js/node/child_process/child-process-stdio.test.js
new file mode 100644
index 000000000..d0c6d9bc7
--- /dev/null
+++ b/test/js/node/child_process/child-process-stdio.test.js
@@ -0,0 +1,112 @@
+import { describe, it, expect, beforeAll } from "bun:test";
+import { spawn, execSync } from "node:child_process";
+import { bunExe, bunEnv } from "harness";
+
+const CHILD_PROCESS_FILE = import.meta.dir + "/spawned-child.js";
+const OUT_FILE = import.meta.dir + "/stdio-test-out.txt";
+
+describe("process.stdout", () => {
+ it("should allow us to write to it", done => {
+ const child = spawn(bunExe(), [CHILD_PROCESS_FILE, "STDOUT"], {
+ env: bunEnv,
+ });
+ child.stdout.setEncoding("utf8");
+ child.stdout.on("data", data => {
+ try {
+ expect(data).toBe("stdout_test");
+ done();
+ } catch (err) {
+ done(err);
+ }
+ });
+ });
+});
+
+describe("process.stdin", () => {
+ it("should allow us to read from stdin in readable mode", done => {
+ const input = "hello\n";
+ // Child should read from stdin and write it back
+ const child = spawn(bunExe(), [CHILD_PROCESS_FILE, "STDIN", "READABLE"], {
+ env: bunEnv,
+ });
+ let data = "";
+ child.stdout.setEncoding("utf8");
+ child.stdout
+ .on("data", chunk => {
+ data += chunk;
+ })
+ .on("end", function () {
+ try {
+ expect(data).toBe(`data: ${input}`);
+ done();
+ } catch (err) {
+ done(err);
+ }
+ });
+ child.stdin.write(input);
+ child.stdin.end();
+ });
+
+ it("should allow us to read from stdin via flowing mode", done => {
+ const input = "hello\n";
+ // Child should read from stdin and write it back
+ const child = spawn(bunExe(), [CHILD_PROCESS_FILE, "STDIN", "FLOWING"], {
+ env: bunEnv,
+ });
+ let data = "";
+ child.stdout.setEncoding("utf8");
+ child.stdout
+ .on("readable", () => {
+ let chunk;
+ while ((chunk = child.stdout.read()) !== null) {
+ data += chunk;
+ }
+ })
+ .on("end", function () {
+ try {
+ expect(data).toBe(`data: ${input}`);
+ done();
+ } catch (err) {
+ done(err);
+ }
+ });
+ child.stdin.write(input);
+ child.stdin.end();
+ });
+
+ it("should allow us to read > 65kb from stdin", done => {
+ const numReps = Math.ceil((66 * 1024) / 5);
+ const input = "hello".repeat(numReps);
+ // Child should read from stdin and write it back
+ const child = spawn(bunExe(), [CHILD_PROCESS_FILE, "STDIN", "FLOWING"], {
+ env: bunEnv,
+ });
+ let data = "";
+ child.stdout.setEncoding("utf8");
+ child.stdout
+ .on("readable", () => {
+ let chunk;
+ while ((chunk = child.stdout.read()) !== null) {
+ data += chunk;
+ }
+ })
+ .on("end", function () {
+ try {
+ expect(data).toBe(`data: ${input}`);
+ done();
+ } catch (err) {
+ done(err);
+ }
+ });
+ child.stdin.write(input);
+ child.stdin.end();
+ });
+
+ it("should allow us to read from a file", () => {
+ const result = execSync(`${bunExe()} ${CHILD_PROCESS_FILE} STDIN FLOWING < ${import.meta.dir}/readFileSync.txt`, {
+ encoding: "utf8",
+ env: bunEnv,
+ });
+ expect(result).toEqual("data: File read successfully");
+ });
+});
diff --git a/test/js/node/child_process/child_process-node.test.js b/test/js/node/child_process/child_process-node.test.js
new file mode 100644
index 000000000..deb3bfb86
--- /dev/null
+++ b/test/js/node/child_process/child_process-node.test.js
@@ -0,0 +1,481 @@
+import { beforeAll, describe, expect, it } from "bun:test";
+import { ChildProcess, spawn, exec } from "node:child_process";
+import { throws, assert, createCallCheckCtx, createDoneDotAll } from "node-harness";
+import { tmpdir } from "node:os";
+const strictEqual = (a, b) => expect(a).toStrictEqual(b);
+const debug = process.env.DEBUG ? console.log : () => {};
+
+const platformTmpDir = require("fs").realpathSync(tmpdir());
+
+const TYPE_ERR_NAME = "TypeError";
+
+console.log(process.cwd());
+
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+const common = {
+ pwdCommand: ["pwd", []],
+};
+
+describe("ChildProcess.constructor", () => {
+ it("should be a function", () => {
+ strictEqual(typeof ChildProcess, "function");
+ });
+});
+
+describe("ChildProcess.spawn()", () => {
+ it("should throw on invalid options", () => {
+ // Verify that invalid options to spawn() throw.
+ const child = new ChildProcess();
+
+ [undefined, null, "foo", 0, 1, NaN, true, false].forEach(options => {
+ throws(
+ () => {
+ child.spawn(options);
+ },
+ {
+ code: "ERR_INVALID_ARG_TYPE",
+ name: TYPE_ERR_NAME,
+ // message:
+ // 'The "options" argument must be of type object.' +
+ // `${common.invalidArgTypeHelper(options)}`,
+ },
+ );
+ });
+ });
+
+ it("should throw if file is not a string", () => {
+ // Verify that spawn throws if file is not a string.
+ const child = new ChildProcess();
+ [undefined, null, 0, 1, NaN, true, false, {}].forEach(file => {
+ throws(
+ () => {
+ child.spawn({ file });
+ },
+ {
+ code: "ERR_INVALID_ARG_TYPE",
+ name: TYPE_ERR_NAME,
+ // message:
+ // 'The "options.file" property must be of type string.' +
+ // `${common.invalidArgTypeHelper(file)}`,
+ },
+ );
+ });
+ });
+
+ it("should throw if envPairs is not an array or undefined", () => {
+ // Verify that spawn throws if envPairs is not an array or undefined.
+ const child = new ChildProcess();
+
+ [null, 0, 1, NaN, true, false, {}, "foo"].forEach(envPairs => {
+ throws(
+ () => {
+ child.spawn({
+ envPairs,
+ stdio: ["ignore", "ignore", "ignore", "ipc"],
+ });
+ },
+ {
+ code: "ERR_INVALID_ARG_TYPE",
+ name: TYPE_ERR_NAME,
+ // message:
+ // 'The "options.envPairs" property must be an instance of Array.' +
+ // common.invalidArgTypeHelper(envPairs),
+ },
+ );
+ });
+ });
+
+ it("should throw if stdio is not an array or undefined", () => {
+ // Verify that spawn throws if args is not an array or undefined.
+ const child = new ChildProcess();
+
+ [null, 0, 1, NaN, true, false, {}, "foo"].forEach(args => {
+ throws(
+ () => {
+ child.spawn({ file: "foo", args });
+ },
+ {
+ code: "ERR_INVALID_ARG_TYPE",
+ name: TYPE_ERR_NAME,
+ // message:
+ // 'The "options.args" property must be an instance of Array.' +
+ // common.invalidArgTypeHelper(args),
+ },
+ );
+ });
+ });
+});
+
+describe("ChildProcess.spawn", () => {
+ function getChild() {
+ const child = new ChildProcess();
+ child.spawn({
+ file: "node",
+ // file: process.execPath,
+ args: ["node", "--interactive"],
+ cwd: process.cwd(),
+ stdio: ["ignore", "ignore", "ignore"],
+ });
+ return child;
+ }
+
+ it("should spawn a process", () => {
+ const child = getChild();
+ // Test that we can call spawn
+
+ strictEqual(Object.hasOwn(child, "pid"), true);
+ assert(Number.isInteger(child.pid));
+ child.kill();
+ });
+
+ it("should throw error on invalid signal", () => {
+ const child = getChild();
+ // Try killing with invalid signal
+ throws(
+ () => {
+ child.kill("foo");
+ },
+ { code: "ERR_UNKNOWN_SIGNAL", name: TYPE_ERR_NAME },
+ );
+ });
+});
+
+describe("ChildProcess spawn bad stdio", () => {
+ // Monkey patch spawn() to create a child process normally, but destroy the
+ // stdout and stderr streams. This replicates the conditions where the streams
+ // cannot be properly created.
+ function createChild(options, callback, done, target) {
+ var __originalSpawn = ChildProcess.prototype.spawn;
+ ChildProcess.prototype.spawn = function () {
+ const err = __originalSpawn.apply(this, arguments);
+
+ this.stdout.destroy();
+ this.stderr.destroy();
+
+ return err;
+ };
+
+ const { mustCall } = createCallCheckCtx(done);
+ let cmd = `bun ${import.meta.dir}/spawned-child.js`;
+ if (target) cmd += " " + target;
+ const child = exec(cmd, options, mustCall(callback));
+ ChildProcess.prototype.spawn = __originalSpawn;
+ return child;
+ }
+
+ it("should handle normal execution of child process", done => {
+ createChild(
+ {},
+ (err, stdout, stderr) => {
+ strictEqual(err, null);
+ strictEqual(stdout, "");
+ strictEqual(stderr, "");
+ },
+ done,
+ );
+ });
+
+ it("should handle error event of child process", done => {
+ const error = new Error(`Command failed: bun ${import.meta.dir}/spawned-child.js ERROR`);
+ createChild(
+ {},
+ (err, stdout, stderr) => {
+ strictEqual(err.message, error.message);
+ strictEqual(stdout, "");
+ strictEqual(stderr, "");
+ },
+ done,
+ "ERROR",
+ );
+ });
+
+ it("should handle killed process", done => {
+ createChild(
+ { timeout: 1 },
+ (err, stdout, stderr) => {
+ strictEqual(err.killed, true);
+ strictEqual(stdout, "");
+ strictEqual(stderr, "");
+ },
+ done,
+ );
+ });
+});
+
+describe("child_process cwd", () => {
+ // Spawns 'pwd' with given options, then test
+ // - whether the child pid is undefined or number,
+ // - whether the exit code equals expectCode,
+ // - optionally whether the trimmed stdout result matches expectData
+ function testCwd(options, { expectPidType, expectCode = 0, expectData }, done = () => {}) {
+ const createDone = createDoneDotAll(done);
+ const { mustCall } = createCallCheckCtx(createDone(1500));
+ const exitDone = createDone(5000);
+
+ const child = spawn(...common.pwdCommand, options);
+
+ strictEqual(typeof child.pid, expectPidType);
+
+ child.stdout.setEncoding("utf8");
+
+ // No need to assert callback since `data` is asserted.
+ let data = "";
+ child.stdout.on("data", chunk => {
+ data += chunk;
+ });
+
+ // TODO: Test exit events
+ // // Can't assert callback, as stayed in to API:
+ // // _The 'exit' event may or may not fire after an error has occurred._
+ child.on("exit", (code, signal) => {
+ try {
+ strictEqual(code, expectCode);
+ exitDone();
+ } catch (err) {
+ exitDone(err);
+ }
+ });
+
+ child.on(
+ "close",
+ mustCall(() => {
+ expectData && strictEqual(data.trim(), expectData);
+ }),
+ );
+
+ return child;
+ }
+
+ // TODO: Make sure this isn't important
+ // Currently Bun.spawn will still spawn even though cwd doesn't exist
+ // // Assume does-not-exist doesn't exist, expect exitCode=-1 and errno=ENOENT
+ // it("should throw an error when given cwd doesn't exist", () => {
+ // testCwd({ cwd: "does-not-exist" }, "undefined", -1).on(
+ // "error",
+ // mustCall(function (e) {
+ // console.log(e);
+ // strictEqual(e.code, "ENOENT");
+ // })
+ // );
+ // });
+
+ // TODO: Make sure this isn't an important test
+ // it("should throw when cwd is a non-file url", () => {
+ // throws(() => {
+ // testCwd(
+ // {
+ // cwd: new URL("http://example.com/"),
+ // },
+ // "number",
+ // 0,
+ // tmpdir.path
+ // );
+ // }, /The URL must be of scheme file/);
+
+ // // if (process.platform !== "win32") {
+ // // throws(() => {
+ // // testCwd(
+ // // {
+ // // cwd: new URL("file://host/dev/null"),
+ // // },
+ // // "number",
+ // // 0,
+ // // tmpdir.path
+ // // );
+ // // }, /File URL host must be "localhost" or empty on/);
+ // // }
+ // });
+
+ it("should work for valid given cwd", done => {
+ const tmpdir = { path: platformTmpDir };
+ const createDone = createDoneDotAll(done);
+
+ // Assume these exist, and 'pwd' gives us the right directory back
+ testCwd(
+ { cwd: tmpdir.path },
+ {
+ expectPidType: "number",
+ expectCode: 0,
+ expectData: platformTmpDir,
+ },
+ createDone(1500),
+ );
+ const shouldExistDir = "/dev";
+ testCwd(
+ { cwd: shouldExistDir },
+ {
+ expectPidType: "number",
+ expectCode: 0,
+ expectData: shouldExistDir,
+ },
+ createDone(1500),
+ );
+ testCwd(
+ { cwd: Bun.pathToFileURL(tmpdir.path) },
+ {
+ expectPidType: "number",
+ expectCode: 0,
+ expectData: platformTmpDir,
+ },
+ createDone(1500),
+ );
+ });
+
+ it.skip("shouldn't try to chdir to an invalid cwd", done => {
+ const createDone = createDoneDotAll(done);
+ // Spawn() shouldn't try to chdir() to invalid arg, so this should just work
+ testCwd({ cwd: "" }, { expectPidType: "number" }, createDone(1500));
+ testCwd({ cwd: undefined }, { expectPidType: "number" }, createDone(1500));
+ testCwd({ cwd: null }, { expectPidType: "number" }, createDone(1500));
+ });
+});
+
+describe("child_process default options", () => {
+ it("should use process.env as default env", done => {
+ globalThis.process.env.TMPDIR = platformTmpDir;
+
+ let child = spawn("printenv", [], {});
+ let response = "";
+
+ child.stdout.setEncoding("utf8");
+ child.stdout.on("data", chunk => {
+ debug(`stdout: ${chunk}`);
+ response += chunk;
+ });
+
+ // NOTE: Original test used child.on("exit"), but this is unreliable
+ // because the process can exit before the stream is closed and the data is read
+ child.stdout.on("close", () => {
+ expect(response.includes(`TMPDIR=${platformTmpDir}`)).toBe(true);
+ done();
+ });
+ });
+});
+
+describe("child_process double pipe", () => {
+ it("should allow two pipes to be used at once", done => {
+ // const { mustCallAtLeast, mustCall } = createCallCheckCtx(done);
+ const mustCallAtLeast = fn => fn;
+ const mustCall = fn => fn;
+ let grep, sed, echo;
+ grep = spawn("grep", ["o"], { stdio: ["pipe", "pipe", "pipe"] });
+ sed = spawn("sed", ["s/o/O/"]);
+ echo = spawn("echo", ["hello\nnode\nand\nworld\n"]);
+
+ // pipe grep | sed
+ grep.stdout.on(
+ "data",
+ mustCallAtLeast(data => {
+ debug(`grep stdout ${data.length}`);
+ if (!sed.stdin.write(data)) {
+ grep.stdout.pause();
+ }
+ }),
+ );
+
+ // print sed's output
+ sed.stdout.on(
+ "data",
+ mustCallAtLeast(data => {
+ result += data.toString("utf8");
+ debug(data);
+ }),
+ );
+
+ echo.stdout.on(
+ "data",
+ mustCallAtLeast(data => {
+ debug(`grep stdin write ${data.length}`);
+ if (!grep.stdin.write(data)) {
+ debug("echo stdout pause");
+ echo.stdout.pause();
+ }
+ }),
+ );
+
+ // TODO(Derrick): We don't implement the full API for this yet,
+ // So stdin has no 'drain' event.
+ // TODO(@jasnell): This does not appear to ever be
+ // emitted. It's not clear if it is necessary.
+ grep.stdin.on("drain", () => {
+ debug("echo stdout resume");
+ echo.stdout.resume();
+ });
+
+ // Propagate end from echo to grep
+ echo.stdout.on(
+ "end",
+ mustCall(() => {
+ debug("echo stdout end");
+ grep.stdin.end();
+ }),
+ );
+
+ echo.on(
+ "exit",
+ mustCall(() => {
+ debug("echo exit");
+ }),
+ );
+
+ grep.on(
+ "exit",
+ mustCall(() => {
+ debug("grep exit");
+ }),
+ );
+
+ sed.on(
+ "exit",
+ mustCall(() => {
+ debug("sed exit");
+ }),
+ );
+
+ // TODO(@jasnell): This does not appear to ever be
+ // emitted. It's not clear if it is necessary.
+ sed.stdin.on("drain", () => {
+ grep.stdout.resume();
+ });
+
+ // Propagate end from grep to sed
+ grep.stdout.on(
+ "end",
+ mustCall(() => {
+ debug("grep stdout end");
+ sed.stdin.end();
+ }),
+ );
+
+ let result = "";
+
+ sed.stdout.on(
+ "end",
+ mustCall(() => {
+ debug("result: " + result);
+ strictEqual(result, `hellO\nnOde\nwOrld\n`);
+ done();
+ }),
+ );
+ });
+});
diff --git a/test/js/node/child_process/child_process.test.ts b/test/js/node/child_process/child_process.test.ts
new file mode 100644
index 000000000..167cbd8b0
--- /dev/null
+++ b/test/js/node/child_process/child_process.test.ts
@@ -0,0 +1,347 @@
+import { describe, it as it_, expect as expect_ } from "bun:test";
+import { gcTick } from "harness";
+import { ChildProcess, spawn, execFile, exec, fork, spawnSync, execFileSync, execSync } from "node:child_process";
+import { tmpdir } from "node:os";
+import { promisify } from "node:util";
+
+const expect: typeof expect_ = (actual: unknown) => {
+ gcTick();
+ const ret = expect_(actual);
+ gcTick();
+ return ret;
+};
+
+const it: typeof it_ = (label, fn) => {
+ const hasDone = fn.length === 1;
+ if (fn.constructor.name === "AsyncFunction" && hasDone) {
+ return it_(label, async done => {
+ gcTick();
+ await fn(done);
+ gcTick();
+ });
+ } else if (hasDone) {
+ return it_(label, done => {
+ gcTick();
+ fn(done);
+ gcTick();
+ });
+ } else if (fn.constructor.name === "AsyncFunction") {
+ return it_(label, async () => {
+ gcTick();
+ await fn(() => {});
+ gcTick();
+ });
+ } else {
+ return it_(label, () => {
+ gcTick();
+ fn(() => {});
+ gcTick();
+ });
+ }
+};
+
+const debug = process.env.DEBUG ? console.log : () => {};
+
+const platformTmpDir = require("fs").realpathSync(tmpdir());
+
+// Semver regex: https://gist.github.com/jhorsman/62eeea161a13b80e39f5249281e17c39?permalink_comment_id=2896416#gistcomment-2896416
+// Not 100% accurate, but good enough for this test
+const SEMVER_REGEX =
+ /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-[a-zA-Z\d][-a-zA-Z.\d]*)?(\+[a-zA-Z\d][-a-zA-Z.\d]*)?$/;
+
+describe("ChildProcess.spawn()", () => {
+ it("should emit `spawn` on spawn", async () => {
+ const proc = new ChildProcess();
+ const result = await new Promise(resolve => {
+ proc.on("spawn", () => {
+ resolve(true);
+ });
+ proc.spawn({ file: "bun", args: ["bun", "-v"] });
+ });
+ expect(result).toBe(true);
+ });
+
+ it("should emit `exit` when killed", async () => {
+ const proc = new ChildProcess();
+ const result = await new Promise(resolve => {
+ proc.on("exit", () => {
+ resolve(true);
+ });
+
+ proc.spawn({ file: "bun", args: ["bun", "-v"] });
+ proc.kill();
+ });
+ expect(result).toBe(true);
+ });
+});
+
+describe("spawn()", () => {
+ it("should spawn a process", () => {
+ const child = spawn("echo", ["hello"]);
+ expect(!!child).toBe(true);
+ });
+
+ it("should disallow invalid filename", () => {
+ let child;
+ let child2;
+ try {
+ // @ts-ignore
+ child = spawn(123);
+ // @ts-ignore
+ child2 = spawn(["echo", "hello"]);
+ } catch (e) {}
+ expect(!!child).toBe(false);
+ expect(!!child2).toBe(false);
+ });
+
+ it("should allow stdout to be read via Node stream.Readable `data` events", async () => {
+ const child = spawn("bun", ["-v"]);
+ const result: string = await new Promise(resolve => {
+ child.stdout.on("error", e => {
+ console.error(e);
+ });
+ child.stdout.on("data", data => {
+ debug(`stdout: ${data}`);
+ resolve(data.toString());
+ });
+ child.stderr.on("data", data => {
+ debug(`stderr: ${data}`);
+ });
+ });
+ expect(SEMVER_REGEX.test(result.trim())).toBe(true);
+ });
+
+ it("should allow stdout to be read via .read() API", async done => {
+ const child = spawn("bun", ["-v"]);
+ const result: string = await new Promise(resolve => {
+ let finalData = "";
+ child.stdout.on("error", e => {
+ done(e);
+ });
+ child.stdout.on("readable", () => {
+ let data;
+
+ while ((data = child.stdout.read()) !== null) {
+ finalData += data.toString();
+ }
+ resolve(finalData);
+ });
+ });
+ expect(SEMVER_REGEX.test(result.trim())).toBe(true);
+ done();
+ });
+
+ it("should accept stdio option with 'ignore' for no stdio fds", async () => {
+ const child1 = spawn("bun", ["-v"], {
+ stdio: "ignore",
+ });
+ const child2 = spawn("bun", ["-v"], {
+ stdio: ["ignore", "ignore", "ignore"],
+ });
+
+ expect(!!child1).toBe(true);
+ expect(child1.stdin).toBe(null);
+ expect(child1.stdout).toBe(null);
+ expect(child1.stderr).toBe(null);
+
+ expect(!!child2).toBe(true);
+ expect(child2.stdin).toBe(null);
+ expect(child2.stdout).toBe(null);
+ expect(child2.stderr).toBe(null);
+ });
+
+ it("should allow us to set cwd", async () => {
+ const child = spawn("pwd", { cwd: platformTmpDir });
+ const result: string = await new Promise(resolve => {
+ child.stdout.on("data", data => {
+ resolve(data.toString());
+ });
+ });
+ expect(result.trim()).toBe(platformTmpDir);
+ });
+
+ it("should allow us to write to stdin", async () => {
+ const child = spawn("tee");
+ const result: string = await new Promise(resolve => {
+ child.stdin.write("hello");
+ child.stdout.on("data", data => {
+ resolve(data.toString());
+ });
+ });
+ expect(result.trim()).toBe("hello");
+ });
+
+ it("should allow us to timeout hanging processes", async () => {
+ const child = spawn("sleep", ["2"], { timeout: 3 });
+ const start = performance.now();
+ let end;
+ await new Promise(resolve => {
+ child.on("exit", () => {
+ end = performance.now();
+ resolve(true);
+ });
+ });
+ expect(end - start < 2000).toBe(true);
+ });
+
+ it("should allow us to set env", async () => {
+ const child = spawn("env", { env: { TEST: "test" } });
+ const result: string = await new Promise(resolve => {
+ child.stdout.on("data", data => {
+ resolve(data.toString());
+ });
+ });
+ expect(/TEST\=test/.test(result)).toBe(true);
+ });
+
+ it("should allow explicit setting of argv0", async () => {
+ var resolve;
+ const promise = new Promise<string>(resolve1 => {
+ resolve = resolve1;
+ });
+ process.env.NO_COLOR = "1";
+ const child = spawn("node", ["--help"], { argv0: "bun" });
+ delete process.env.NO_COLOR;
+ let msg = "";
+
+ child.stdout.on("data", data => {
+ msg += data.toString();
+ });
+
+ child.stdout.on("close", () => {
+ resolve(msg);
+ });
+
+ const result = await promise;
+ expect(/Open bun's Discord server/.test(result)).toBe(true);
+ });
+
+ it("should allow us to spawn in a shell", async () => {
+ const result1: string = await new Promise(resolve => {
+ const child1 = spawn("echo", ["$0"], { shell: true });
+ child1.stdout.on("data", data => {
+ resolve(data.toString());
+ });
+ });
+ const result2: string = await new Promise(resolve => {
+ const child2 = spawn("echo", ["$0"], { shell: "bash" });
+ child2.stdout.on("data", data => {
+ resolve(data.toString());
+ });
+ });
+ expect(result1.trim()).toBe(Bun.which("sh"));
+ expect(result2.trim()).toBe(Bun.which("bash"));
+ });
+ it("should spawn a process synchronously", () => {
+ const { stdout } = spawnSync("echo", ["hello"], { encoding: "utf8" });
+ expect(stdout.trim()).toBe("hello");
+ });
+});
+
+describe("execFile()", () => {
+ it("should execute a file", async () => {
+ const result: Buffer = await new Promise((resolve, reject) => {
+ execFile("bun", ["-v"], { encoding: "buffer" }, (error, stdout, stderr) => {
+ if (error) {
+ reject(error);
+ }
+ resolve(stdout);
+ });
+ });
+ expect(SEMVER_REGEX.test(result.toString().trim())).toBe(true);
+ });
+});
+
+describe("exec()", () => {
+ it("should execute a command in a shell", async () => {
+ const result: Buffer = await new Promise((resolve, reject) => {
+ exec("bun -v", { encoding: "buffer" }, (error, stdout, stderr) => {
+ if (error) {
+ reject(error);
+ }
+ resolve(stdout);
+ });
+ });
+ expect(SEMVER_REGEX.test(result.toString().trim())).toBe(true);
+ });
+
+ it("should return an object w/ stdout and stderr when promisified", async () => {
+ const result = await promisify(exec)("bun -v");
+ expect(typeof result).toBe("object");
+ expect(typeof result.stdout).toBe("string");
+ expect(typeof result.stderr).toBe("string");
+
+ const { stdout, stderr } = result;
+ expect(SEMVER_REGEX.test(stdout.trim())).toBe(true);
+ expect(stderr.trim()).toBe("");
+ });
+});
+
+describe("fork()", () => {
+ it("should throw an error when used", () => {
+ let err;
+ try {
+ fork("index.js");
+ } catch (e) {
+ err = e;
+ }
+ expect(err instanceof Error).toBe(true);
+ });
+});
+
+describe("spawnSync()", () => {
+ it("should spawn a process synchronously", () => {
+ const { stdout } = spawnSync("echo", ["hello"], { encoding: "utf8" });
+ expect(stdout.trim()).toBe("hello");
+ });
+});
+
+describe("execFileSync()", () => {
+ it("should execute a file synchronously", () => {
+ const result = execFileSync("bun", ["-v"], { encoding: "utf8" });
+ expect(SEMVER_REGEX.test(result.trim())).toBe(true);
+ });
+
+ it("should allow us to pass input to the command", () => {
+ const result = execFileSync("node", [import.meta.dir + "/spawned-child.js", "STDIN"], {
+ input: "hello world!",
+ encoding: "utf8",
+ });
+ expect(result.trim()).toBe("data: hello world!");
+ });
+});
+
+describe("execSync()", () => {
+ it("should execute a command in the shell synchronously", () => {
+ const result = execSync("bun -v", { encoding: "utf8" });
+ expect(SEMVER_REGEX.test(result.trim())).toBe(true);
+ });
+});
+
+describe("Bun.spawn()", () => {
+ it("should return exit code 0 on successful execution", async () => {
+ const proc = Bun.spawn({
+ cmd: ["echo", "hello"],
+ stdout: "pipe",
+ });
+
+ for await (const chunk of proc.stdout!) {
+ const text = new TextDecoder().decode(chunk);
+ expect(text.trim()).toBe("hello");
+ }
+
+ const result = await new Promise(resolve => {
+ const maybeExited = Bun.peek(proc.exited);
+ if (maybeExited === proc.exited) {
+ proc.exited.then(code => resolve(code));
+ } else {
+ resolve(maybeExited);
+ }
+ });
+ expect(result).toBe(0);
+ });
+ // it("should fail when given an invalid cwd", () => {
+ // const child = Bun.spawn({ cmd: ["echo", "hello"], cwd: "/invalid" });
+ // expect(child.pid).toBe(undefined);
+ // });
+});
diff --git a/test/js/node/child_process/readFileSync.txt b/test/js/node/child_process/readFileSync.txt
new file mode 100644
index 000000000..ddc94b988
--- /dev/null
+++ b/test/js/node/child_process/readFileSync.txt
@@ -0,0 +1 @@
+File read successfully \ No newline at end of file
diff --git a/test/js/node/child_process/spawned-child.js b/test/js/node/child_process/spawned-child.js
new file mode 100644
index 000000000..263c566f9
--- /dev/null
+++ b/test/js/node/child_process/spawned-child.js
@@ -0,0 +1,29 @@
+const TARGET = process.argv[2];
+const MODE = process.argv[3];
+
+if (TARGET === "STDIN") {
+ let data = "";
+ process.stdin.setEncoding("utf8");
+ if (MODE === "READABLE") {
+ process.stdin.on("readable", () => {
+ let chunk;
+ while ((chunk = process.stdin.read()) !== null) {
+ data += chunk;
+ }
+ });
+ } else {
+ process.stdin.on("data", chunk => {
+ data += chunk;
+ });
+ }
+ process.stdin.on("end", () => {
+ process.stdout.write("data: ");
+ process.stdout.write(data);
+ });
+} else if (TARGET === "STDOUT") {
+ process.stdout.write("stdout_test");
+} else if (TARGET === "ERROR") {
+ console.log("oops");
+} else {
+ // nothing
+}
diff --git a/test/js/node/crypto/crypto-scrypt.test.js b/test/js/node/crypto/crypto-scrypt.test.js
new file mode 100644
index 000000000..4b7412251
--- /dev/null
+++ b/test/js/node/crypto/crypto-scrypt.test.js
@@ -0,0 +1,232 @@
+// most of these tests are taken from Node.js
+// thank you Node.js team for the tests
+import { expect, it } from "bun:test";
+const crypto = require("crypto");
+
+const good = [
+ // Zero-length key is legal, functions as a parameter validation check.
+ {
+ pass: "",
+ salt: "",
+ keylen: 0,
+ N: 16,
+ p: 1,
+ r: 1,
+ expected: "",
+ },
+ // Test vectors from https://tools.ietf.org/html/rfc7914#page-13 that
+ // should pass. Note that the test vector with N=1048576 is omitted
+ // because it takes too long to complete and uses over 1 GB of memory.
+ {
+ pass: "",
+ salt: "",
+ keylen: 64,
+ N: 16,
+ p: 1,
+ r: 1,
+ expected:
+ "77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442" +
+ "fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906",
+ },
+ {
+ pass: "password",
+ salt: "NaCl",
+ keylen: 64,
+ N: 1024,
+ p: 16,
+ r: 8,
+ expected:
+ "fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b373162" +
+ "2eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640",
+ },
+ {
+ pass: "pleaseletmein",
+ salt: "SodiumChloride",
+ keylen: 64,
+ N: 16384,
+ p: 1,
+ r: 8,
+ expected:
+ "7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2" +
+ "d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887",
+ },
+ {
+ pass: "",
+ salt: "",
+ keylen: 64,
+ cost: 16,
+ parallelization: 1,
+ blockSize: 1,
+ expected:
+ "77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442" +
+ "fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906",
+ },
+ {
+ pass: "password",
+ salt: "NaCl",
+ keylen: 64,
+ cost: 1024,
+ parallelization: 16,
+ blockSize: 8,
+ expected:
+ "fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b373162" +
+ "2eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640",
+ },
+ {
+ pass: "pleaseletmein",
+ salt: "SodiumChloride",
+ keylen: 64,
+ cost: 16384,
+ parallelization: 1,
+ blockSize: 8,
+ expected:
+ "7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2" +
+ "d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887",
+ },
+];
+
+// Test vectors that should fail.
+const bad = [
+ { N: 1, p: 1, r: 1 }, // N < 2
+ { N: 3, p: 1, r: 1 }, // Not power of 2.
+ { N: 1, cost: 1 }, // Both N and cost
+ // TODO: these should error, but I don't quite understand why.
+ // { p: 1, parallelization: 1 }, // Both p and parallelization
+ // { r: 1, blockSize: 1 }, // Both r and blocksize
+];
+
+// Test vectors where 128*N*r exceeds maxmem.
+const toobig = [
+ { N: 2 ** 16, p: 1, r: 1 }, // N >= 2**(r*16)
+ { N: 2, p: 2 ** 30, r: 1 }, // p > (2**30-1)/r
+ { N: 2 ** 20, p: 1, r: 8 },
+ { N: 2 ** 10, p: 1, r: 8, maxmem: 2 ** 20 },
+];
+
+const badargs = [
+ {
+ args: [],
+ expected: { code: "ERR_INVALID_ARG_TYPE" /*message: /"password"/ */ },
+ },
+ {
+ args: [null],
+ expected: { code: "ERR_INVALID_ARG_TYPE" /*message: /"password"/ */ },
+ },
+ {
+ args: [""],
+ expected: { code: "ERR_INVALID_ARG_TYPE" /*message: /"salt"/ */ },
+ },
+ {
+ args: ["", null],
+ expected: { code: "ERR_INVALID_ARG_TYPE" /*message: /"salt"/ */ },
+ },
+ {
+ args: ["", ""],
+ expected: { code: "ERR_INVALID_ARG_TYPE" /*message: /"keylen"/ */ },
+ },
+ {
+ args: ["", "", null],
+ expected: { code: "ERR_INVALID_ARG_TYPE" /*message: /"keylen"/ */ },
+ },
+ {
+ args: ["", "", 42, null],
+ expected: { code: "ERR_INVALID_ARG_TYPE" },
+ },
+ // TODO: throw on these
+ // {
+ // args: ["", "", 42, {}],
+ // expected: { code: "ERR_INVALID_ARG_TYPE" },
+ // },
+ // {
+ // args: ["", "", 42, {}, {}],
+ // expected: { code: "ERR_INVALID_ARG_TYPE" },
+ // },
+ // {
+ // args: ["", "", 42, {}, null],
+ // expected: { code: "ERR_INVALID_ARG_TYPE" },
+ // },
+ // {
+ // args: ["", "", 0.42],
+ // expected: { code: "ERR_OUT_OF_RANGE" /*message: /"keylen"/ */ },
+ // },
+ // {
+ // args: ["", "", -42],
+ // expected: { code: "ERR_OUT_OF_RANGE" /*message: /"keylen"/ */ },
+ // },
+ // {
+ // args: ["", "", 2147485780],
+ // expected: { code: "ERR_OUT_OF_RANGE" /*message: /"keylen"/ */ },
+ // },
+ // {
+ // args: ["", "", 0, { maxmem: 2 ** 53 }],
+ // expected: { code: "ERR_OUT_OF_RANGE" /*message: /"keylen"/ */ },
+ // },
+];
+
+it("scrypt good", () => {
+ for (const options of good) {
+ const { pass, salt, keylen, expected } = options;
+ const actual = crypto.scryptSync(pass, salt, keylen, options);
+ expect(actual.toString("hex")).toBe(expected);
+ }
+});
+
+it("scrypt bad", () => {
+ for (const options of bad) {
+ expect(() => crypto.scryptSync("pass", "salt", 1, options)).toThrow(/Invalid scrypt param/);
+ }
+});
+
+it("scrypt toobig", () => {
+ for (const options of toobig) {
+ expect(() => crypto.scryptSync("pass", "salt", 1, options)).toThrow(/Invalid scrypt param/);
+ }
+});
+
+it("scrypt defaults eql", () => {
+ const defaults = { N: 16384, p: 1, r: 8 };
+ const expected = crypto.scryptSync("pass", "salt", 1, defaults);
+ const actual = crypto.scryptSync("pass", "salt", 1);
+ expect(actual.toString("hex")).toBe(expected.toString("hex"));
+});
+
+// TODO: DEFAULT_ENCODING is read-only
+// it("scrypt defaults encoding", () => {
+// {
+// const defaultEncoding = crypto.DEFAULT_ENCODING;
+// const defaults = { N: 16384, p: 1, r: 8 };
+// const expected = crypto.scryptSync("pass", "salt", 1, defaults);
+
+// const testEncoding = "latin1";
+// crypto.DEFAULT_ENCODING = testEncoding;
+// const actual = crypto.scryptSync("pass", "salt", 1);
+// expect(actual).toBe(expected.toString(testEncoding));
+
+// crypto.DEFAULT_ENCODING = defaultEncoding;
+// }
+// });
+
+it("scrypt badargs", () => {
+ for (const { args, expected } of badargs) {
+ try {
+ crypto.scryptSync(...args);
+ expect(() => {}).toThrow();
+ } catch (e) {
+ if (!("code" in e)) throw e;
+ expect(e.code).toBe(expected.code);
+ }
+ }
+
+ // {
+ // // Values for maxmem that do not fit in 32 bits but that are still safe
+ // // integers should be allowed.
+ // crypto.scrypt(
+ // "",
+ // "",
+ // 4,
+ // { maxmem: 2 ** 52 },
+ // common.mustSucceed((actual) => {
+ // expect(actual.toString("hex")).toBe("d72c87d0");
+ // }),
+ // );
+});
diff --git a/test/js/node/crypto/crypto.test.js b/test/js/node/crypto/crypto.test.js
new file mode 100644
index 000000000..b5b8e9286
--- /dev/null
+++ b/test/js/node/crypto/crypto.test.js
@@ -0,0 +1,106 @@
+import { sha, MD5, MD4, SHA1, SHA224, SHA256, SHA384, SHA512, SHA512_256, gc, CryptoHasher } from "bun";
+import { it, expect, describe } from "bun:test";
+import { readFileSync } from "fs";
+
+const HashClasses = [MD5, MD4, SHA1, SHA224, SHA256, SHA384, SHA512, SHA512_256];
+
+describe("CryptoHasher", () => {
+ it("CryptoHasher.algorithms", () => {
+ expect(CryptoHasher.algorithms).toEqual([
+ "blake2b256",
+ "md4",
+ "md5",
+ "ripemd160",
+ "sha1",
+ "sha224",
+ "sha256",
+ "sha384",
+ "sha512",
+ "sha512-256",
+ ]);
+ });
+
+ it("CryptoHasher md5", () => {
+ var hasher = new CryptoHasher("md5");
+ hasher.update("hello world");
+ expect(hasher.digest("hex")).toBe("5eb63bbbe01eeed093cb22bb8f5acdc3");
+ expect(hasher.algorithm).toBe("md5");
+ });
+
+ it("CryptoHasher blake2b256", () => {
+ var hasher = new CryptoHasher("blake2b256");
+ hasher.update("hello world");
+ expect(hasher.algorithm).toBe("blake2b256");
+
+ expect(hasher.digest("hex")).toBe(
+ // b2sum --length=256
+ "256c83b297114d201b30179f3f0ef0cace9783622da5974326b436178aeef610",
+ );
+ });
+
+ it("CryptoHasher sha512", () => {
+ var hasher = new CryptoHasher("sha512");
+ hasher.update("hello world");
+ expect(hasher.digest("hex")).toBe(
+ "309ecc489c12d6eb4cc40f50c902f2b4d0ed77ee511a7c7a9bcd3ca86d4cd86f989dd35bc5ff499670da34255b45b0cfd830e81f605dcf7dc5542e93ae9cd76f",
+ );
+ expect(hasher.algorithm).toBe("sha512");
+ });
+});
+
+describe("crypto", () => {
+ for (let Hash of HashClasses) {
+ for (let [input, label] of [
+ ["hello world", '"hello world"'],
+ ["hello world".repeat(20).slice(), '"hello world" x 20'],
+ ["", "empty string"],
+ ["a", '"a"'],
+ ]) {
+ describe(label, () => {
+ gc(true);
+
+ it(`${Hash.name} base64`, () => {
+ gc(true);
+ const result = new Hash();
+ result.update(input);
+ expect(typeof result.digest("base64")).toBe("string");
+ gc(true);
+ });
+
+ it(`${Hash.name} hash base64`, () => {
+ Hash.hash(input, "base64");
+ gc(true);
+ });
+
+ it(`${Hash.name} hex`, () => {
+ const result = new Hash();
+ result.update(input);
+ expect(typeof result.digest("hex")).toBe("string");
+ gc(true);
+ });
+
+ it(`${Hash.name} hash hex`, () => {
+ expect(typeof Hash.hash(input, "hex")).toBe("string");
+ gc(true);
+ });
+
+ it(`${Hash.name} buffer`, () => {
+ var buf = new Uint8Array(256);
+ const result = new Hash();
+
+ result.update(input);
+ expect(result.digest(buf)).toBe(buf);
+ expect(buf[0] != 0).toBe(true);
+ gc(true);
+ });
+
+ it(`${Hash.name} buffer`, () => {
+ var buf = new Uint8Array(256);
+
+ expect(Hash.hash(input, buf) instanceof Uint8Array).toBe(true);
+ gc(true);
+ });
+ });
+ }
+ }
+});
diff --git a/test/js/node/crypto/node-crypto.test.js b/test/js/node/crypto/node-crypto.test.js
new file mode 100644
index 000000000..f148f4fe9
--- /dev/null
+++ b/test/js/node/crypto/node-crypto.test.js
@@ -0,0 +1,29 @@
+import { it, expect } from "bun:test";
+
+import crypto from "node:crypto";
+
+it("crypto.randomBytes should return a Buffer", () => {
+ expect(crypto.randomBytes(1) instanceof Buffer).toBe(true);
+ expect(Buffer.isBuffer(crypto.randomBytes(1))).toBe(true);
+});
+
+// https://github.com/oven-sh/bun/issues/1839
+it("crypto.createHash ", () => {
+ function fn() {
+ crypto.createHash("sha1").update(Math.random(), "ascii").digest("base64");
+ }
+
+ for (let i = 0; i < 10; i++) fn();
+});
+
+it("crypto.createHmac", () => {
+ const result = crypto.createHmac("sha256", "key").update("message").digest("base64");
+
+ expect(result).toBe("bp7ym3X//Ft6uuUn1Y/a2y/kLnIZARl2kXNDBl9Y7Uo=");
+});
+
+it("web crypto", async () => {
+ let bytes = new Uint8Array(32);
+ crypto.getRandomValues(bytes);
+ await crypto.subtle.digest("SHA-256", bytes);
+});
diff --git a/test/js/node/dirname.test.js b/test/js/node/dirname.test.js
new file mode 100644
index 000000000..98292dc49
--- /dev/null
+++ b/test/js/node/dirname.test.js
@@ -0,0 +1,9 @@
+import { expect, it } from "bun:test";
+
+it("__dirname should work", () => {
+ expect(import.meta.dir).toBe(__dirname);
+});
+
+it("__filename should work", () => {
+ expect(import.meta.path).toBe(__filename);
+});
diff --git a/test/js/node/disabled-module.test.js b/test/js/node/disabled-module.test.js
new file mode 100644
index 000000000..c12676959
--- /dev/null
+++ b/test/js/node/disabled-module.test.js
@@ -0,0 +1,38 @@
+import { expect, test } from "bun:test";
+
+test("not implemented yet module masquerades as undefined and throws an error", () => {
+ const worker_threads = import.meta.require("worker_threads");
+
+ expect(typeof worker_threads).toBe("undefined");
+ expect(typeof worker_threads.getEnvironmentData).toBe("undefined");
+});
+
+test("AsyncLocalStorage polyfill", () => {
+ const { AsyncLocalStorage } = import.meta.require("async_hooks");
+
+ const store = new AsyncLocalStorage();
+ var called = false;
+ expect(store.getStore()).toBe(null);
+ store.run({ foo: "bar" }, () => {
+ expect(store.getStore()).toEqual({ foo: "bar" });
+ called = true;
+ });
+ expect(store.getStore()).toBe(null);
+ expect(called).toBe(true);
+});
+
+test("AsyncResource polyfill", () => {
+ const { AsyncResource } = import.meta.require("async_hooks");
+
+ const resource = new AsyncResource("test");
+ var called = false;
+ resource.runInAsyncScope(
+ () => {
+ called = true;
+ },
+ null,
+ "foo",
+ "bar",
+ );
+ expect(called).toBe(true);
+});
diff --git a/test/js/node/dns/dns.node.mjs b/test/js/node/dns/dns.node.mjs
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/test/js/node/dns/dns.node.mjs
diff --git a/test/js/node/dns/node-dns.test.js b/test/js/node/dns/node-dns.test.js
new file mode 100644
index 000000000..6f4cac22f
--- /dev/null
+++ b/test/js/node/dns/node-dns.test.js
@@ -0,0 +1,148 @@
+import { expect, test } from "bun:test";
+import * as dns from "node:dns";
+
+// TODO:
+test("it exists", () => {
+ expect(dns).toBeDefined();
+ expect(dns.lookup).toBeDefined();
+ expect(dns.lookupService).toBeDefined();
+ expect(dns.resolve).toBeDefined();
+ expect(dns.resolve4).toBeDefined();
+ expect(dns.resolve6).toBeDefined();
+ expect(dns.resolveSrv).toBeDefined();
+ expect(dns.resolveTxt).toBeDefined();
+ expect(dns.resolveSoa).toBeDefined();
+ expect(dns.resolveNaptr).toBeDefined();
+ expect(dns.resolveMx).toBeDefined();
+ expect(dns.resolveCaa).toBeDefined();
+ expect(dns.resolveNs).toBeDefined();
+ expect(dns.resolvePtr).toBeDefined();
+ expect(dns.resolveCname).toBeDefined();
+});
+
+// //TODO: use a bun.sh SRV for testing
+test("dns.resolveSrv (_test._tcp.test.socketify.dev)", done => {
+ dns.resolveSrv("_test._tcp.test.socketify.dev", (err, results) => {
+ expect(err).toBeNull();
+ expect(results instanceof Array).toBe(true);
+ expect(results[0].name).toBe("_dc-srv.130c90ab9de1._test._tcp.test.socketify.dev");
+ expect(results[0].priority).toBe(50);
+ expect(results[0].weight).toBe(50);
+ expect(results[0].port).toBe(80);
+ done(err);
+ });
+});
+
+test("dns.resolveSrv (_test._tcp.invalid.localhost)", done => {
+ dns.resolveSrv("_test._tcp.invalid.localhost", (err, results) => {
+ expect(err).toBeTruthy();
+ expect(results).toBeUndefined(true);
+ done();
+ });
+});
+
+test("dns.resolveTxt (txt.socketify.dev)", done => {
+ dns.resolveTxt("txt.socketify.dev", (err, results) => {
+ expect(err).toBeNull();
+ expect(results instanceof Array).toBe(true);
+ expect(results[0][0]).toBe("bun_test;test");
+ done(err);
+ });
+});
+
+test("dns.resolveSoa (bun.sh)", done => {
+ dns.resolveSoa("bun.sh", (err, result) => {
+ expect(err).toBeNull();
+ expect(typeof result.serial).toBe("number");
+ expect(result.refresh).toBe(10000);
+ expect(result.retry).toBe(2400);
+ expect(result.expire).toBe(604800);
+ expect(result.minttl).toBe(3600);
+ expect(result.nsname).toBe("hans.ns.cloudflare.com");
+ expect(result.hostmaster).toBe("dns.cloudflare.com");
+ done(err);
+ });
+});
+
+test("dns.resolveNaptr (naptr.socketify.dev)", done => {
+ dns.resolveNaptr("naptr.socketify.dev", (err, results) => {
+ expect(err).toBeNull();
+ expect(results instanceof Array).toBe(true);
+ expect(results[0].flags).toBe("S");
+ expect(results[0].service).toBe("test");
+ expect(results[0].regexp).toBe("");
+ expect(results[0].replacement).toBe("");
+ expect(results[0].order).toBe(1);
+ expect(results[0].preference).toBe(12);
+ done(err);
+ });
+});
+
+test("dns.resolveCaa (caa.socketify.dev)", done => {
+ dns.resolveCaa("caa.socketify.dev", (err, results) => {
+ expect(err).toBeNull();
+ expect(results instanceof Array).toBe(true);
+ expect(results[0].critical).toBe(0);
+ expect(results[0].issue).toBe("bun.sh");
+ done(err);
+ });
+});
+
+test("dns.resolveMx (bun.sh)", done => {
+ dns.resolveMx("bun.sh", (err, results) => {
+ expect(err).toBeNull();
+ expect(results instanceof Array).toBe(true);
+ const priority = results[0].priority;
+ expect(priority >= 0 && priority < 65535).toBe(true);
+ expect(results[0].exchange.includes(".registrar-servers.com")).toBe(true);
+ done(err);
+ });
+});
+
+test("dns.resolveNs (bun.sh) ", done => {
+ dns.resolveNs("bun.sh", (err, results) => {
+ expect(err).toBeNull();
+ expect(results instanceof Array).toBe(true);
+ expect(results[0].includes(".ns.cloudflare.com")).toBe(true);
+ done(err);
+ });
+});
+
+test("dns.resolvePtr (ptr.socketify.dev)", done => {
+ dns.resolvePtr("ptr.socketify.dev", (err, results) => {
+ expect(err).toBeNull();
+ expect(results instanceof Array).toBe(true);
+ expect(results[0]).toBe("bun.sh");
+ done(err);
+ });
+});
+
+test("dns.resolveCname (cname.socketify.dev)", done => {
+ dns.resolveCname("cname.socketify.dev", (err, results) => {
+ expect(err).toBeNull();
+ expect(results instanceof Array).toBe(true);
+ expect(results[0]).toBe("bun.sh");
+ done(err);
+ });
+});
+
+test("dns.lookup (example.com)", done => {
+ dns.lookup("example.com", (err, address, family) => {
+ expect(err).toBeNull();
+ expect(typeof address).toBe("string");
+ done(err);
+ });
+});
+
+test("dns.lookup (localhost)", done => {
+ dns.lookup("localhost", (err, address, family) => {
+ expect(err).toBeNull();
+ if (family === 6) {
+ expect(address).toBe("::1");
+ } else {
+ expect(address).toBe("127.0.0.1");
+ }
+
+ done(err);
+ });
+});
diff --git a/test/js/node/events/event-emitter.test.ts b/test/js/node/events/event-emitter.test.ts
new file mode 100644
index 000000000..2bb891778
--- /dev/null
+++ b/test/js/node/events/event-emitter.test.ts
@@ -0,0 +1,169 @@
+import { test, describe, expect, it } from "bun:test";
+import fs from "node:fs";
+
+// this is also testing that imports with default and named imports in the same statement work
+// our transpiler transform changes this to a var with import.meta.require
+import EventEmitter, { getEventListeners, captureRejectionSymbol } from "node:events";
+import { heapStats } from "bun:jsc";
+
+describe("EventEmitter", () => {
+ it("captureRejectionSymbol", () => {
+ expect(EventEmitter.captureRejectionSymbol).toBeDefined();
+ expect(captureRejectionSymbol).toBeDefined();
+ });
+ test("getEventListeners", () => {
+ expect(getEventListeners(new EventEmitter(), "hey").length).toBe(0);
+ });
+ test("EventEmitter constructor", () => {
+ var emitter = new EventEmitter();
+ emitter.setMaxListeners(100);
+ expect(emitter.getMaxListeners()).toBe(100);
+ });
+
+ test("EventEmitter.removeAllListeners()", () => {
+ var emitter = new EventEmitter();
+ var ran = false;
+ emitter.on("hey", () => {
+ ran = true;
+ });
+ emitter.removeAllListeners();
+ expect(emitter.listenerCount("hey")).toBe(0);
+ emitter.emit("hey");
+ expect(ran).toBe(false);
+ emitter.on("hey", () => {
+ ran = true;
+ });
+ emitter.emit("hey");
+ expect(ran).toBe(true);
+ expect(emitter.listenerCount("hey")).toBe(1);
+ });
+
+ // These are also tests for the done() function in the test runner.
+ test("EventEmitter emit (different tick)", done => {
+ var emitter = new EventEmitter();
+ emitter.on("wow", () => done());
+ queueMicrotask(() => {
+ emitter.emit("wow");
+ });
+ });
+
+ // Unlike Jest, bun supports async and done
+ test("async EventEmitter emit (microtask)", async done => {
+ await 1;
+ var emitter = new EventEmitter();
+ emitter.on("wow", () => done());
+ emitter.emit("wow");
+ });
+
+ test("async EventEmitter emit (microtask) after", async done => {
+ var emitter = new EventEmitter();
+ emitter.on("wow", () => done());
+ await 1;
+ emitter.emit("wow");
+ });
+
+ test("EventEmitter emit (same tick)", done => {
+ var emitter = new EventEmitter();
+
+ emitter.on("wow", () => done());
+
+ emitter.emit("wow");
+ });
+
+ test("EventEmitter emit (setTimeout task)", done => {
+ var emitter = new EventEmitter();
+ emitter.on("wow", () => done());
+ setTimeout(() => emitter.emit("wow"), 1);
+ });
+});
+
+const waysOfCreating = [
+ () => Object.create(EventEmitter.prototype),
+ () => new EventEmitter(),
+ () => new (class extends EventEmitter {})(),
+ () => {
+ class MyEmitter extends EventEmitter {}
+ return new MyEmitter();
+ },
+ () => {
+ var foo = {};
+ Object.setPrototypeOf(foo, EventEmitter.prototype);
+ return foo;
+ },
+ () => {
+ const FakeEmitter = function FakeEmitter() {
+ return EventEmitter.call(this);
+ };
+ Object.setPrototypeOf(FakeEmitter.prototype, EventEmitter.prototype);
+ Object.setPrototypeOf(FakeEmitter, EventEmitter);
+ return new FakeEmitter();
+ },
+ () => {
+ const FakeEmitter = function FakeEmitter() {
+ EventEmitter.call(this);
+ };
+ Object.assign(FakeEmitter.prototype, EventEmitter.prototype);
+ Object.assign(FakeEmitter, EventEmitter);
+ return new FakeEmitter();
+ },
+ () => {
+ var foo = {};
+ Object.assign(foo, EventEmitter.prototype);
+ return foo;
+ },
+];
+
+for (let create of waysOfCreating) {
+ it(`${create.toString().slice(10, 40).replaceAll("\n", "\\n").trim()} should work`, () => {
+ var myEmitter = create();
+ var called = false;
+ myEmitter.once("event", function () {
+ called = true;
+ expect(this as any).toBe(myEmitter);
+ });
+ var firstEvents = myEmitter._events;
+ expect(myEmitter.listenerCount("event")).toBe(1);
+
+ expect(myEmitter.emit("event")).toBe(true);
+ expect(myEmitter.listenerCount("event")).toBe(0);
+
+ expect(firstEvents).toBe(myEmitter._events);
+ expect(called).toBe(true);
+ });
+}
+
+test("EventEmitter.on", () => {
+ var myEmitter = new EventEmitter();
+ expect(myEmitter.on("foo", () => {})).toBe(myEmitter);
+});
+
+test("EventEmitter.off", () => {
+ var myEmitter = new EventEmitter();
+ expect(myEmitter.off("foo", () => {})).toBe(myEmitter);
+});
+
+// Internally, EventEmitter has a JSC::Weak with the thisValue of the listener
+test("EventEmitter GCs", () => {
+ Bun.gc(true);
+
+ const startCount = heapStats().objectTypeCounts["EventEmitter"] || 0;
+ (function () {
+ Bun.gc(true);
+
+ function EventEmitterSubclass(this: any) {
+ EventEmitter.call(this);
+ }
+
+ Object.setPrototypeOf(EventEmitterSubclass.prototype, EventEmitter.prototype);
+ Object.setPrototypeOf(EventEmitterSubclass, EventEmitter);
+
+ var myEmitter = new EventEmitterSubclass();
+ myEmitter.on("foo", () => {});
+ myEmitter.emit("foo");
+ Bun.gc(true);
+ })();
+ Bun.gc(true);
+
+ const endCount = heapStats().objectTypeCounts["EventEmitter"] || 0;
+ expect(endCount).toBe(startCount);
+});
diff --git a/test/js/node/events/node-builtins.test.js b/test/js/node/events/node-builtins.test.js
new file mode 100644
index 000000000..67050f31a
--- /dev/null
+++ b/test/js/node/events/node-builtins.test.js
@@ -0,0 +1,18 @@
+import { describe, it, expect } from "bun:test";
+
+import { EventEmitter } from "events";
+var emitters = [EventEmitter, require("events")];
+describe("EventEmitter", () => {
+ it("should emit events", () => {
+ for (let Emitter of emitters) {
+ const emitter = new Emitter();
+ var called = false;
+ const listener = () => {
+ called = true;
+ };
+ emitter.on("test", listener);
+ emitter.emit("test");
+ expect(called).toBe(true);
+ }
+ });
+});
diff --git a/test/js/node/fs/export-*-from.ts b/test/js/node/fs/export-*-from.ts
new file mode 100644
index 000000000..1c2b97875
--- /dev/null
+++ b/test/js/node/fs/export-*-from.ts
@@ -0,0 +1 @@
+export * from "node:fs";
diff --git a/test/js/node/fs/export-from.ts b/test/js/node/fs/export-from.ts
new file mode 100644
index 000000000..5258f9fde
--- /dev/null
+++ b/test/js/node/fs/export-from.ts
@@ -0,0 +1 @@
+export { ReadStream, WriteStream } from "node:fs";
diff --git a/test/js/node/fs/fs-stream.js b/test/js/node/fs/fs-stream.js
new file mode 100644
index 000000000..db56fdfaa
--- /dev/null
+++ b/test/js/node/fs/fs-stream.js
@@ -0,0 +1,21 @@
+import { createReadStream, createWriteStream, readFileSync } from "fs";
+
+await new Promise((resolve, reject) => {
+ createReadStream("fs-stream.js")
+ .pipe(createWriteStream("/tmp/fs-stream.copy.js"))
+ .once("error", err => reject(err))
+ .once("finish", () => {
+ try {
+ const copied = readFileSync("/tmp/fs-stream.copy.js", "utf8");
+ const real = readFileSync("/tmp/fs-stream.js", "utf8");
+ if (copied !== real) {
+ reject(new Error("fs-stream.js is not the same as fs-stream.copy.js"));
+ return;
+ }
+
+ resolve(true);
+ } catch (err) {
+ reject(err);
+ }
+ });
+});
diff --git a/test/js/node/fs/fs-stream.link.js b/test/js/node/fs/fs-stream.link.js
new file mode 120000
index 000000000..0cadae0e5
--- /dev/null
+++ b/test/js/node/fs/fs-stream.link.js
@@ -0,0 +1 @@
+./test/bun.js/fs-stream.js \ No newline at end of file
diff --git a/test/js/node/fs/fs.test.ts b/test/js/node/fs/fs.test.ts
new file mode 100644
index 000000000..48abef6cb
--- /dev/null
+++ b/test/js/node/fs/fs.test.ts
@@ -0,0 +1,1013 @@
+import { beforeEach, describe, expect, it } from "bun:test";
+import { gc, gcTick } from "harness";
+import fs, {
+ closeSync,
+ existsSync,
+ mkdirSync,
+ openSync,
+ readdirSync,
+ readFile,
+ readFileSync,
+ readSync,
+ writeFileSync,
+ writeSync,
+ statSync,
+ lstatSync,
+ copyFileSync,
+ rmSync,
+ rmdir,
+ rmdirSync,
+ createReadStream,
+ createWriteStream,
+ promises,
+ unlinkSync,
+ mkdtempSync,
+ constants,
+ Dirent,
+ Stats,
+} from "node:fs";
+
+import _promises from "node:fs/promises";
+
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+
+import { ReadStream as ReadStream_, WriteStream as WriteStream_ } from "./export-from.js";
+import { ReadStream as ReadStreamStar_, WriteStream as WriteStreamStar_ } from "./export-*-from.js";
+
+const Buffer = globalThis.Buffer || Uint8Array;
+
+if (!import.meta.dir) {
+ import.meta.dir = ".";
+}
+
+function mkdirForce(path) {
+ if (!existsSync(path)) mkdirSync(path, { recursive: true });
+}
+
+describe("copyFileSync", () => {
+ it("should work for files < 128 KB", () => {
+ const tempdir = `/tmp/fs.test.js/${Date.now()}/1234/hi`;
+ expect(existsSync(tempdir)).toBe(false);
+ expect(tempdir.includes(mkdirSync(tempdir, { recursive: true })!)).toBe(true);
+
+ // that don't exist
+ copyFileSync(import.meta.path, tempdir + "/copyFileSync.js");
+ expect(existsSync(tempdir + "/copyFileSync.js")).toBe(true);
+ expect(readFileSync(tempdir + "/copyFileSync.js", "utf-8")).toBe(readFileSync(import.meta.path, "utf-8"));
+
+ // that do exist
+ copyFileSync(tempdir + "/copyFileSync.js", tempdir + "/copyFileSync.js1");
+ writeFileSync(tempdir + "/copyFileSync.js1", "hello");
+ copyFileSync(tempdir + "/copyFileSync.js1", tempdir + "/copyFileSync.js");
+
+ expect(readFileSync(tempdir + "/copyFileSync.js", "utf-8")).toBe("hello");
+ });
+
+ it("should work for files > 128 KB ", () => {
+ const tempdir = `/tmp/fs.test.js/${Date.now()}-1/1234/hi`;
+ expect(existsSync(tempdir)).toBe(false);
+ expect(tempdir.includes(mkdirSync(tempdir, { recursive: true })!)).toBe(true);
+ var buffer = new Int32Array(128 * 1024);
+ for (let i = 0; i < buffer.length; i++) {
+ buffer[i] = i % 256;
+ }
+
+ const hash = Bun.hash(buffer.buffer);
+ writeFileSync(tempdir + "/copyFileSync.src.blob", buffer.buffer);
+
+ expect(existsSync(tempdir + "/copyFileSync.dest.blob")).toBe(false);
+ expect(existsSync(tempdir + "/copyFileSync.src.blob")).toBe(true);
+ copyFileSync(tempdir + "/copyFileSync.src.blob", tempdir + "/copyFileSync.dest.blob");
+
+ expect(Bun.hash(readFileSync(tempdir + "/copyFileSync.dest.blob"))).toBe(hash);
+ buffer[0] = 255;
+ writeFileSync(tempdir + "/copyFileSync.src.blob", buffer.buffer);
+ copyFileSync(tempdir + "/copyFileSync.src.blob", tempdir + "/copyFileSync.dest.blob");
+ expect(Bun.hash(readFileSync(tempdir + "/copyFileSync.dest.blob"))).toBe(Bun.hash(buffer.buffer));
+ });
+});
+
+describe("mkdirSync", () => {
+ it("should create a directory", () => {
+ const tempdir = `/tmp/fs.test.js/${Date.now()}/1234/hi`;
+ expect(existsSync(tempdir)).toBe(false);
+ expect(tempdir.includes(mkdirSync(tempdir, { recursive: true })!)).toBe(true);
+ expect(existsSync(tempdir)).toBe(true);
+ });
+});
+
+it("readdirSync on import.meta.dir", () => {
+ const dirs = readdirSync(import.meta.dir);
+ expect(dirs.length > 0).toBe(true);
+ var match = false;
+ gc(true);
+ for (let i = 0; i < dirs.length; i++) {
+ if (dirs[i] === import.meta.file) {
+ match = true;
+ }
+ }
+ gc(true);
+ expect(match).toBe(true);
+});
+
+// https://github.com/oven-sh/bun/issues/1887
+it("mkdtempSync, readdirSync, rmdirSync and unlinkSync with non-ascii", () => {
+ const tempdir = mkdtempSync(`/tmp/emoji-fruit-🍇 🍈 🍉 🍊 🍋`);
+ expect(existsSync(tempdir)).toBe(true);
+ writeFileSync(tempdir + "/non-ascii-👍.txt", "hello");
+ const dirs = readdirSync(tempdir);
+ expect(dirs.length > 0).toBe(true);
+ var match = false;
+ gc(true);
+ for (let i = 0; i < dirs.length; i++) {
+ if (dirs[i].endsWith("non-ascii-👍.txt")) {
+ match = true;
+ break;
+ }
+ }
+ gc(true);
+ expect(match).toBe(true);
+ unlinkSync(tempdir + "/non-ascii-👍.txt");
+ expect(existsSync(tempdir + "/non-ascii-👍.txt")).toBe(false);
+ rmdirSync(tempdir);
+ expect(existsSync(tempdir)).toBe(false);
+});
+
+it("mkdtempSync() empty name", () => {
+ // @ts-ignore-next-line
+ const tempdir = mkdtempSync();
+ expect(existsSync(tempdir)).toBe(true);
+ writeFileSync(tempdir + "/non-ascii-👍.txt", "hello");
+ const dirs = readdirSync(tempdir);
+ expect(dirs.length > 0).toBe(true);
+ var match = false;
+ gc(true);
+ for (let i = 0; i < dirs.length; i++) {
+ if (dirs[i].endsWith("non-ascii-👍.txt")) {
+ match = true;
+ break;
+ }
+ }
+ gc(true);
+ expect(match).toBe(true);
+ unlinkSync(tempdir + "/non-ascii-👍.txt");
+ expect(existsSync(tempdir + "/non-ascii-👍.txt")).toBe(false);
+ rmdirSync(tempdir);
+ expect(existsSync(tempdir)).toBe(false);
+});
+
+it("readdirSync on import.meta.dir with trailing slash", () => {
+ const dirs = readdirSync(import.meta.dir + "/");
+ expect(dirs.length > 0).toBe(true);
+ // this file should exist in it
+ var match = false;
+ for (let i = 0; i < dirs.length; i++) {
+ if (dirs[i] === import.meta.file) {
+ match = true;
+ }
+ }
+ expect(match).toBe(true);
+});
+
+it("readdirSync works on empty directories", () => {
+ const path = `/tmp/fs-test-empty-dir-${(Math.random() * 100000 + 100).toString(32)}`;
+ mkdirSync(path, { recursive: true });
+ expect(readdirSync(path).length).toBe(0);
+});
+
+it("readdirSync works on directories with under 32 files", () => {
+ const path = `/tmp/fs-test-one-dir-${(Math.random() * 100000 + 100).toString(32)}`;
+ mkdirSync(path, { recursive: true });
+ writeFileSync(`${path}/a`, "a");
+ const results = readdirSync(path);
+ expect(results.length).toBe(1);
+ expect(results[0]).toBe("a");
+});
+
+it("readdirSync throws when given a file path", () => {
+ try {
+ readdirSync(import.meta.path);
+ throw new Error("should not get here");
+ } catch (exception: any) {
+ expect(exception.name).toBe("ENOTDIR");
+ }
+});
+
+it("readdirSync throws when given a path that doesn't exist", () => {
+ try {
+ readdirSync(import.meta.path + "/does-not-exist/really");
+ throw new Error("should not get here");
+ } catch (exception: any) {
+ expect(exception.name).toBe("ENOTDIR");
+ }
+});
+
+it("readdirSync throws when given a file path with trailing slash", () => {
+ try {
+ readdirSync(import.meta.path + "/");
+ throw new Error("should not get here");
+ } catch (exception: any) {
+ expect(exception.name).toBe("ENOTDIR");
+ }
+});
+
+describe("readSync", () => {
+ const firstFourBytes = new Uint32Array(new TextEncoder().encode("File").buffer)[0];
+ it("works with a position set to 0", () => {
+ const fd = openSync(import.meta.dir + "/readFileSync.txt", "r");
+ const four = new Uint8Array(4);
+
+ {
+ const count = readSync(fd, four, 0, 4, 0);
+ const u32 = new Uint32Array(four.buffer)[0];
+ expect(u32).toBe(firstFourBytes);
+ expect(count).toBe(4);
+ }
+ closeSync(fd);
+ });
+ it("works without position set", () => {
+ const fd = openSync(import.meta.dir + "/readFileSync.txt", "r");
+ const four = new Uint8Array(4);
+ {
+ const count = readSync(fd, four);
+ const u32 = new Uint32Array(four.buffer)[0];
+ expect(u32).toBe(firstFourBytes);
+ expect(count).toBe(4);
+ }
+ closeSync(fd);
+ });
+});
+
+describe("writeSync", () => {
+ it("works with a position set to 0", () => {
+ const fd = openSync(import.meta.dir + "/writeFileSync.txt", "w+");
+ const four = new Uint8Array(4);
+
+ {
+ const count = writeSync(fd, new TextEncoder().encode("File"), 0, 4, 0);
+ expect(count).toBe(4);
+ }
+ closeSync(fd);
+ });
+ it("works without position set", () => {
+ const fd = openSync(import.meta.dir + "/writeFileSync.txt", "w+");
+ const four = new Uint8Array(4);
+ {
+ const count = writeSync(fd, new TextEncoder().encode("File"));
+ expect(count).toBe(4);
+ }
+ closeSync(fd);
+ });
+});
+
+describe("readFileSync", () => {
+ it("works", () => {
+ gc();
+ const text = readFileSync(import.meta.dir + "/readFileSync.txt", "utf8");
+ gc();
+ expect(text).toBe("File read successfully");
+ gc();
+ });
+
+ it("works with a file url", () => {
+ gc();
+ const text = readFileSync(new URL("file://" + import.meta.dir + "/readFileSync.txt"), "utf8");
+ gc();
+ expect(text).toBe("File read successfully");
+ });
+
+ it("works with special files in the filesystem", () => {
+ {
+ const text = readFileSync("/dev/null", "utf8");
+ gc();
+ expect(text).toBe("");
+ }
+
+ if (process.platform === "linux") {
+ const text = readFileSync("/proc/filesystems");
+ gc();
+ expect(text.length > 0).toBe(true);
+ }
+ });
+
+ it("returning Buffer works", () => {
+ const text = readFileSync(import.meta.dir + "/readFileSync.txt");
+ const encoded = [
+ 70, 105, 108, 101, 32, 114, 101, 97, 100, 32, 115, 117, 99, 99, 101, 115, 115, 102, 117, 108, 108, 121,
+ ];
+ for (let i = 0; i < encoded.length; i++) {
+ expect(text[i]).toBe(encoded[i]);
+ }
+ });
+});
+
+describe("readFile", () => {
+ it("works", async () => {
+ gc();
+ await new Promise((resolve, reject) => {
+ readFile(import.meta.dir + "/readFileSync.txt", "utf8", (err, text) => {
+ gc();
+ expect(text).toBe("File read successfully");
+ resolve(true);
+ });
+ });
+ });
+
+ it("returning Buffer works", async () => {
+ gc();
+ await new Promise((resolve, reject) => {
+ gc();
+ readFile(import.meta.dir + "/readFileSync.txt", (err, text) => {
+ const encoded = [
+ 70, 105, 108, 101, 32, 114, 101, 97, 100, 32, 115, 117, 99, 99, 101, 115, 115, 102, 117, 108, 108, 121,
+ ];
+ gc();
+ for (let i = 0; i < encoded.length; i++) {
+ expect(text[i]).toBe(encoded[i]);
+ }
+ resolve(true);
+ });
+ });
+ });
+});
+
+describe("writeFileSync", () => {
+ it("works", () => {
+ const path = `/tmp/${Date.now()}.writeFileSync.txt`;
+ writeFileSync(path, "File written successfully", "utf8");
+
+ expect(readFileSync(path, "utf8")).toBe("File written successfully");
+ });
+
+ it("returning Buffer works", () => {
+ const buffer = new Buffer([
+ 70, 105, 108, 101, 32, 119, 114, 105, 116, 116, 101, 110, 32, 115, 117, 99, 99, 101, 115, 115, 102, 117, 108, 108,
+ 121,
+ ]);
+ const path = `/tmp/${Date.now()}.blob.writeFileSync.txt`;
+ writeFileSync(path, buffer);
+ const out = readFileSync(path);
+
+ for (let i = 0; i < buffer.length; i++) {
+ expect(buffer[i]).toBe(out[i]);
+ }
+ });
+ it("returning ArrayBuffer works", () => {
+ const buffer = new Buffer([
+ 70, 105, 108, 101, 32, 119, 114, 105, 116, 116, 101, 110, 32, 115, 117, 99, 99, 101, 115, 115, 102, 117, 108, 108,
+ 121,
+ ]);
+ const path = `/tmp/${Date.now()}.blob2.writeFileSync.txt`;
+ writeFileSync(path, buffer);
+ const out = readFileSync(path);
+
+ for (let i = 0; i < buffer.length; i++) {
+ expect(buffer[i]).toBe(out[i]);
+ }
+ });
+});
+
+function triggerDOMJIT(target, fn, result) {
+ for (let i = 0; i < 9999; i++) {
+ if (fn.apply(target) !== result) {
+ throw new Error("DOMJIT failed");
+ }
+ }
+}
+
+describe("lstat", () => {
+ it("file metadata is correct", () => {
+ const fileStats = lstatSync(new URL("./fs-stream.js", import.meta.url).toString().slice("file://".length - 1));
+ expect(fileStats.isSymbolicLink()).toBe(false);
+ expect(fileStats.isFile()).toBe(true);
+ expect(fileStats.isDirectory()).toBe(false);
+
+ triggerDOMJIT(fileStats, fileStats.isFile, true);
+ triggerDOMJIT(fileStats, fileStats.isDirectory, false);
+ triggerDOMJIT(fileStats, fileStats.isSymbolicLink, false);
+ });
+
+ it("folder metadata is correct", () => {
+ const fileStats = lstatSync(new URL("../../test", import.meta.url).toString().slice("file://".length - 1));
+ expect(fileStats.isSymbolicLink()).toBe(false);
+ expect(fileStats.isFile()).toBe(false);
+ expect(fileStats.isDirectory()).toBe(true);
+
+ triggerDOMJIT(fileStats, fileStats.isFile, false);
+ triggerDOMJIT(fileStats, fileStats.isDirectory, true);
+ triggerDOMJIT(fileStats, fileStats.isSymbolicLink, false);
+ });
+
+ it("symlink metadata is correct", () => {
+ const linkStats = lstatSync(new URL("./fs-stream.link.js", import.meta.url).toString().slice("file://".length - 1));
+ expect(linkStats.isSymbolicLink()).toBe(true);
+ expect(linkStats.isFile()).toBe(false);
+ expect(linkStats.isDirectory()).toBe(false);
+
+ triggerDOMJIT(linkStats, linkStats.isFile, false);
+ triggerDOMJIT(linkStats, linkStats.isDirectory, false);
+ triggerDOMJIT(linkStats, linkStats.isSymbolicLink, true);
+ });
+});
+
+describe("stat", () => {
+ it("file metadata is correct", () => {
+ const fileStats = statSync(new URL("./fs-stream.js", import.meta.url).toString().slice("file://".length - 1));
+ expect(fileStats.isSymbolicLink()).toBe(false);
+ expect(fileStats.isFile()).toBe(true);
+ expect(fileStats.isDirectory()).toBe(false);
+
+ triggerDOMJIT(fileStats, fileStats.isFile, true);
+ triggerDOMJIT(fileStats, fileStats.isDirectory, false);
+ triggerDOMJIT(fileStats, fileStats.isSymbolicLink, false);
+ });
+
+ it("folder metadata is correct", () => {
+ const fileStats = statSync(new URL("../../test", import.meta.url).toString().slice("file://".length - 1));
+ expect(fileStats.isSymbolicLink()).toBe(false);
+ expect(fileStats.isFile()).toBe(false);
+ expect(fileStats.isDirectory()).toBe(true);
+ expect(typeof fileStats.dev).toBe("number");
+ expect(typeof fileStats.ino).toBe("number");
+ expect(typeof fileStats.mode).toBe("number");
+ expect(typeof fileStats.nlink).toBe("number");
+ expect(typeof fileStats.uid).toBe("number");
+ expect(typeof fileStats.gid).toBe("number");
+ expect(typeof fileStats.rdev).toBe("number");
+ expect(typeof fileStats.size).toBe("number");
+ expect(typeof fileStats.blksize).toBe("number");
+ expect(typeof fileStats.blocks).toBe("number");
+ expect(typeof fileStats.atimeMs).toBe("number");
+ expect(typeof fileStats.mtimeMs).toBe("number");
+ expect(typeof fileStats.ctimeMs).toBe("number");
+ expect(typeof fileStats.birthtimeMs).toBe("number");
+ expect(typeof fileStats.atime).toBe("object");
+ expect(typeof fileStats.mtime).toBe("object");
+ expect(typeof fileStats.ctime).toBe("object");
+ expect(typeof fileStats.birthtime).toBe("object");
+
+ triggerDOMJIT(fileStats, fileStats.isFile, false);
+ triggerDOMJIT(fileStats, fileStats.isDirectory, true);
+ triggerDOMJIT(fileStats, fileStats.isSymbolicLink, false);
+ });
+
+ it("stat returns ENOENT", () => {
+ try {
+ statSync("/tmp/doesntexist");
+ throw "statSync should throw";
+ } catch (e: any) {
+ expect(e.code).toBe("ENOENT");
+ }
+ });
+});
+
+describe("rm", () => {
+ it("removes a file", () => {
+ const path = `/tmp/${Date.now()}.rm.txt`;
+ writeFileSync(path, "File written successfully", "utf8");
+ expect(existsSync(path)).toBe(true);
+ rmSync(path);
+ expect(existsSync(path)).toBe(false);
+ });
+
+ it("removes a dir", () => {
+ const path = `/tmp/${Date.now()}.rm.dir`;
+ try {
+ mkdirSync(path);
+ } catch (e) {}
+ expect(existsSync(path)).toBe(true);
+ rmSync(path);
+ expect(existsSync(path)).toBe(false);
+ });
+
+ it("removes a dir recursively", () => {
+ const path = `/tmp/${Date.now()}.rm.dir/foo/bar`;
+ try {
+ mkdirSync(path, { recursive: true });
+ } catch (e) {}
+ expect(existsSync(path)).toBe(true);
+ rmSync(join(path, "../../"), { recursive: true });
+ expect(existsSync(path)).toBe(false);
+ });
+});
+
+describe("rmdir", () => {
+ it("removes a file", done => {
+ const path = `/tmp/${Date.now()}.rm.txt`;
+ writeFileSync(path, "File written successfully", "utf8");
+ expect(existsSync(path)).toBe(true);
+ rmdir(path, err => {
+ try {
+ expect(err).toBeDefined();
+ expect(err!.code).toBe("EPERM");
+ expect(err!.message).toBe("Operation not permitted");
+ expect(existsSync(path)).toBe(true);
+ } catch (e) {
+ return done(e);
+ } finally {
+ done();
+ }
+ });
+ });
+
+ it("removes a dir", done => {
+ const path = `/tmp/${Date.now()}.rm.dir`;
+ try {
+ mkdirSync(path);
+ } catch (e) {}
+ expect(existsSync(path)).toBe(true);
+ rmdir(path, err => {
+ if (err) return done(err);
+ expect(existsSync(path)).toBe(false);
+ done();
+ });
+ });
+ // TODO support `recursive: true`
+ it("removes a dir recursively", done => {
+ const path = `/tmp/${Date.now()}.rm.dir/foo/bar`;
+ try {
+ mkdirSync(path, { recursive: true });
+ } catch (e) {}
+ expect(existsSync(path)).toBe(true);
+ rmdir(join(path, "../../"), { recursive: true }, err => {
+ try {
+ expect(existsSync(path)).toBe(false);
+ done(err);
+ } catch (e) {
+ return done(e);
+ } finally {
+ done();
+ }
+ });
+ });
+});
+
+describe("rmdirSync", () => {
+ it("removes a file", () => {
+ const path = `/tmp/${Date.now()}.rm.txt`;
+ writeFileSync(path, "File written successfully", "utf8");
+ expect(existsSync(path)).toBe(true);
+ expect(() => {
+ rmdirSync(path);
+ }).toThrow("Operation not permitted");
+ expect(existsSync(path)).toBe(true);
+ });
+ it("removes a dir", () => {
+ const path = `/tmp/${Date.now()}.rm.dir`;
+ try {
+ mkdirSync(path);
+ } catch (e) {}
+ expect(existsSync(path)).toBe(true);
+ rmdirSync(path);
+ expect(existsSync(path)).toBe(false);
+ });
+ // TODO support `recursive: true`
+ it("removes a dir recursively", () => {
+ const path = `/tmp/${Date.now()}.rm.dir/foo/bar`;
+ try {
+ mkdirSync(path, { recursive: true });
+ } catch (e) {}
+ expect(existsSync(path)).toBe(true);
+ rmdirSync(join(path, "../../"), { recursive: true });
+ expect(existsSync(path)).toBe(false);
+ });
+});
+
+describe("createReadStream", () => {
+ it("works (1 chunk)", async () => {
+ return await new Promise((resolve, reject) => {
+ var stream = createReadStream(import.meta.dir + "/readFileSync.txt", {});
+
+ stream.on("error", e => {
+ reject(e);
+ });
+
+ stream.on("data", chunk => {
+ expect(chunk instanceof Buffer).toBe(true);
+ expect(chunk.length).toBe("File read successfully".length);
+ expect(chunk.toString()).toBe("File read successfully");
+ });
+
+ stream.on("close", () => {
+ resolve(true);
+ });
+ });
+ });
+
+ it("works (22 chunk)", async () => {
+ var stream = createReadStream(import.meta.dir + "/readFileSync.txt", {
+ highWaterMark: 1,
+ });
+
+ var data = readFileSync(import.meta.dir + "/readFileSync.txt", "utf8");
+ var i = 0;
+ return await new Promise(resolve => {
+ stream.on("data", chunk => {
+ expect(chunk instanceof Buffer).toBe(true);
+ expect(chunk.length).toBe(1);
+ expect(chunk.toString()).toBe(data[i++]);
+ });
+
+ stream.on("end", () => {
+ resolve(true);
+ });
+ });
+ });
+});
+
+describe("fs.WriteStream", () => {
+ it("should be exported", () => {
+ expect(fs.WriteStream).toBeDefined();
+ });
+
+ it("should be constructable", () => {
+ // @ts-ignore-next-line
+ const stream = new fs.WriteStream("test.txt");
+ expect(stream instanceof fs.WriteStream).toBe(true);
+ });
+
+ it("should be able to write to a file", done => {
+ const pathToDir = `${tmpdir()}/${Date.now()}`;
+ mkdirForce(pathToDir);
+ const path = join(pathToDir, `fs-writestream-test.txt`);
+
+ // @ts-ignore-next-line
+ const stream = new fs.WriteStream(path, { flags: "w+" });
+ stream.write("Test file written successfully");
+ stream.end();
+
+ stream.on("error", e => {
+ done(e instanceof Error ? e : new Error(e));
+ });
+
+ stream.on("finish", () => {
+ expect(readFileSync(path, "utf8")).toBe("Test file written successfully");
+ done();
+ });
+ });
+
+ it("should work if re-exported by name", () => {
+ // @ts-ignore-next-line
+ const stream = new WriteStream_("test.txt");
+ expect(stream instanceof WriteStream_).toBe(true);
+ expect(stream instanceof WriteStreamStar_).toBe(true);
+ expect(stream instanceof fs.WriteStream).toBe(true);
+ });
+
+ it("should work if re-exported by name, called without new", () => {
+ // @ts-ignore-next-line
+ const stream = WriteStream_("test.txt");
+ expect(stream instanceof WriteStream_).toBe(true);
+ expect(stream instanceof WriteStreamStar_).toBe(true);
+ expect(stream instanceof fs.WriteStream).toBe(true);
+ });
+
+ it("should work if re-exported, as export * from ...", () => {
+ // @ts-ignore-next-line
+ const stream = new WriteStreamStar_("test.txt");
+ expect(stream instanceof WriteStream_).toBe(true);
+ expect(stream instanceof WriteStreamStar_).toBe(true);
+ expect(stream instanceof fs.WriteStream).toBe(true);
+ });
+
+ it("should work if re-exported, as export * from..., called without new", () => {
+ // @ts-ignore-next-line
+ const stream = WriteStreamStar_("test.txt");
+ expect(stream instanceof WriteStream_).toBe(true);
+ expect(stream instanceof WriteStreamStar_).toBe(true);
+ expect(stream instanceof fs.WriteStream).toBe(true);
+ });
+
+ it("should be able to write to a file with re-exported WriteStream", done => {
+ const pathToDir = `${tmpdir()}/${Date.now()}`;
+ mkdirForce(pathToDir);
+ const path = join(pathToDir, `fs-writestream-re-exported-test.txt`);
+ // @ts-ignore-next-line
+ const stream = new WriteStream_(path, { flags: "w+" });
+ stream.write("Test file written successfully");
+ stream.end();
+
+ stream.on("error", e => {
+ done(e instanceof Error ? e : new Error(e));
+ });
+
+ stream.on("finish", () => {
+ expect(readFileSync(path, "utf8")).toBe("Test file written successfully");
+ done();
+ });
+ });
+});
+
+describe("fs.ReadStream", () => {
+ it("should be exported", () => {
+ expect(fs.ReadStream).toBeDefined();
+ });
+
+ it("should be constructable", () => {
+ // @ts-ignore-next-line
+ const stream = new fs.ReadStream("test.txt");
+ expect(stream instanceof fs.ReadStream).toBe(true);
+ });
+
+ it("should be able to read from a file", done => {
+ const pathToDir = `${tmpdir()}/${Date.now()}`;
+ mkdirForce(pathToDir);
+ const path = join(pathToDir, `fs-readstream-test.txt`);
+
+ writeFileSync(path, "Test file written successfully", {
+ encoding: "utf8",
+ flag: "w+",
+ });
+ // @ts-ignore-next-line
+ const stream = new fs.ReadStream(path);
+ stream.setEncoding("utf8");
+ stream.on("error", e => {
+ done(e instanceof Error ? e : new Error(e));
+ });
+
+ let data = "";
+
+ stream.on("data", chunk => {
+ data += chunk;
+ });
+
+ stream.on("end", () => {
+ expect(data).toBe("Test file written successfully");
+ done();
+ });
+ });
+
+ it("should work if re-exported by name", () => {
+ // @ts-ignore-next-line
+ const stream = new ReadStream_("test.txt");
+ expect(stream instanceof ReadStream_).toBe(true);
+ expect(stream instanceof ReadStreamStar_).toBe(true);
+ expect(stream instanceof fs.ReadStream).toBe(true);
+ });
+
+ it("should work if re-exported by name, called without new", () => {
+ // @ts-ignore-next-line
+ const stream = ReadStream_("test.txt");
+ expect(stream instanceof ReadStream_).toBe(true);
+ expect(stream instanceof ReadStreamStar_).toBe(true);
+ expect(stream instanceof fs.ReadStream).toBe(true);
+ });
+
+ it("should work if re-exported as export * from ...", () => {
+ // @ts-ignore-next-line
+ const stream = new ReadStreamStar_("test.txt");
+ expect(stream instanceof ReadStreamStar_).toBe(true);
+ expect(stream instanceof ReadStream_).toBe(true);
+ expect(stream instanceof fs.ReadStream).toBe(true);
+ });
+
+ it("should work if re-exported as export * from ..., called without new", () => {
+ // @ts-ignore-next-line
+ const stream = ReadStreamStar_("test.txt");
+ expect(stream instanceof ReadStreamStar_).toBe(true);
+ expect(stream instanceof ReadStream_).toBe(true);
+ expect(stream instanceof fs.ReadStream).toBe(true);
+ });
+
+ it("should be able to read from a file, with re-exported ReadStream", done => {
+ const pathToDir = `${tmpdir()}/${Date.now()}`;
+ mkdirForce(pathToDir);
+ const path = join(pathToDir, `fs-readstream-re-exported-test.txt`);
+
+ writeFileSync(path, "Test file written successfully", {
+ encoding: "utf8",
+ flag: "w+",
+ });
+
+ // @ts-ignore-next-line
+ const stream = new ReadStream_(path);
+ stream.setEncoding("utf8");
+ stream.on("error", e => {
+ done(e instanceof Error ? e : new Error(e));
+ });
+
+ let data = "";
+
+ stream.on("data", chunk => {
+ data += chunk;
+ });
+
+ stream.on("end", () => {
+ expect(data).toBe("Test file written successfully");
+ done();
+ });
+ });
+});
+
+describe("createWriteStream", () => {
+ it("simple write stream finishes", async () => {
+ const path = `/tmp/fs.test.js/${Date.now()}.createWriteStream.txt`;
+ const stream = createWriteStream(path);
+ stream.write("Test file written successfully");
+ stream.end();
+
+ return await new Promise((resolve, reject) => {
+ stream.on("error", e => {
+ reject(e);
+ });
+
+ stream.on("finish", () => {
+ expect(readFileSync(path, "utf8")).toBe("Test file written successfully");
+ resolve(true);
+ });
+ });
+ });
+
+ it("writing null throws ERR_STREAM_NULL_VALUES", async () => {
+ const path = `/tmp/fs.test.js/${Date.now()}.createWriteStreamNulls.txt`;
+ const stream = createWriteStream(path);
+ try {
+ stream.write(null);
+ expect(() => {}).toThrow(Error);
+ } catch (exception: any) {
+ expect(exception.code).toBe("ERR_STREAM_NULL_VALUES");
+ }
+ });
+
+ it("writing null throws ERR_STREAM_NULL_VALUES (objectMode: true)", async () => {
+ const path = `/tmp/fs.test.js/${Date.now()}.createWriteStreamNulls.txt`;
+ const stream = createWriteStream(path, {
+ // @ts-ignore-next-line
+ objectMode: true,
+ });
+ try {
+ stream.write(null);
+ expect(() => {}).toThrow(Error);
+ } catch (exception: any) {
+ expect(exception.code).toBe("ERR_STREAM_NULL_VALUES");
+ }
+ });
+
+ it("writing false throws ERR_INVALID_ARG_TYPE", async () => {
+ const path = `/tmp/fs.test.js/${Date.now()}.createWriteStreamFalse.txt`;
+ const stream = createWriteStream(path);
+ try {
+ stream.write(false);
+ expect(() => {}).toThrow(Error);
+ } catch (exception: any) {
+ expect(exception.code).toBe("ERR_INVALID_ARG_TYPE");
+ }
+ });
+
+ it("writing false throws ERR_INVALID_ARG_TYPE (objectMode: true)", async () => {
+ const path = `/tmp/fs.test.js/${Date.now()}.createWriteStreamFalse.txt`;
+ const stream = createWriteStream(path, {
+ // @ts-ignore-next-line
+ objectMode: true,
+ });
+ try {
+ stream.write(false);
+ expect(() => {}).toThrow(Error);
+ } catch (exception: any) {
+ expect(exception.code).toBe("ERR_INVALID_ARG_TYPE");
+ }
+ });
+});
+
+describe("fs/promises", () => {
+ const { exists, mkdir, readFile, rmdir, stat, writeFile } = promises;
+
+ it("should not segfault on exception", async () => {
+ try {
+ await stat("foo/bar");
+ } catch (e) {}
+ });
+
+ it("readFile", async () => {
+ const data = await readFile(import.meta.dir + "/readFileSync.txt", "utf8");
+ expect(data).toBe("File read successfully");
+ });
+
+ it("writeFile", async () => {
+ const path = `/tmp/fs.test.js/${Date.now()}.writeFile.txt`;
+ await writeFile(path, "File written successfully");
+ expect(readFileSync(path, "utf8")).toBe("File written successfully");
+ });
+
+ it("readdir()", async () => {
+ const files = await promises.readdir(import.meta.dir);
+ expect(files.length).toBeGreaterThan(0);
+ });
+
+ it("readdir() no args doesnt segfault", async () => {
+ const fizz = [
+ [],
+ [Symbol("ok")],
+ [Symbol("ok"), Symbol("ok")],
+ [Symbol("ok"), Symbol("ok"), Symbol("ok")],
+ [Infinity, -NaN, -Infinity],
+ "\0\0\0\0",
+ "\r\n",
+ ];
+ for (const args of fizz) {
+ try {
+ // check it doens't segfault when called with invalid arguments
+ await promises.readdir(...(args as [any, ...any[]]));
+ } catch (e) {
+ // check that producing the error doesn't cause any crashes
+ Bun.inspect(e);
+ }
+ }
+ });
+
+ describe("rmdir", () => {
+ it("removes a file", async () => {
+ const path = `/tmp/${Date.now()}.rm.txt`;
+ await writeFile(path, "File written successfully", "utf8");
+ expect(await exists(path)).toBe(true);
+ try {
+ await rmdir(path);
+ expect(() => {}).toThrow();
+ } catch (err: any) {
+ expect(err.code).toBe("ENOTDIR");
+ // expect(err.message).toBe("Operation not permitted");
+ expect(await exists(path)).toBe(true);
+ }
+ });
+
+ it("removes a dir", async () => {
+ const path = `/tmp/${Date.now()}.rm.dir`;
+ try {
+ await mkdir(path);
+ } catch (e) {}
+ expect(await exists(path)).toBe(true);
+ await rmdir(path);
+ expect(await exists(path)).toBe(false);
+ });
+ // TODO support `recursive: true`
+ // it("removes a dir recursively", async () => {
+ // const path = `/tmp/${Date.now()}.rm.dir/foo/bar`;
+ // try {
+ // await mkdir(path, { recursive: true });
+ // } catch (e) {}
+ // expect(await exists(path)).toBe(true);
+ // await rmdir(join(path, "../../"), { recursive: true });
+ // expect(await exists(path)).toBe(false);
+ // });
+ });
+});
+
+it("fs.constants", () => {
+ expect(constants).toBeDefined();
+ expect(constants.F_OK).toBeDefined();
+ expect(constants.R_OK).toBeDefined();
+ expect(constants.W_OK).toBeDefined();
+ expect(constants.X_OK).toBeDefined();
+ expect(constants.O_RDONLY).toBeDefined();
+ expect(constants.O_WRONLY).toBeDefined();
+ expect(constants.O_RDWR).toBeDefined();
+ expect(constants.O_CREAT).toBeDefined();
+ expect(constants.O_EXCL).toBeDefined();
+ expect(constants.O_NOCTTY).toBeDefined();
+ expect(constants.O_TRUNC).toBeDefined();
+ expect(constants.O_APPEND).toBeDefined();
+ expect(constants.O_DIRECTORY).toBeDefined();
+ expect(constants.O_NOATIME).toBeDefined();
+ expect(constants.O_NOFOLLOW).toBeDefined();
+ expect(constants.O_SYNC).toBeDefined();
+ expect(constants.O_DSYNC).toBeDefined();
+ expect(constants.O_SYMLINK).toBeDefined();
+ expect(constants.O_DIRECT).toBeDefined();
+ expect(constants.O_NONBLOCK).toBeDefined();
+ expect(constants.S_IFMT).toBeDefined();
+ expect(constants.S_IFREG).toBeDefined();
+ expect(constants.S_IFDIR).toBeDefined();
+ expect(constants.S_IFCHR).toBeDefined();
+ expect(constants.S_IFBLK).toBeDefined();
+ expect(constants.S_IFIFO).toBeDefined();
+ expect(constants.S_IFLNK).toBeDefined();
+ expect(constants.S_IFSOCK).toBeDefined();
+ expect(constants.S_IRWXU).toBeDefined();
+ expect(constants.S_IRUSR).toBeDefined();
+ expect(constants.S_IWUSR).toBeDefined();
+ expect(constants.S_IXUSR).toBeDefined();
+ expect(constants.S_IRWXG).toBeDefined();
+ expect(constants.S_IRGRP).toBeDefined();
+ expect(constants.S_IWGRP).toBeDefined();
+ expect(constants.S_IXGRP).toBeDefined();
+ expect(constants.S_IRWXO).toBeDefined();
+ expect(constants.S_IROTH).toBeDefined();
+ expect(constants.S_IWOTH).toBeDefined();
+});
+
+it("fs.Dirent", () => {
+ expect(Dirent).toBeDefined();
+});
+
+it("fs.Stats", () => {
+ expect(Stats).toBeDefined();
+});
+
+it("repro 1516: can use undefined/null to specify default flag", () => {
+ const path = "/tmp/repro_1516.txt";
+ writeFileSync(path, "b", { flag: undefined });
+ // @ts-ignore-next-line
+ expect(readFileSync(path, { encoding: "utf8", flag: null })).toBe("b");
+ rmSync(path);
+});
diff --git a/test/js/node/fs/readFileSync.txt b/test/js/node/fs/readFileSync.txt
new file mode 100644
index 000000000..ddc94b988
--- /dev/null
+++ b/test/js/node/fs/readFileSync.txt
@@ -0,0 +1 @@
+File read successfully \ No newline at end of file
diff --git a/test/js/node/fs/test.txt b/test/js/node/fs/test.txt
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/test/js/node/fs/test.txt
diff --git a/test/js/node/fs/writeFileSync.txt b/test/js/node/fs/writeFileSync.txt
new file mode 100644
index 000000000..a0fe4515f
--- /dev/null
+++ b/test/js/node/fs/writeFileSync.txt
@@ -0,0 +1 @@
+File \ No newline at end of file
diff --git a/test/js/node/harness.test.js b/test/js/node/harness.test.js
new file mode 100644
index 000000000..de00e30ca
--- /dev/null
+++ b/test/js/node/harness.test.js
@@ -0,0 +1,169 @@
+// import { describe, it, expect } from "bun:test";
+// import {
+// throws,
+// assert,
+// strictEqual,
+// createCallCheckCtx,
+// createDoneDotAll,
+// } from "./node-test-helpers";
+
+// describe("NodeTestHelpers.throws()", () => {
+// it("should pass when the function throws", () => {
+// throws(() => {
+// throw new Error("THROWN!");
+// });
+// });
+
+// it("should fail when the function doesn't throw", () => {
+// let err;
+// try {
+// throws(() => {}, Error);
+// } catch (e) {
+// err = e;
+// }
+
+// expect(err instanceof Error).toBe(true);
+// });
+// });
+
+// describe("NodeTestHelpers.assert()", () => {
+// it("should pass when the provided value is true", () => {
+// assert(true);
+// });
+
+// it("should fail when the provided value is false", () => {
+// let err;
+// try {
+// assert(false);
+// } catch (e) {
+// err = e;
+// }
+// expect(err instanceof Error).toBe(true);
+// });
+// });
+
+// describe("NodeTestHelpers.strictEqual()", () => {
+// it("should pass when the provided values are deeply equal", () => {
+// strictEqual(1, 1);
+// strictEqual("hello", "hello");
+// const testing = { hello: "world" };
+// const testing2 = testing;
+// testing2.hello = "bla";
+// strictEqual(testing, testing2);
+// strictEqual(NaN, NaN);
+// strictEqual(Infinity, Infinity);
+// strictEqual(-Infinity, -Infinity);
+// strictEqual(null, null);
+// strictEqual(undefined, undefined);
+// });
+
+// it("should fail when the provided values are not deeply equal", () => {
+// let err = null;
+// try {
+// strictEqual(1, 5);
+// } catch (e) {
+// err = e;
+// }
+// expect(err instanceof Error).toBe(true);
+// err = null;
+// try {
+// strictEqual({ foo: "bar" }, { foo: "bar" });
+// } catch (e) {
+// err = e;
+// }
+// expect(err instanceof Error).toBe(true);
+// err = null;
+// try {
+// strictEqual("1", 1);
+// } catch (e) {
+// err = e;
+// }
+// expect(err instanceof Error).toBe(true);
+// err = null;
+// const obj1 = { foo: "bar" };
+// const obj2 = JSON.parse(JSON.stringify(obj1));
+// try {
+// strictEqual(obj1, obj2);
+// } catch (e) {
+// err = e;
+// }
+// expect(err instanceof Error).toBe(true);
+// });
+// });
+
+// describe("NodeTestHelpers.createCallCheckCtx", () => {
+// it("should pass when all mustCall marked callbacks have been called", (done) => {
+// const { mustCall } = createCallCheckCtx(done);
+// const fn1 = mustCall(() => {});
+// const fn2 = mustCall(() => {});
+// fn1();
+// fn2();
+// });
+
+// it("should fail when all mustCall marked callbacks have NOT been called", (done) => {
+// const mockDone = (result) => {
+// expect(result instanceof Error).toBe(true);
+// done();
+// };
+// const { mustCall } = createCallCheckCtx(mockDone, 600);
+// const fn1 = mustCall(() => {});
+// mustCall(() => {});
+// fn1();
+// });
+
+// it("should allow us to get the args of the wrapped callback from mustCall", (done) => {
+// const { mustCall } = createCallCheckCtx(done);
+// const fn1 = mustCall((arg1, arg2) => {
+// expect(arg1).toBe("hello");
+// expect(arg2).toBe("world");
+// });
+// fn1("hello", "world");
+// });
+// });
+
+// describe("NodeTestHelpers.createDoneDotAll()", () => {
+// it("should pass when all dones have been called", (done) => {
+// const createDone = createDoneDotAll(done);
+// const done1 = createDone(600);
+// const done2 = createDone(600);
+// setTimeout(() => done1(), 300);
+// setTimeout(() => done2(), 450);
+// });
+
+// it("should fail when all dones have NOT been called before timeout", (done) => {
+// const mockDone = (result) => {
+// expect(result instanceof Error).toBe(true);
+// done();
+// };
+// const createDone = createDoneDotAll(mockDone);
+// const done1 = createDone(400);
+// createDone(400);
+// setTimeout(() => done1(), 200);
+// });
+
+// it("should allow us to combine mustCall and multiple dones", (done) => {
+// const createDone = createDoneDotAll(done);
+// const { mustCall } = createCallCheckCtx(createDone(600));
+// const done1 = createDone(600);
+// const done2 = createDone(600);
+// const fn1 = mustCall(() => {});
+// const fn2 = mustCall(() => {});
+// setTimeout(() => done1(), 300);
+// setTimeout(() => done2(), 450);
+// setTimeout(() => fn1(), 200);
+// setTimeout(() => fn2(), 200);
+// });
+
+// it("should fail if a done is called with an error", (done) => {
+// const mockDone = (result) => {
+// expect(result instanceof Error).toBe(true);
+// done();
+// };
+// const createDone = createDoneDotAll(mockDone);
+
+// const done1 = createDone(600);
+// const done2 = createDone(600);
+// setTimeout(() => done1(), 300);
+// setTimeout(() => done2(new Error("ERROR!")), 450);
+// });
+// });
diff --git a/test/js/node/harness.ts b/test/js/node/harness.ts
new file mode 100644
index 000000000..227009a64
--- /dev/null
+++ b/test/js/node/harness.ts
@@ -0,0 +1,201 @@
+import { expect as expect_ } from "bun:test";
+import { gcTick } from "harness";
+import assertNode from "node:assert";
+
+type DoneCb = (err?: Error) => any;
+function noop() {}
+
+const expect = actual => {
+ gcTick();
+ const ret = expect_(actual);
+ gcTick();
+ return ret;
+};
+
+// Assert
+export const strictEqual = (...args: Parameters<typeof assertNode.strictEqual>) => {
+ assertNode.strictEqual.apply(this, args);
+ expect(true).toBe(true);
+};
+
+export const notStrictEqual = (...args: Parameters<typeof assertNode.notStrictEqual>) => {
+ assertNode.notStrictEqual.apply(this, args);
+ expect(true).toBe(true);
+};
+
+export const deepStrictEqual = (...args: Parameters<typeof assertNode.deepStrictEqual>) => {
+ assertNode.deepStrictEqual.apply(this, args);
+ expect(true).toBe(true);
+};
+
+export const throws = (...args: Parameters<typeof assertNode.throws>) => {
+ assertNode.throws.apply(this, args);
+ expect(true).toBe(true);
+};
+
+export const ok = (...args: Parameters<typeof assertNode.ok>) => {
+ assertNode.ok.apply(this, args);
+ expect(true).toBe(true);
+};
+
+export const ifError = (...args: Parameters<typeof assertNode.ifError>) => {
+ assertNode.ifError.apply(this, args);
+ expect(true).toBe(true);
+};
+
+export const match = (...args: Parameters<typeof assertNode.match>) => {
+ assertNode.match.apply(this, args);
+ expect(true).toBe(true);
+};
+
+export const assert = function (...args: any[]) {
+ // @ts-ignore
+ assertNode(...args);
+};
+
+Object.assign(assert, {
+ strictEqual,
+ deepStrictEqual,
+ notStrictEqual,
+ throws,
+ ok,
+ ifError,
+ match,
+});
+
+// End assert
+
+export const createCallCheckCtx = (done: DoneCb) => {
+ const createDone = createDoneDotAll(done);
+
+ // const mustCallChecks = [];
+
+ // failed.forEach(function (context) {
+ // console.log(
+ // "Mismatched %s function calls. Expected %s, actual %d.",
+ // context.name,
+ // context.messageSegment,
+ // context.actual
+ // );
+ // console.log(context.stack.split("\n").slice(2).join("\n"));
+ // });
+
+ // TODO: Implement this to be exact only
+ function mustCall(fn?: (...args) => any, exact?: number) {
+ return mustCallAtLeast(fn, exact);
+ }
+
+ function mustNotCall(reason: string = "function should not have been called") {
+ const localDone = createDone();
+ setTimeout(() => localDone(), 200);
+ return () => {
+ done(new Error(reason));
+ };
+ }
+
+ function mustSucceed(fn: () => any, exact?: number) {
+ return mustCall(function (err, ...args) {
+ ifError(err);
+ // @ts-ignore
+ if (typeof fn === "function") return fn.apply(this, args as []);
+ }, exact);
+ }
+
+ function mustCallAtLeast(fn, minimum) {
+ return _mustCallInner(fn, minimum, "minimum");
+ }
+
+ function _mustCallInner(fn, criteria = 1, field) {
+ if (process._exiting) throw new Error("Cannot use common.mustCall*() in process exit handler");
+ if (typeof fn === "number") {
+ criteria = fn;
+ fn = noop;
+ } else if (fn === undefined) {
+ fn = noop;
+ }
+
+ if (typeof criteria !== "number") throw new TypeError(`Invalid ${field} value: ${criteria}`);
+
+ let actual = 0;
+ let expected = criteria;
+
+ // mustCallChecks.push(context);
+ const done = createDone();
+ const _return = (...args) => {
+ try {
+ // @ts-ignore
+ const result = fn.apply(this, args);
+ actual++;
+ if (actual >= expected) {
+ done();
+ }
+ return result;
+ } catch (err) {
+ if (err instanceof Error) done(err);
+ else if (err?.toString) done(new Error(err?.toString()));
+ else {
+ console.error("Unknown error", err);
+ done(new Error("Unknown error"));
+ }
+ }
+ };
+ // Function instances have own properties that may be relevant.
+ // Let's replicate those properties to the returned function.
+ // Refs: https://tc39.es/ecma262/#sec-function-instances
+ Object.defineProperties(_return, {
+ name: {
+ value: fn.name,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+ },
+ length: {
+ value: fn.length,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+ },
+ });
+ return _return;
+ }
+ return {
+ mustSucceed,
+ mustCall,
+ mustCallAtLeast,
+ mustNotCall,
+ };
+};
+
+export function createDoneDotAll(done: DoneCb, globalTimeout?: number) {
+ let toComplete = 0;
+ let completed = 0;
+ const globalTimer = globalTimeout
+ ? setTimeout(() => {
+ console.log("Global Timeout");
+ done(new Error("Timed out!"));
+ }, globalTimeout)
+ : undefined;
+ function createDoneCb(timeout?: number) {
+ toComplete += 1;
+ const timer =
+ timeout !== undefined
+ ? setTimeout(() => {
+ console.log("Timeout");
+ done(new Error("Timed out!"));
+ }, timeout)
+ : timeout;
+ return (result?: Error) => {
+ if (timer) clearTimeout(timer);
+ if (globalTimer) clearTimeout(globalTimer);
+ if (result instanceof Error) {
+ done(result);
+ return;
+ }
+ completed += 1;
+ if (completed === toComplete) {
+ done();
+ }
+ };
+ }
+ return createDoneCb;
+}
diff --git a/test/js/node/http/node-http.fixme.ts b/test/js/node/http/node-http.fixme.ts
new file mode 100644
index 000000000..d47002a6f
--- /dev/null
+++ b/test/js/node/http/node-http.fixme.ts
@@ -0,0 +1,604 @@
+import { describe, expect, it, beforeAll, afterAll } from "bun:test";
+import { createServer, request, get, Agent, globalAgent, Server } from "node:http";
+import { createDoneDotAll } from "node-harness";
+
+function listen(server: any): Promise<URL> {
+ return new Promise((resolve, reject) => {
+ server.listen({ port: 0 }, (err, hostname, port) => {
+ if (err) {
+ reject(err);
+ } else {
+ resolve(new URL(`http://${hostname}:${port}`));
+ }
+ });
+ setTimeout(() => reject("Timed out"), 5000);
+ });
+}
+
+describe("node:http", () => {
+ describe("createServer", async () => {
+ it("hello world", async () => {
+ const server = createServer((req, res) => {
+ expect(req.url).toBe("/hello?world");
+ res.writeHead(200, { "Content-Type": "text/plain" });
+ res.end("Hello World");
+ });
+ const url = await listen(server);
+ const res = await fetch(new URL("/hello?world", url));
+ expect(await res.text()).toBe("Hello World");
+ server.close();
+ });
+
+ it("request & response body streaming (large)", async () => {
+ const bodyBlob = new Blob(["hello world", "hello world".repeat(9000)]);
+
+ const input = await bodyBlob.text();
+
+ const server = createServer((req, res) => {
+ res.writeHead(200, { "Content-Type": "text/plain" });
+ req.on("data", chunk => {
+ res.write(chunk);
+ });
+
+ req.on("end", () => {
+ res.end();
+ });
+ });
+ const url = await listen(server);
+ const res = await fetch(url, {
+ method: "POST",
+ body: bodyBlob,
+ });
+
+ const out = await res.text();
+ expect(out).toBe(input);
+ server.close();
+ });
+
+ it("request & response body streaming (small)", async () => {
+ const bodyBlob = new Blob(["hello world", "hello world".repeat(4)]);
+
+ const input = await bodyBlob.text();
+
+ const server = createServer((req, res) => {
+ res.writeHead(200, { "Content-Type": "text/plain" });
+ req.on("data", chunk => {
+ res.write(chunk);
+ });
+
+ req.on("end", () => {
+ res.end();
+ });
+ });
+ const url = await listen(server);
+ const res = await fetch(url, {
+ method: "POST",
+ body: bodyBlob,
+ });
+
+ const out = await res.text();
+ expect(out).toBe(input);
+ server.close();
+ });
+
+ it("listen should return server", async () => {
+ const server = createServer();
+ const listenResponse = server.listen(0);
+ expect(listenResponse instanceof Server).toBe(true);
+ expect(listenResponse).toBe(server);
+ listenResponse.close();
+ });
+ });
+
+ describe("request", () => {
+ let server;
+ let serverPort;
+ let timer: Timer | null = null;
+ beforeAll(() => {
+ server = createServer((req, res) => {
+ const reqUrl = new URL(req.url!, `http://${req.headers.host}`);
+ if (reqUrl.pathname) {
+ if (reqUrl.pathname === "/redirect") {
+ // Temporary redirect
+ res.writeHead(301, {
+ Location: `http://localhost:${serverPort}/redirected`,
+ });
+ res.end("Got redirect!\n");
+ return;
+ }
+ if (reqUrl.pathname === "/redirected") {
+ res.writeHead(404, { "Content-Type": "text/plain" });
+ res.end("Not Found");
+ return;
+ }
+ if (reqUrl.pathname === "/lowerCaseHeaders") {
+ res.writeHead(200, { "content-type": "text/plain", "X-Custom-Header": "custom_value" });
+ res.end("Hello World");
+ return;
+ }
+ if (reqUrl.pathname.includes("timeout")) {
+ if (timer) clearTimeout(timer);
+ timer = setTimeout(() => {
+ res.end("Hello World");
+ timer = null;
+ }, 3000);
+ return;
+ }
+ if (reqUrl.pathname === "/pathTest") {
+ res.end("Path correct!\n");
+ return;
+ }
+ }
+
+ res.writeHead(200, { "Content-Type": "text/plain" });
+
+ if (req.headers["x-test"]) {
+ res.write(`x-test: ${req.headers["x-test"]}\n`);
+ }
+
+ // Check for body
+ if (req.method === "POST") {
+ req.on("data", chunk => {
+ res.write(chunk);
+ });
+
+ req.on("end", () => {
+ res.write("POST\n");
+ res.end("Hello World");
+ });
+ } else {
+ if (req.headers["X-Test"] !== undefined) {
+ res.write(`X-Test: test\n`);
+ }
+ res.write("Maybe GET maybe not\n");
+ res.end("Hello World");
+ }
+ });
+ server.listen({ port: 0 }, (_, __, port) => {
+ serverPort = port;
+ });
+ });
+ afterAll(() => {
+ server.close();
+ if (timer) clearTimeout(timer);
+ });
+
+ it("check for expected fields", done => {
+ const req = request({ host: "localhost", port: serverPort, method: "GET" }, res => {
+ res.on("end", () => {
+ done();
+ });
+ res.on("error", err => done(err));
+ });
+ expect(req.path).toEqual("/");
+ expect(req.method).toEqual("GET");
+ expect(req.host).toEqual("localhost");
+ expect(req.protocol).toEqual("http:");
+ req.end();
+ });
+
+ it("should make a standard GET request when passed string as first arg", done => {
+ const req = request(`http://localhost:${serverPort}`, res => {
+ let data = "";
+ res.setEncoding("utf8");
+ res.on("data", chunk => {
+ data += chunk;
+ });
+ res.on("end", () => {
+ expect(data).toBe("Maybe GET maybe not\nHello World");
+ done();
+ });
+ res.on("error", err => done(err));
+ });
+ req.end();
+ });
+
+ it("should make a https:// GET request when passed string as first arg", done => {
+ const req = request("https://example.com", res => {
+ let data = "";
+ res.setEncoding("utf8");
+ res.on("data", chunk => {
+ data += chunk;
+ });
+ res.on("end", () => {
+ expect(data).toContain("This domain is for use in illustrative examples in documents");
+ done();
+ });
+ res.on("error", err => done(err));
+ });
+ req.end();
+ });
+
+ it("should make a POST request when provided POST method, even without a body", done => {
+ const req = request({ host: "localhost", port: serverPort, method: "POST" }, res => {
+ let data = "";
+ res.setEncoding("utf8");
+ res.on("data", chunk => {
+ data += chunk;
+ });
+ res.on("end", () => {
+ expect(data).toBe("POST\nHello World");
+ done();
+ });
+ res.on("error", err => done(err));
+ });
+ req.end();
+ });
+
+ it("should correctly handle a POST request with a body", done => {
+ const req = request({ host: "localhost", port: serverPort, method: "POST" }, res => {
+ let data = "";
+ res.setEncoding("utf8");
+ res.on("data", chunk => {
+ data += chunk;
+ });
+ res.on("end", () => {
+ expect(data).toBe("Posting\nPOST\nHello World");
+ done();
+ });
+ res.on("error", err => done(err));
+ });
+ req.write("Posting\n");
+ req.end();
+ });
+
+ it("should noop request.setSocketKeepAlive without error", () => {
+ const req = request(`http://localhost:${serverPort}`);
+ req.setSocketKeepAlive(true, 1000);
+ req.end();
+ expect(true).toBe(true);
+ });
+
+ it("should allow us to set timeout with request.setTimeout or `timeout` in options", done => {
+ const createDone = createDoneDotAll(done);
+ const req1Done = createDone();
+ const req2Done = createDone();
+
+ // const start = Date.now();
+ const req1 = request(
+ {
+ host: "localhost",
+ port: serverPort,
+ path: "/timeout",
+ timeout: 500,
+ },
+ res => {
+ req1Done(new Error("Should not have received response"));
+ },
+ );
+ req1.on("timeout", () => req1Done());
+
+ const req2 = request(
+ {
+ host: "localhost",
+ port: serverPort,
+ path: "/timeout",
+ },
+ res => {
+ req2Done(new Error("Should not have received response"));
+ },
+ );
+
+ req2.setTimeout(500, () => {
+ req2Done();
+ });
+ req1.end();
+ req2.end();
+ });
+
+ it("should correctly set path when path provided", done => {
+ const createDone = createDoneDotAll(done);
+ const req1Done = createDone();
+ const req2Done = createDone();
+
+ const req1 = request(`http://localhost:${serverPort}/pathTest`, res => {
+ let data = "";
+ res.setEncoding("utf8");
+ res.on("data", chunk => {
+ data += chunk;
+ });
+ res.on("end", () => {
+ expect(data).toBe("Path correct!\n");
+ req1Done();
+ });
+ res.on("error", err => req1Done(err));
+ });
+
+ const req2 = request(`http://localhost:${serverPort}`, { path: "/pathTest" }, res => {
+ let data = "";
+ res.setEncoding("utf8");
+ res.on("data", chunk => {
+ data += chunk;
+ });
+ res.on("end", () => {
+ expect(data).toBe("Path correct!\n");
+ req2Done();
+ });
+ res.on("error", err => req2Done(err));
+ });
+
+ req1.end();
+ req2.end();
+
+ expect(req1.path).toBe("/pathTest");
+ expect(req2.path).toBe("/pathTest");
+ });
+
+ it("should emit response when response received", done => {
+ const req = request(`http://localhost:${serverPort}`);
+
+ req.on("response", res => {
+ expect(res.statusCode).toBe(200);
+ done();
+ });
+ req.end();
+ });
+
+ // NOTE: Node http.request doesn't follow redirects by default
+ it("should handle redirects properly", done => {
+ const req = request(`http://localhost:${serverPort}/redirect`, res => {
+ let data = "";
+ res.setEncoding("utf8");
+ res.on("data", chunk => {
+ data += chunk;
+ });
+ res.on("end", () => {
+ expect(data).toBe("Got redirect!\n");
+ done();
+ });
+ res.on("error", err => done(err));
+ });
+ req.end();
+ });
+
+ it("should correctly attach headers to request", done => {
+ const req = request({ host: "localhost", port: serverPort, headers: { "X-Test": "test" } }, res => {
+ let data = "";
+ res.setEncoding("utf8");
+ res.on("data", chunk => {
+ data += chunk;
+ });
+ res.on("end", () => {
+ expect(data).toBe("x-test: test\nMaybe GET maybe not\nHello World");
+ done();
+ });
+ res.on("error", err => done(err));
+ });
+ req.end();
+ expect(req.getHeader("X-Test")).toBe("test");
+ });
+
+ it("should correct casing of method param", done => {
+ const req = request({ host: "localhost", port: serverPort, method: "get" }, res => {
+ let data = "";
+ res.setEncoding("utf8");
+ res.on("data", chunk => {
+ data += chunk;
+ });
+ res.on("end", () => {
+ expect(data).toBe("Maybe GET maybe not\nHello World");
+ done();
+ });
+ res.on("error", err => done(err));
+ });
+ req.end();
+ });
+
+ it("should allow for port as a string", done => {
+ const req = request({ host: "localhost", port: `${serverPort}`, method: "GET" }, res => {
+ let data = "";
+ res.setEncoding("utf8");
+ res.on("data", chunk => {
+ data += chunk;
+ });
+ res.on("end", () => {
+ expect(data).toBe("Maybe GET maybe not\nHello World");
+ done();
+ });
+ res.on("error", err => done(err));
+ });
+ req.end();
+ });
+
+ it("should allow us to pass a URL object", done => {
+ const req = request(new URL(`http://localhost:${serverPort}`), { method: "POST" }, res => {
+ let data = "";
+ res.setEncoding("utf8");
+ res.on("data", chunk => {
+ data += chunk;
+ });
+ res.on("end", () => {
+ expect(data).toBe("Hello WorldPOST\nHello World");
+ done();
+ });
+ res.on("error", err => done(err));
+ });
+ req.write("Hello World");
+ req.end();
+ });
+
+ it("should ignore body when method is GET/HEAD/OPTIONS", done => {
+ const createDone = createDoneDotAll(done);
+ const methods = ["GET", "HEAD", "OPTIONS"];
+ const dones = {};
+ for (const method of methods) {
+ dones[method] = createDone();
+ }
+ for (const method of methods) {
+ const req = request(`http://localhost:${serverPort}`, { method }, res => {
+ let data = "";
+ res.setEncoding("utf8");
+ res.on("data", chunk => {
+ data += chunk;
+ });
+ res.on("end", () => {
+ expect(data).toBe(method === "GET" ? "Maybe GET maybe not\nHello World" : "");
+ dones[method]();
+ });
+ res.on("error", err => dones[method](err));
+ });
+ req.write("BODY");
+ req.end();
+ }
+ });
+
+ it("should return response with lowercase headers", done => {
+ const req = request(`http://localhost:${serverPort}/lowerCaseHeaders`, res => {
+ console.log(res.headers);
+ expect(res.headers["content-type"]).toBe("text/plain");
+ expect(res.headers["x-custom-header"]).toBe("custom_value");
+ done();
+ });
+ req.end();
+ });
+ });
+
+ describe("signal", () => {
+ it("should abort and close the server", done => {
+ const server = createServer((req, res) => {
+ res.writeHead(200, { "Content-Type": "text/plain" });
+ res.end("Hello World");
+ });
+
+ //force timeout to not hang tests
+ const interval = setTimeout(() => {
+ expect(false).toBe(true);
+ server.close();
+ done();
+ }, 100);
+
+ const signal = AbortSignal.timeout(30);
+ signal.addEventListener("abort", () => {
+ clearTimeout(interval);
+ expect(true).toBe(true);
+ done();
+ });
+
+ server.listen({ signal, port: 0 });
+ });
+ });
+
+ describe("get", () => {
+ let server;
+ let url;
+ beforeAll(async () => {
+ server = createServer((req, res) => {
+ res.writeHead(200, { "Content-Type": "text/plain" });
+ res.end("Hello World");
+ });
+ url = await listen(server);
+ });
+ afterAll(() => {
+ server.close();
+ });
+ it("should make a standard GET request, like request", done => {
+ get(url, res => {
+ let data = "";
+ res.setEncoding("utf8");
+ res.on("data", chunk => {
+ data += chunk;
+ });
+ res.on("end", () => {
+ expect(data).toBe("Hello World");
+ done();
+ });
+ res.on("error", err => done(err));
+ });
+ });
+ });
+
+ describe("Agent", () => {
+ let server;
+ let dummyReq;
+ let dummyAgent;
+ beforeAll(() => {
+ dummyAgent = new Agent();
+ server = createServer((req, res) => {
+ res.writeHead(200, { "Content-Type": "text/plain" });
+ res.end("Hello World");
+ });
+ server.listen({ port: 0 }, (_, host, port) => {
+ // Setup request after server is listening
+ dummyReq = request(
+ {
+ host,
+ port,
+ agent: dummyAgent,
+ },
+ res => {},
+ );
+ dummyReq.on("error", () => {});
+ });
+ });
+
+ afterAll(() => {
+ dummyReq.end();
+ server.close();
+ });
+
+ it("should be a class", () => {
+ expect(Agent instanceof Function).toBe(true);
+ });
+
+ it("should have a default maxSockets of Infinity", () => {
+ expect(dummyAgent.maxSockets).toBe(Infinity);
+ });
+
+ it("should have a keepAlive value", () => {
+ expect(dummyAgent.keepAlive).toBe(false);
+ });
+
+ it("should noop keepSocketAlive", () => {
+ const agent = new Agent({ keepAlive: true });
+ // @ts-ignore
+ expect(agent.keepAlive).toBe(true);
+
+ agent.keepSocketAlive(dummyReq.socket);
+ });
+
+ it("should provide globalAgent", () => {
+ expect(globalAgent instanceof Agent).toBe(true);
+ });
+ });
+
+ describe("ClientRequest.signal", () => {
+ let server;
+ let server_port;
+ let server_host;
+ beforeAll(() => {
+ server = createServer((req, res) => {
+ Bun.sleep(10).then(() => {
+ res.writeHead(200, { "Content-Type": "text/plain" });
+ res.end("Hello World");
+ });
+ });
+ server.listen({ port: 0 }, (_err, host, port) => {
+ server_port = port;
+ server_host = host;
+ });
+ });
+ afterAll(() => {
+ server.close();
+ });
+ it("should attempt to make a standard GET request and abort", done => {
+ get(`http://${server_host}:${server_port}`, { signal: AbortSignal.timeout(5) }, res => {
+ let data = "";
+ res.setEncoding("utf8");
+ res.on("data", chunk => {
+ data += chunk;
+ });
+ res.on("end", () => {
+ expect(true).toBeFalsy();
+ done();
+ });
+ res.on("error", _ => {
+ expect(true).toBeFalsy();
+ done();
+ });
+ }).on("error", err => {
+ expect(err?.name).toBe("AbortError");
+ done();
+ });
+ });
+ });
+});
diff --git a/test/js/node/module/node-module-module.test.js b/test/js/node/module/node-module-module.test.js
new file mode 100644
index 000000000..549b5e085
--- /dev/null
+++ b/test/js/node/module/node-module-module.test.js
@@ -0,0 +1,5 @@
+import { expect, test } from "bun:test";
+
+test("module.globalPaths exists", () => {
+ expect(Array.isArray(require("module").globalPaths)).toBe(true);
+});
diff --git a/test/js/node/net/node-net.test.ts b/test/js/node/net/node-net.test.ts
new file mode 100644
index 000000000..e6c17d931
--- /dev/null
+++ b/test/js/node/net/node-net.test.ts
@@ -0,0 +1,268 @@
+import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from "bun:test";
+import { connect, isIP, isIPv4, isIPv6, Socket } from "net";
+
+it("should support net.isIP()", () => {
+ expect(isIP("::1")).toBe(6);
+ expect(isIP("foobar")).toBe(0);
+ expect(isIP("127.0.0.1")).toBe(4);
+ expect(isIP("127.0.0.1/24")).toBe(0);
+ expect(isIP("127.000.000.001")).toBe(0);
+});
+
+it("should support net.isIPv4()", () => {
+ expect(isIPv4("::1")).toBe(false);
+ expect(isIPv4("foobar")).toBe(false);
+ expect(isIPv4("127.0.0.1")).toBe(true);
+ expect(isIPv4("127.0.0.1/24")).toBe(false);
+ expect(isIPv4("127.000.000.001")).toBe(false);
+});
+
+it("should support net.isIPv6()", () => {
+ expect(isIPv6("::1")).toBe(true);
+ expect(isIPv6("foobar")).toBe(false);
+ expect(isIPv6("127.0.0.1")).toBe(false);
+ expect(isIPv6("127.0.0.1/24")).toBe(false);
+ expect(isIPv6("127.000.000.001")).toBe(false);
+});
+
+describe("net.Socket read", () => {
+ var port = 12345;
+ for (let [message, label] of [
+ // ["Hello World!".repeat(1024), "long message"],
+ ["Hello!", "short message"],
+ ]) {
+ describe(label, () => {
+ function runWithServer(cb) {
+ return done => {
+ function drain(socket) {
+ const message = socket.data.message;
+ const written = socket.write(message);
+ if (written < message.length) {
+ socket.data.message = message.slice(written);
+ } else {
+ socket.end();
+ }
+ }
+
+ var server = Bun.listen({
+ hostname: "localhost",
+ port: port++,
+ socket: {
+ open(socket) {
+ socket.data.message = message;
+ drain(socket);
+ },
+ drain,
+ error(socket, err) {
+ done(err);
+ },
+ },
+ data: {
+ message: "",
+ },
+ });
+
+ function onDone(err) {
+ server.stop();
+ done(err);
+ }
+
+ try {
+ cb(server, drain, onDone);
+ } catch (e) {
+ onDone(e);
+ }
+ };
+ }
+
+ it(
+ "should work with .connect(port)",
+ runWithServer((server, drain, done) => {
+ var data = "";
+ const socket = new Socket()
+ .connect(server.port)
+ .on("connect", () => {
+ expect(socket).toBeDefined();
+ expect(socket.connecting).toBe(false);
+ })
+ .setEncoding("utf8")
+ .on("data", chunk => {
+ data += chunk;
+ })
+ .on("end", () => {
+ try {
+ expect(data).toBe(message);
+ done();
+ } catch (e) {
+ server.stop();
+ done(e);
+ }
+ })
+ .on("error", done);
+ }),
+ );
+
+ it(
+ "should work with .connect(port, listener)",
+ runWithServer((server, drain, done) => {
+ var data = "";
+ const socket = new Socket()
+ .connect(server.port, () => {
+ expect(socket).toBeDefined();
+ expect(socket.connecting).toBe(false);
+ })
+ .setEncoding("utf8")
+ .on("data", chunk => {
+ data += chunk;
+ })
+ .on("end", () => {
+ try {
+ expect(data).toBe(message);
+ done();
+ } catch (e) {
+ server.stop();
+ done(e);
+ }
+ })
+ .on("error", done);
+ }),
+ );
+
+ it(
+ "should work with .connect(port, host, listener)",
+ runWithServer((server, drain, done) => {
+ var data = "";
+ const socket = new Socket()
+ .connect(server.port, "localhost", () => {
+ expect(socket).toBeDefined();
+ expect(socket.connecting).toBe(false);
+ })
+ .setEncoding("utf8")
+ .on("data", chunk => {
+ data += chunk;
+ })
+ .on("end", () => {
+ try {
+ expect(data).toBe(message);
+ done();
+ } catch (e) {
+ done(e);
+ }
+ })
+ .on("error", done);
+ }),
+ );
+ });
+ }
+});
+
+describe("net.Socket write", () => {
+ const message = "Hello World!".repeat(1024);
+ let port = 53213;
+
+ function runWithServer(cb) {
+ return done => {
+ let server;
+
+ function close(socket) {
+ expect(Buffer.concat(socket.data).toString("utf8")).toBe(message);
+ done();
+ }
+
+ var leaky;
+ server = Bun.listen({
+ hostname: "0.0.0.0",
+ port: port++,
+ socket: {
+ close,
+ data(socket, buffer) {
+ leaky = socket;
+ if (!Buffer.isBuffer(buffer)) {
+ done(new Error("buffer is not a Buffer"));
+ }
+
+ socket.data.push(buffer);
+ },
+ end: close,
+ error(socket, err) {
+ leaky = socket;
+ done(err);
+ },
+ open(socket) {
+ leaky = socket;
+ socket.data = [];
+ },
+ },
+ data: [] as Buffer[],
+ });
+
+ function onDone(err) {
+ server.stop();
+ done(err);
+ }
+
+ try {
+ cb(server, onDone);
+ } catch (e) {
+ onDone(e);
+ }
+ };
+ }
+
+ it(
+ "should work with .end(data)",
+ runWithServer((server, done) => {
+ const socket = new Socket()
+ .connect(server.port)
+ .on("ready", () => {
+ expect(socket).toBeDefined();
+ expect(socket.connecting).toBe(false);
+ })
+ .on("error", done)
+ .end(message);
+ }),
+ );
+
+ it(
+ "should work with .write(data).end()",
+ runWithServer((server, done) => {
+ const socket = new Socket()
+ .connect(server.port, () => {
+ expect(socket).toBeDefined();
+ expect(socket.connecting).toBe(false);
+ })
+ .on("error", done);
+ socket.write(message);
+ socket.end();
+ }),
+ );
+
+ it(
+ "should work with multiple .write()s",
+ runWithServer((server, done) => {
+ const socket = new Socket()
+ .connect(server.port, server.hostname, () => {
+ expect(socket).toBeDefined();
+ expect(socket.connecting).toBe(false);
+ })
+ .on("error", done);
+ const size = 10;
+ for (let i = 0; i < message.length; i += size) {
+ socket.write(message.slice(i, i + size));
+ }
+ socket.end();
+ }),
+ );
+});
+
+it("should handle connection error", done => {
+ var data = {};
+ connect(55555, () => {
+ done(new Error("Should not have connected"));
+ }).on("error", error => {
+ expect(error).toBeDefined();
+ expect(error.name).toBe("SystemError");
+ expect(error.message).toBe("Failed to connect");
+ done();
+ });
+});
diff --git a/test/js/node/os/os.test.js b/test/js/node/os/os.test.js
new file mode 100644
index 000000000..ea685cdb7
--- /dev/null
+++ b/test/js/node/os/os.test.js
@@ -0,0 +1,150 @@
+import { it, expect } from "bun:test";
+import * as os from "node:os";
+
+it("arch", () => {
+ expect(["x64", "x86", "arm64"].some(arch => os.arch() === arch)).toBe(true);
+});
+
+it("endianness", () => {
+ expect(/[BL]E/.test(os.endianness())).toBe(true);
+});
+
+it("freemem", () => {
+ expect(os.freemem() > 0).toBe(true);
+});
+
+it("totalmem", () => {
+ expect(os.totalmem() > 0).toBe(true);
+});
+
+it("getPriority", () => {
+ expect(os.getPriority()).toBe(0);
+ expect(os.getPriority(0)).toBe(0);
+});
+
+it("setPriority", () => {
+ expect(os.setPriority(0, 2)).toBe(undefined);
+ expect(os.getPriority()).toBe(2);
+ expect(os.setPriority(5)).toBe(undefined);
+ expect(os.getPriority()).toBe(5);
+});
+
+it("loadavg", () => {
+ expect(os.loadavg().length === 3).toBe(true);
+});
+
+it("homedir", () => {
+ expect(os.homedir() !== "unknown").toBe(true);
+});
+
+it("tmpdir", () => {
+ if (process.platform === "win32") {
+ expect(os.tmpdir()).toBe(process.env.TEMP || process.env.TMP);
+ expect(os.tmpdir()).toBe(`${process.env.SystemRoot || process.env.windir}\\temp`);
+ } else {
+ let dir = process.env.TMPDIR || process.env.TMP || process.env.TEMP || "/tmp";
+ if (dir.length > 1 && dir.endsWith("/")) {
+ dir = dir.substring(0, dir.length - 1);
+ }
+ expect(os.tmpdir()).toBe(dir);
+ }
+});
+
+it("hostname", () => {
+ expect(os.hostname() !== "unknown").toBe(true);
+});
+
+it("platform", () => {
+ expect(["win32", "darwin", "linux", "wasm"].some(platform => os.platform() === platform)).toBe(true);
+});
+
+it("release", () => {
+ expect(os.release().length > 1).toBe(true);
+});
+
+it("type", () => {
+ expect(["Windows_NT", "Darwin", "Linux"].some(type => os.type() === type)).toBe(true);
+});
+
+it("uptime", () => {
+ expect(os.uptime() > 0).toBe(true);
+});
+
+it("version", () => {
+ expect(typeof os.version() === "string").toBe(true);
+});
+
+it("userInfo", () => {
+ const info = os.userInfo();
+
+ if (process.platform !== "win32") {
+ expect(info.username).toBe(process.env.USER);
+ expect(info.shell).toBe(process.env.SHELL);
+ expect(info.uid >= 0).toBe(true);
+ expect(info.gid >= 0).toBe(true);
+ } else {
+ expect(info.username).toBe(process.env.USERNAME);
+ expect(info.shell).toBe(null);
+ expect(info.uid).toBe(-1);
+ expect(info.gid).toBe(-1);
+ }
+});
+
+it("cpus", () => {
+ const cpus = os.cpus();
+
+ for (const cpu of cpus) {
+ expect(typeof cpu.model === "string").toBe(true);
+ expect(typeof cpu.speed === "number").toBe(true);
+ expect(typeof cpu.times.idle === "number").toBe(true);
+ expect(typeof cpu.times.irq === "number").toBe(true);
+ expect(typeof cpu.times.nice === "number").toBe(true);
+ expect(typeof cpu.times.sys === "number").toBe(true);
+ expect(typeof cpu.times.user === "number").toBe(true);
+ }
+});
+
+it("networkInterfaces", () => {
+ const networkInterfaces = os.networkInterfaces();
+
+ for (const networkInterface of Object.values(networkInterfaces)) {
+ for (const nI of networkInterface) {
+ expect(typeof nI.address === "string").toBe(true);
+ expect(typeof nI.netmask === "string").toBe(true);
+ expect(typeof nI.family === "string").toBe(true);
+ expect(typeof nI.mac === "string").toBe(true);
+ expect(typeof nI.internal === "boolean").toBe(true);
+ if (nI.cidr)
+ // may be null
+ expect(typeof nI.cidr).toBe("string");
+ }
+ }
+});
+
+it("machine", () => {
+ const possibleValues = [
+ "arm",
+ "arm64",
+ "aarch64",
+ "mips",
+ "mips64",
+ "ppc64",
+ "ppc64le",
+ "s390",
+ "s390x",
+ "i386",
+ "i686",
+ "x86_64",
+ ];
+ expect(possibleValues.includes(os.machine())).toBe(true);
+});
+
+it("EOL", () => {
+ if (process.platform === "win32") expect(os.EOL).toBe("\\r\\n");
+ else expect(os.EOL).toBe("\n");
+});
+
+it("devNull", () => {
+ if (process.platform === "win32") expect(os.devNull).toBe("\\\\.\\nul");
+ else expect(os.devNull).toBe("/dev/null");
+});
diff --git a/test/js/node/path/path.test.js b/test/js/node/path/path.test.js
new file mode 100644
index 000000000..ad5688ea7
--- /dev/null
+++ b/test/js/node/path/path.test.js
@@ -0,0 +1,426 @@
+const { file } = import.meta;
+
+import { describe, it, expect } from "bun:test";
+import * as path from "node:path";
+import assert from "assert";
+
+const strictEqual = (...args) => {
+ assert.strictEqual(...args);
+ expect(true).toBe(true);
+};
+
+it("path.basename", () => {
+ strictEqual(path.basename(file), "path.test.js");
+ strictEqual(path.basename(file, ".js"), "path.test");
+ strictEqual(path.basename(".js", ".js"), "");
+ strictEqual(path.basename(""), "");
+ strictEqual(path.basename("/dir/basename.ext"), "basename.ext");
+ strictEqual(path.basename("/basename.ext"), "basename.ext");
+ strictEqual(path.basename("basename.ext"), "basename.ext");
+ strictEqual(path.basename("basename.ext/"), "basename.ext");
+ strictEqual(path.basename("basename.ext//"), "basename.ext");
+ strictEqual(path.basename("aaa/bbb", "/bbb"), "bbb");
+ strictEqual(path.basename("aaa/bbb", "a/bbb"), "bbb");
+ strictEqual(path.basename("aaa/bbb", "bbb"), "bbb");
+ strictEqual(path.basename("aaa/bbb//", "bbb"), "bbb");
+ strictEqual(path.basename("aaa/bbb", "bb"), "b");
+ strictEqual(path.basename("aaa/bbb", "b"), "bb");
+ strictEqual(path.basename("/aaa/bbb", "/bbb"), "bbb");
+ strictEqual(path.basename("/aaa/bbb", "a/bbb"), "bbb");
+ strictEqual(path.basename("/aaa/bbb", "bbb"), "bbb");
+ strictEqual(path.basename("/aaa/bbb//", "bbb"), "bbb");
+ strictEqual(path.basename("/aaa/bbb", "bb"), "b");
+ strictEqual(path.basename("/aaa/bbb", "b"), "bb");
+ strictEqual(path.basename("/aaa/bbb"), "bbb");
+ strictEqual(path.basename("/aaa/"), "aaa");
+ strictEqual(path.basename("/aaa/b"), "b");
+ strictEqual(path.basename("/a/b"), "b");
+ strictEqual(path.basename("//a"), "a");
+ strictEqual(path.basename("a", "a"), "");
+
+ // // On Windows a backslash acts as a path separator.
+ strictEqual(path.win32.basename("\\dir\\basename.ext"), "basename.ext");
+ strictEqual(path.win32.basename("\\basename.ext"), "basename.ext");
+ strictEqual(path.win32.basename("basename.ext"), "basename.ext");
+ strictEqual(path.win32.basename("basename.ext\\"), "basename.ext");
+ strictEqual(path.win32.basename("basename.ext\\\\"), "basename.ext");
+ strictEqual(path.win32.basename("foo"), "foo");
+ strictEqual(path.win32.basename("aaa\\bbb", "\\bbb"), "bbb");
+ strictEqual(path.win32.basename("aaa\\bbb", "a\\bbb"), "bbb");
+ strictEqual(path.win32.basename("aaa\\bbb", "bbb"), "bbb");
+ strictEqual(path.win32.basename("aaa\\bbb\\\\\\\\", "bbb"), "bbb");
+ strictEqual(path.win32.basename("aaa\\bbb", "bb"), "b");
+ strictEqual(path.win32.basename("aaa\\bbb", "b"), "bb");
+ strictEqual(path.win32.basename("C:"), "");
+ strictEqual(path.win32.basename("C:."), ".");
+ strictEqual(path.win32.basename("C:\\"), "");
+ strictEqual(path.win32.basename("C:\\dir\\base.ext"), "base.ext");
+ strictEqual(path.win32.basename("C:\\basename.ext"), "basename.ext");
+ strictEqual(path.win32.basename("C:basename.ext"), "basename.ext");
+ strictEqual(path.win32.basename("C:basename.ext\\"), "basename.ext");
+ strictEqual(path.win32.basename("C:basename.ext\\\\"), "basename.ext");
+ strictEqual(path.win32.basename("C:foo"), "foo");
+ strictEqual(path.win32.basename("file:stream"), "file:stream");
+ strictEqual(path.win32.basename("a", "a"), "");
+
+ // On unix a backslash is just treated as any other character.
+ strictEqual(path.posix.basename("\\dir\\basename.ext"), "\\dir\\basename.ext");
+ strictEqual(path.posix.basename("\\basename.ext"), "\\basename.ext");
+ strictEqual(path.posix.basename("basename.ext"), "basename.ext");
+ strictEqual(path.posix.basename("basename.ext\\"), "basename.ext\\");
+ strictEqual(path.posix.basename("basename.ext\\\\"), "basename.ext\\\\");
+ strictEqual(path.posix.basename("foo"), "foo");
+
+ // POSIX filenames may include control characters
+ // c.f. http://www.dwheeler.com/essays/fixing-unix-linux-filenames.html
+ const controlCharFilename = `Icon${String.fromCharCode(13)}`;
+ strictEqual(path.posix.basename(`/a/b/${controlCharFilename}`), controlCharFilename);
+});
+
+it("path.join", () => {
+ const failures = [];
+ const backslashRE = /\\/g;
+
+ const joinTests = [
+ [
+ [path.posix.join],
+ // Arguments result
+ [
+ [[".", "x/b", "..", "/b/c.js"], "x/b/c.js"],
+ // [[], '.'],
+ [["/.", "x/b", "..", "/b/c.js"], "/x/b/c.js"],
+ [["/foo", "../../../bar"], "/bar"],
+ [["foo", "../../../bar"], "../../bar"],
+ [["foo/", "../../../bar"], "../../bar"],
+ [["foo/x", "../../../bar"], "../bar"],
+ [["foo/x", "./bar"], "foo/x/bar"],
+ [["foo/x/", "./bar"], "foo/x/bar"],
+ [["foo/x/", ".", "bar"], "foo/x/bar"],
+ [["./"], "./"],
+ [[".", "./"], "./"],
+ [[".", ".", "."], "."],
+ [[".", "./", "."], "."],
+ [[".", "/./", "."], "."],
+ [[".", "/////./", "."], "."],
+ [["."], "."],
+ [["", "."], "."],
+ [["", "foo"], "foo"],
+ [["foo", "/bar"], "foo/bar"],
+ [["", "/foo"], "/foo"],
+ [["", "", "/foo"], "/foo"],
+ [["", "", "foo"], "foo"],
+ [["foo", ""], "foo"],
+ [["foo/", ""], "foo/"],
+ [["foo", "", "/bar"], "foo/bar"],
+ [["./", "..", "/foo"], "../foo"],
+ [["./", "..", "..", "/foo"], "../../foo"],
+ [[".", "..", "..", "/foo"], "../../foo"],
+ [["", "..", "..", "/foo"], "../../foo"],
+ [["/"], "/"],
+ [["/", "."], "/"],
+ [["/", ".."], "/"],
+ [["/", "..", ".."], "/"],
+ [[""], "."],
+ [["", ""], "."],
+ [[" /foo"], " /foo"],
+ [[" ", "foo"], " /foo"],
+ [[" ", "."], " "],
+ [[" ", "/"], " /"],
+ [[" ", ""], " "],
+ [["/", "foo"], "/foo"],
+ [["/", "/foo"], "/foo"],
+ [["/", "//foo"], "/foo"],
+ [["/", "", "/foo"], "/foo"],
+ [["", "/", "foo"], "/foo"],
+ [["", "/", "/foo"], "/foo"],
+ ],
+ ],
+ ];
+
+ // // Windows-specific join tests
+ // joinTests.push([
+ // path.win32.join,
+ // joinTests[0][1].slice(0).concat([
+ // // Arguments result
+ // // UNC path expected
+ // [["//foo/bar"], "\\\\foo\\bar\\"],
+ // [["\\/foo/bar"], "\\\\foo\\bar\\"],
+ // [["\\\\foo/bar"], "\\\\foo\\bar\\"],
+ // // UNC path expected - server and share separate
+ // [["//foo", "bar"], "\\\\foo\\bar\\"],
+ // [["//foo/", "bar"], "\\\\foo\\bar\\"],
+ // [["//foo", "/bar"], "\\\\foo\\bar\\"],
+ // // UNC path expected - questionable
+ // [["//foo", "", "bar"], "\\\\foo\\bar\\"],
+ // [["//foo/", "", "bar"], "\\\\foo\\bar\\"],
+ // [["//foo/", "", "/bar"], "\\\\foo\\bar\\"],
+ // // UNC path expected - even more questionable
+ // [["", "//foo", "bar"], "\\\\foo\\bar\\"],
+ // [["", "//foo/", "bar"], "\\\\foo\\bar\\"],
+ // [["", "//foo/", "/bar"], "\\\\foo\\bar\\"],
+ // // No UNC path expected (no double slash in first component)
+ // [["\\", "foo/bar"], "\\foo\\bar"],
+ // [["\\", "/foo/bar"], "\\foo\\bar"],
+ // [["", "/", "/foo/bar"], "\\foo\\bar"],
+ // // No UNC path expected (no non-slashes in first component -
+ // // questionable)
+ // [["//", "foo/bar"], "\\foo\\bar"],
+ // [["//", "/foo/bar"], "\\foo\\bar"],
+ // [["\\\\", "/", "/foo/bar"], "\\foo\\bar"],
+ // [["//"], "\\"],
+ // // No UNC path expected (share name missing - questionable).
+ // [["//foo"], "\\foo"],
+ // [["//foo/"], "\\foo\\"],
+ // [["//foo", "/"], "\\foo\\"],
+ // [["//foo", "", "/"], "\\foo\\"],
+ // // No UNC path expected (too many leading slashes - questionable)
+ // [["///foo/bar"], "\\foo\\bar"],
+ // [["////foo", "bar"], "\\foo\\bar"],
+ // [["\\\\\\/foo/bar"], "\\foo\\bar"],
+ // // Drive-relative vs drive-absolute paths. This merely describes the
+ // // status quo, rather than being obviously right
+ // [["c:"], "c:."],
+ // [["c:."], "c:."],
+ // [["c:", ""], "c:."],
+ // [["", "c:"], "c:."],
+ // [["c:.", "/"], "c:.\\"],
+ // [["c:.", "file"], "c:file"],
+ // [["c:", "/"], "c:\\"],
+ // [["c:", "file"], "c:\\file"],
+ // ]),
+ // ]);
+ joinTests.forEach(test => {
+ if (!Array.isArray(test[0])) test[0] = [test[0]];
+ test[0].forEach(join => {
+ test[1].forEach(test => {
+ const actual = join.apply(null, test[0]);
+ const expected = test[1];
+ // For non-Windows specific tests with the Windows join(), we need to try
+ // replacing the slashes since the non-Windows specific tests' `expected`
+ // use forward slashes
+ let actualAlt;
+ let os;
+ if (join === path.win32.join) {
+ actualAlt = actual.replace(backslashRE, "/");
+ os = "win32";
+ } else {
+ os = "posix";
+ }
+ if (actual !== expected && actualAlt !== expected) {
+ const delimiter = test[0].map(JSON.stringify).join(",");
+ const message = `path.${os}.join(${delimiter})\n expect=${JSON.stringify(
+ expected,
+ )}\n actual=${JSON.stringify(actual)}`;
+ failures.push(`\n${message}`);
+ }
+ });
+ });
+ });
+ strictEqual(failures.length, 0, failures.join(""));
+});
+
+it("path.relative", () => {
+ const failures = [];
+
+ const relativeTests = [
+ // [
+ // path.win32.relative,
+ // // Arguments result
+ // [
+ // ["c:/blah\\blah", "d:/games", "d:\\games"],
+ // ["c:/aaaa/bbbb", "c:/aaaa", ".."],
+ // ["c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc"],
+ // ["c:/aaaa/bbbb", "c:/aaaa/bbbb", ""],
+ // ["c:/aaaa/bbbb", "c:/aaaa/cccc", "..\\cccc"],
+ // ["c:/aaaa/", "c:/aaaa/cccc", "cccc"],
+ // ["c:/", "c:\\aaaa\\bbbb", "aaaa\\bbbb"],
+ // ["c:/aaaa/bbbb", "d:\\", "d:\\"],
+ // ["c:/AaAa/bbbb", "c:/aaaa/bbbb", ""],
+ // ["c:/aaaaa/", "c:/aaaa/cccc", "..\\aaaa\\cccc"],
+ // ["C:\\foo\\bar\\baz\\quux", "C:\\", "..\\..\\..\\.."],
+ // [
+ // "C:\\foo\\test",
+ // "C:\\foo\\test\\bar\\package.json",
+ // "bar\\package.json",
+ // ],
+ // ["C:\\foo\\bar\\baz-quux", "C:\\foo\\bar\\baz", "..\\baz"],
+ // ["C:\\foo\\bar\\baz", "C:\\foo\\bar\\baz-quux", "..\\baz-quux"],
+ // ["\\\\foo\\bar", "\\\\foo\\bar\\baz", "baz"],
+ // ["\\\\foo\\bar\\baz", "\\\\foo\\bar", ".."],
+ // ["\\\\foo\\bar\\baz-quux", "\\\\foo\\bar\\baz", "..\\baz"],
+ // ["\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz-quux", "..\\baz-quux"],
+ // ["C:\\baz-quux", "C:\\baz", "..\\baz"],
+ // ["C:\\baz", "C:\\baz-quux", "..\\baz-quux"],
+ // ["\\\\foo\\baz-quux", "\\\\foo\\baz", "..\\baz"],
+ // ["\\\\foo\\baz", "\\\\foo\\baz-quux", "..\\baz-quux"],
+ // ["C:\\baz", "\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz"],
+ // ["\\\\foo\\bar\\baz", "C:\\baz", "C:\\baz"],
+ // ],
+ // ],
+ [
+ path.posix.relative,
+ // Arguments result
+ [
+ ["/var/lib", "/var", ".."],
+ ["/var/lib", "/bin", "../../bin"],
+ ["/var/lib", "/var/lib", ""],
+ ["/var/lib", "/var/apache", "../apache"],
+ ["/var/", "/var/lib", "lib"],
+ ["/", "/var/lib", "var/lib"],
+ ["/foo/test", "/foo/test/bar/package.json", "bar/package.json"],
+ ["/Users/a/web/b/test/mails", "/Users/a/web/b", "../.."],
+ ["/foo/bar/baz-quux", "/foo/bar/baz", "../baz"],
+ ["/foo/bar/baz", "/foo/bar/baz-quux", "../baz-quux"],
+ ["/baz-quux", "/baz", "../baz"],
+ ["/baz", "/baz-quux", "../baz-quux"],
+ ["/page1/page2/foo", "/", "../../.."],
+ [process.cwd(), "foo", "foo"],
+ ],
+ ],
+ ];
+
+ relativeTests.forEach(test => {
+ const relative = test[0];
+ test[1].forEach(test => {
+ const actual = relative(test[0], test[1]);
+ const expected = test[2];
+ if (actual !== expected) {
+ const os = relative === path.win32.relative ? "win32" : "posix";
+ const message = `path.${os}.relative(${test
+ .slice(0, 2)
+ .map(JSON.stringify)
+ .join(",")})\n expect=${JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`;
+ failures.push(`\n${message}`);
+ }
+ });
+ });
+
+ strictEqual(failures.length, 0, failures.join(""));
+ expect(true).toBe(true);
+});
+
+it("path.normalize", () => {
+ // strictEqual(
+ // path.win32.normalize("./fixtures///b/../b/c.js"),
+ // "fixtures\\b\\c.js"
+ // );
+ // strictEqual(path.win32.normalize("/foo/../../../bar"), "\\bar");
+ // strictEqual(path.win32.normalize("a//b//../b"), "a\\b");
+ // strictEqual(path.win32.normalize("a//b//./c"), "a\\b\\c");
+ // strictEqual(path.win32.normalize("a//b//."), "a\\b");
+ // strictEqual(
+ // path.win32.normalize("//server/share/dir/file.ext"),
+ // "\\\\server\\share\\dir\\file.ext"
+ // );
+ // strictEqual(path.win32.normalize("/a/b/c/../../../x/y/z"), "\\x\\y\\z");
+ // strictEqual(path.win32.normalize("C:"), "C:.");
+ // strictEqual(path.win32.normalize("C:..\\abc"), "C:..\\abc");
+ // strictEqual(path.win32.normalize("C:..\\..\\abc\\..\\def"), "C:..\\..\\def");
+ // strictEqual(path.win32.normalize("C:\\."), "C:\\");
+ // strictEqual(path.win32.normalize("file:stream"), "file:stream");
+ // strictEqual(path.win32.normalize("bar\\foo..\\..\\"), "bar\\");
+ // strictEqual(path.win32.normalize("bar\\foo..\\.."), "bar");
+ // strictEqual(path.win32.normalize("bar\\foo..\\..\\baz"), "bar\\baz");
+ // strictEqual(path.win32.normalize("bar\\foo..\\"), "bar\\foo..\\");
+ // strictEqual(path.win32.normalize("bar\\foo.."), "bar\\foo..");
+ // strictEqual(path.win32.normalize("..\\foo..\\..\\..\\bar"), "..\\..\\bar");
+ // strictEqual(
+ // path.win32.normalize("..\\...\\..\\.\\...\\..\\..\\bar"),
+ // "..\\..\\bar"
+ // );
+ // strictEqual(
+ // path.win32.normalize("../../../foo/../../../bar"),
+ // "..\\..\\..\\..\\..\\bar"
+ // );
+ // strictEqual(
+ // path.win32.normalize("../../../foo/../../../bar/../../"),
+ // "..\\..\\..\\..\\..\\..\\"
+ // );
+ // strictEqual(
+ // path.win32.normalize("../foobar/barfoo/foo/../../../bar/../../"),
+ // "..\\..\\"
+ // );
+ // strictEqual(
+ // path.win32.normalize("../.../../foobar/../../../bar/../../baz"),
+ // "..\\..\\..\\..\\baz"
+ // );
+ // strictEqual(path.win32.normalize("foo/bar\\baz"), "foo\\bar\\baz");
+
+ strictEqual(path.posix.normalize("./fixtures///b/../b/c.js"), "fixtures/b/c.js");
+ strictEqual(path.posix.normalize("/foo/../../../bar"), "/bar");
+ strictEqual(path.posix.normalize("a//b//../b"), "a/b");
+ strictEqual(path.posix.normalize("a//b//./c"), "a/b/c");
+ strictEqual(path.posix.normalize("a//b//."), "a/b");
+ strictEqual(path.posix.normalize("/a/b/c/../../../x/y/z"), "/x/y/z");
+ strictEqual(path.posix.normalize("///..//./foo/.//bar"), "/foo/bar");
+ strictEqual(path.posix.normalize("bar/foo../../"), "bar/");
+ strictEqual(path.posix.normalize("bar/foo../.."), "bar");
+ strictEqual(path.posix.normalize("bar/foo../../baz"), "bar/baz");
+ strictEqual(path.posix.normalize("bar/foo../"), "bar/foo../");
+ strictEqual(path.posix.normalize("bar/foo.."), "bar/foo..");
+ strictEqual(path.posix.normalize("../foo../../../bar"), "../../bar");
+ strictEqual(path.posix.normalize("../.../.././.../../../bar"), "../../bar");
+ strictEqual(path.posix.normalize("../../../foo/../../../bar"), "../../../../../bar");
+ strictEqual(path.posix.normalize("../../../foo/../../../bar/../../"), "../../../../../../");
+ strictEqual(path.posix.normalize("../foobar/barfoo/foo/../../../bar/../../"), "../../");
+ strictEqual(path.posix.normalize("../.../../foobar/../../../bar/../../baz"), "../../../../baz");
+ strictEqual(path.posix.normalize("foo/bar\\baz"), "foo/bar\\baz");
+});
+
+it("path.resolve", () => {
+ const failures = [];
+ const slashRE = /\//g;
+ const backslashRE = /\\/g;
+
+ const resolveTests = [
+ // [
+ // path.win32.resolve,
+ // // Arguments result
+ // [
+ // [["c:/blah\\blah", "d:/games", "c:../a"], "c:\\blah\\a"],
+ // [["c:/ignore", "d:\\a/b\\c/d", "\\e.exe"], "d:\\e.exe"],
+ // [["c:/ignore", "c:/some/file"], "c:\\some\\file"],
+ // [["d:/ignore", "d:some/dir//"], "d:\\ignore\\some\\dir"],
+ // [["."], process.cwd()],
+ // [["//server/share", "..", "relative\\"], "\\\\server\\share\\relative"],
+ // [["c:/", "//"], "c:\\"],
+ // [["c:/", "//dir"], "c:\\dir"],
+ // [["c:/", "//server/share"], "\\\\server\\share\\"],
+ // [["c:/", "//server//share"], "\\\\server\\share\\"],
+ // [["c:/", "///some//dir"], "c:\\some\\dir"],
+ // [
+ // ["C:\\foo\\tmp.3\\", "..\\tmp.3\\cycles\\root.js"],
+ // "C:\\foo\\tmp.3\\cycles\\root.js",
+ // ],
+ // ],
+ // ],
+ [
+ path.posix.resolve,
+ // Arguments result
+ [
+ [["/var/lib", "../", "file/"], "/var/file"],
+ [["/var/lib", "/../", "file/"], "/file"],
+ [["a/b/c/", "../../.."], process.cwd()],
+ [["."], process.cwd()],
+ [["/some/dir", ".", "/absolute/"], "/absolute"],
+ [["/foo/tmp.3/", "../tmp.3/cycles/root.js"], "/foo/tmp.3/cycles/root.js"],
+ ],
+ ],
+ ];
+ const isWindows = false;
+ resolveTests.forEach(([resolve, tests]) => {
+ tests.forEach(([test, expected]) => {
+ const actual = resolve.apply(null, test);
+ let actualAlt;
+ const os = resolve === path.win32.resolve ? "win32" : "posix";
+ if (resolve === path.win32.resolve && !isWindows) actualAlt = actual.replace(backslashRE, "/");
+ else if (resolve !== path.win32.resolve && isWindows) actualAlt = actual.replace(slashRE, "\\");
+
+ const message = `path.${os}.resolve(${test.map(JSON.stringify).join(",")})\n expect=${JSON.stringify(
+ expected,
+ )}\n actual=${JSON.stringify(actual)}`;
+ if (actual !== expected && actualAlt !== expected) failures.push(message);
+ });
+ });
+ strictEqual(failures.length, 0, failures.join("\n"));
+});
diff --git a/test/js/node/process/print-process-args.js b/test/js/node/process/print-process-args.js
new file mode 100644
index 000000000..0ab238122
--- /dev/null
+++ b/test/js/node/process/print-process-args.js
@@ -0,0 +1,4 @@
+var writer = Bun.stdout.writer();
+writer.write(JSON.stringify(process.argv));
+await writer.flush(true);
+process.exit(0);
diff --git a/test/js/node/process/process-args.test.js b/test/js/node/process/process-args.test.js
new file mode 100644
index 000000000..4da3a5381
--- /dev/null
+++ b/test/js/node/process/process-args.test.js
@@ -0,0 +1,40 @@
+import { spawn } from "bun";
+import { test, expect } from "bun:test";
+import { bunExe } from "harness";
+
+test("args exclude run", async () => {
+ const arg0 = process.argv[0];
+ const arg1 = import.meta.dir + "/print-process-args.js";
+ const exe = bunExe();
+ const { stdout: s1 } = spawn([exe, "print-process-args.js"], {
+ cwd: import.meta.dir,
+ env: { BUN_DEBUG_QUIET_LOGS: "1" },
+ });
+ const t1 = JSON.parse(await new Response(s1).text());
+ expect(t1[0]).toBe(arg0);
+ expect(t1[1]).toBe(arg1);
+ const { stdout: s2 } = spawn([exe, "print-process-args.js", "arg1"], {
+ cwd: import.meta.dir,
+ env: { BUN_DEBUG_QUIET_LOGS: "1" },
+ });
+ const t2 = JSON.parse(await new Response(s2).text());
+ expect(t2[0]).toBe(arg0);
+ expect(t2[1]).toBe(arg1);
+ expect(t2[2]).toBe("arg1");
+ const { stdout: s3 } = spawn([exe, "run", "print-process-args.js"], {
+ cwd: import.meta.dir,
+ env: { BUN_DEBUG_QUIET_LOGS: "1" },
+ });
+ const t3 = JSON.parse(await new Response(s3).text());
+ expect(t3[0]).toBe(arg0);
+ expect(t3[1]).toBe(arg1);
+ const { stdout: s4 } = spawn([exe, "run", "print-process-args.js", "arg1", "arg2"], {
+ cwd: import.meta.dir,
+ env: { BUN_DEBUG_QUIET_LOGS: "1" },
+ });
+ const t4 = JSON.parse(await new Response(s4).text());
+ expect(t4[0]).toBe(arg0);
+ expect(t4[1]).toBe(arg1);
+ expect(t4[2]).toBe("arg1");
+ expect(t4[3]).toBe("arg2");
+});
diff --git a/test/js/node/process/process-nexttick.js b/test/js/node/process/process-nexttick.js
new file mode 100644
index 000000000..c6b24ba14
--- /dev/null
+++ b/test/js/node/process/process-nexttick.js
@@ -0,0 +1,84 @@
+// You can verify this test is correct by copy pasting this into a browser's console and checking it doesn't throw an error.
+var run = 0;
+
+var queueMicrotask = process.nextTick;
+
+await new Promise((resolve, reject) => {
+ queueMicrotask(() => {
+ if (run++ != 0) {
+ reject(new Error("Microtask execution order is wrong: " + run));
+ }
+ queueMicrotask(() => {
+ if (run++ != 3) {
+ reject(new Error("Microtask execution order is wrong: " + run));
+ }
+ });
+ });
+ queueMicrotask(() => {
+ if (run++ != 1) {
+ reject(new Error("Microtask execution order is wrong: " + run));
+ }
+ queueMicrotask(() => {
+ if (run++ != 4) {
+ reject(new Error("Microtask execution order is wrong: " + run));
+ }
+
+ queueMicrotask(() => {
+ if (run++ != 6) {
+ reject(new Error("Microtask execution order is wrong: " + run));
+ }
+ });
+ });
+ });
+ queueMicrotask(() => {
+ if (run++ != 2) {
+ reject(new Error("Microtask execution order is wrong: " + run));
+ }
+ queueMicrotask(() => {
+ if (run++ != 5) {
+ reject(new Error("Microtask execution order is wrong: " + run));
+ }
+
+ queueMicrotask(() => {
+ if (run++ != 7) {
+ reject(new Error("Microtask execution order is wrong: " + run));
+ }
+ resolve(true);
+ });
+ });
+ });
+});
+
+{
+ var passed = false;
+ try {
+ queueMicrotask(1234);
+ } catch (exception) {
+ passed = exception instanceof TypeError;
+ }
+
+ if (!passed) throw new Error("queueMicrotask should throw a TypeError if the argument is not a function");
+}
+
+{
+ var passed = false;
+ try {
+ queueMicrotask();
+ } catch (exception) {
+ passed = exception instanceof TypeError;
+ }
+
+ if (!passed) throw new Error("queueMicrotask should throw a TypeError if the argument is empty");
+}
+
+await new Promise((resolve, reject) => {
+ process.nextTick(
+ (first, second) => {
+ console.log(first, second);
+ if (first !== 12345 || second !== "hello") reject(new Error("process.nextTick called with wrong arguments"));
+ resolve(true);
+ },
+ 12345,
+ "hello",
+ );
+});
diff --git a/test/js/node/process/process-nexttick.test.js b/test/js/node/process/process-nexttick.test.js
new file mode 100644
index 000000000..becf3c236
--- /dev/null
+++ b/test/js/node/process/process-nexttick.test.js
@@ -0,0 +1,99 @@
+import { it } from "bun:test";
+
+it("process.nextTick", async () => {
+ // You can verify this test is correct by copy pasting this into a browser's console and checking it doesn't throw an error.
+ var run = 0;
+ var queueMicrotask = process.nextTick;
+
+ await new Promise((resolve, reject) => {
+ queueMicrotask(() => {
+ if (run++ != 0) {
+ reject(new Error("Microtask execution order is wrong: " + run));
+ }
+ queueMicrotask(() => {
+ if (run++ != 3) {
+ reject(new Error("Microtask execution order is wrong: " + run));
+ }
+ });
+ });
+ queueMicrotask(() => {
+ if (run++ != 1) {
+ reject(new Error("Microtask execution order is wrong: " + run));
+ }
+ queueMicrotask(() => {
+ if (run++ != 4) {
+ reject(new Error("Microtask execution order is wrong: " + run));
+ }
+
+ queueMicrotask(() => {
+ if (run++ != 6) {
+ reject(new Error("Microtask execution order is wrong: " + run));
+ }
+ });
+ });
+ });
+ queueMicrotask(() => {
+ if (run++ != 2) {
+ reject(new Error("Microtask execution order is wrong: " + run));
+ }
+ queueMicrotask(() => {
+ if (run++ != 5) {
+ reject(new Error("Microtask execution order is wrong: " + run));
+ }
+
+ queueMicrotask(() => {
+ if (run++ != 7) {
+ reject(new Error("Microtask execution order is wrong: " + run));
+ }
+ resolve(true);
+ });
+ });
+ });
+ });
+
+ {
+ var passed = false;
+ try {
+ queueMicrotask(1234);
+ } catch (exception) {
+ passed = exception instanceof TypeError;
+ }
+
+ if (!passed) throw new Error("queueMicrotask should throw a TypeError if the argument is not a function");
+ }
+
+ {
+ var passed = false;
+ try {
+ queueMicrotask();
+ } catch (exception) {
+ passed = exception instanceof TypeError;
+ }
+
+ if (!passed) throw new Error("queueMicrotask should throw a TypeError if the argument is empty");
+ }
+});
+
+it("process.nextTick 2 args", async () => {
+ await new Promise((resolve, reject) => {
+ process.nextTick(
+ (first, second) => {
+ if (first !== 12345 || second !== "hello") reject(new Error("process.nextTick called with wrong arguments"));
+ resolve(true);
+ },
+ 12345,
+ "hello",
+ );
+ });
+});
+
+it("process.nextTick 5 args", async () => {
+ await new Promise((resolve, reject) => {
+ var args = [12345, "hello", "hello", "hello", 5];
+ process.nextTick((...receivedArgs) => {
+ if (!args.every((arg, index) => arg === receivedArgs[index]))
+ reject(new Error("process.nextTick called with wrong arguments"));
+ resolve(true);
+ }, ...args);
+ });
+});
diff --git a/test/js/node/process/process-stdin-echo.js b/test/js/node/process/process-stdin-echo.js
new file mode 100644
index 000000000..e265cc76d
--- /dev/null
+++ b/test/js/node/process/process-stdin-echo.js
@@ -0,0 +1,11 @@
+process.stdin.setEncoding("utf8");
+process.stdin.on("data", data => {
+ process.stdout.write(data);
+});
+process.stdin.once("end", () => {
+ process.stdout.write("ENDED");
+});
+if (process.argv[2] == "resume") {
+ process.stdout.write("RESUMED");
+ process.stdin.resume();
+}
diff --git a/test/js/node/process/process-stdio.test.ts b/test/js/node/process/process-stdio.test.ts
new file mode 100644
index 000000000..463ab5fda
--- /dev/null
+++ b/test/js/node/process/process-stdio.test.ts
@@ -0,0 +1,128 @@
+import { spawn, spawnSync } from "bun";
+import { describe, expect, it, test } from "bun:test";
+import { bunExe } from "harness";
+import { isatty } from "tty";
+
+test("process.stdin", () => {
+ expect(process.stdin).toBeDefined();
+ expect(process.stdout.isTTY).toBe(isatty(0));
+ expect(process.stdin.on("close", function () {})).toBe(process.stdin);
+ expect(process.stdin.once("end", function () {})).toBe(process.stdin);
+});
+
+test("process.stdin - read", async () => {
+ const { stdin, stdout } = spawn({
+ cmd: [bunExe(), import.meta.dir + "/process-stdin-echo.js"],
+ stdout: "pipe",
+ stdin: "pipe",
+ stderr: null,
+ env: {
+ ...process.env,
+ BUN_DEBUG_QUIET_LOGS: "1",
+ },
+ });
+ expect(stdin).toBeDefined();
+ expect(stdout).toBeDefined();
+ var lines = ["Get Emoji", "— All Emojis to ✂️ Copy and 📋 Paste", "👌", ""];
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i];
+ setTimeout(() => {
+ if (line) {
+ stdin?.write(line + "\n");
+ stdin?.flush();
+ } else {
+ stdin?.end();
+ }
+ }, i * 200);
+ }
+ var text = await new Response(stdout).text();
+ expect(text).toBe(lines.join("\n") + "ENDED");
+});
+
+test("process.stdin - resume", async () => {
+ const { stdin, stdout } = spawn({
+ cmd: [bunExe(), import.meta.dir + "/process-stdin-echo.js", "resume"],
+ stdout: "pipe",
+ stdin: "pipe",
+ stderr: null,
+ env: {
+ ...process.env,
+ BUN_DEBUG_QUIET_LOGS: "1",
+ },
+ });
+ expect(stdin).toBeDefined();
+ expect(stdout).toBeDefined();
+ var lines = ["Get Emoji", "— All Emojis to ✂️ Copy and 📋 Paste", "👌", ""];
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i];
+ setTimeout(() => {
+ if (line) {
+ stdin?.write(line + "\n");
+ stdin?.flush();
+ } else {
+ stdin?.end();
+ }
+ }, i * 200);
+ }
+ var text = await new Response(stdout).text();
+ expect(text).toBe("RESUMED" + lines.join("\n") + "ENDED");
+});
+
+test("process.stdout", () => {
+ expect(process.stdout).toBeDefined();
+ expect(process.stdout.isTTY).toBe(isatty(1));
+});
+
+test("process.stderr", () => {
+ expect(process.stderr).toBeDefined();
+ expect(process.stderr.isTTY).toBe(isatty(2));
+});
+
+test("process.stdout - write", () => {
+ const { stdout } = spawnSync({
+ cmd: [bunExe(), import.meta.dir + "/stdio-test-instance.js"],
+ stdout: "pipe",
+ stdin: null,
+ stderr: null,
+ env: {
+ ...process.env,
+ BUN_DEBUG_QUIET_LOGS: "1",
+ },
+ });
+
+ expect(stdout?.toString()).toBe(`hello worldhello again|😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌`);
+});
+
+test("process.stdout - write a lot (string)", () => {
+ const { stdout } = spawnSync({
+ cmd: [bunExe(), import.meta.dir + "/stdio-test-instance-a-lot.js"],
+ stdout: "pipe",
+ stdin: null,
+ stderr: null,
+ env: {
+ ...process.env,
+ BUN_DEBUG_QUIET_LOGS: "1",
+ TEST_STDIO_STRING: "1",
+ },
+ });
+
+ expect(stdout?.toString()).toBe(
+ `hello worldhello again|😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌`.repeat(9999),
+ );
+});
+
+test("process.stdout - write a lot (bytes)", () => {
+ const { stdout } = spawnSync({
+ cmd: [bunExe(), import.meta.dir + "/stdio-test-instance-a-lot.js"],
+ stdout: "pipe",
+ stdin: null,
+ stderr: null,
+ env: {
+ ...process.env,
+ BUN_DEBUG_QUIET_LOGS: "1",
+ },
+ });
+ expect(stdout?.toString()).toBe(
+ `hello worldhello again|😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌`.repeat(9999),
+ );
+});
diff --git a/test/js/node/process/process.test.js b/test/js/node/process/process.test.js
new file mode 100644
index 000000000..59f54c53f
--- /dev/null
+++ b/test/js/node/process/process.test.js
@@ -0,0 +1,185 @@
+import { resolveSync, which } from "bun";
+import { describe, expect, it } from "bun:test";
+import { existsSync, readFileSync, realpathSync } from "fs";
+import { basename } from "path";
+
+it("process", () => {
+ // this property isn't implemented yet but it should at least return a string
+ const isNode = !process.isBun;
+
+ if (!isNode && process.title !== "bun") throw new Error("process.title is not 'bun'");
+
+ if (typeof process.env.USER !== "string") throw new Error("process.env is not an object");
+
+ if (process.env.USER.length === 0) throw new Error("process.env is missing a USER property");
+
+ if (process.platform !== "darwin" && process.platform !== "linux") throw new Error("process.platform is invalid");
+
+ if (isNode) throw new Error("process.isBun is invalid");
+
+ // partially to test it doesn't crash due to various strange types
+ process.env.BACON = "yummy";
+ if (process.env.BACON !== "yummy") {
+ throw new Error("process.env is not writable");
+ }
+
+ delete process.env.BACON;
+ if (typeof process.env.BACON !== "undefined") {
+ throw new Error("process.env is not deletable");
+ }
+
+ process.env.BACON = "yummy";
+ if (process.env.BACON !== "yummy") {
+ throw new Error("process.env is not re-writable");
+ }
+ if (!JSON.stringify(process.env)) {
+ throw new Error("process.env is not serializable");
+ }
+
+ if (typeof JSON.parse(JSON.stringify(process.env)).toJSON !== "undefined") {
+ throw new Error("process.env should call toJSON to hide its internal state");
+ }
+
+ var { env, ...proces } = process;
+ console.log(proces);
+
+ console.log("CWD", process.cwd());
+ console.log("SET CWD", process.chdir("../"));
+ console.log("CWD", process.cwd());
+});
+
+it("process.hrtime()", () => {
+ const start = process.hrtime();
+ const end = process.hrtime(start);
+ const end2 = process.hrtime();
+ expect(end[0]).toBe(0);
+ expect(end2[1] > start[1]).toBe(true);
+});
+
+it("process.hrtime.bigint()", () => {
+ const start = process.hrtime.bigint();
+ const end = process.hrtime.bigint();
+ expect(end > start).toBe(true);
+});
+
+it("process.release", () => {
+ expect(process.release.name).toBe("bun");
+ expect(process.release.sourceUrl).toBe(
+ `https://github.com/oven-sh/bun/release/bun-v${process.versions.bun}/bun-${process.platform}-${
+ { arm64: "aarch64", x64: "x64" }[process.arch] || process.arch
+ }.zip`,
+ );
+});
+
+it("process.env", () => {
+ process.env["LOL SMILE UTF16 😂"] = "😂";
+ expect(process.env["LOL SMILE UTF16 😂"]).toBe("😂");
+ delete process.env["LOL SMILE UTF16 😂"];
+ expect(process.env["LOL SMILE UTF16 😂"]).toBe(undefined);
+
+ process.env["LOL SMILE latin1 <abc>"] = "<abc>";
+ expect(process.env["LOL SMILE latin1 <abc>"]).toBe("<abc>");
+ delete process.env["LOL SMILE latin1 <abc>"];
+ expect(process.env["LOL SMILE latin1 <abc>"]).toBe(undefined);
+});
+
+it("process.env is spreadable and editable", () => {
+ process.env["LOL SMILE UTF16 😂"] = "😂";
+ const { "LOL SMILE UTF16 😂": lol, ...rest } = process.env;
+ expect(lol).toBe("😂");
+ delete process.env["LOL SMILE UTF16 😂"];
+ expect(rest).toEqual(process.env);
+ const orig = (getter => process.env[getter])("USER");
+ expect(process.env).toEqual(process.env);
+ eval(`globalThis.process.env.USER = 'bun';`);
+ expect(eval(`globalThis.process.env.USER`)).toBe("bun");
+ expect(eval(`globalThis.process.env.USER = "${orig}"`)).toBe(orig);
+});
+
+it("process.version starts with v", () => {
+ expect(process.version.startsWith("v")).toBeTruthy();
+});
+
+it("process.argv0", () => {
+ expect(basename(process.argv0)).toBe(basename(process.argv[0]));
+});
+
+it("process.execPath", () => {
+ expect(process.execPath).not.toBe(basename(process.argv0));
+ expect(which(process.execPath)).not.toBeNull();
+});
+
+it("process.uptime()", () => {
+ expect(process.uptime()).toBeGreaterThan(0);
+ expect(Math.floor(process.uptime())).toBe(Math.floor(performance.now() / 1000));
+});
+
+it("process.umask()", () => {
+ const orig = process.umask(777);
+ expect(orig).toBeGreaterThan(0);
+ expect(process.umask(orig)).toBe(777);
+});
+
+const versions = existsSync(import.meta.dir + "/../../src/generated_versions_list.zig");
+(versions ? it : it.skip)("process.versions", () => {
+ // Generate a list of all the versions in the versions object
+ // example:
+ // pub const boringssl = "b275c5ce1c88bc06f5a967026d3c0ce1df2be815";
+ // pub const libarchive = "dc321febde83dd0f31158e1be61a7aedda65e7a2";
+ // pub const mimalloc = "3c7079967a269027e438a2aac83197076d9fe09d";
+ // pub const picohttpparser = "066d2b1e9ab820703db0837a7255d92d30f0c9f5";
+ // pub const uws = "70b1b9fc1341e8b791b42c5447f90505c2abe156";
+ // pub const webkit = "60d11703a533fd694cd1d6ddda04813eecb5d69f";
+ // pub const zlib = "885674026394870b7e7a05b7bf1ec5eb7bd8a9c0";
+ // pub const tinycc = "2d3ad9e0d32194ad7fd867b66ebe218dcc8cb5cd";
+ // pub const lolhtml = "2eed349dcdfa4ff5c19fe7c6e501cfd687601033";
+ // pub const c_ares = "0e7a5dee0fbb04080750cf6eabbe89d8bae87faa";
+ // pub const usockets = "fafc241e8664243fc0c51d69684d5d02b9805134";
+ const versions = Object.fromEntries(
+ readFileSync(import.meta.dir + "/../../src/generated_versions_list.zig", "utf8")
+ .split("\n")
+ .filter(line => line.startsWith("pub const") && !line.includes("zig") && line.includes(' = "'))
+ .map(line => line.split(" = "))
+ .map(([name, hash]) => [name.slice(9).trim(), hash.slice(1, -2)]),
+ );
+ versions.uwebsockets = versions.uws;
+ delete versions.uws;
+ versions["ares"] = versions.c_ares;
+ delete versions.c_ares;
+
+ for (const name in versions) {
+ expect(process.versions).toHaveProperty(name);
+ expect(process.versions[name]).toBe(versions[name]);
+ }
+});
+
+it("process.config", () => {
+ expect(process.config).toEqual({
+ variables: {
+ v8_enable_i8n_support: 1,
+ },
+ target_defaults: {},
+ });
+});
+
+it("process.emitWarning", () => {
+ process.emitWarning("-- Testing process.emitWarning --");
+ var called = 0;
+ process.on("warning", err => {
+ called++;
+ expect(err.message).toBe("-- Testing process.on('warning') --");
+ });
+ process.emitWarning("-- Testing process.on('warning') --");
+ expect(called).toBe(1);
+ expect(process.off("warning")).toBe(process);
+ process.emitWarning("-- Testing process.on('warning') --");
+ expect(called).toBe(1);
+});
+
+it("process.execArgv", () => {
+ expect(process.execArgv instanceof Array).toBe(true);
+});
+
+it("process.binding", () => {
+ expect(() => process.binding("buffer")).toThrow();
+});
diff --git a/test/js/node/readline/readline.node.test.ts b/test/js/node/readline/readline.node.test.ts
new file mode 100644
index 000000000..7dfa51ac8
--- /dev/null
+++ b/test/js/node/readline/readline.node.test.ts
@@ -0,0 +1,2001 @@
+import { beforeEach, describe, it } from "bun:test";
+import readline from "node:readline";
+import { Writable, PassThrough } from "node:stream";
+import { EventEmitter } from "node:events";
+import { createDoneDotAll, createCallCheckCtx, assert } from "node-harness";
+
+var {
+ CSI,
+ utils: { getStringWidth, stripVTControlCharacters },
+} = readline[Symbol.for("__BUN_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED__")];
+
+// ----------------------------------------------------------------------------
+// Helpers
+// ----------------------------------------------------------------------------
+
+class TestWritable extends Writable {
+ data;
+ constructor() {
+ super();
+ this.data = "";
+ }
+ _write(chunk, encoding, callback) {
+ this.data += chunk.toString();
+ callback();
+ }
+}
+
+class FakeInput extends EventEmitter {
+ resume() {}
+ pause() {}
+ write() {}
+ end() {}
+}
+
+function isWarned(emitter) {
+ for (const name in emitter) {
+ const listeners = emitter[name];
+ if (listeners.warned) return true;
+ }
+ return false;
+}
+
+function getInterface(options) {
+ const fi = new FakeInput();
+ const rli = new readline.Interface({
+ input: fi,
+ output: fi,
+ ...options,
+ });
+ return [rli, fi];
+}
+
+function assertCursorRowsAndCols(rli, rows, cols) {
+ const cursorPos = rli.getCursorPos();
+ assert.strictEqual(cursorPos.rows, rows);
+ assert.strictEqual(cursorPos.cols, cols);
+}
+
+const writable = new TestWritable();
+const input = new FakeInput();
+
+// ----------------------------------------------------------------------------
+// Tests
+// ----------------------------------------------------------------------------
+
+describe("CSI", () => {
+ it("should be defined", () => {
+ assert.ok(CSI);
+ });
+
+ it("should have all the correct clear sequences", () => {
+ assert.strictEqual(CSI.kClearToLineBeginning, "\x1b[1K");
+ assert.strictEqual(CSI.kClearToLineEnd, "\x1b[0K");
+ assert.strictEqual(CSI.kClearLine, "\x1b[2K");
+ assert.strictEqual(CSI.kClearScreenDown, "\x1b[0J");
+ assert.strictEqual(CSI`1${2}3`, "\x1b[123");
+ });
+});
+
+describe("readline.clearScreenDown()", () => {
+ it("should put clear screen sequence into writable when called", done => {
+ const { mustCall } = createCallCheckCtx(done);
+
+ assert.strictEqual(readline.clearScreenDown(writable), true);
+ assert.deepStrictEqual(writable.data, CSI.kClearScreenDown);
+ assert.strictEqual(readline.clearScreenDown(writable, mustCall()), true);
+ });
+
+ it("should throw on invalid callback", () => {
+ // Verify that clearScreenDown() throws on invalid callback.
+ assert.throws(() => {
+ readline.clearScreenDown(writable, null);
+ }, /ERR_INVALID_ARG_TYPE/);
+ });
+
+ it("should that clearScreenDown() does not throw on null or undefined stream", done => {
+ const { mustCall } = createCallCheckCtx(done);
+ assert.strictEqual(
+ readline.clearScreenDown(
+ null,
+ mustCall(err => {
+ assert.strictEqual(err, null);
+ }),
+ ),
+ true,
+ );
+ assert.strictEqual(readline.clearScreenDown(undefined, mustCall()), true);
+ });
+});
+
+describe("readline.clearLine()", () => {
+ beforeEach(() => {
+ writable.data = "";
+ });
+
+ it("should clear to the left of cursor when given -1 as direction", () => {
+ assert.strictEqual(readline.clearLine(writable, -1), true);
+ assert.deepStrictEqual(writable.data, CSI.kClearToLineBeginning);
+ });
+
+ it("should clear to the right of cursor when given 1 as direction", () => {
+ assert.strictEqual(readline.clearLine(writable, 1), true);
+ assert.deepStrictEqual(writable.data, CSI.kClearToLineEnd);
+ });
+
+ it("should clear whole line when given 0 as direction", () => {
+ assert.strictEqual(readline.clearLine(writable, 0), true);
+ assert.deepStrictEqual(writable.data, CSI.kClearLine);
+ });
+
+ it("should call callback after clearing line", done => {
+ const { mustCall } = createCallCheckCtx(done);
+ assert.strictEqual(readline.clearLine(writable, -1, mustCall()), true);
+ assert.deepStrictEqual(writable.data, CSI.kClearToLineBeginning);
+ });
+
+ it("should throw on an invalid callback", () => {
+ // Verify that clearLine() throws on invalid callback.
+ assert.throws(() => {
+ readline.clearLine(writable, 0, null);
+ }, /ERR_INVALID_ARG_TYPE/);
+ });
+
+ it("shouldn't throw on on null or undefined stream", done => {
+ const { mustCall } = createCallCheckCtx(done);
+ // Verify that clearLine() does not throw on null or undefined stream.
+ assert.strictEqual(readline.clearLine(null, 0), true);
+ assert.strictEqual(readline.clearLine(undefined, 0), true);
+ assert.strictEqual(
+ readline.clearLine(
+ null,
+ 0,
+ mustCall(err => {
+ assert.strictEqual(err, null);
+ }),
+ ),
+ true,
+ );
+ assert.strictEqual(readline.clearLine(undefined, 0, mustCall()), true);
+ });
+});
+
+describe("readline.moveCursor()", () => {
+ it("shouldn't write when moveCursor(0, 0) is called", done => {
+ const { mustCall } = createCallCheckCtx(done);
+ // Nothing is written when moveCursor 0, 0
+ [
+ [0, 0, ""],
+ [1, 0, "\x1b[1C"],
+ [-1, 0, "\x1b[1D"],
+ [0, 1, "\x1b[1B"],
+ [0, -1, "\x1b[1A"],
+ [1, 1, "\x1b[1C\x1b[1B"],
+ [-1, 1, "\x1b[1D\x1b[1B"],
+ [-1, -1, "\x1b[1D\x1b[1A"],
+ [1, -1, "\x1b[1C\x1b[1A"],
+ ].forEach(set => {
+ writable.data = "";
+ assert.strictEqual(readline.moveCursor(writable, set[0], set[1]), true);
+ assert.deepStrictEqual(writable.data, set[2]);
+ writable.data = "";
+ assert.strictEqual(readline.moveCursor(writable, set[0], set[1], mustCall()), true);
+ assert.deepStrictEqual(writable.data, set[2]);
+ });
+ });
+
+ it("should throw on invalid callback", () => {
+ // Verify that moveCursor() throws on invalid callback.
+ assert.throws(() => {
+ readline.moveCursor(writable, 1, 1, null);
+ }, /ERR_INVALID_ARG_TYPE/);
+ });
+
+ it("should not throw on null or undefined stream", done => {
+ const { mustCall } = createCallCheckCtx(done);
+ // Verify that moveCursor() does not throw on null or undefined stream.
+ assert.strictEqual(readline.moveCursor(null, 1, 1), true);
+ assert.strictEqual(readline.moveCursor(undefined, 1, 1), true);
+ assert.strictEqual(
+ readline.moveCursor(
+ null,
+ 1,
+ 1,
+ mustCall(err => {
+ assert.strictEqual(err, null);
+ }),
+ ),
+ true,
+ );
+ assert.strictEqual(readline.moveCursor(undefined, 1, 1, mustCall()), true);
+ });
+});
+
+describe("readline.cursorTo()", () => {
+ beforeEach(() => {
+ writable.data = "";
+ });
+
+ it("should not throw on undefined or null as stream", done => {
+ const { mustCall } = createCallCheckCtx(done);
+ // Undefined or null as stream should not throw.
+ assert.strictEqual(readline.cursorTo(null), true);
+ assert.strictEqual(readline.cursorTo(), true);
+ assert.strictEqual(readline.cursorTo(null, 1, 1, mustCall()), true);
+ assert.strictEqual(
+ readline.cursorTo(
+ undefined,
+ 1,
+ 1,
+ mustCall(err => {
+ assert.strictEqual(err, null);
+ }),
+ ),
+ true,
+ );
+ });
+
+ it("should not write if given invalid cursor position - [string, undefined]", () => {
+ assert.strictEqual(readline.cursorTo(writable, "a"), true);
+ assert.strictEqual(writable.data, "");
+ });
+
+ it("should not write if given invalid cursor position - [string, string]", () => {
+ assert.strictEqual(readline.cursorTo(writable, "a", "b"), true);
+ assert.strictEqual(writable.data, "");
+ });
+
+ it("should throw when x is not a number", () => {
+ assert.throws(() => readline.cursorTo(writable, "a", 1), {
+ name: "TypeError",
+ code: "ERR_INVALID_CURSOR_POS",
+ message: "Cannot set cursor row without setting its column",
+ });
+ assert.strictEqual(writable.data, "");
+ });
+
+ it("should write when given value cursor positions", done => {
+ const { mustCall } = createCallCheckCtx(done);
+
+ assert.strictEqual(readline.cursorTo(writable, 1, "a"), true);
+ assert.strictEqual(writable.data, "\x1b[2G");
+
+ writable.data = "";
+ assert.strictEqual(readline.cursorTo(writable, 1), true);
+ assert.strictEqual(writable.data, "\x1b[2G");
+
+ writable.data = "";
+ assert.strictEqual(readline.cursorTo(writable, 1, 2), true);
+ assert.strictEqual(writable.data, "\x1b[3;2H");
+
+ writable.data = "";
+ assert.strictEqual(readline.cursorTo(writable, 1, 2, mustCall()), true);
+ assert.strictEqual(writable.data, "\x1b[3;2H");
+
+ writable.data = "";
+ assert.strictEqual(readline.cursorTo(writable, 1, mustCall()), true);
+ assert.strictEqual(writable.data, "\x1b[2G");
+ });
+
+ it("should throw on invalid callback", () => {
+ // Verify that cursorTo() throws on invalid callback.
+ assert.throws(() => {
+ readline.cursorTo(writable, 1, 1, null);
+ }, /ERR_INVALID_ARG_TYPE/);
+ });
+
+ it("should throw if x or y is NaN", () => {
+ // Verify that cursorTo() throws if x or y is NaN.
+ assert.throws(() => {
+ readline.cursorTo(writable, NaN);
+ }, /ERR_INVALID_ARG_VALUE/);
+
+ assert.throws(() => {
+ readline.cursorTo(writable, 1, NaN);
+ }, /ERR_INVALID_ARG_VALUE/);
+
+ assert.throws(() => {
+ readline.cursorTo(writable, NaN, NaN);
+ }, /ERR_INVALID_ARG_VALUE/);
+ });
+});
+
+describe("readline.emitKeyPressEvents()", () => {
+ // emitKeypressEvents is thoroughly tested in test-readline-keys.js.
+ // However, that test calls it implicitly. This is just a quick sanity check
+ // to verify that it works when called explicitly.
+
+ const expectedSequence = ["f", "o", "o"];
+ const expectedKeys = [
+ { sequence: "f", name: "f", ctrl: false, meta: false, shift: false },
+ { sequence: "o", name: "o", ctrl: false, meta: false, shift: false },
+ { sequence: "o", name: "o", ctrl: false, meta: false, shift: false },
+ ];
+
+ it("should emit the expected sequence when keypress listener added after called", () => {
+ const stream = new PassThrough();
+ const sequence: any[] = [];
+ const keys: any[] = [];
+
+ readline.emitKeypressEvents(stream);
+ stream.on("keypress", (s, k) => {
+ sequence.push(s);
+ keys.push(k);
+ });
+ stream.write("foo");
+
+ assert.deepStrictEqual(sequence, expectedSequence);
+ assert.deepStrictEqual(keys, expectedKeys);
+ });
+
+ it("should emit the expected sequence when keypress listener added before called", () => {
+ const stream = new PassThrough();
+ const sequence: any[] = [];
+ const keys: any[] = [];
+
+ stream.on("keypress", (s, k) => {
+ sequence.push(s);
+ keys.push(k);
+ });
+ readline.emitKeypressEvents(stream);
+ stream.write("foo");
+
+ assert.deepStrictEqual(sequence, expectedSequence);
+ assert.deepStrictEqual(keys, expectedKeys);
+ });
+
+ it("should allow keypress listeners to be removed and added again", () => {
+ const stream = new PassThrough();
+ const sequence: any[] = [];
+ const keys: any[] = [];
+ const keypressListener = (s, k) => {
+ sequence.push(s);
+ keys.push(k);
+ };
+
+ stream.on("keypress", keypressListener);
+ readline.emitKeypressEvents(stream);
+ stream.removeListener("keypress", keypressListener);
+ stream.write("foo");
+
+ assert.deepStrictEqual(sequence, []);
+ assert.deepStrictEqual(keys, []);
+
+ stream.on("keypress", keypressListener);
+ stream.write("foo");
+
+ assert.deepStrictEqual(sequence, expectedSequence);
+ assert.deepStrictEqual(keys, expectedKeys);
+ });
+});
+
+describe("readline.Interface", () => {
+ it("should allow valid escapeCodeTimeout to be set", () => {
+ const fi = new FakeInput();
+ const rli = new readline.Interface({
+ input: fi,
+ output: fi,
+ escapeCodeTimeout: 50,
+ });
+ assert.strictEqual(rli.escapeCodeTimeout, 50);
+ rli.close();
+ });
+
+ it("should throw on invalid escapeCodeTimeout", () => {
+ [null, {}, NaN, "50"].forEach(invalidInput => {
+ assert.throws(
+ () => {
+ const fi = new FakeInput();
+ const rli = new readline.Interface({
+ input: fi,
+ output: fi,
+ escapeCodeTimeout: invalidInput,
+ });
+ rli.close();
+ },
+ {
+ name: "TypeError",
+ code: "ERR_INVALID_ARG_VALUE",
+ },
+ );
+ });
+ });
+
+ it("should create valid instances of readline.Interface", () => {
+ const input = new FakeInput();
+ const rl = readline.Interface({ input });
+ assert.ok(rl instanceof readline.Interface);
+ });
+
+ it("should call completer when input emits data", done => {
+ const { mustCall } = createCallCheckCtx(done);
+ const fi = new FakeInput();
+ const rli = new readline.Interface(
+ fi,
+ fi,
+ mustCall(line => [[], line]),
+ true,
+ );
+
+ assert.ok(rli instanceof readline.Interface);
+ fi.emit("data", "a\t");
+ rli.close();
+ });
+
+ it("should allow crlfDelay to be set", () => {
+ [undefined, 50, 0, 100.5, 5000].forEach(crlfDelay => {
+ const [rli] = getInterface({ crlfDelay });
+ assert.strictEqual(rli.crlfDelay, Math.max(crlfDelay || 100, 100));
+ rli.close();
+ });
+ });
+
+ it("should throw if completer is not a function or is undefined", () => {
+ ["not an array", 123, 123n, {}, true, Symbol(), null].forEach(invalid => {
+ assert.throws(
+ () => {
+ readline.createInterface({
+ input,
+ completer: invalid,
+ });
+ },
+ {
+ name: "TypeError",
+ code: "ERR_INVALID_ARG_VALUE",
+ },
+ );
+ });
+ });
+
+ it("should throw if history is not an array", () => {
+ ["not an array", 123, 123, {}, true, Symbol(), null].forEach(history => {
+ assert.throws(
+ () => {
+ readline.createInterface({
+ input,
+ history,
+ });
+ },
+ {
+ name: "TypeError",
+ code: "ERR_INVALID_ARG_TYPE",
+ },
+ );
+ });
+ });
+
+ it("should throw if historySize is not a positive number", () => {
+ ["not a number", -1, NaN, {}, true, Symbol(), null].forEach(historySize => {
+ assert.throws(
+ () => {
+ readline.createInterface({
+ input,
+ historySize,
+ });
+ },
+ {
+ // TODO: Revert to Range error when properly implemented errors with multiple bases
+ // name: "RangeError",
+ name: "TypeError",
+ code: "ERR_INVALID_ARG_VALUE",
+ },
+ );
+ });
+ });
+
+ it("should throw on invalid tabSize", () => {
+ // Check for invalid tab sizes.
+ assert.throws(
+ () =>
+ new readline.Interface({
+ input,
+ tabSize: 0,
+ }),
+ { code: "ERR_OUT_OF_RANGE" },
+ );
+
+ assert.throws(
+ () =>
+ new readline.Interface({
+ input,
+ tabSize: "4",
+ }),
+ { code: "ERR_INVALID_ARG_TYPE" },
+ );
+
+ assert.throws(
+ () =>
+ new readline.Interface({
+ input,
+ tabSize: 4.5,
+ }),
+ {
+ code: "ERR_OUT_OF_RANGE",
+ // message:
+ // 'The value of "tabSize" is out of range. ' +
+ // "It must be an integer. Received 4.5",
+ },
+ );
+ });
+
+ // Sending a single character with no newline
+ it("should not emit line when only a single character sent with no newline", done => {
+ const { mustNotCall } = createCallCheckCtx(done);
+ const fi = new FakeInput();
+ const rli = new readline.Interface(fi, {});
+ rli.on("line", mustNotCall());
+ fi.emit("data", "a");
+ rli.close();
+ });
+
+ it("should treat \\r like \\n when alone", done => {
+ const { mustCall } = createCallCheckCtx(done);
+ // Sending multiple newlines at once that does not end with a new line and a
+ // `end` event(last line is). \r should behave like \n when alone.
+ const [rli, fi] = getInterface({ terminal: true });
+ const expectedLines = ["foo", "bar", "baz", "bat"];
+ rli.on(
+ "line",
+ mustCall(line => {
+ assert.strictEqual(line, expectedLines.shift());
+ }, expectedLines.length - 1),
+ );
+ fi.emit("data", expectedLines.join("\r"));
+ rli.close();
+ });
+
+ // \r at start of input should output blank line
+ it("should output blank line when \\r at start of input", done => {
+ const { mustCall } = createCallCheckCtx(done);
+ const [rli, fi] = getInterface({ terminal: true });
+ const expectedLines = ["", "foo"];
+ rli.on(
+ "line",
+ mustCall(line => {
+ assert.strictEqual(line, expectedLines.shift());
+ }, expectedLines.length),
+ );
+ fi.emit("data", "\rfoo\r");
+ rli.close();
+ });
+
+ // \t does not become part of the input when there is a completer function
+ it("should not include \\t in input when there is a completer function", done => {
+ const { mustCall } = createCallCheckCtx(done);
+ const completer = line => [[], line];
+ const [rli, fi] = getInterface({ terminal: true, completer });
+ rli.on(
+ "line",
+ mustCall(line => {
+ assert.strictEqual(line, "foo");
+ }),
+ );
+ for (const character of "\tfo\to\t") {
+ fi.emit("data", character);
+ }
+ fi.emit("data", "\n");
+ rli.close();
+ });
+
+ // \t when there is no completer function should behave like an ordinary
+ // character
+ it("should treat \\t as an ordinary character when there is no completer function", done => {
+ const { mustCall } = createCallCheckCtx(done);
+ const [rli, fi] = getInterface({ terminal: true });
+ rli.on(
+ "line",
+ mustCall(line => {
+ assert.strictEqual(line, "\t");
+ }),
+ );
+ fi.emit("data", "\t");
+ fi.emit("data", "\n");
+ rli.close();
+ });
+
+ // Adding history lines should emit the history event with
+ // the history array
+ it("should emit history event when adding history lines", done => {
+ const { mustCall } = createCallCheckCtx(done);
+ const [rli, fi] = getInterface({ terminal: true });
+ const expectedLines = ["foo", "bar", "baz", "bat"];
+ rli.on(
+ "history",
+ mustCall(history => {
+ const expectedHistory = expectedLines.slice(0, history.length).reverse();
+ assert.deepStrictEqual(history, expectedHistory);
+ }, expectedLines.length),
+ );
+ for (const line of expectedLines) {
+ fi.emit("data", `${line}\n`);
+ }
+ rli.close();
+ });
+
+ // Altering the history array in the listener should not alter
+ // the line being processed
+ it("should not alter the line being processed when history is altered", done => {
+ const { mustCall } = createCallCheckCtx(done);
+ const [rli, fi] = getInterface({ terminal: true });
+ const expectedLine = "foo";
+ rli.on(
+ "history",
+ mustCall(history => {
+ assert.strictEqual(history[0], expectedLine);
+ history.shift();
+ }),
+ );
+ rli.on(
+ "line",
+ mustCall(line => {
+ assert.strictEqual(line, expectedLine);
+ assert.strictEqual(rli.history.length, 0);
+ }),
+ );
+ fi.emit("data", `${expectedLine}\n`);
+ rli.close();
+ });
+
+ // Duplicate lines are removed from history when
+ // `options.removeHistoryDuplicates` is `true`
+ it("should remove duplicate lines from history when removeHistoryDuplicates is true", () => {
+ const [rli, fi] = getInterface({
+ terminal: true,
+ removeHistoryDuplicates: true,
+ });
+ const expectedLines = ["foo", "bar", "baz", "bar", "bat", "bat"];
+ // ['foo', 'baz', 'bar', bat'];
+ let callCount = 0;
+ rli.on("line", line => {
+ assert.strictEqual(line, expectedLines[callCount]);
+ callCount++;
+ });
+ fi.emit("data", `${expectedLines.join("\n")}\n`);
+ assert.strictEqual(callCount, expectedLines.length);
+ fi.emit("keypress", ".", { name: "up" }); // 'bat'
+ assert.strictEqual(rli.line, expectedLines[--callCount]);
+ fi.emit("keypress", ".", { name: "up" }); // 'bar'
+ assert.notStrictEqual(rli.line, expectedLines[--callCount]);
+ assert.strictEqual(rli.line, expectedLines[--callCount]);
+ fi.emit("keypress", ".", { name: "up" }); // 'baz'
+ assert.strictEqual(rli.line, expectedLines[--callCount]);
+ fi.emit("keypress", ".", { name: "up" }); // 'foo'
+ assert.notStrictEqual(rli.line, expectedLines[--callCount]);
+ assert.strictEqual(rli.line, expectedLines[--callCount]);
+ assert.strictEqual(callCount, 0);
+ fi.emit("keypress", ".", { name: "down" }); // 'baz'
+ assert.strictEqual(rli.line, "baz");
+ assert.strictEqual(rli.historyIndex, 2);
+ fi.emit("keypress", ".", { name: "n", ctrl: true }); // 'bar'
+ assert.strictEqual(rli.line, "bar");
+ assert.strictEqual(rli.historyIndex, 1);
+ fi.emit("keypress", ".", { name: "n", ctrl: true });
+ assert.strictEqual(rli.line, "bat");
+ assert.strictEqual(rli.historyIndex, 0);
+ // Activate the substring history search.
+ fi.emit("keypress", ".", { name: "down" }); // 'bat'
+ assert.strictEqual(rli.line, "bat");
+ assert.strictEqual(rli.historyIndex, -1);
+ // Deactivate substring history search.
+ fi.emit("keypress", ".", { name: "backspace" }); // 'ba'
+ assert.strictEqual(rli.historyIndex, -1);
+ assert.strictEqual(rli.line, "ba");
+ // Activate the substring history search.
+ fi.emit("keypress", ".", { name: "down" }); // 'ba'
+ assert.strictEqual(rli.historyIndex, -1);
+ assert.strictEqual(rli.line, "ba");
+ fi.emit("keypress", ".", { name: "down" }); // 'ba'
+ assert.strictEqual(rli.historyIndex, -1);
+ assert.strictEqual(rli.line, "ba");
+ fi.emit("keypress", ".", { name: "up" }); // 'bat'
+ assert.strictEqual(rli.historyIndex, 0);
+ assert.strictEqual(rli.line, "bat");
+ fi.emit("keypress", ".", { name: "up" }); // 'bar'
+ assert.strictEqual(rli.historyIndex, 1);
+ assert.strictEqual(rli.line, "bar");
+ fi.emit("keypress", ".", { name: "up" }); // 'baz'
+ assert.strictEqual(rli.historyIndex, 2);
+ assert.strictEqual(rli.line, "baz");
+ fi.emit("keypress", ".", { name: "up" }); // 'ba'
+ assert.strictEqual(rli.historyIndex, 4);
+ assert.strictEqual(rli.line, "ba");
+ fi.emit("keypress", ".", { name: "up" }); // 'ba'
+ assert.strictEqual(rli.historyIndex, 4);
+ assert.strictEqual(rli.line, "ba");
+ // Deactivate substring history search and reset history index.
+ fi.emit("keypress", ".", { name: "right" }); // 'ba'
+ assert.strictEqual(rli.historyIndex, -1);
+ assert.strictEqual(rli.line, "ba");
+ // Substring history search activated.
+ fi.emit("keypress", ".", { name: "up" }); // 'ba'
+ assert.strictEqual(rli.historyIndex, 0);
+ assert.strictEqual(rli.line, "bat");
+ rli.close();
+ });
+
+ // Duplicate lines are not removed from history when
+ // `options.removeHistoryDuplicates` is `false`
+ it("should not remove duplicate lines from history when removeHistoryDuplicates is false", () => {
+ const [rli, fi] = getInterface({
+ terminal: true,
+ removeHistoryDuplicates: false,
+ });
+ const expectedLines = ["foo", "bar", "baz", "bar", "bat", "bat"];
+ let callCount = 0;
+ rli.on("line", line => {
+ assert.strictEqual(line, expectedLines[callCount]);
+ callCount++;
+ });
+ fi.emit("data", `${expectedLines.join("\n")}\n`);
+ assert.strictEqual(callCount, expectedLines.length);
+ fi.emit("keypress", ".", { name: "up" }); // 'bat'
+ assert.strictEqual(rli.line, expectedLines[--callCount]);
+ fi.emit("keypress", ".", { name: "up" }); // 'bar'
+ assert.notStrictEqual(rli.line, expectedLines[--callCount]);
+ assert.strictEqual(rli.line, expectedLines[--callCount]);
+ fi.emit("keypress", ".", { name: "up" }); // 'baz'
+ assert.strictEqual(rli.line, expectedLines[--callCount]);
+ fi.emit("keypress", ".", { name: "up" }); // 'bar'
+ assert.strictEqual(rli.line, expectedLines[--callCount]);
+ fi.emit("keypress", ".", { name: "up" }); // 'foo'
+ assert.strictEqual(rli.line, expectedLines[--callCount]);
+ assert.strictEqual(callCount, 0);
+ rli.close();
+ });
+
+ // Regression test for repl freeze, #1968:
+ // check that nothing fails if 'keypress' event throws.
+ it("should not fail if keypress throws", () => {
+ const [rli, fi] = getInterface({ terminal: true });
+ const keys = [] as string[];
+ const err = new Error("bad thing happened");
+ fi.on("keypress", (key: string) => {
+ keys.push(key);
+ if (key === "X") {
+ throw err;
+ }
+ });
+ assert.throws(
+ () => fi.emit("data", "fooX"),
+ e => {
+ console.log("ERRROR!", e);
+ assert.strictEqual(e, err);
+ return true;
+ },
+ );
+ fi.emit("data", "bar");
+ assert.strictEqual(keys.join(""), "fooXbar");
+ rli.close();
+ });
+
+ // History is bound
+ it("should bind history", () => {
+ const [rli, fi] = getInterface({ terminal: true, historySize: 2 });
+ const lines = ["line 1", "line 2", "line 3"];
+ fi.emit("data", lines.join("\n") + "\n");
+ assert.strictEqual(rli.history.length, 2);
+ assert.strictEqual(rli.history[0], "line 3");
+ assert.strictEqual(rli.history[1], "line 2");
+ });
+
+ // Question
+ it("should handle question", () => {
+ const [rli] = getInterface({ terminal: true });
+ const expectedLines = ["foo"];
+ rli.question(expectedLines[0], () => rli.close());
+ assertCursorRowsAndCols(rli, 0, expectedLines[0].length);
+ rli.close();
+ });
+
+ // Sending a multi-line question
+ it("should handle multi-line questions", () => {
+ const [rli] = getInterface({ terminal: true });
+ const expectedLines = ["foo", "bar"];
+ rli.question(expectedLines.join("\n"), () => rli.close());
+ assertCursorRowsAndCols(rli, expectedLines.length - 1, expectedLines.slice(-1)[0].length);
+ rli.close();
+ });
+
+ it("should handle beginning and end of line", () => {
+ // Beginning and end of line
+ const [rli, fi] = getInterface({ terminal: true, prompt: "" });
+ fi.emit("data", "the quick brown fox");
+ fi.emit("keypress", ".", { ctrl: true, name: "a" });
+ assertCursorRowsAndCols(rli, 0, 0);
+ fi.emit("keypress", ".", { ctrl: true, name: "e" });
+ assertCursorRowsAndCols(rli, 0, 19);
+ rli.close();
+ });
+
+ it("should handle back and forward one character", () => {
+ // Back and Forward one character
+ const [rli, fi] = getInterface({ terminal: true, prompt: "" });
+ fi.emit("data", "the quick brown fox");
+ assertCursorRowsAndCols(rli, 0, 19);
+
+ // Back one character
+ fi.emit("keypress", ".", { ctrl: true, name: "b" });
+ assertCursorRowsAndCols(rli, 0, 18);
+ // Back one character
+ fi.emit("keypress", ".", { ctrl: true, name: "b" });
+ assertCursorRowsAndCols(rli, 0, 17);
+ // Forward one character
+ fi.emit("keypress", ".", { ctrl: true, name: "f" });
+ assertCursorRowsAndCols(rli, 0, 18);
+ // Forward one character
+ fi.emit("keypress", ".", { ctrl: true, name: "f" });
+ assertCursorRowsAndCols(rli, 0, 19);
+ rli.close();
+ });
+
+ // Back and Forward one astral character
+ it("should handle going back and forward one astral character", done => {
+ const { mustCall } = createCallCheckCtx(done);
+ const [rli, fi] = getInterface({ terminal: true, prompt: "" });
+ fi.emit("data", "💻");
+
+ // Move left one character/code point
+ fi.emit("keypress", ".", { name: "left" });
+ assertCursorRowsAndCols(rli, 0, 0);
+
+ // Move right one character/code point
+ fi.emit("keypress", ".", { name: "right" });
+ assertCursorRowsAndCols(rli, 0, 2);
+
+ rli.on(
+ "line",
+ mustCall(line => {
+ assert.strictEqual(line, "💻");
+ }),
+ );
+ fi.emit("data", "\n");
+ rli.close();
+ });
+
+ // Two astral characters left
+ it("should handle two astral characters left", done => {
+ const { mustCall } = createCallCheckCtx(done);
+ const [rli, fi] = getInterface({ terminal: true, prompt: "" });
+ fi.emit("data", "💻");
+
+ // Move left one character/code point
+ fi.emit("keypress", ".", { name: "left" });
+ assertCursorRowsAndCols(rli, 0, 0);
+
+ fi.emit("data", "🐕");
+ assertCursorRowsAndCols(rli, 0, 2);
+
+ rli.on(
+ "line",
+ mustCall(line => {
+ assert.strictEqual(line, "🐕💻");
+ }),
+ );
+ fi.emit("data", "\n");
+ rli.close();
+ });
+
+ // Two astral characters right
+ it("should handle two astral characters right", done => {
+ const { mustCall } = createCallCheckCtx(done);
+ const [rli, fi] = getInterface({ terminal: true, prompt: "" });
+ fi.emit("data", "💻");
+
+ // Move left one character/code point
+ fi.emit("keypress", ".", { name: "right" });
+ assertCursorRowsAndCols(rli, 0, 2);
+
+ fi.emit("data", "🐕");
+ assertCursorRowsAndCols(rli, 0, 4);
+
+ rli.on(
+ "line",
+ mustCall(line => {
+ assert.strictEqual(line, "💻🐕");
+ }),
+ );
+ fi.emit("data", "\n");
+ rli.close();
+ });
+
+ it("should handle wordLeft and wordRight", () => {
+ // `wordLeft` and `wordRight`
+ const [rli, fi] = getInterface({ terminal: true, prompt: "" });
+ fi.emit("data", "the quick brown fox");
+ fi.emit("keypress", ".", { ctrl: true, name: "left" });
+ assertCursorRowsAndCols(rli, 0, 16);
+ fi.emit("keypress", ".", { meta: true, name: "b" });
+ assertCursorRowsAndCols(rli, 0, 10);
+ fi.emit("keypress", ".", { ctrl: true, name: "right" });
+ assertCursorRowsAndCols(rli, 0, 16);
+ fi.emit("keypress", ".", { meta: true, name: "f" });
+ assertCursorRowsAndCols(rli, 0, 19);
+ rli.close();
+ });
+
+ // `deleteWordLeft`
+ it("should handle deleteWordLeft", done => {
+ const { mustCall } = createCallCheckCtx(done);
+ [
+ { ctrl: true, name: "w" },
+ { ctrl: true, name: "backspace" },
+ { meta: true, name: "backspace" },
+ ].forEach(deleteWordLeftKey => {
+ let [rli, fi] = getInterface({ terminal: true, prompt: "" });
+ fi.emit("data", "the quick brown fox");
+ fi.emit("keypress", ".", { ctrl: true, name: "left" });
+ rli.on(
+ "line",
+ mustCall(line => {
+ assert.strictEqual(line, "the quick fox");
+ }),
+ );
+ fi.emit("keypress", ".", deleteWordLeftKey);
+ fi.emit("data", "\n");
+ rli.close();
+
+ // No effect if pressed at beginning of line
+ [rli, fi] = getInterface({ terminal: true, prompt: "" });
+ fi.emit("data", "the quick brown fox");
+ fi.emit("keypress", ".", { ctrl: true, name: "a" });
+ rli.on(
+ "line",
+ mustCall(line => {
+ assert.strictEqual(line, "the quick brown fox");
+ }),
+ );
+ fi.emit("keypress", ".", deleteWordLeftKey);
+ fi.emit("data", "\n");
+ rli.close();
+ });
+ });
+
+ // `deleteWordRight`
+ it("should handle deleteWordRight", done => {
+ const { mustCall } = createCallCheckCtx(done);
+ [
+ { ctrl: true, name: "delete" },
+ { meta: true, name: "delete" },
+ { meta: true, name: "d" },
+ ].forEach(deleteWordRightKey => {
+ let [rli, fi] = getInterface({ terminal: true, prompt: "" });
+ fi.emit("data", "the quick brown fox");
+ fi.emit("keypress", ".", { ctrl: true, name: "left" });
+ fi.emit("keypress", ".", { ctrl: true, name: "left" });
+ rli.on(
+ "line",
+ mustCall(line => {
+ assert.strictEqual(line, "the quick fox");
+ }),
+ );
+ fi.emit("keypress", ".", deleteWordRightKey);
+ fi.emit("data", "\n");
+ rli.close();
+
+ // No effect if pressed at end of line
+ [rli, fi] = getInterface({ terminal: true, prompt: "" });
+ fi.emit("data", "the quick brown fox");
+ rli.on(
+ "line",
+ mustCall(line => {
+ assert.strictEqual(line, "the quick brown fox");
+ }),
+ );
+ fi.emit("keypress", ".", deleteWordRightKey);
+ fi.emit("data", "\n");
+ rli.close();
+ });
+ });
+
+ // deleteLeft
+ it("should handle deleteLeft", done => {
+ const { mustCall } = createCallCheckCtx(done);
+ const [rli, fi] = getInterface({ terminal: true, prompt: "" });
+ fi.emit("data", "the quick brown fox");
+ assertCursorRowsAndCols(rli, 0, 19);
+
+ // Delete left character
+ fi.emit("keypress", ".", { ctrl: true, name: "h" });
+ assertCursorRowsAndCols(rli, 0, 18);
+ rli.on(
+ "line",
+ mustCall(line => {
+ assert.strictEqual(line, "the quick brown fo");
+ }),
+ );
+ fi.emit("data", "\n");
+ rli.close();
+ });
+
+ // deleteLeft astral character
+ it("should handle deleteLeft astral character", done => {
+ const { mustCall } = createCallCheckCtx(done);
+ const [rli, fi] = getInterface({ terminal: true, prompt: "" });
+ fi.emit("data", "💻");
+ assertCursorRowsAndCols(rli, 0, 2);
+ // Delete left character
+ fi.emit("keypress", ".", { ctrl: true, name: "h" });
+ assertCursorRowsAndCols(rli, 0, 0);
+ rli.on(
+ "line",
+ mustCall(line => {
+ assert.strictEqual(line, "");
+ }),
+ );
+ fi.emit("data", "\n");
+ rli.close();
+ });
+
+ // deleteRight
+ it("should handle deleteRight", done => {
+ const { mustCall } = createCallCheckCtx(done);
+ const [rli, fi] = getInterface({ terminal: true, prompt: "" });
+ fi.emit("data", "the quick brown fox");
+
+ // Go to the start of the line
+ fi.emit("keypress", ".", { ctrl: true, name: "a" });
+ assertCursorRowsAndCols(rli, 0, 0);
+
+ // Delete right character
+ fi.emit("keypress", ".", { ctrl: true, name: "d" });
+ assertCursorRowsAndCols(rli, 0, 0);
+ rli.on(
+ "line",
+ mustCall(line => {
+ assert.strictEqual(line, "he quick brown fox");
+ }),
+ );
+ fi.emit("data", "\n");
+ rli.close();
+ });
+
+ // deleteRight astral character
+ it("should handle deleteRight of astral characters", done => {
+ const { mustCall } = createCallCheckCtx(done);
+ const [rli, fi] = getInterface({ terminal: true, prompt: "" });
+ fi.emit("data", "💻");
+
+ // Go to the start of the line
+ fi.emit("keypress", ".", { ctrl: true, name: "a" });
+ assertCursorRowsAndCols(rli, 0, 0);
+
+ // Delete right character
+ fi.emit("keypress", ".", { ctrl: true, name: "d" });
+ assertCursorRowsAndCols(rli, 0, 0);
+ rli.on(
+ "line",
+ mustCall(line => {
+ assert.strictEqual(line, "");
+ }),
+ );
+ fi.emit("data", "\n");
+ rli.close();
+ });
+
+ // deleteLineLeft
+ it("should handle deleteLineLeft", done => {
+ const { mustCall } = createCallCheckCtx(done);
+ const [rli, fi] = getInterface({ terminal: true, prompt: "" });
+ fi.emit("data", "the quick brown fox");
+ assertCursorRowsAndCols(rli, 0, 19);
+
+ // Delete from current to start of line
+ fi.emit("keypress", ".", { ctrl: true, shift: true, name: "backspace" });
+ assertCursorRowsAndCols(rli, 0, 0);
+ rli.on(
+ "line",
+ mustCall(line => {
+ assert.strictEqual(line, "");
+ }),
+ );
+ fi.emit("data", "\n");
+ rli.close();
+ });
+
+ // deleteLineRight
+ it("should handle deleteLineRight", done => {
+ const { mustCall } = createCallCheckCtx(done);
+ const [rli, fi] = getInterface({ terminal: true, prompt: "" });
+ fi.emit("data", "the quick brown fox");
+
+ // Go to the start of the line
+ fi.emit("keypress", ".", { ctrl: true, name: "a" });
+ assertCursorRowsAndCols(rli, 0, 0);
+
+ // Delete from current to end of line
+ fi.emit("keypress", ".", { ctrl: true, shift: true, name: "delete" });
+ assertCursorRowsAndCols(rli, 0, 0);
+ rli.on(
+ "line",
+ mustCall(line => {
+ assert.strictEqual(line, "");
+ }),
+ );
+ fi.emit("data", "\n");
+ rli.close();
+ });
+
+ // yank
+ it("should handle yank", done => {
+ const { mustCall } = createCallCheckCtx(done);
+ const [rli, fi] = getInterface({ terminal: true, prompt: "" });
+ fi.emit("data", "the quick brown fox");
+ assertCursorRowsAndCols(rli, 0, 19);
+
+ // Go to the start of the line
+ fi.emit("keypress", ".", { ctrl: true, name: "a" });
+ // Move forward one char
+ fi.emit("keypress", ".", { ctrl: true, name: "f" });
+ // Delete the right part
+ fi.emit("keypress", ".", { ctrl: true, shift: true, name: "delete" });
+ assertCursorRowsAndCols(rli, 0, 1);
+
+ // Yank
+ fi.emit("keypress", ".", { ctrl: true, name: "y" });
+ assertCursorRowsAndCols(rli, 0, 19);
+
+ rli.on(
+ "line",
+ mustCall(line => {
+ assert.strictEqual(line, "the quick brown fox");
+ }),
+ );
+
+ fi.emit("data", "\n");
+ rli.close();
+ });
+
+ // yank pop
+ it("should handle yank pop", done => {
+ const { mustCall } = createCallCheckCtx(done);
+ const [rli, fi] = getInterface({ terminal: true, prompt: "" });
+ fi.emit("data", "the quick brown fox");
+ assertCursorRowsAndCols(rli, 0, 19);
+
+ // Go to the start of the line
+ fi.emit("keypress", ".", { ctrl: true, name: "a" });
+ // Move forward one char
+ fi.emit("keypress", ".", { ctrl: true, name: "f" });
+ // Delete the right part
+ fi.emit("keypress", ".", { ctrl: true, shift: true, name: "delete" });
+ assertCursorRowsAndCols(rli, 0, 1);
+ // Yank
+ fi.emit("keypress", ".", { ctrl: true, name: "y" });
+ assertCursorRowsAndCols(rli, 0, 19);
+
+ // Go to the start of the line
+ fi.emit("keypress", ".", { ctrl: true, name: "a" });
+ // Move forward four chars
+ fi.emit("keypress", ".", { ctrl: true, name: "f" });
+ fi.emit("keypress", ".", { ctrl: true, name: "f" });
+ fi.emit("keypress", ".", { ctrl: true, name: "f" });
+ fi.emit("keypress", ".", { ctrl: true, name: "f" });
+ // Delete the right part
+ fi.emit("keypress", ".", { ctrl: true, shift: true, name: "delete" });
+ assertCursorRowsAndCols(rli, 0, 4);
+ // Go to the start of the line
+ fi.emit("keypress", ".", { ctrl: true, name: "a" });
+ assertCursorRowsAndCols(rli, 0, 0);
+
+ // Yank: 'quick brown fox|the '
+ fi.emit("keypress", ".", { ctrl: true, name: "y" });
+ // Yank pop: 'he quick brown fox|the'
+ fi.emit("keypress", ".", { meta: true, name: "y" });
+ assertCursorRowsAndCols(rli, 0, 18);
+
+ rli.on(
+ "line",
+ mustCall(line => {
+ assert.strictEqual(line, "he quick brown foxthe ");
+ }),
+ );
+
+ fi.emit("data", "\n");
+ rli.close();
+ });
+
+ // Close readline interface
+ it("Should close readline interface", () => {
+ const [rli, fi] = getInterface({ terminal: true, prompt: "" });
+ fi.emit("keypress", ".", { ctrl: true, name: "c" });
+ assert.ok(rli.closed);
+ });
+
+ // Multi-line input cursor position
+ it("should handle multi-line input cursors", () => {
+ const [rli, fi] = getInterface({ terminal: true, prompt: "" });
+ fi.columns = 10;
+ fi.emit("data", "multi-line text");
+ assertCursorRowsAndCols(rli, 1, 5);
+ rli.close();
+ });
+
+ // Multi-line input cursor position and long tabs
+ it("should handle long tabs", () => {
+ const [rli, fi] = getInterface({
+ tabSize: 16,
+ terminal: true,
+ prompt: "",
+ });
+ fi.columns = 10;
+ fi.emit("data", "multi-line\ttext \t");
+ assert.strictEqual(rli.cursor, 17);
+ assertCursorRowsAndCols(rli, 3, 2);
+ rli.close();
+ });
+
+ // Check for the default tab size.
+ it("should use the default tab size", () => {
+ const [rli, fi] = getInterface({ terminal: true, prompt: "" });
+ fi.emit("data", "the quick\tbrown\tfox");
+ assert.strictEqual(rli.cursor, 19);
+ // The first tab is 7 spaces long, the second one 3 spaces.
+ assertCursorRowsAndCols(rli, 0, 27);
+ });
+
+ // Multi-line prompt cursor position
+ it("should handle multi-line prompt cursor position", () => {
+ const [rli, fi] = getInterface({
+ terminal: true,
+ prompt: "\nfilledline\nwraping text\n> ",
+ });
+ fi.columns = 10;
+ fi.emit("data", "t");
+ assertCursorRowsAndCols(rli, 4, 3);
+ rli.close();
+ });
+
+ // Undo & Redo
+ it("should undo and redo", () => {
+ const [rli, fi] = getInterface({ terminal: true, prompt: "" });
+ fi.emit("data", "the quick brown fox");
+ assertCursorRowsAndCols(rli, 0, 19);
+
+ // Delete the last eight chars
+ fi.emit("keypress", ".", { ctrl: true, shift: false, name: "b" });
+ fi.emit("keypress", ".", { ctrl: true, shift: false, name: "b" });
+ fi.emit("keypress", ".", { ctrl: true, shift: false, name: "b" });
+ fi.emit("keypress", ".", { ctrl: true, shift: false, name: "b" });
+ fi.emit("keypress", ",", { ctrl: true, shift: false, name: "k" });
+
+ fi.emit("keypress", ".", { ctrl: true, shift: false, name: "b" });
+ fi.emit("keypress", ".", { ctrl: true, shift: false, name: "b" });
+ fi.emit("keypress", ".", { ctrl: true, shift: false, name: "b" });
+ fi.emit("keypress", ".", { ctrl: true, shift: false, name: "b" });
+ fi.emit("keypress", ",", { ctrl: true, shift: false, name: "k" });
+
+ assertCursorRowsAndCols(rli, 0, 11);
+ // Perform undo twice
+ fi.emit("keypress", ",", { sequence: "\x1F" });
+ assert.strictEqual(rli.line, "the quick brown");
+ fi.emit("keypress", ",", { sequence: "\x1F" });
+ assert.strictEqual(rli.line, "the quick brown fox");
+ // Perform redo twice
+ fi.emit("keypress", ",", { sequence: "\x1E" });
+ assert.strictEqual(rli.line, "the quick brown");
+ fi.emit("keypress", ",", { sequence: "\x1E" });
+ assert.strictEqual(rli.line, "the quick b");
+ fi.emit("data", "\n");
+ rli.close();
+ });
+
+ // Clear the whole screen
+ it("should clear the whole screen", done => {
+ const { mustCall } = createCallCheckCtx(done);
+ const [rli, fi] = getInterface({ terminal: true, prompt: "" });
+ const lines = ["line 1", "line 2", "line 3"];
+ fi.emit("data", lines.join("\n"));
+ fi.emit("keypress", ".", { ctrl: true, name: "l" });
+ assertCursorRowsAndCols(rli, 0, 6);
+ rli.on(
+ "line",
+ mustCall(line => {
+ assert.strictEqual(line, "line 3");
+ }),
+ );
+ fi.emit("data", "\n");
+ rli.close();
+ });
+
+ it("should treat wide characters as two columns", () => {
+ assert.strictEqual(getStringWidth("a"), 1);
+ assert.strictEqual(getStringWidth("あ"), 2);
+ assert.strictEqual(getStringWidth("谢"), 2);
+ assert.strictEqual(getStringWidth("고"), 2);
+ assert.strictEqual(getStringWidth(String.fromCodePoint(0x1f251)), 2);
+ assert.strictEqual(getStringWidth("abcde"), 5);
+ assert.strictEqual(getStringWidth("古池や"), 6);
+ assert.strictEqual(getStringWidth("ノード.js"), 9);
+ assert.strictEqual(getStringWidth("你好"), 4);
+ assert.strictEqual(getStringWidth("안녕하세요"), 10);
+ assert.strictEqual(getStringWidth("A\ud83c\ude00BC"), 5);
+ assert.strictEqual(getStringWidth("👨‍👩‍👦‍👦"), 8);
+ assert.strictEqual(getStringWidth("🐕𐐷あ💻😀"), 9);
+ // TODO(BridgeAR): This should have a width of 4.
+ assert.strictEqual(getStringWidth("⓬⓪"), 2);
+ assert.strictEqual(getStringWidth("\u0301\u200D\u200E"), 0);
+ });
+
+ // // Check if vt control chars are stripped
+ // assert.strictEqual(stripVTControlCharacters('\u001b[31m> \u001b[39m'), '> ');
+ // assert.strictEqual(
+ // stripVTControlCharacters('\u001b[31m> \u001b[39m> '),
+ // '> > '
+ // );
+ // assert.strictEqual(stripVTControlCharacters('\u001b[31m\u001b[39m'), '');
+ // assert.strictEqual(stripVTControlCharacters('> '), '> ');
+ // assert.strictEqual(getStringWidth('\u001b[31m> \u001b[39m'), 2);
+ // assert.strictEqual(getStringWidth('\u001b[31m> \u001b[39m> '), 4);
+ // assert.strictEqual(getStringWidth('\u001b[31m\u001b[39m'), 0);
+ // assert.strictEqual(getStringWidth('> '), 2);
+
+ // // Check EventEmitter memory leak
+ // for (let i = 0; i < 12; i++) {
+ // const rl = readline.createInterface({
+ // input: process.stdin,
+ // output: process.stdout
+ // });
+ // rl.close();
+ // assert.strictEqual(isWarned(process.stdin._events), false);
+ // assert.strictEqual(isWarned(process.stdout._events), false);
+ // }
+
+ // [true, false].forEach((terminal) => {
+ // // Disable history
+ // {
+ // const [rli, fi] = getInterface({ terminal, historySize: 0 });
+ // assert.strictEqual(rli.historySize, 0);
+
+ // fi.emit('data', 'asdf\n');
+ // assert.deepStrictEqual(rli.history, []);
+ // rli.close();
+ // }
+
+ // // Default history size 30
+ // {
+ // const [rli, fi] = getInterface({ terminal });
+ // assert.strictEqual(rli.historySize, 30);
+
+ // fi.emit('data', 'asdf\n');
+ // assert.deepStrictEqual(rli.history, terminal ? ['asdf'] : []);
+ // rli.close();
+ // }
+
+ // // Sending a full line
+ // {
+ // const [rli, fi] = getInterface({ terminal });
+ // rli.on('line', mustCall((line) => {
+ // assert.strictEqual(line, 'asdf');
+ // }));
+ // fi.emit('data', 'asdf\n');
+ // }
+
+ // // Sending a blank line
+ // {
+ // const [rli, fi] = getInterface({ terminal });
+ // rli.on('line', mustCall((line) => {
+ // assert.strictEqual(line, '');
+ // }));
+ // fi.emit('data', '\n');
+ // }
+
+ // // Sending a single character with no newline and then a newline
+ // {
+ // const [rli, fi] = getInterface({ terminal });
+ // let called = false;
+ // rli.on('line', (line) => {
+ // called = true;
+ // assert.strictEqual(line, 'a');
+ // });
+ // fi.emit('data', 'a');
+ // assert.ok(!called);
+ // fi.emit('data', '\n');
+ // assert.ok(called);
+ // rli.close();
+ // }
+
+ // // Sending multiple newlines at once
+ // {
+ // const [rli, fi] = getInterface({ terminal });
+ // const expectedLines = ['foo', 'bar', 'baz'];
+ // rli.on('line', mustCall((line) => {
+ // assert.strictEqual(line, expectedLines.shift());
+ // }, expectedLines.length));
+ // fi.emit('data', `${expectedLines.join('\n')}\n`);
+ // rli.close();
+ // }
+
+ // // Sending multiple newlines at once that does not end with a new line
+ // {
+ // const [rli, fi] = getInterface({ terminal });
+ // const expectedLines = ['foo', 'bar', 'baz', 'bat'];
+ // rli.on('line', mustCall((line) => {
+ // assert.strictEqual(line, expectedLines.shift());
+ // }, expectedLines.length - 1));
+ // fi.emit('data', expectedLines.join('\n'));
+ // rli.close();
+ // }
+
+ // // Sending multiple newlines at once that does not end with a new(empty)
+ // // line and a `end` event
+ // {
+ // const [rli, fi] = getInterface({ terminal });
+ // const expectedLines = ['foo', 'bar', 'baz', ''];
+ // rli.on('line', mustCall((line) => {
+ // assert.strictEqual(line, expectedLines.shift());
+ // }, expectedLines.length - 1));
+ // rli.on('close', mustCall());
+ // fi.emit('data', expectedLines.join('\n'));
+ // fi.emit('end');
+ // rli.close();
+ // }
+
+ // // Sending a multi-byte utf8 char over multiple writes
+ // {
+ // const buf = Buffer.from('☮', 'utf8');
+ // const [rli, fi] = getInterface({ terminal });
+ // let callCount = 0;
+ // rli.on('line', (line) => {
+ // callCount++;
+ // assert.strictEqual(line, buf.toString('utf8'));
+ // });
+ // for (const i of buf) {
+ // fi.emit('data', Buffer.from([i]));
+ // }
+ // assert.strictEqual(callCount, 0);
+ // fi.emit('data', '\n');
+ // assert.strictEqual(callCount, 1);
+ // rli.close();
+ // }
+
+ // // Calling readline without `new`
+ // {
+ // const [rli, fi] = getInterface({ terminal });
+ // rli.on('line', mustCall((line) => {
+ // assert.strictEqual(line, 'asdf');
+ // }));
+ // fi.emit('data', 'asdf\n');
+ // rli.close();
+ // }
+
+ // // Calling the question callback
+ // {
+ // const [rli] = getInterface({ terminal });
+ // rli.question('foo?', mustCall((answer) => {
+ // assert.strictEqual(answer, 'bar');
+ // }));
+ // rli.write('bar\n');
+ // rli.close();
+ // }
+
+ // // Calling the question callback with abort signal
+ // {
+ // const [rli] = getInterface({ terminal });
+ // const { signal } = new AbortController();
+ // rli.question('foo?', { signal }, mustCall((answer) => {
+ // assert.strictEqual(answer, 'bar');
+ // }));
+ // rli.write('bar\n');
+ // rli.close();
+ // }
+
+ // // Calling the question multiple times
+ // {
+ // const [rli] = getInterface({ terminal });
+ // rli.question('foo?', mustCall((answer) => {
+ // assert.strictEqual(answer, 'baz');
+ // }));
+ // rli.question('bar?', mustNotCall(() => {
+ // }));
+ // rli.write('baz\n');
+ // rli.close();
+ // }
+
+ // // Calling the promisified question
+ // {
+ // const [rli] = getInterface({ terminal });
+ // const question = util.promisify(rli.question).bind(rli);
+ // question('foo?')
+ // .then(mustCall((answer) => {
+ // assert.strictEqual(answer, 'bar');
+ // }));
+ // rli.write('bar\n');
+ // rli.close();
+ // }
+
+ // // Calling the promisified question with abort signal
+ // {
+ // const [rli] = getInterface({ terminal });
+ // const question = util.promisify(rli.question).bind(rli);
+ // const { signal } = new AbortController();
+ // question('foo?', { signal })
+ // .then(mustCall((answer) => {
+ // assert.strictEqual(answer, 'bar');
+ // }));
+ // rli.write('bar\n');
+ // rli.close();
+ // }
+
+ // // Aborting a question
+ // {
+ // const ac = new AbortController();
+ // const signal = ac.signal;
+ // const [rli] = getInterface({ terminal });
+ // rli.on('line', mustCall((line) => {
+ // assert.strictEqual(line, 'bar');
+ // }));
+ // rli.question('hello?', { signal }, mustNotCall());
+ // ac.abort();
+ // rli.write('bar\n');
+ // rli.close();
+ // }
+
+ // // Aborting a promisified question
+ // {
+ // const ac = new AbortController();
+ // const signal = ac.signal;
+ // const [rli] = getInterface({ terminal });
+ // const question = util.promisify(rli.question).bind(rli);
+ // rli.on('line', mustCall((line) => {
+ // assert.strictEqual(line, 'bar');
+ // }));
+ // question('hello?', { signal })
+ // .then(mustNotCall())
+ // .catch(mustCall((error) => {
+ // assert.strictEqual(error.name, 'AbortError');
+ // }));
+ // ac.abort();
+ // rli.write('bar\n');
+ // rli.close();
+ // }
+
+ // // pre-aborted signal
+ // {
+ // const signal = AbortSignal.abort();
+ // const [rli] = getInterface({ terminal });
+ // rli.pause();
+ // rli.on('resume', mustNotCall());
+ // rli.question('hello?', { signal }, mustNotCall());
+ // rli.close();
+ // }
+
+ // // pre-aborted signal promisified question
+ // {
+ // const signal = AbortSignal.abort();
+ // const [rli] = getInterface({ terminal });
+ // const question = util.promisify(rli.question).bind(rli);
+ // rli.on('resume', mustNotCall());
+ // rli.pause();
+ // question('hello?', { signal })
+ // .then(mustNotCall())
+ // .catch(mustCall((error) => {
+ // assert.strictEqual(error.name, 'AbortError');
+ // }));
+ // rli.close();
+ // }
+
+ // // Call question after close
+ // {
+ // const [rli, fi] = getInterface({ terminal });
+ // rli.question('What\'s your name?', mustCall((name) => {
+ // assert.strictEqual(name, 'Node.js');
+ // rli.close();
+ // assert.throws(() => {
+ // rli.question('How are you?', mustNotCall());
+ // }, {
+ // name: 'Error',
+ // code: 'ERR_USE_AFTER_CLOSE'
+ // });
+ // assert.notStrictEqual(rli.getPrompt(), 'How are you?');
+ // }));
+ // fi.emit('data', 'Node.js\n');
+ // }
+
+ // // Call promisified question after close
+ // {
+ // const [rli, fi] = getInterface({ terminal });
+ // const question = util.promisify(rli.question).bind(rli);
+ // question('What\'s your name?').then(mustCall((name) => {
+ // assert.strictEqual(name, 'Node.js');
+ // rli.close();
+ // question('How are you?')
+ // .then(mustNotCall(), expectsError({
+ // code: 'ERR_USE_AFTER_CLOSE',
+ // name: 'Error'
+ // }));
+ // assert.notStrictEqual(rli.getPrompt(), 'How are you?');
+ // }));
+ // fi.emit('data', 'Node.js\n');
+ // }
+
+ // // Can create a new readline Interface with a null output argument
+ // {
+ // const [rli, fi] = getInterface({ output: null, terminal });
+ // rli.on('line', mustCall((line) => {
+ // assert.strictEqual(line, 'asdf');
+ // }));
+ // fi.emit('data', 'asdf\n');
+
+ // rli.setPrompt('ddd> ');
+ // rli.prompt();
+ // rli.write("really shouldn't be seeing this");
+ // rli.question('What do you think of node.js? ', (answer) => {
+ // console.log('Thank you for your valuable feedback:', answer);
+ // rli.close();
+ // });
+ // }
+
+ // // Calling the getPrompt method
+ // {
+ // const expectedPrompts = ['$ ', '> '];
+ // const [rli] = getInterface({ terminal });
+ // for (const prompt of expectedPrompts) {
+ // rli.setPrompt(prompt);
+ // assert.strictEqual(rli.getPrompt(), prompt);
+ // }
+ // }
+
+ // {
+ // const expected = terminal ?
+ // ['\u001b[1G', '\u001b[0J', '$ ', '\u001b[3G'] :
+ // ['$ '];
+
+ // const output = new Writable({
+ // write: mustCall((chunk, enc, cb) => {
+ // assert.strictEqual(chunk.toString(), expected.shift());
+ // cb();
+ // rl.close();
+ // }, expected.length)
+ // });
+
+ // const rl = readline.createInterface({
+ // input: new Readable({ read: mustCall() }),
+ // output,
+ // prompt: '$ ',
+ // terminal
+ // });
+
+ // rl.prompt();
+
+ // assert.strictEqual(rl.getPrompt(), '$ ');
+ // }
+
+ // {
+ // const fi = new FakeInput();
+ // assert.deepStrictEqual(fi.listeners(terminal ? 'keypress' : 'data'), []);
+ // }
+
+ // // Emit two line events when the delay
+ // // between \r and \n exceeds crlfDelay
+ // {
+ // const crlfDelay = 200;
+ // const [rli, fi] = getInterface({ terminal, crlfDelay });
+ // let callCount = 0;
+ // rli.on('line', () => {
+ // callCount++;
+ // });
+ // fi.emit('data', '\r');
+ // setTimeout(mustCall(() => {
+ // fi.emit('data', '\n');
+ // assert.strictEqual(callCount, 2);
+ // rli.close();
+ // }), crlfDelay + 10);
+ // }
+
+ // // For the purposes of the following tests, we do not care about the exact
+ // // value of crlfDelay, only that the behaviour conforms to what's expected.
+ // // Setting it to Infinity allows the test to succeed even under extreme
+ // // CPU stress.
+ // const crlfDelay = Infinity;
+
+ // // Set crlfDelay to `Infinity` is allowed
+ // {
+ // const delay = 200;
+ // const [rli, fi] = getInterface({ terminal, crlfDelay });
+ // let callCount = 0;
+ // rli.on('line', () => {
+ // callCount++;
+ // });
+ // fi.emit('data', '\r');
+ // setTimeout(mustCall(() => {
+ // fi.emit('data', '\n');
+ // assert.strictEqual(callCount, 1);
+ // rli.close();
+ // }), delay);
+ // }
+
+ // // Sending multiple newlines at once that does not end with a new line
+ // // and a `end` event(last line is)
+
+ // // \r\n should emit one line event, not two
+ // {
+ // const [rli, fi] = getInterface({ terminal, crlfDelay });
+ // const expectedLines = ['foo', 'bar', 'baz', 'bat'];
+ // rli.on('line', mustCall((line) => {
+ // assert.strictEqual(line, expectedLines.shift());
+ // }, expectedLines.length - 1));
+ // fi.emit('data', expectedLines.join('\r\n'));
+ // rli.close();
+ // }
+
+ // // \r\n should emit one line event when split across multiple writes.
+ // {
+ // const [rli, fi] = getInterface({ terminal, crlfDelay });
+ // const expectedLines = ['foo', 'bar', 'baz', 'bat'];
+ // let callCount = 0;
+ // rli.on('line', mustCall((line) => {
+ // assert.strictEqual(line, expectedLines[callCount]);
+ // callCount++;
+ // }, expectedLines.length));
+ // expectedLines.forEach((line) => {
+ // fi.emit('data', `${line}\r`);
+ // fi.emit('data', '\n');
+ // });
+ // rli.close();
+ // }
+
+ // // Emit one line event when the delay between \r and \n is
+ // // over the default crlfDelay but within the setting value.
+ // {
+ // const delay = 125;
+ // const [rli, fi] = getInterface({ terminal, crlfDelay });
+ // let callCount = 0;
+ // rli.on('line', () => callCount++);
+ // fi.emit('data', '\r');
+ // setTimeout(mustCall(() => {
+ // fi.emit('data', '\n');
+ // assert.strictEqual(callCount, 1);
+ // rli.close();
+ // }), delay);
+ // }
+ // });
+
+ // // Ensure that the _wordLeft method works even for large input
+ // {
+ // const input = new Readable({
+ // read() {
+ // this.push('\x1B[1;5D'); // CTRL + Left
+ // this.push(null);
+ // },
+ // });
+ // const output = new Writable({
+ // write: mustCall((data, encoding, cb) => {
+ // assert.strictEqual(rl.cursor, rl.line.length - 1);
+ // cb();
+ // }),
+ // });
+ // const rl = new readline.createInterface({
+ // input,
+ // output,
+ // terminal: true,
+ // });
+ // rl.line = `a${' '.repeat(1e6)}a`;
+ // rl.cursor = rl.line.length;
+ // }
+
+ // {
+ // const fi = new FakeInput();
+ // const signal = AbortSignal.abort();
+
+ // const rl = readline.createInterface({
+ // input: fi,
+ // output: fi,
+ // signal,
+ // });
+ // rl.on('close', mustCall());
+ // assert.strictEqual(getEventListeners(signal, 'abort').length, 0);
+ // }
+
+ // {
+ // const fi = new FakeInput();
+ // const ac = new AbortController();
+ // const { signal } = ac;
+ // const rl = readline.createInterface({
+ // input: fi,
+ // output: fi,
+ // signal,
+ // });
+ // assert.strictEqual(getEventListeners(signal, 'abort').length, 1);
+ // rl.on('close', mustCall());
+ // ac.abort();
+ // assert.strictEqual(getEventListeners(signal, 'abort').length, 0);
+ // }
+
+ // {
+ // const fi = new FakeInput();
+ // const ac = new AbortController();
+ // const { signal } = ac;
+ // const rl = readline.createInterface({
+ // input: fi,
+ // output: fi,
+ // signal,
+ // });
+ // assert.strictEqual(getEventListeners(signal, "abort").length, 1);
+ // rl.close();
+ // assert.strictEqual(getEventListeners(signal, "abort").length, 0);
+ // }
+
+ // {
+ // // Constructor throws if signal is not an abort signal
+ // assert.throws(() => {
+ // readline.createInterface({
+ // input: new FakeInput(),
+ // signal: {},
+ // });
+ // }, {
+ // name: 'TypeError',
+ // code: 'ERR_INVALID_ARG_TYPE'
+ // });
+ // }
+});
+
+describe("readline.createInterface()", () => {
+ it("should emit line when input ends line", done => {
+ const createDone = createDoneDotAll(done);
+ const lineDone = createDone(2000);
+ const { mustCall } = createCallCheckCtx(createDone(2000));
+ const input = new PassThrough();
+ const rl = readline.createInterface({
+ terminal: true,
+ input: input,
+ });
+
+ rl.on(
+ "line",
+ mustCall(data => {
+ assert.strictEqual(data, "abc");
+ lineDone();
+ }),
+ );
+
+ input.end("abc");
+ });
+
+ it("should not emit line when input ends without newline", done => {
+ const { mustNotCall } = createCallCheckCtx(done);
+
+ const input = new PassThrough();
+ const rl = readline.createInterface({
+ terminal: true,
+ input: input,
+ });
+
+ rl.on("line", mustNotCall("must not be called before newline"));
+ input.write("abc");
+ });
+
+ it("should read line by line", done => {
+ const createDone = createDoneDotAll(done);
+ const { mustCall } = createCallCheckCtx(createDone(3000));
+ const lineDone = createDone(2000);
+ const input = new PassThrough();
+ const rl = readline.createInterface({
+ terminal: true,
+ input: input,
+ });
+
+ rl.on(
+ "line",
+ mustCall(data => {
+ assert.strictEqual(data, "abc");
+ lineDone();
+ }),
+ );
+
+ input.write("abc\n");
+ });
+
+ it("should respond to home and end sequences for common pttys ", () => {
+ const input = new PassThrough();
+ const rl = readline.createInterface({
+ terminal: true,
+ input: input,
+ });
+
+ rl.write("foo");
+ assert.strictEqual(rl.cursor, 3);
+
+ const key = {
+ xterm: {
+ home: ["\x1b[H", { ctrl: true, name: "a" }],
+ end: ["\x1b[F", { ctrl: true, name: "e" }],
+ },
+ gnome: {
+ home: ["\x1bOH", { ctrl: true, name: "a" }],
+ end: ["\x1bOF", { ctrl: true, name: "e" }],
+ },
+ rxvt: {
+ home: ["\x1b[7", { ctrl: true, name: "a" }],
+ end: ["\x1b[8", { ctrl: true, name: "e" }],
+ },
+ putty: {
+ home: ["\x1b[1~", { ctrl: true, name: "a" }],
+ end: ["\x1b[>~", { ctrl: true, name: "e" }],
+ },
+ };
+
+ [key.xterm, key.gnome, key.rxvt, key.putty].forEach(key => {
+ rl.write.apply(rl, key.home);
+ assert.strictEqual(rl.cursor, 0);
+ rl.write.apply(rl, key.end);
+ assert.strictEqual(rl.cursor, 3);
+ });
+ });
+
+ it("should allow for cursor movement with meta-f and meta-b", () => {
+ const input = new PassThrough();
+ const rl = readline.createInterface({
+ terminal: true,
+ input: input,
+ });
+
+ const key = {
+ xterm: {
+ home: ["\x1b[H", { ctrl: true, name: "a" }],
+ metab: ["\x1bb", { meta: true, name: "b" }],
+ metaf: ["\x1bf", { meta: true, name: "f" }],
+ },
+ };
+
+ rl.write("foo bar.hop/zoo");
+ rl.write.apply(rl, key.xterm.home);
+ [
+ { cursor: 4, key: key.xterm.metaf },
+ { cursor: 7, key: key.xterm.metaf },
+ { cursor: 8, key: key.xterm.metaf },
+ { cursor: 11, key: key.xterm.metaf },
+ { cursor: 12, key: key.xterm.metaf },
+ { cursor: 15, key: key.xterm.metaf },
+ { cursor: 12, key: key.xterm.metab },
+ { cursor: 11, key: key.xterm.metab },
+ { cursor: 8, key: key.xterm.metab },
+ { cursor: 7, key: key.xterm.metab },
+ { cursor: 4, key: key.xterm.metab },
+ { cursor: 0, key: key.xterm.metab },
+ ].forEach(function (action) {
+ rl.write.apply(rl, action.key);
+ assert.strictEqual(rl.cursor, action.cursor);
+ });
+ });
+
+ it("should properly allow for cursor movement with meta-d", () => {
+ const input = new PassThrough();
+ const rl = readline.createInterface({
+ terminal: true,
+ input: input,
+ });
+
+ const key = {
+ xterm: {
+ home: ["\x1b[H", { ctrl: true, name: "a" }],
+ metad: ["\x1bd", { meta: true, name: "d" }],
+ },
+ };
+
+ rl.write("foo bar.hop/zoo");
+ rl.write.apply(rl, key.xterm.home);
+ ["bar.hop/zoo", ".hop/zoo", "hop/zoo", "/zoo", "zoo", ""].forEach(function (expectedLine) {
+ rl.write.apply(rl, key.xterm.metad);
+ assert.strictEqual(rl.cursor, 0);
+ assert.strictEqual(rl.line, expectedLine);
+ });
+ });
+
+ // TODO: Actual pseudo-tty test
+ // it("should operate correctly when process.env.DUMB is set", () => {
+ // process.env.TERM = "dumb";
+ // const rl = readline.createInterface({
+ // input: process.stdin,
+ // output: process.stdout,
+ // });
+ // rl.write("text");
+ // rl.write(null, { ctrl: true, name: "u" });
+ // rl.write(null, { name: "return" });
+ // rl.write("text");
+ // rl.write(null, { name: "backspace" });
+ // rl.write(null, { name: "escape" });
+ // rl.write(null, { name: "enter" });
+ // rl.write("text");
+ // rl.write(null, { ctrl: true, name: "c" });
+ // });
+});
diff --git a/test/js/node/readline/readline_promises.node.test.ts b/test/js/node/readline/readline_promises.node.test.ts
new file mode 100644
index 000000000..c75d254ca
--- /dev/null
+++ b/test/js/node/readline/readline_promises.node.test.ts
@@ -0,0 +1,51 @@
+import { describe, it } from "bun:test";
+import readlinePromises from "node:readline/promises";
+import { EventEmitter } from "node:events";
+import { createDoneDotAll, createCallCheckCtx, assert } from "node-harness";
+
+// ----------------------------------------------------------------------------
+// Helpers
+// ----------------------------------------------------------------------------
+
+class FakeInput extends EventEmitter {
+ output = "";
+ resume() {}
+ pause() {}
+ write(data) {
+ this.output += data;
+ }
+ end() {}
+ reset() {
+ this.output = "";
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Tests
+// ----------------------------------------------------------------------------
+
+describe("readline/promises.createInterface()", () => {
+ it("should throw an error when failed completion", done => {
+ const createDone = createDoneDotAll(done);
+ const { mustCall, mustNotCall } = createCallCheckCtx(createDone());
+
+ const fi = new FakeInput();
+ const rli = new readlinePromises.Interface({
+ input: fi,
+ output: fi,
+ terminal: true,
+ completer: mustCall(() => Promise.reject(new Error("message"))),
+ });
+
+ rli.on("line", mustNotCall());
+ fi.emit("data", "\t");
+ const outCheckDone = createDone();
+ process.nextTick(() => {
+ console.log("output", fi.output);
+ assert.match(fi.output, /^Tab completion error/);
+ fi.reset();
+ outCheckDone();
+ });
+ rli.close();
+ });
+});
diff --git a/test/js/node/stream/bufferlist.test.ts b/test/js/node/stream/bufferlist.test.ts
new file mode 100644
index 000000000..b8a5443ea
--- /dev/null
+++ b/test/js/node/stream/bufferlist.test.ts
@@ -0,0 +1,221 @@
+import { Readable } from "stream";
+import { it, expect } from "bun:test";
+
+function makeUint8Array(str) {
+ return new Uint8Array(
+ [].map.call(str, function (ch) {
+ return ch.charCodeAt(0);
+ }),
+ );
+}
+
+it("should work with .clear()", () => {
+ const list = new Readable().readableBuffer;
+ expect(list.length).toBe(0);
+ expect(list.push({})).toBeUndefined();
+ expect(list.length).toBe(1);
+ expect(list.push({})).toBeUndefined();
+ expect(list.length).toBe(2);
+ expect(list.clear()).toBeUndefined();
+ expect(list.length).toBe(0);
+});
+
+it("should work with .concat()", () => {
+ const list = new Readable().readableBuffer;
+ expect(list.length).toBe(0);
+ expect(list.push(makeUint8Array("foo"))).toBeUndefined();
+ expect(list.length).toBe(1);
+ expect(list.concat(3)).toEqual(new Uint8Array([102, 111, 111]));
+ expect(list.push(makeUint8Array("bar"))).toBeUndefined();
+ expect(list.length).toBe(2);
+ expect(list.concat(10)).toEqual(new Uint8Array([102, 111, 111, 98, 97, 114, 0, 0, 0, 0]));
+});
+
+it("should fail on .concat() with invalid items", () => {
+ const list = new Readable().readableBuffer;
+ expect(list.length).toBe(0);
+ expect(list.push("foo")).toBeUndefined();
+ expect(() => {
+ list.concat(42);
+ }).toThrow(TypeError);
+});
+
+it("should fail on .concat() buffer overflow", () => {
+ const list = new Readable().readableBuffer;
+ expect(list.length).toBe(0);
+ expect(list.push(makeUint8Array("foo"))).toBeUndefined();
+ expect(list.length).toBe(1);
+ expect(() => {
+ list.concat(2);
+ }).toThrow(RangeError);
+ expect(list.push(makeUint8Array("bar"))).toBeUndefined();
+ expect(list.length).toBe(2);
+ expect(() => {
+ list.concat(5);
+ }).toThrow(RangeError);
+});
+
+it("should work with .consume() on strings", () => {
+ const list = new Readable().readableBuffer;
+ expect(list.length).toBe(0);
+ expect(list.consume(42, true)).toBe("");
+ expect(list.push("foo")).toBeUndefined();
+ expect(list.push("bar")).toBeUndefined();
+ expect(list.push("baz")).toBeUndefined();
+ expect(list.push("moo")).toBeUndefined();
+ expect(list.push("moz")).toBeUndefined();
+ expect(list.length).toBe(5);
+ expect(list.consume(3, true)).toBe("foo");
+ expect(list.length).toBe(4);
+ expect(list.consume(4, true)).toBe("barb");
+ expect(list.length).toBe(3);
+ expect(list.consume(256, true)).toBe("azmoomoz");
+ expect(list.length).toBe(0);
+});
+
+it("should work with .consume() on buffers", () => {
+ const list = new Readable().readableBuffer;
+ expect(list.length).toBe(0);
+ expect(list.consume(42, false)).toEqual(new Uint8Array());
+ expect(list.push(makeUint8Array("foo"))).toBeUndefined();
+ expect(list.push(makeUint8Array("bar"))).toBeUndefined();
+ expect(list.push(makeUint8Array("baz"))).toBeUndefined();
+ expect(list.push(makeUint8Array("moo"))).toBeUndefined();
+ expect(list.push(makeUint8Array("moz"))).toBeUndefined();
+ expect(list.length).toBe(5);
+ expect(list.consume(3, false)).toEqual(makeUint8Array("foo"));
+ expect(list.length).toBe(4);
+ expect(list.consume(2, false)).toEqual(makeUint8Array("ba"));
+ expect(list.length).toBe(4);
+ expect(list.consume(4, false)).toEqual(makeUint8Array("rbaz"));
+ expect(list.length).toBe(2);
+ expect(list.consume(10, false)).toEqual(new Uint8Array([109, 111, 111, 109, 111, 122, 0, 0, 0, 0]));
+ expect(list.length).toBe(0);
+});
+
+it("should fail on .consume() with invalid items", () => {
+ const list = new Readable().readableBuffer;
+ expect(list.length).toBe(0);
+ expect(list.push("foo")).toBeUndefined();
+ expect(list.length).toBe(1);
+ expect(list.consume(0, false)).toEqual(new Uint8Array([]));
+ expect(() => {
+ list.consume(1, false);
+ }).toThrow(TypeError);
+ expect(list.consume(3, true)).toBe("foo");
+ expect(list.length).toBe(0);
+ expect(list.push(makeUint8Array("bar"))).toBeUndefined();
+ expect(list.length).toBe(1);
+ expect(list.consume(0, true)).toEqual("");
+ expect(() => {
+ list.consume(1, true);
+ }).toThrow(TypeError);
+ expect(list.consume(3, false)).toEqual(new Uint8Array([98, 97, 114]));
+});
+
+it("should work with .first()", () => {
+ const list = new Readable().readableBuffer;
+ expect(list.length).toBe(0);
+ expect(list.first()).toBeUndefined();
+ const item = {};
+ expect(list.push(item)).toBeUndefined();
+ expect(list.length).toBe(1);
+ expect(list.first()).toBe(item);
+});
+
+it("should work with .join()", () => {
+ const list = new Readable().readableBuffer;
+ expect(list.length).toBe(0);
+ expect(list.push(42)).toBeUndefined();
+ expect(list.push(null)).toBeUndefined();
+ expect(list.push("foo")).toBeUndefined();
+ expect(list.push(makeUint8Array("bar"))).toBeUndefined();
+ expect(list.length).toBe(4);
+ expect(list.join("")).toBe("42nullfoo98,97,114");
+ expect(list.join(",")).toBe("42,null,foo,98,97,114");
+ expect(list.join(" baz ")).toBe("42 baz null baz foo baz 98,97,114");
+});
+
+it("should work with .push()", () => {
+ const list = new Readable().readableBuffer;
+ expect(list.length).toBe(0);
+ const item1 = {};
+ expect(list.push(item1)).toBeUndefined();
+ expect(list.length).toBe(1);
+ expect(list.first()).toBe(item1);
+ const item2 = {};
+ expect(list.push(item2)).toBeUndefined();
+ expect(list.length).toBe(2);
+ expect(list.shift()).toBe(item1);
+ expect(list.shift()).toBe(item2);
+ expect(list.shift()).toBeUndefined();
+});
+
+it("should work with .shift()", () => {
+ const list = new Readable().readableBuffer;
+ expect(list.length).toBe(0);
+ expect(list.shift()).toBeUndefined();
+ const item = {};
+ expect(list.push(item)).toBeUndefined();
+ expect(list.length).toBe(1);
+ expect(list.shift()).toBe(item);
+ expect(list.shift()).toBeUndefined();
+});
+
+it("should work with .unshift()", () => {
+ const list = new Readable().readableBuffer;
+ expect(list.length).toBe(0);
+ const item1 = {};
+ expect(list.unshift(item1)).toBeUndefined();
+ expect(list.length).toBe(1);
+ expect(list.first()).toBe(item1);
+ const item2 = {};
+ expect(list.push(item2)).toBeUndefined();
+ expect(list.length).toBe(2);
+ expect(list.first()).toBe(item1);
+ const item3 = {};
+ expect(list.unshift(item3)).toBeUndefined();
+ expect(list.length).toBe(3);
+ expect(list.shift()).toBe(item3);
+ expect(list.shift()).toBe(item1);
+ expect(list.shift()).toBe(item2);
+ expect(list.shift()).toBeUndefined();
+});
+
+it("should work with partial .consume() followed by .first()", () => {
+ const list = new Readable().readableBuffer;
+ expect(list.length).toBe(0);
+ expect(list.push("foo")).toBeUndefined();
+ expect(list.push("bar")).toBeUndefined();
+ expect(list.length).toBe(2);
+ expect(list.consume(4, true)).toEqual("foob");
+ expect(list.length).toBe(1);
+ expect(list.first()).toEqual("ar");
+ expect(list.length).toBe(1);
+});
+
+it("should work with partial .consume() followed by .shift()", () => {
+ const list = new Readable().readableBuffer;
+ expect(list.length).toBe(0);
+ expect(list.push(makeUint8Array("foo"))).toBeUndefined();
+ expect(list.push(makeUint8Array("bar"))).toBeUndefined();
+ expect(list.length).toBe(2);
+ expect(list.consume(4, false)).toEqual(makeUint8Array("foob"));
+ expect(list.length).toBe(1);
+ expect(list.shift()).toEqual(makeUint8Array("ar"));
+ expect(list.length).toBe(0);
+});
+
+it("should work with partial .consume() followed by .unshift()", () => {
+ const list = new Readable().readableBuffer;
+ expect(list.length).toBe(0);
+ expect(list.push(makeUint8Array("😋😋😋"))).toBeUndefined();
+ expect(list.push(makeUint8Array("📋📋📋"))).toBeUndefined();
+ expect(list.length).toBe(2);
+ expect(list.consume(7, false)).toEqual(new Uint8Array([61, 11, 61, 11, 61, 11, 61]));
+ expect(list.length).toBe(1);
+ expect(list.unshift(makeUint8Array("👌👌👌"))).toBeUndefined();
+ expect(list.length).toBe(2);
+ expect(list.consume(12, false)).toEqual(new Uint8Array([61, 76, 61, 76, 61, 76, 203, 61, 203, 61, 203, 0]));
+ expect(list.length).toBe(0);
+});
diff --git a/test/js/node/stream/node-stream-uint8array.test.ts b/test/js/node/stream/node-stream-uint8array.test.ts
new file mode 100644
index 000000000..ec2e95d34
--- /dev/null
+++ b/test/js/node/stream/node-stream-uint8array.test.ts
@@ -0,0 +1,111 @@
+import { beforeEach, describe, expect, it } from "bun:test";
+import { Readable, Writable } from "stream";
+
+const ABC = new Uint8Array([0x41, 0x42, 0x43]);
+const DEF = new Uint8Array([0x44, 0x45, 0x46]);
+const GHI = new Uint8Array([0x47, 0x48, 0x49]);
+
+describe("Writable", () => {
+ let called;
+
+ function logCall(fn, id) {
+ return function () {
+ called[id] = (called[id] || 0) + 1;
+ return fn.apply(this, arguments);
+ };
+ }
+
+ beforeEach(() => {
+ called = [];
+ });
+
+ it("should perform simple operations", () => {
+ let n = 0;
+ const writable = new Writable({
+ write: logCall((chunk, encoding, cb) => {
+ expect(chunk instanceof Buffer).toBe(true);
+ if (n++ === 0) {
+ expect(String(chunk)).toBe("ABC");
+ } else {
+ expect(String(chunk)).toBe("DEF");
+ }
+
+ cb();
+ }, 0),
+ });
+
+ writable.write(ABC);
+ writable.end(DEF);
+ expect(called).toEqual([2]);
+ });
+
+ it("should pass in Uint8Array in object mode", () => {
+ const writable = new Writable({
+ objectMode: true,
+ write: logCall((chunk, encoding, cb) => {
+ expect(chunk instanceof Buffer).toBe(false);
+ expect(chunk instanceof Uint8Array).toBe(true);
+ expect(chunk).toStrictEqual(ABC);
+ expect(encoding).toBe("utf8");
+ cb();
+ }, 0),
+ });
+
+ writable.end(ABC);
+ expect(called).toEqual([1]);
+ });
+
+ it("should handle multiple writes carried out via writev()", () => {
+ let callback;
+
+ const writable = new Writable({
+ write: logCall((chunk, encoding, cb) => {
+ expect(chunk instanceof Buffer).toBe(true);
+ expect(encoding).toBe("buffer");
+ expect(String(chunk)).toBe("ABC");
+ callback = cb;
+ }, 0),
+ writev: logCall((chunks, cb) => {
+ expect(chunks.length).toBe(2);
+ expect(chunks[0].encoding).toBe("buffer");
+ expect(chunks[1].encoding).toBe("buffer");
+ expect(chunks[0].chunk + chunks[1].chunk).toBe("DEFGHI");
+ }, 1),
+ });
+
+ writable.write(ABC);
+ writable.write(DEF);
+ writable.end(GHI);
+ callback();
+ expect(called).toEqual([1, 1]);
+ });
+});
+
+describe("Readable", () => {
+ it("should perform simple operations", () => {
+ const readable = new Readable({
+ read() {},
+ });
+
+ readable.push(DEF);
+ readable.unshift(ABC);
+
+ const buf = readable.read();
+ expect(buf instanceof Buffer).toBe(true);
+ expect([...buf]).toEqual([...ABC, ...DEF]);
+ });
+
+ it("should work with setEncoding()", () => {
+ const readable = new Readable({
+ read() {},
+ });
+
+ readable.setEncoding("utf8");
+
+ readable.push(DEF);
+ readable.unshift(ABC);
+
+ const out = readable.read();
+ expect(out).toBe("ABCDEF");
+ });
+});
diff --git a/test/js/node/stream/node-stream.test.js b/test/js/node/stream/node-stream.test.js
new file mode 100644
index 000000000..6bff28b94
--- /dev/null
+++ b/test/js/node/stream/node-stream.test.js
@@ -0,0 +1,86 @@
+import { expect, describe, it } from "bun:test";
+import { Readable, Writable, Duplex, Transform, PassThrough } from "node:stream";
+
+describe("Readable", () => {
+ it("should be able to be created without _construct method defined", done => {
+ const readable = new Readable({
+ read() {
+ this.push("Hello World!\n");
+ this.push(null);
+ },
+ });
+ expect(readable instanceof Readable).toBe(true);
+ let data = "";
+ readable.on("data", chunk => {
+ data += chunk.toString();
+ });
+ readable.on("end", () => {
+ expect(data).toBe("Hello World!\n");
+ done();
+ });
+ });
+
+ it("should be able to be piped via .pipe", done => {
+ const readable = new Readable({
+ read() {
+ this.push("Hello World!");
+ this.push(null);
+ },
+ });
+
+ const writable = new Writable({
+ write(chunk, encoding, callback) {
+ expect(chunk.toString()).toBe("Hello World!");
+ callback();
+ done();
+ },
+ });
+
+ readable.pipe(writable);
+ });
+});
+
+describe("Duplex", () => {
+ it("should allow subclasses to be derived via .call() on class", () => {
+ function Subclass(opts) {
+ if (!(this instanceof Subclass)) return new Subclass(opts);
+ Duplex.call(this, opts);
+ }
+
+ Object.setPrototypeOf(Subclass.prototype, Duplex.prototype);
+ Object.setPrototypeOf(Subclass, Duplex);
+
+ const subclass = new Subclass();
+ expect(subclass instanceof Duplex).toBe(true);
+ });
+});
+
+describe("Transform", () => {
+ it("should allow subclasses to be derived via .call() on class", () => {
+ function Subclass(opts) {
+ if (!(this instanceof Subclass)) return new Subclass(opts);
+ Transform.call(this, opts);
+ }
+
+ Object.setPrototypeOf(Subclass.prototype, Transform.prototype);
+ Object.setPrototypeOf(Subclass, Transform);
+
+ const subclass = new Subclass();
+ expect(subclass instanceof Transform).toBe(true);
+ });
+});
+
+describe("PassThrough", () => {
+ it("should allow subclasses to be derived via .call() on class", () => {
+ function Subclass(opts) {
+ if (!(this instanceof Subclass)) return new Subclass(opts);
+ PassThrough.call(this, opts);
+ }
+
+ Object.setPrototypeOf(Subclass.prototype, PassThrough.prototype);
+ Object.setPrototypeOf(Subclass, PassThrough);
+
+ const subclass = new Subclass();
+ expect(subclass instanceof PassThrough).toBe(true);
+ });
+});
diff --git a/test/js/node/string_decoder/string-decoder.test.js b/test/js/node/string_decoder/string-decoder.test.js
new file mode 100644
index 000000000..f37326678
--- /dev/null
+++ b/test/js/node/string_decoder/string-decoder.test.js
@@ -0,0 +1,243 @@
+import { describe, expect, it } from "bun:test";
+import { withoutAggressiveGC } from "harness";
+
+const RealStringDecoder = require("string_decoder").StringDecoder;
+
+it("require('string_decoder')", async () => {
+ expect((await import("string_decoder")).StringDecoder).toBe(RealStringDecoder);
+});
+
+it("Bun.inspect(StringDecoder)", async () => {
+ expect((await Bun.inspect(RealStringDecoder).length) > 0).toBe(true);
+});
+
+function FakeStringDecoderCall() {
+ RealStringDecoder.apply(this, arguments);
+}
+require("util").inherits(FakeStringDecoderCall, RealStringDecoder);
+
+// extending StringDecoder is not supported
+for (const StringDecoder of [FakeStringDecoderCall, RealStringDecoder]) {
+ describe(StringDecoder.name, () => {
+ it("StringDecoder-utf8", () => {
+ test("utf-8", Buffer.from("$", "utf-8"), "$");
+ test("utf-8", Buffer.from("¢", "utf-8"), "¢");
+ test("utf-8", Buffer.from("€", "utf-8"), "€");
+ test("utf-8", Buffer.from("𤭢", "utf-8"), "𤭢");
+ // A mixed ascii and non-ascii string
+ // Test stolen from deps/v8/test/cctest/test-strings.cc
+ // U+02E4 -> CB A4
+ // U+0064 -> 64
+ // U+12E4 -> E1 8B A4
+ // U+0030 -> 30
+ // U+3045 -> E3 81 85
+ test(
+ "utf-8",
+ Buffer.from([0xcb, 0xa4, 0x64, 0xe1, 0x8b, 0xa4, 0x30, 0xe3, 0x81, 0x85]),
+ "\u02e4\u0064\u12e4\u0030\u3045",
+ );
+ });
+
+ it("StringDecoder-ucs-2", () => {
+ test("ucs2", Buffer.from("ababc", "ucs2"), "ababc");
+ });
+
+ it("StringDecoder-utf16le", () => {
+ test("utf16le", Buffer.from("3DD84DDC", "hex"), "\ud83d\udc4d");
+ });
+
+ it("StringDecoder-utf8-additional", () => {
+ let decoder = new StringDecoder("utf8");
+ expect(decoder.write(Buffer.from("E18B", "hex"))).toBe("");
+ expect(decoder.end()).toBe("\ufffd");
+
+ decoder = new StringDecoder("utf8");
+ expect(decoder.write(Buffer.from("\ufffd"))).toBe("\ufffd");
+ expect(decoder.end()).toBe("");
+
+ decoder = new StringDecoder("utf8");
+ expect(decoder.write(Buffer.from("\ufffd\ufffd\ufffd"))).toBe("\ufffd\ufffd\ufffd");
+ expect(decoder.end()).toBe("");
+
+ decoder = new StringDecoder("utf8");
+ expect(decoder.write(Buffer.from("EFBFBDE2", "hex"))).toBe("\ufffd");
+ expect(decoder.end()).toBe("\ufffd");
+
+ decoder = new StringDecoder("utf8");
+ expect(decoder.write(Buffer.from("F1", "hex"))).toBe("");
+ expect(decoder.write(Buffer.from("41F2", "hex"))).toBe("\ufffdA");
+ expect(decoder.end()).toBe("\ufffd");
+
+ // Additional utf8Text test
+ decoder = new StringDecoder("utf8");
+ expect(decoder.text(Buffer.from([0x41]), 2)).toBe("");
+ });
+
+ it("StringDecoder-utf16le-additional", () => {
+ // Additional UTF-16LE surrogate pair tests
+ let decoder = new StringDecoder("utf16le");
+ expect(decoder.write(Buffer.from("3DD8", "hex"))).toBe("");
+ expect(decoder.write(Buffer.from("4D", "hex"))).toBe("");
+ expect(decoder.write(Buffer.from("DC", "hex"))).toBe("\ud83d\udc4d");
+ expect(decoder.end()).toBe("");
+
+ decoder = new StringDecoder("utf16le");
+ expect(decoder.write(Buffer.from("3DD8", "hex"))).toBe("");
+ expect(decoder.end()).toBe("\ud83d");
+
+ decoder = new StringDecoder("utf16le");
+ expect(decoder.write(Buffer.from("3DD8", "hex"))).toBe("");
+ expect(decoder.write(Buffer.from("4D", "hex"))).toBe("");
+ expect(decoder.end()).toBe("\ud83d");
+
+ decoder = new StringDecoder("utf16le");
+ expect(decoder.write(Buffer.from("3DD84D", "hex"))).toBe("\ud83d");
+ expect(decoder.end()).toBe("");
+ });
+
+ // Test verifies that StringDecoder will correctly decode the given input
+ // buffer with the given encoding to the expected output. It will attempt all
+ // possible ways to write() the input buffer, see writeSequences(). The
+ // singleSequence allows for easy debugging of a specific sequence which is
+ // useful in case of test failures.
+ function test(encoding, input, expected, singleSequence) {
+ withoutAggressiveGC(() => {
+ let sequences;
+ if (!singleSequence) {
+ sequences = writeSequences(input.length);
+ } else {
+ sequences = [singleSequence];
+ }
+ sequences.forEach(sequence => {
+ const decoder = new StringDecoder(encoding);
+ let output = "";
+ sequence.forEach(write => {
+ output += decoder.write(input.slice(write[0], write[1]));
+ });
+ output += decoder.end();
+ expect(output).toBe(expected);
+ });
+ });
+ }
+
+ // writeSequences returns an array of arrays that describes all possible ways a
+ // buffer of the given length could be split up and passed to sequential write
+ // calls.
+ //
+ // e.G. writeSequences(3) will return: [
+ // [ [ 0, 3 ] ],
+ // [ [ 0, 2 ], [ 2, 3 ] ],
+ // [ [ 0, 1 ], [ 1, 3 ] ],
+ // [ [ 0, 1 ], [ 1, 2 ], [ 2, 3 ] ]
+ // ]
+ function writeSequences(length, start, sequence) {
+ if (start === undefined) {
+ start = 0;
+ sequence = [];
+ } else if (start === length) {
+ return [sequence];
+ }
+ let sequences = [];
+ for (let end = length; end > start; end--) {
+ const subSequence = sequence.concat([[start, end]]);
+ const subSequences = writeSequences(length, end, subSequence, sequences);
+ sequences = sequences.concat(subSequences);
+ }
+ return sequences;
+ }
+
+ describe("StringDecoder.end", () => {
+ const encodings = ["base64", "base64url", "hex", "utf8", "utf16le", "ucs2"];
+
+ const bufs = ["☃💩", "asdf"].map(b => Buffer.from(b));
+
+ // Also test just arbitrary bytes from 0-15.
+ for (let i = 1; i <= 16; i++) {
+ const bytes = "."
+ .repeat(i - 1)
+ .split(".")
+ .map((_, j) => j + 0x78);
+ bufs.push(Buffer.from(bytes));
+ }
+
+ encodings.forEach(testEncoding);
+
+ testEnd("utf8", Buffer.of(0xe2), Buffer.of(0x61), "\uFFFDa");
+ testEnd("utf8", Buffer.of(0xe2), Buffer.of(0x82), "\uFFFD\uFFFD");
+ testEnd("utf8", Buffer.of(0xe2), Buffer.of(0xe2), "\uFFFD\uFFFD");
+ testEnd("utf8", Buffer.of(0xe2, 0x82), Buffer.of(0x61), "\uFFFDa");
+ testEnd("utf8", Buffer.of(0xe2, 0x82), Buffer.of(0xac), "\uFFFD\uFFFD");
+ testEnd("utf8", Buffer.of(0xe2, 0x82), Buffer.of(0xe2), "\uFFFD\uFFFD");
+ testEnd("utf8", Buffer.of(0xe2, 0x82, 0xac), Buffer.of(0x61), "€a");
+
+ testEnd("utf16le", Buffer.of(0x3d), Buffer.of(0x61, 0x00), "a");
+ testEnd("utf16le", Buffer.of(0x3d), Buffer.of(0xd8, 0x4d, 0xdc), "\u4DD8");
+ testEnd("utf16le", Buffer.of(0x3d, 0xd8), Buffer.of(), "\uD83D");
+ testEnd("utf16le", Buffer.of(0x3d, 0xd8), Buffer.of(0x61, 0x00), "\uD83Da");
+ testEnd("utf16le", Buffer.of(0x3d, 0xd8), Buffer.of(0x4d, 0xdc), "\uD83D\uDC4D");
+ testEnd("utf16le", Buffer.of(0x3d, 0xd8, 0x4d), Buffer.of(), "\uD83D");
+ testEnd("utf16le", Buffer.of(0x3d, 0xd8, 0x4d), Buffer.of(0x61, 0x00), "\uD83Da");
+ testEnd("utf16le", Buffer.of(0x3d, 0xd8, 0x4d), Buffer.of(0xdc), "\uD83D");
+ testEnd("utf16le", Buffer.of(0x3d, 0xd8, 0x4d, 0xdc), Buffer.of(0x61, 0x00), "👍a");
+
+ testEnd("base64", Buffer.of(0x61), Buffer.of(), "YQ==");
+ testEnd("base64", Buffer.of(0x61), Buffer.of(0x61), "YQ==YQ==");
+ testEnd("base64", Buffer.of(0x61, 0x61), Buffer.of(), "YWE=");
+ testEnd("base64", Buffer.of(0x61, 0x61), Buffer.of(0x61), "YWE=YQ==");
+ testEnd("base64", Buffer.of(0x61, 0x61, 0x61), Buffer.of(), "YWFh");
+ testEnd("base64", Buffer.of(0x61, 0x61, 0x61), Buffer.of(0x61), "YWFhYQ==");
+
+ testEnd("base64url", Buffer.of(0x61), Buffer.of(), "YQ");
+ testEnd("base64url", Buffer.of(0x61), Buffer.of(0x61), "YQYQ");
+ testEnd("base64url", Buffer.of(0x61, 0x61), Buffer.of(), "YWE");
+ testEnd("base64url", Buffer.of(0x61, 0x61), Buffer.of(0x61), "YWEYQ");
+ testEnd("base64url", Buffer.of(0x61, 0x61, 0x61), Buffer.of(), "YWFh");
+ testEnd("base64url", Buffer.of(0x61, 0x61, 0x61), Buffer.of(0x61), "YWFhYQ");
+
+ function testEncoding(encoding) {
+ it(encoding + " testbuf", () => {
+ bufs.forEach(buf => {
+ testBuf(encoding, buf);
+ });
+ });
+ }
+
+ function testBuf(encoding, buf) {
+ // Write one byte at a time.
+ let s = new StringDecoder(encoding);
+ let res1 = "";
+ for (let i = 0; i < buf.length; i++) {
+ res1 += s.write(buf.slice(i, i + 1));
+ }
+ res1 += s.end();
+
+ // Write the whole buffer at once.
+ let res2 = "";
+ s = new StringDecoder(encoding);
+ res2 += s.write(buf);
+ res2 += s.end();
+
+ // .toString() on the buffer
+ const res3 = buf.toString(encoding);
+
+ // One byte at a time should match toString
+ expect(res1).toEqual(res3);
+ // All bytes at once should match toString
+ expect(res2).toEqual(res3);
+ }
+
+ function testEnd(encoding, incomplete, next, expected) {
+ it(`${encoding} partial ${JSON.stringify(expected)}`, () => {
+ let res = "";
+ const s = new StringDecoder(encoding);
+ res += s.write(incomplete);
+ res += s.end();
+ res += s.write(next);
+ res += s.end();
+
+ expect(res).toEqual(expected);
+ });
+ }
+ });
+ });
+}
diff --git a/test/js/node/timers/node-timers.test.ts b/test/js/node/timers/node-timers.test.ts
new file mode 100644
index 000000000..e6fa48010
--- /dev/null
+++ b/test/js/node/timers/node-timers.test.ts
@@ -0,0 +1,17 @@
+import { describe, test } from "bun:test";
+import { setTimeout, clearTimeout, setInterval, setImmediate } from "node:timers";
+
+for (const fn of [setTimeout, setInterval, setImmediate]) {
+ describe(fn.name, () => {
+ test("unref is possible", done => {
+ const timer = fn(() => {
+ done(new Error("should not be called"));
+ }, 1);
+ fn(() => {
+ done();
+ }, 2);
+ timer.unref();
+ if (fn !== setImmediate) clearTimeout(timer);
+ });
+ });
+}
diff --git a/test/js/node/util/test-util-types.test.js b/test/js/node/util/test-util-types.test.js
new file mode 100644
index 000000000..f33ab4b1a
--- /dev/null
+++ b/test/js/node/util/test-util-types.test.js
@@ -0,0 +1,240 @@
+const assert = require("assert");
+import { test, expect } from "bun:test";
+const types = require("util/types");
+
+function inspect(val) {
+ return Bun.inspect(val);
+}
+
+for (const [value, _method] of [
+ [new Date()],
+ [
+ (function () {
+ return arguments;
+ })(),
+ "isArgumentsObject",
+ ],
+ [new Boolean(), "isBooleanObject"],
+ [new Number(), "isNumberObject"],
+ [new String(), "isStringObject"],
+ [Object(Symbol()), "isSymbolObject"],
+ [Object(BigInt(0)), "isBigIntObject"],
+ [new Error(), "isNativeError"],
+ [new RegExp()],
+ [async function () {}, "isAsyncFunction"],
+ [function* () {}, "isGeneratorFunction"],
+ [(function* () {})(), "isGeneratorObject"],
+ [Promise.resolve()],
+ [new Map()],
+ [new Set()],
+ [new Map()[Symbol.iterator](), "isMapIterator"],
+ [new Set()[Symbol.iterator](), "isSetIterator"],
+ [new WeakMap()],
+ [new WeakSet()],
+ [new ArrayBuffer()],
+ [new Uint8Array()],
+ [new Uint8ClampedArray()],
+ [new Uint16Array()],
+ [new Uint32Array()],
+ [new Int8Array()],
+ [new Int16Array()],
+ [new Int32Array()],
+ [new Float32Array()],
+ [new Float64Array()],
+ [new BigInt64Array()],
+ [new BigUint64Array()],
+ [new DataView(new ArrayBuffer())],
+ [new SharedArrayBuffer()],
+ [new Proxy({}, {}), "isProxy"],
+]) {
+ const method = _method || `is${value.constructor.name}`;
+ test(method, () => {
+ assert(method in types, `Missing ${method} for ${inspect(value)}`);
+ assert(types[method](value), `Want ${inspect(value)} to match ${method}`);
+
+ for (const key of Object.keys(types)) {
+ if (
+ ((types.isArrayBufferView(value) || types.isAnyArrayBuffer(value)) && key.includes("Array")) ||
+ key === "isBoxedPrimitive"
+ ) {
+ continue;
+ }
+
+ expect(types[key](value)).toBe(key === method);
+ }
+ });
+}
+
+// Check boxed primitives.
+test("isBoxedPrimitive", () => {
+ [new Boolean(), new Number(), new String(), Object(Symbol()), Object(BigInt(0))].forEach(entry =>
+ assert(types.isBoxedPrimitive(entry)),
+ );
+});
+
+{
+ const primitive = true;
+ const arrayBuffer = new ArrayBuffer();
+ const buffer = Buffer.from(arrayBuffer);
+ const dataView = new DataView(arrayBuffer);
+ const uint8Array = new Uint8Array(arrayBuffer);
+ const uint8ClampedArray = new Uint8ClampedArray(arrayBuffer);
+ const uint16Array = new Uint16Array(arrayBuffer);
+ const uint32Array = new Uint32Array(arrayBuffer);
+ const int8Array = new Int8Array(arrayBuffer);
+ const int16Array = new Int16Array(arrayBuffer);
+ const int32Array = new Int32Array(arrayBuffer);
+ const float32Array = new Float32Array(arrayBuffer);
+ const float64Array = new Float64Array(arrayBuffer);
+ const bigInt64Array = new BigInt64Array(arrayBuffer);
+ const bigUint64Array = new BigUint64Array(arrayBuffer);
+
+ const fakeBuffer = Object.create(Buffer.prototype);
+ const fakeDataView = Object.create(DataView.prototype);
+ const fakeUint8Array = Object.create(Uint8Array.prototype);
+ const fakeUint8ClampedArray = Object.create(Uint8ClampedArray.prototype);
+ const fakeUint16Array = Object.create(Uint16Array.prototype);
+ const fakeUint32Array = Object.create(Uint32Array.prototype);
+ const fakeInt8Array = Object.create(Int8Array.prototype);
+ const fakeInt16Array = Object.create(Int16Array.prototype);
+ const fakeInt32Array = Object.create(Int32Array.prototype);
+ const fakeFloat32Array = Object.create(Float32Array.prototype);
+ const fakeFloat64Array = Object.create(Float64Array.prototype);
+ const fakeBigInt64Array = Object.create(BigInt64Array.prototype);
+ const fakeBigUint64Array = Object.create(BigUint64Array.prototype);
+
+ const stealthyDataView = Object.setPrototypeOf(new DataView(arrayBuffer), Uint8Array.prototype);
+ const stealthyUint8Array = Object.setPrototypeOf(new Uint8Array(arrayBuffer), ArrayBuffer.prototype);
+ const stealthyUint8ClampedArray = Object.setPrototypeOf(new Uint8ClampedArray(arrayBuffer), ArrayBuffer.prototype);
+ const stealthyUint16Array = Object.setPrototypeOf(new Uint16Array(arrayBuffer), Uint16Array.prototype);
+ const stealthyUint32Array = Object.setPrototypeOf(new Uint32Array(arrayBuffer), Uint32Array.prototype);
+ const stealthyInt8Array = Object.setPrototypeOf(new Int8Array(arrayBuffer), Int8Array.prototype);
+ const stealthyInt16Array = Object.setPrototypeOf(new Int16Array(arrayBuffer), Int16Array.prototype);
+ const stealthyInt32Array = Object.setPrototypeOf(new Int32Array(arrayBuffer), Int32Array.prototype);
+ const stealthyFloat32Array = Object.setPrototypeOf(new Float32Array(arrayBuffer), Float32Array.prototype);
+ const stealthyFloat64Array = Object.setPrototypeOf(new Float64Array(arrayBuffer), Float64Array.prototype);
+ const stealthyBigInt64Array = Object.setPrototypeOf(new BigInt64Array(arrayBuffer), BigInt64Array.prototype);
+ const stealthyBigUint64Array = Object.setPrototypeOf(new BigUint64Array(arrayBuffer), BigUint64Array.prototype);
+
+ const all = [
+ primitive,
+ arrayBuffer,
+ buffer,
+ fakeBuffer,
+ dataView,
+ fakeDataView,
+ stealthyDataView,
+ uint8Array,
+ fakeUint8Array,
+ stealthyUint8Array,
+ uint8ClampedArray,
+ fakeUint8ClampedArray,
+ stealthyUint8ClampedArray,
+ uint16Array,
+ fakeUint16Array,
+ stealthyUint16Array,
+ uint32Array,
+ fakeUint32Array,
+ stealthyUint32Array,
+ int8Array,
+ fakeInt8Array,
+ stealthyInt8Array,
+ int16Array,
+ fakeInt16Array,
+ stealthyInt16Array,
+ int32Array,
+ fakeInt32Array,
+ stealthyInt32Array,
+ float32Array,
+ fakeFloat32Array,
+ stealthyFloat32Array,
+ float64Array,
+ fakeFloat64Array,
+ stealthyFloat64Array,
+ bigInt64Array,
+ fakeBigInt64Array,
+ stealthyBigInt64Array,
+ bigUint64Array,
+ fakeBigUint64Array,
+ stealthyBigUint64Array,
+ ];
+
+ const expected = {
+ isArrayBufferView: [
+ buffer,
+ dataView,
+ stealthyDataView,
+ uint8Array,
+ stealthyUint8Array,
+ uint8ClampedArray,
+ stealthyUint8ClampedArray,
+ uint16Array,
+ stealthyUint16Array,
+ uint32Array,
+ stealthyUint32Array,
+ int8Array,
+ stealthyInt8Array,
+ int16Array,
+ stealthyInt16Array,
+ int32Array,
+ stealthyInt32Array,
+ float32Array,
+ stealthyFloat32Array,
+ float64Array,
+ stealthyFloat64Array,
+ bigInt64Array,
+ stealthyBigInt64Array,
+ bigUint64Array,
+ stealthyBigUint64Array,
+ ],
+ isTypedArray: [
+ buffer,
+ uint8Array,
+ stealthyUint8Array,
+ uint8ClampedArray,
+ stealthyUint8ClampedArray,
+ uint16Array,
+ stealthyUint16Array,
+ uint32Array,
+ stealthyUint32Array,
+ int8Array,
+ stealthyInt8Array,
+ int16Array,
+ stealthyInt16Array,
+ int32Array,
+ stealthyInt32Array,
+ float32Array,
+ stealthyFloat32Array,
+ float64Array,
+ stealthyFloat64Array,
+ bigInt64Array,
+ stealthyBigInt64Array,
+ bigUint64Array,
+ stealthyBigUint64Array,
+ ],
+ isUint8Array: [buffer, uint8Array, stealthyUint8Array],
+ isUint8ClampedArray: [uint8ClampedArray, stealthyUint8ClampedArray],
+ isUint16Array: [uint16Array, stealthyUint16Array],
+ isUint32Array: [uint32Array, stealthyUint32Array],
+ isInt8Array: [int8Array, stealthyInt8Array],
+ isInt16Array: [int16Array, stealthyInt16Array],
+ isInt32Array: [int32Array, stealthyInt32Array],
+ isFloat32Array: [float32Array, stealthyFloat32Array],
+ isFloat64Array: [float64Array, stealthyFloat64Array],
+ isBigInt64Array: [bigInt64Array, stealthyBigInt64Array],
+ isBigUint64Array: [bigUint64Array, stealthyBigUint64Array],
+ };
+
+ for (const testedFunc of Object.keys(expected)) {
+ test(testedFunc, () => {
+ const func = types[testedFunc];
+ const yup = [];
+ for (const value of all) {
+ if (func(value)) {
+ yup.push(value);
+ }
+ }
+ expect(yup).toEqual(expected[testedFunc]);
+ });
+ }
+}
diff --git a/test/js/node/util/util-promisify.test.js b/test/js/node/util/util-promisify.test.js
new file mode 100644
index 000000000..336e070f7
--- /dev/null
+++ b/test/js/node/util/util-promisify.test.js
@@ -0,0 +1,304 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// Tests adapted from https://github.com/nodejs/node/blob/main/test/parallel/test-util-promisify.js
+import { describe, it } from "bun:test";
+import fs from "node:fs";
+// TODO: vm module not implemented by bun yet
+// import vm from 'node:vm';
+import { promisify } from "util";
+import assert from "assert";
+
+const stat = promisify(fs.stat);
+
+// A helper function to simplify checking for ERR_INVALID_ARG_TYPE output.
+function invalidArgTypeHelper(input) {
+ if (input == null) {
+ return ` Received ${input}`;
+ }
+ if (typeof input === "function" && input.name) {
+ return ` Received function ${input.name}`;
+ }
+ if (typeof input === "object") {
+ if (input.constructor?.name) {
+ return ` Received an instance of ${input.constructor.name}`;
+ }
+ return ` Received ${inspect(input, { depth: -1 })}`;
+ }
+
+ let inspected = inspect(input, { colors: false });
+ if (inspected.length > 28) {
+ inspected = `${inspected.slice(inspected, 0, 25)}...`;
+ }
+
+ return ` Received type ${typeof input} (${inspected})`;
+}
+
+describe("util.promisify", () => {
+ describe("promisify fs calls", () => {
+ // TODO: common.mustCall is not implemented here yet
+ // https://github.com/nodejs/node/blob/main/test/common/index.js#L398
+ it.skip("all cases", () => {
+ const promise = stat(__filename);
+ assert.equal(promise instanceof Promise, true);
+ promise.then(
+ common.mustCall(value => {
+ assert.deepStrictEqual(value, fs.statSync(__filename));
+ }),
+ );
+
+ const promiseFileDontExist = stat("/dontexist");
+ promiseFileDontExist.catch(
+ common.mustCall(error => {
+ assert(error.message.includes("ENOENT: no such file or directory, stat"));
+ }),
+ );
+ });
+ });
+
+ describe("promisify.custom", () => {
+ it("double promisify", () => {
+ function fn() {}
+
+ function promisifedFn() {}
+ fn[promisify.custom] = promisifedFn;
+ assert.strictEqual(promisify(fn), promisifedFn);
+ assert.strictEqual(promisify(promisify(fn)), promisifedFn);
+ });
+
+ it.skip("should register shared promisify symbol", () => {
+ function fn() {}
+
+ function promisifiedFn() {}
+
+ // TODO: register shared symbol promisify.custom
+ // util.promisify.custom is a shared symbol which can be accessed
+ // as `Symbol.for("nodejs.util.promisify.custom")`.
+ const kCustomPromisifiedSymbol = Symbol.for("nodejs.util.promisify.custom");
+ fn[kCustomPromisifiedSymbol] = promisifiedFn;
+
+ assert.strictEqual(kCustomPromisifiedSymbol, promisify.custom);
+ assert.strictEqual(promisify(fn), promisifiedFn);
+ assert.strictEqual(promisify(promisify(fn)), promisifiedFn);
+ });
+ });
+
+ it("should fail when type is not a function", () => {
+ function fn() {}
+ fn[promisify.custom] = 42;
+ assert.throws(
+ () => promisify(fn),
+ // TODO: error code is not the same as node's.
+ // { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError' }
+ { name: "TypeError" },
+ );
+ });
+
+ it("should call custom promised promised function with proper args", () => {
+ const firstValue = 5;
+ const secondValue = 17;
+ var called = false;
+
+ function fn(callback) {
+ called = true;
+ callback(null, firstValue, secondValue);
+ }
+
+ fn[Symbol("customPromisifyArgs")] = ["first", "second"];
+
+ promisify(fn)().then((firstValue, secondValue) => {
+ assert.strictEqual(called, true);
+ assert.strictEqual(firstValue, 5);
+ assert.strictEqual(secondValue, 17);
+ });
+ });
+
+ // TODO: unable to test since vm module not implemented
+ // it("should run in new vm context", () => {
+ // const fn = vm.runInNewContext('(function() {})');
+ // assert.notStrictEqual(Object.getPrototypeOf(promisify(fn)),Function.prototype);
+ // });
+
+ describe("callback cases", () => {
+ it("should run basic callback", async () => {
+ var called = false;
+ function fn(callback) {
+ called = true;
+ callback(null, "foo", "bar");
+ }
+ await promisify(fn)().then(value => {
+ assert.strictEqual(value, "foo");
+ assert.strictEqual(called, true);
+ });
+ });
+
+ it("should not require value to be returned in callback", async () => {
+ var called = false;
+ function fn(callback) {
+ called = true;
+ callback(null);
+ }
+ await promisify(fn)().then(value => {
+ assert.strictEqual(value, undefined);
+ assert.strictEqual(called, true);
+ });
+ });
+
+ it("should not require error to be passed", async () => {
+ var called = false;
+ function fn(callback) {
+ called = true;
+ callback();
+ }
+ await promisify(fn)().then(value => {
+ assert.strictEqual(value, undefined);
+ assert.strictEqual(called, true);
+ });
+ });
+
+ it("custom callback", async () => {
+ var called = false;
+ function fn(err, val, callback) {
+ called = true;
+ callback(err, val);
+ }
+ await promisify(fn)(null, 42).then(value => {
+ assert.strictEqual(value, 42);
+ assert.strictEqual(called, true);
+ });
+ });
+
+ it("should catch error", async () => {
+ var called = false;
+ function fn(err, val, callback) {
+ called = true;
+ callback(err, val);
+ }
+ await promisify(fn)(new Error("oops"), null).catch(err => {
+ assert.strictEqual(err.message, "oops");
+ assert.strictEqual(called, true);
+ });
+ });
+
+ it("should call promisify properly inside async block", async () => {
+ var called = false;
+ function fn(err, val, callback) {
+ called = true;
+ callback(err, val);
+ }
+
+ await (async () => {
+ const value = await promisify(fn)(null, 42);
+ assert.strictEqual(value, 42);
+ })().then(() => {
+ assert.strictEqual(called, true);
+ });
+ });
+
+ it("should not break this reference", async () => {
+ const o = {};
+ var called = false;
+ const fn = promisify(function (cb) {
+ called = true;
+ cb(null, this === o);
+ });
+
+ o.fn = fn;
+
+ await o.fn().then(val => {
+ assert.strictEqual(called, true);
+ assert.strictEqual(val, true);
+ });
+ });
+
+ it("should not have called callback with error", async () => {
+ const err = new Error("Should not have called the callback with the error.");
+ const stack = err.stack;
+ var called = false;
+
+ const fn = promisify(function (cb) {
+ called = true;
+ cb(null);
+ cb(err);
+ });
+
+ await (async () => {
+ await fn();
+ await Promise.resolve();
+ return assert.strictEqual(stack, err.stack);
+ })().then(() => {
+ assert.strictEqual(called, true);
+ });
+ });
+
+ it("should compare promised objects properly", () => {
+ function c() {}
+ const a = promisify(function () {});
+ const b = promisify(a);
+ assert.notStrictEqual(c, a);
+ assert.strictEqual(a, b);
+ });
+
+ it("should throw error", async () => {
+ let errToThrow;
+ const thrower = promisify(function (a, b, c, cb) {
+ errToThrow = new Error();
+ throw errToThrow;
+ });
+ await thrower(1, 2, 3)
+ .then(assert.fail)
+ .then(assert.fail, e => assert.strictEqual(e, errToThrow));
+ });
+
+ it("should also throw error inside Promise.all", async () => {
+ const err = new Error();
+
+ const a = promisify(cb => cb(err))();
+ const b = promisify(() => {
+ throw err;
+ })();
+
+ await Promise.all([
+ a.then(assert.fail, function (e) {
+ assert.strictEqual(err, e);
+ }),
+ b.then(assert.fail, function (e) {
+ assert.strictEqual(err, e);
+ }),
+ ]);
+ });
+ });
+
+ describe("invalid input", () => {
+ // This test is failing because 'code' property
+ // is not thrown in the error. does it have different
+ // throw error implementation in bun?
+ it("should throw on invalid inputs for promisify", () => {
+ [undefined, null, true, 0, "str", {}, [], Symbol()].forEach(input => {
+ assert.throws(() => promisify(input), {
+ code: "ERR_INVALID_ARG_TYPE",
+ name: "TypeError",
+ message: 'The "original" argument must be of type Function',
+ });
+ });
+ });
+ });
+});
diff --git a/test/js/node/util/util.test.js b/test/js/node/util/util.test.js
new file mode 100644
index 000000000..ff01b508b
--- /dev/null
+++ b/test/js/node/util/util.test.js
@@ -0,0 +1,269 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// Tests adapted from https://github.com/nodejs/node/blob/main/test/parallel/test-util.js
+
+import { expect, describe, it } from "bun:test";
+import util from "util";
+import assert from "assert";
+// const context = require('vm').runInNewContext; // TODO: Use a vm polyfill
+
+const strictEqual = (...args) => {
+ expect(args[0]).toStrictEqual(args[1]);
+};
+
+const deepStrictEqual = (...args) => {
+ expect(args[0]).toEqual(args[1]);
+};
+
+// Tests adapted from https://github.com/nodejs/node/blob/main/test/parallel/test-util.js
+describe("util", () => {
+ describe("isArray", () => {
+ it("all cases", () => {
+ strictEqual(util.isArray([]), true);
+ strictEqual(util.isArray(Array()), true);
+ strictEqual(util.isArray(new Array()), true);
+ strictEqual(util.isArray(new Array(5)), true);
+ strictEqual(util.isArray(new Array("with", "some", "entries")), true);
+ // strictEqual(util.isArray(context('Array')()), true); unable to test due to dependency on context
+ strictEqual(util.isArray({}), false);
+ strictEqual(util.isArray({ push: function () {} }), false);
+ strictEqual(util.isArray(/regexp/), false);
+ strictEqual(util.isArray(new Error()), false);
+ strictEqual(util.isArray(Object.create(Array.prototype)), false);
+ });
+ });
+ describe("isRegExp", () => {
+ it("all cases", () => {
+ strictEqual(util.isRegExp(/regexp/), true);
+ strictEqual(util.isRegExp(RegExp(), "foo"), true);
+ strictEqual(util.isRegExp(new RegExp()), true);
+ // strictEqual(util.isRegExp(context("RegExp")()), true); unable to test due to dependency on context
+ strictEqual(util.isRegExp({}), false);
+ strictEqual(util.isRegExp([]), false);
+ strictEqual(util.isRegExp(new Date()), false);
+ strictEqual(util.isRegExp(Object.create(RegExp.prototype)), false);
+ });
+ });
+ describe("isDate", () => {
+ it("all cases", () => {
+ strictEqual(util.isDate(new Date()), true);
+ strictEqual(util.isDate(new Date(0), "foo"), true);
+ // strictEqual(util.isDate(new (context("Date"))()), true); unable to test due to dependency on context
+ strictEqual(util.isDate(Date()), false);
+ strictEqual(util.isDate({}), false);
+ strictEqual(util.isDate([]), false);
+ strictEqual(util.isDate(new Error()), false);
+ strictEqual(util.isDate(Object.create(Date.prototype)), false);
+ });
+ });
+
+ describe("isError", () => {
+ it("all cases", () => {
+ strictEqual(util.isError(new Error()), true);
+ strictEqual(util.isError(new TypeError()), true);
+ strictEqual(util.isError(new SyntaxError()), true);
+ // strictEqual(util.isError(new (context("Error"))()), true); unable to test due to dependency on context
+ // strictEqual(util.isError(new (context("TypeError"))()), true); unable to test due to dependency on context
+ // strictEqual(util.isError(new (context("SyntaxError"))()), true); unable to test due to dependency on context
+ strictEqual(util.isError({}), false);
+ strictEqual(util.isError({ name: "Error", message: "" }), false);
+ strictEqual(util.isError([]), false);
+ strictEqual(util.isError(Object.create(Error.prototype)), true);
+ });
+ });
+
+ describe("isObject", () => {
+ it("all cases", () => {
+ strictEqual(util.isObject({}), true);
+ strictEqual(util.isObject([]), true);
+ strictEqual(util.isObject(new Number(3)), true);
+ strictEqual(util.isObject(Number(4)), false);
+ strictEqual(util.isObject(1), false);
+ });
+ });
+
+ describe("isPrimitive", () => {
+ it("all cases", () => {
+ strictEqual(util.isPrimitive({}), false);
+ strictEqual(util.isPrimitive(new Error()), false);
+ strictEqual(util.isPrimitive(new Date()), false);
+ strictEqual(util.isPrimitive([]), false);
+ strictEqual(util.isPrimitive(/regexp/), false);
+ strictEqual(
+ util.isPrimitive(function () {}),
+ false,
+ );
+ strictEqual(util.isPrimitive(new Number(1)), false);
+ strictEqual(util.isPrimitive(new String("bla")), false);
+ strictEqual(util.isPrimitive(new Boolean(true)), false);
+ strictEqual(util.isPrimitive(1), true);
+ strictEqual(util.isPrimitive("bla"), true);
+ strictEqual(util.isPrimitive(true), true);
+ strictEqual(util.isPrimitive(undefined), true);
+ strictEqual(util.isPrimitive(null), true);
+ strictEqual(util.isPrimitive(Infinity), true);
+ strictEqual(util.isPrimitive(NaN), true);
+ strictEqual(util.isPrimitive(Symbol("symbol")), true);
+ });
+ });
+
+ describe("isBuffer", () => {
+ it("all cases", () => {
+ strictEqual(util.isBuffer("foo"), false);
+ strictEqual(util.isBuffer(Buffer.from("foo")), true);
+ });
+ });
+
+ describe("_extend", () => {
+ it("all cases", () => {
+ deepStrictEqual(util._extend({ a: 1 }), { a: 1 });
+ deepStrictEqual(util._extend({ a: 1 }, []), { a: 1 });
+ deepStrictEqual(util._extend({ a: 1 }, null), { a: 1 });
+ deepStrictEqual(util._extend({ a: 1 }, true), { a: 1 });
+ deepStrictEqual(util._extend({ a: 1 }, false), { a: 1 });
+ deepStrictEqual(util._extend({ a: 1 }, { b: 2 }), { a: 1, b: 2 });
+ deepStrictEqual(util._extend({ a: 1, b: 2 }, { b: 3 }), { a: 1, b: 3 });
+ });
+ });
+
+ describe("isBoolean", () => {
+ it("all cases", () => {
+ strictEqual(util.isBoolean(true), true);
+ strictEqual(util.isBoolean(false), true);
+ strictEqual(util.isBoolean("string"), false);
+ });
+ });
+
+ describe("isNull", () => {
+ it("all cases", () => {
+ strictEqual(util.isNull(null), true);
+ strictEqual(util.isNull(undefined), false);
+ strictEqual(util.isNull(), false);
+ strictEqual(util.isNull("string"), false);
+ });
+ });
+
+ describe("isUndefined", () => {
+ it("all cases", () => {
+ strictEqual(util.isUndefined(undefined), true);
+ strictEqual(util.isUndefined(), true);
+ strictEqual(util.isUndefined(null), false);
+ strictEqual(util.isUndefined("string"), false);
+ });
+ });
+
+ describe("isNullOrUndefined", () => {
+ it("all cases", () => {
+ strictEqual(util.isNullOrUndefined(null), true);
+ strictEqual(util.isNullOrUndefined(undefined), true);
+ strictEqual(util.isNullOrUndefined(), true);
+ strictEqual(util.isNullOrUndefined("string"), false);
+ });
+ });
+
+ describe("isNumber", () => {
+ it("all cases", () => {
+ strictEqual(util.isNumber(42), true);
+ strictEqual(util.isNumber(), false);
+ strictEqual(util.isNumber("string"), false);
+ });
+ });
+
+ describe("isString", () => {
+ it("all cases", () => {
+ strictEqual(util.isString("string"), true);
+ strictEqual(util.isString(), false);
+ strictEqual(util.isString(42), false);
+ });
+ });
+
+ describe("isSymbol", () => {
+ it("all cases", () => {
+ strictEqual(util.isSymbol(Symbol()), true);
+ strictEqual(util.isSymbol(), false);
+ strictEqual(util.isSymbol("string"), false);
+ });
+ });
+
+ describe("isFunction", () => {
+ it("all cases", () => {
+ strictEqual(
+ util.isFunction(() => {}),
+ true,
+ );
+ strictEqual(
+ util.isFunction(function () {}),
+ true,
+ );
+ strictEqual(util.isFunction(), false);
+ strictEqual(util.isFunction("string"), false);
+ });
+ });
+
+ describe("types.isNativeError", () => {
+ it("all cases", () => {
+ strictEqual(util.types.isNativeError(new Error()), true);
+ strictEqual(util.types.isNativeError(new TypeError()), true);
+ strictEqual(util.types.isNativeError(new SyntaxError()), true);
+ // TODO: unable to test due to dependency on context
+ // strictEqual(util.types.isNativeError(new (context("Error"))()), true);
+ // strictEqual(util.types.isNativeError(new (context("TypeError"))()), true);
+ // strictEqual(
+ // util.types.isNativeError(new (context("SyntaxError"))()),
+ // true
+ // );
+ strictEqual(util.types.isNativeError({}), false);
+ strictEqual(util.types.isNativeError({ name: "Error", message: "" }), false);
+ strictEqual(util.types.isNativeError([]), false);
+ // strictEqual( // FIXME: failing test
+ // util.types.isNativeError(Object.create(Error.prototype)),
+ // false
+ // );
+ // strictEqual( // FIXME: failing test
+ // util.types.isNativeError(new errors.codes.ERR_IPC_CHANNEL_CLOSED()),
+ // true
+ // );
+ });
+ });
+
+ // describe("", () => {
+ // it("all cases", () => {
+ // strictEqual(util.toUSVString("string\ud801"), "string\ufffd"); // TODO: currently unsupported
+ // });
+ // });
+
+ describe("TextEncoder", () => {
+ // test/bun.js/text-encoder.test.js covers test cases for TextEncoder
+ // here we test only if we use the same via util.TextEncoder
+ it("is same as global TextEncoder", () => {
+ expect(util.TextEncoder === globalThis.TextEncoder).toBe(true);
+ });
+ });
+
+ describe("TextDecoder", () => {
+ // test/bun.js/text-decoder.test.js covers test cases for TextDecoder
+ // here we test only if we use the same via util.TextDecoder
+ it("is same as global TextDecoder", () => {
+ expect(util.TextDecoder === globalThis.TextDecoder).toBe(true);
+ });
+ });
+});
diff --git a/test/js/node/v8/capture-stack-trace.test.js b/test/js/node/v8/capture-stack-trace.test.js
new file mode 100644
index 000000000..789503960
--- /dev/null
+++ b/test/js/node/v8/capture-stack-trace.test.js
@@ -0,0 +1,303 @@
+import { test, expect } from "bun:test";
+
+test("capture stack trace", () => {
+ function f1() {
+ f2();
+ }
+
+ function f2() {
+ f3();
+ }
+
+ function f3() {
+ logErrorStackTrace();
+ }
+
+ function logErrorStackTrace() {
+ let error = {};
+ Error.captureStackTrace(error);
+ expect(error.stack !== undefined).toBe(true);
+ }
+
+ f1();
+});
+
+test("capture stack trace with message", () => {
+ function f1() {
+ f2();
+ }
+
+ function f2() {
+ f3();
+ }
+
+ function f3() {
+ logErrorStackTrace();
+ }
+
+ function logErrorStackTrace() {
+ let e1 = { message: "bad error!" };
+ Error.captureStackTrace(e1);
+ expect(e1.message === "bad error!").toBe(true);
+
+ let e2 = new Error("bad error!");
+ Error.captureStackTrace(e2);
+ expect(e2.message === "bad error!").toBe(true);
+ }
+
+ f1();
+});
+
+test("capture stack trace with constructor", () => {
+ class S {
+ constructor() {
+ captureStackTrace();
+ }
+ }
+
+ function captureStackTrace() {
+ let e1 = {};
+ Error.captureStackTrace(e1);
+ expect(e1.stack.split("\n")[2].includes("new S")).toBe(true);
+ }
+
+ let s = new S();
+});
+
+test("capture stack trace limit", () => {
+ function f1() {
+ f2();
+ }
+
+ function f2() {
+ f3();
+ }
+
+ function f3() {
+ f4();
+ }
+
+ function f4() {
+ f5();
+ }
+
+ function f5() {
+ f6();
+ }
+
+ function f6() {
+ f7();
+ }
+
+ function f7() {
+ f8();
+ }
+
+ function f8() {
+ f9();
+ }
+
+ function f9() {
+ f10();
+ }
+
+ function f10() {
+ captureStackTrace();
+ }
+
+ function captureStackTrace() {
+ let e1 = {};
+ Error.captureStackTrace(e1);
+ expect(e1.stack.split("\n").length).toBe(11);
+
+ let e2 = new Error();
+ Error.captureStackTrace(e2);
+ expect(e2.stack.split("\n").length).toBe(11);
+
+ let e3 = {};
+ Error.stackTraceLimit = 4;
+ Error.captureStackTrace(e3);
+ expect(e3.stack.split("\n").length).toBe(5);
+
+ let e4 = new Error();
+ Error.captureStackTrace(e4);
+ expect(e4.stack.split("\n").length).toBe(5);
+
+ let e5 = { stackTraceLimit: 2 };
+ Error.captureStackTrace(e5);
+ expect(e5.stack.split("\n").length).toBe(5);
+
+ let e6 = {};
+ Error.stackTraceLimit = Infinity;
+ Error.captureStackTrace(e6);
+ expect(e6.stack.split("\n").length).toBe(13);
+ }
+
+ f1();
+});
+
+test("prepare stack trace", () => {
+ function f1() {
+ f2();
+ }
+
+ function f2() {
+ let e = {};
+ let prevPrepareStackTrace = Error.prepareStackTrace;
+ Error.prepareStackTrace = (e, stack) => {
+ return "custom stack trace";
+ };
+ Error.captureStackTrace(e);
+ expect(e.stack).toBe("custom stack trace");
+ Error.prepareStackTrace = prevPrepareStackTrace;
+ f3();
+ }
+
+ function f3() {
+ let e = { message: "bad error!" };
+ let prevPrepareStackTrace = Error.prepareStackTrace;
+ Error.prepareStackTrace = (e, s) => {
+ expect(e.message === "bad error!").toBe(true);
+ expect(s.length).toBe(4);
+ };
+ Error.stackTraceLimit = 10;
+ Error.captureStackTrace(e);
+ expect(e.stack === undefined).toBe(true);
+ Error.prepareStackTrace = prevPrepareStackTrace;
+ }
+
+ f1();
+});
+
+test("capture stack trace second argument", () => {
+ function f0() {
+ let s = new S();
+ }
+
+ class S {
+ constructor() {
+ f1();
+ }
+ }
+
+ function f1() {
+ f2();
+ }
+
+ function f2() {
+ f3();
+ }
+
+ function f3() {
+ f4();
+ }
+
+ function f4() {
+ f5();
+ }
+
+ function f5() {
+ f6();
+ }
+
+ function f6() {
+ let e = { message: "bad error!" };
+ Error.captureStackTrace(e);
+ expect(e.stack.split("\n")[1].includes("at f6")).toBe(true);
+ expect(e.stack.split("\n")[2].includes("at f5")).toBe(true);
+
+ let e2 = {};
+ Error.captureStackTrace(e2, f3);
+ expect(e2.stack.split("\n")[1].includes("at f2")).toBe(true);
+ expect(e2.stack.split("\n")[2].includes("at f1")).toBe(true);
+
+ let e3 = {};
+ Error.captureStackTrace(e3, f9);
+ expect(e3.stack.split("\n").length).toBe(1);
+
+ let e4 = { message: "exclude constructor!" };
+ Error.captureStackTrace(e4, S.constructor);
+ expect(e4.stack.split("\n").length).toBe(1);
+
+ let e5 = { message: "actually exclude constructor!" };
+ Error.captureStackTrace(e5, S);
+ expect(e5.stack.split("\n")[1].includes("at f0")).toBe(true);
+ }
+
+ function f9() {
+ // nothing
+ }
+
+ f0();
+});
+
+test("capture stack trace edge cases", () => {
+ let e1 = {};
+ Error.captureStackTrace(e1, null);
+ expect(e1.stack !== undefined).toBe(true);
+
+ let e2 = {};
+ Error.captureStackTrace(e2, undefined);
+ expect(e2.stack !== undefined).toBe(true);
+
+ let e3 = {};
+ Error.captureStackTrace(e3, 1);
+ expect(e3.stack !== undefined).toBe(true);
+
+ let e4 = {};
+ Error.captureStackTrace(e4, "foo");
+ expect(e4.stack !== undefined).toBe(true);
+
+ let e5 = {};
+ Error.captureStackTrace(e5, {});
+ expect(e5.stack !== undefined).toBe(true);
+
+ expect(Error.captureStackTrace({})).toBe(undefined);
+ expect(Error.captureStackTrace({}, () => {})).toBe(undefined);
+ expect(Error.captureStackTrace({}, undefined)).toBe(undefined);
+ expect(Error.captureStackTrace({}, null)).toBe(undefined);
+ expect(Error.captureStackTrace({}, 1)).toBe(undefined);
+ expect(Error.captureStackTrace({}, "foo")).toBe(undefined);
+ expect(Error.captureStackTrace({}, {})).toBe(undefined);
+ expect(Error.captureStackTrace({}, [])).toBe(undefined);
+ expect(Error.captureStackTrace({}, true)).toBe(undefined);
+});
+
+test("prepare stack trace call sites", () => {
+ function f1() {
+ f2();
+ }
+
+ function f2() {
+ f3();
+ }
+
+ function f3() {
+ let e = { message: "bad error!" };
+ // let e = new Error("bad error!");
+ let prevPrepareStackTrace = Error.prepareStackTrace;
+ Error.prepareStackTrace = (e, s) => {
+ expect(s[0].getThis !== undefined).toBe(true);
+ expect(s[0].getTypeName !== undefined).toBe(true);
+ expect(s[0].getFunction !== undefined).toBe(true);
+ expect(s[0].getFunctionName !== undefined).toBe(true);
+ expect(s[0].getMethodName !== undefined).toBe(true);
+ expect(s[0].getFileName !== undefined).toBe(true);
+ expect(s[0].getLineNumber !== undefined).toBe(true);
+ expect(s[0].getColumnNumber !== undefined).toBe(true);
+ expect(s[0].getEvalOrigin !== undefined).toBe(true);
+ expect(s[0].isToplevel !== undefined).toBe(true);
+ expect(s[0].isEval !== undefined).toBe(true);
+ expect(s[0].isNative !== undefined).toBe(true);
+ expect(s[0].isConstructor !== undefined).toBe(true);
+ expect(s[0].isAsync !== undefined).toBe(true);
+ expect(s[0].isPromiseAll !== undefined).toBe(true);
+ expect(s[0].getPromiseIndex !== undefined).toBe(true);
+ };
+ Error.captureStackTrace(e);
+ expect(e.stack === undefined).toBe(true);
+ Error.prepareStackTrace = prevPrepareStackTrace;
+ }
+
+ f1();
+});
diff --git a/test/js/node/zlib/fixture.html.gz b/test/js/node/zlib/fixture.html.gz
new file mode 100644
index 000000000..0bb85d4cb
--- /dev/null
+++ b/test/js/node/zlib/fixture.html.gz
Binary files differ
diff --git a/test/js/node/zlib/zlib.test.js b/test/js/node/zlib/zlib.test.js
new file mode 100644
index 000000000..60f3fe23a
--- /dev/null
+++ b/test/js/node/zlib/zlib.test.js
@@ -0,0 +1,39 @@
+import { describe, it, expect } from "bun:test";
+import { gzipSync, deflateSync, inflateSync, gunzipSync } from "bun";
+
+describe("zlib", () => {
+ it("should be able to deflate and inflate", () => {
+ const data = new TextEncoder().encode("Hello World!".repeat(1));
+ const compressed = deflateSync(data);
+ const decompressed = inflateSync(compressed);
+ expect(decompressed.join("")).toBe(data.join(""));
+ });
+
+ it("should be able to gzip and gunzip", () => {
+ const data = new TextEncoder().encode("Hello World!".repeat(1));
+ const compressed = gzipSync(data);
+ const decompressed = gunzipSync(compressed);
+ expect(decompressed.join("")).toBe(data.join(""));
+ });
+});
+
+import * as zlib from "node:zlib";
+import * as fs from "node:fs";
+import * as buffer from "node:buffer";
+
+describe("zlib.gunzip", () => {
+ it("should be able to unzip a Buffer and return an unzipped Buffer", async () => {
+ const content = fs.readFileSync(import.meta.dir + "/fixture.html.gz");
+ return new Promise((resolve, reject) => {
+ zlib.gunzip(content, (error, data) => {
+ if (error) {
+ reject(error);
+ return;
+ }
+ expect(data !== null).toBe(true);
+ expect(buffer.Buffer.isBuffer(data)).toBe(true);
+ resolve(true);
+ });
+ });
+ });
+});
diff --git a/test/js/third_party/body-parser/bun.lockb b/test/js/third_party/body-parser/bun.lockb
new file mode 100755
index 000000000..3986c6911
--- /dev/null
+++ b/test/js/third_party/body-parser/bun.lockb
Binary files differ
diff --git a/test/js/third_party/body-parser/express-body-parser-test.test.ts b/test/js/third_party/body-parser/express-body-parser-test.test.ts
new file mode 100644
index 000000000..1f95b318e
--- /dev/null
+++ b/test/js/third_party/body-parser/express-body-parser-test.test.ts
@@ -0,0 +1,59 @@
+import { test, expect } from "bun:test";
+import express, { Application, Request, Response } from "express";
+import { json } from "body-parser";
+
+// Express uses iconv-lite
+test("iconv works", () => {
+ var iconv = require("iconv-lite");
+
+ // Convert from an encoded buffer to a js string.
+ var str = iconv.decode(Buffer.from([0x68, 0x65, 0x6c, 0x6c, 0x6f]), "win1251");
+
+ // Convert from a js string to an encoded buffer.
+ var buf = iconv.encode("Sample input string", "win1251");
+ expect(str).toBe("hello");
+ expect(iconv.decode(buf, "win1251")).toBe("Sample input string");
+
+ // Check if encoding is supported
+ expect(iconv.encodingExists("us-ascii")).toBe(true);
+});
+
+// https://github.com/oven-sh/bun/issues/1913
+test("httpServer", async done => {
+ // Constants
+ const PORT = 8412;
+
+ // App handlers
+ const app: Application = express();
+ const httpServer = require("http").createServer(app);
+
+ app.on("error", err => {
+ console.error(err);
+ done(err);
+ });
+ app.use(json());
+
+ var reached = false;
+ // This throws a TypeError since it uses body-parser.json
+ app.post("/ping", (request: Request, response: Response) => {
+ expect(request.body).toEqual({ hello: "world" });
+ reached = true;
+ response.status(200).send("POST - pong");
+ httpServer.close();
+ done();
+ });
+
+ httpServer.listen(PORT);
+ const resp = await fetch(`http://localhost:${PORT}/ping`, {
+ method: "POST",
+ body: JSON.stringify({ hello: "world" }),
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+ expect(await resp.text()).toBe("POST - pong");
+ expect(resp.status).toBe(200);
+
+ expect(reached).toBe(true);
+ done();
+});
diff --git a/test/js/third_party/body-parser/package.json b/test/js/third_party/body-parser/package.json
new file mode 100644
index 000000000..0dfa98c59
--- /dev/null
+++ b/test/js/third_party/body-parser/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "body-parser-test",
+ "version": "1.0.0",
+ "dependencies": {
+ "express": "4.18.2",
+ "body-parser": "1.20.1",
+ "iconv-lite": "0.6.3"
+ }
+}
diff --git a/test/js/third_party/esbuild/bun.lockb b/test/js/third_party/esbuild/bun.lockb
new file mode 100755
index 000000000..f46c6aa5d
--- /dev/null
+++ b/test/js/third_party/esbuild/bun.lockb
Binary files differ
diff --git a/test/js/third_party/esbuild/esbuild-child_process.test.ts b/test/js/third_party/esbuild/esbuild-child_process.test.ts
new file mode 100644
index 000000000..adda89f9b
--- /dev/null
+++ b/test/js/third_party/esbuild/esbuild-child_process.test.ts
@@ -0,0 +1,17 @@
+import { spawnSync } from "bun";
+import { describe, it, expect, test } from "bun:test";
+import { bunExe } from "harness";
+
+test("esbuild", () => {
+ const { exitCode, stderr, stdout } = spawnSync([bunExe(), import.meta.dir + "/esbuild-test.js"], {
+ env: {
+ BUN_DEBUG_QUIET_LOGS: "1",
+ },
+ });
+ const out = "" + stderr?.toString() + stdout?.toString();
+ if (exitCode !== 0 && out?.length) {
+ throw new Error(out);
+ }
+
+ expect(exitCode).toBe(0);
+});
diff --git a/test/js/third_party/esbuild/esbuild-test.js b/test/js/third_party/esbuild/esbuild-test.js
new file mode 100644
index 000000000..beb34b283
--- /dev/null
+++ b/test/js/third_party/esbuild/esbuild-test.js
@@ -0,0 +1,37 @@
+import { transform, transformSync } from "esbuild";
+
+{
+ const result = await transform("console.log('hello world')", {
+ loader: "js",
+ target: "node12",
+ });
+ if (result.code !== 'console.log("hello world");\n') {
+ throw new Error("Test failed.");
+ }
+}
+
+{
+ const hugeString = `console.log(${JSON.stringify("a".repeat(1000000))});`;
+
+ for (let i = 0; i < 2; i++) {
+ const result = await transform(hugeString, {
+ loader: "js",
+ target: "node12",
+ });
+ if (result.code !== hugeString + "\n") {
+ throw new Error("Test failed.");
+ }
+ }
+}
+
+{
+ const result = transformSync("console.log('hello world')", {
+ loader: "js",
+ target: "node12",
+ });
+ if (result.code !== 'console.log("hello world");\n') {
+ throw new Error("Test failed.");
+ }
+}
+
+process.exit(0);
diff --git a/test/js/third_party/esbuild/package.json b/test/js/third_party/esbuild/package.json
new file mode 100644
index 000000000..ad6bc55f6
--- /dev/null
+++ b/test/js/third_party/esbuild/package.json
@@ -0,0 +1,6 @@
+{
+ "type": "module",
+ "dependencies": {
+ "esbuild": "^0.17.11"
+ }
+} \ No newline at end of file
diff --git a/test/js/third_party/napi_create_external/bun.lockb b/test/js/third_party/napi_create_external/bun.lockb
new file mode 100755
index 000000000..7f6d3e95d
--- /dev/null
+++ b/test/js/third_party/napi_create_external/bun.lockb
Binary files differ
diff --git a/test/js/third_party/napi_create_external/napi-create-external.test.ts b/test/js/third_party/napi_create_external/napi-create-external.test.ts
new file mode 100644
index 000000000..c3fe5ad65
--- /dev/null
+++ b/test/js/third_party/napi_create_external/napi-create-external.test.ts
@@ -0,0 +1,194 @@
+import { test, it, describe, expect } from "bun:test";
+import { withoutAggressiveGC } from "harness";
+import * as _ from "lodash";
+
+function rebase(str, inBase, outBase) {
+ const mapBase = b => (b === 2 ? 32 : b === 16 ? 8 : null);
+ const stride = mapBase(inBase);
+ const pad = mapBase(outBase);
+ if (!stride) throw new Error(`Bad inBase ${inBase}`);
+ if (!pad) throw new Error(`Bad outBase ${outBase}`);
+ if (str.length % stride) throw new Error(`Bad string length ${str.length}`);
+ const out = [];
+ for (let i = 0; i < str.length; i += stride)
+ out.push(
+ parseInt(str.slice(i, i + stride), inBase)
+ .toString(outBase)
+ .padStart(pad, "0"),
+ );
+ return out.join("");
+}
+
+function expectDeepEqual(a, b) {
+ expect(a).toEqual(b);
+}
+class HashMaker {
+ constructor(length) {
+ this.length = length;
+ this._dist = {};
+ }
+ length: number;
+ _dist: any;
+
+ binToHex(binHash) {
+ if (binHash.length !== this.length) throw new Error(`Hash length mismatch ${this.length} != ${binHash.length}`);
+ return rebase(binHash, 2, 16);
+ }
+
+ makeBits() {
+ const bits = [];
+ for (let i = 0; i < this.length; i++) bits.push(i);
+ return _.shuffle(bits);
+ }
+
+ makeRandom() {
+ const bits = [];
+ for (let i = 0; i < this.length; i++) bits.push(Math.random() < 0.5 ? 1 : 0);
+ return bits;
+ }
+
+ get keySet() {
+ return (this._set = this._set || new Set(this.data));
+ }
+
+ randomKey() {
+ while (true) {
+ const hash = this.binToHex(this.makeRandom().join(""));
+ if (!this.keySet.has(hash)) return hash;
+ }
+ }
+
+ get data() {
+ return (this._data =
+ this._data ||
+ (() => {
+ const bits = this.makeBits();
+ const base = this.makeRandom();
+ const data = [];
+ for (let stride = 0; bits.length; stride++) {
+ const flip = bits.splice(0, stride);
+ for (const bit of flip) base[bit] = 1 - base[bit];
+ data.push(this.binToHex(base.join("")));
+ }
+ return data;
+ })());
+ }
+
+ get random() {
+ const d = this.data;
+ return d[Math.floor(Math.random() * d.length)];
+ }
+
+ distance(a, b) {
+ const bitCount = n => {
+ n = n - ((n >> 1) & 0x55555555);
+ n = (n & 0x33333333) + ((n >> 2) & 0x33333333);
+ return (((n + (n >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24;
+ };
+
+ if (a === b) return 0;
+ if (a > b) return this.distance(b, a);
+ const hash = a + "-" + b;
+ return (this._dist[hash] =
+ this._dist[hash] ||
+ (() => {
+ let dist = 0;
+ for (let i = 0; i < a.length; i += 8) {
+ const va = parseInt(a.slice(i, i + 8), 16);
+ const vb = parseInt(b.slice(i, i + 8), 16);
+ dist += bitCount(va ^ vb);
+ }
+ return dist;
+ })());
+ }
+
+ query(baseKey, maxDist) {
+ const out = [];
+ for (const key of this.data) {
+ const distance = this.distance(key, baseKey);
+ if (distance <= maxDist) out.push({ key, distance });
+ }
+ return out.sort((a, b) => a.distance - b.distance);
+ }
+}
+
+const treeClass = require("bktree-fast/native");
+
+withoutAggressiveGC(() => {
+ // this test is too slow
+ for (let keyLen = 64; keyLen <= 64; keyLen += 64) {
+ // for (let keyLen = 64; keyLen <= 512; keyLen += 64) {
+ const hm = new HashMaker(keyLen);
+ describe(`Key length: ${keyLen}`, () => {
+ it("should compute distance", () => {
+ const tree = new treeClass(keyLen);
+ for (const a of hm.data) for (const b of hm.data) expect(tree.distance(a, b)).toBe(hm.distance(a, b));
+ });
+
+ it("should know which keys it has", () => {
+ const tree = new treeClass(keyLen).add(hm.data);
+ expectDeepEqual(
+ hm.data.map(hash => tree.has(hash)),
+ hm.data.map(() => true),
+ );
+ // Not interested in the hash
+ for (const hash of hm.data) expect(tree.has(hm.randomKey())).toBe(false);
+ });
+
+ it("should know the tree size", () => {
+ const tree = new treeClass(keyLen, { foo: 1 });
+ expect(tree.size).toBe(0);
+ tree.add(hm.data);
+ expect(tree.size).toBe(hm.data.length);
+ tree.add(hm.data);
+ expect(tree.size).toBe(hm.data.length);
+ });
+
+ it("should walk the tree", () => {
+ const tree = new treeClass(keyLen).add(hm.data);
+ const got = [];
+ tree.walk((hash, depth) => got.push(hash));
+ expectDeepEqual(got.sort(), hm.data.slice(0).sort());
+ });
+
+ it("should query", () => {
+ ((treeClass, expectDeepEqual) => {
+ const tree = new treeClass(keyLen).add(hm.data);
+
+ for (let dist = 0; dist <= hm.length; dist++) {
+ for (const baseKey of [hm.random, hm.data[0]]) {
+ const baseKey = hm.random;
+ const got = [];
+ tree.query(baseKey, dist, (key, distance) => got.push({ key, distance }));
+ const want = hm.query(baseKey, dist);
+ expectDeepEqual(
+ got.sort((a, b) => a.distance - b.distance),
+ want,
+ );
+ expectDeepEqual(tree.find(baseKey, dist), want);
+ }
+ }
+ })(treeClass, expectDeepEqual);
+ });
+ });
+ }
+
+ describe("Misc functions", () => {
+ it("should pad keys", () => {
+ const tree = new treeClass(64);
+ expect(tree.padKey("1")).toBe("0000000000000001");
+ tree.add(["1", "2", "3"]);
+
+ const got = [];
+ tree.query("2", 3, (hash, distance) => got.push({ hash, distance }));
+ const res = got.sort((a, b) => a.distance - b.distance);
+ const want = [
+ { hash: "0000000000000002", distance: 0 },
+ { hash: "0000000000000003", distance: 1 },
+ { hash: "0000000000000001", distance: 2 },
+ ];
+
+ expectDeepEqual(res, want);
+ });
+ });
+});
diff --git a/test/js/third_party/napi_create_external/package.json b/test/js/third_party/napi_create_external/package.json
new file mode 100644
index 000000000..659b279b2
--- /dev/null
+++ b/test/js/third_party/napi_create_external/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "napi-create-external-test",
+ "type": "module",
+ "version": "1.0.0",
+ "description": "Test for napi_create_external",
+ "dependencies": {
+ "bktree-fast": "0.0.7",
+ "lodash": "^4.17.21"
+ },
+ "scripts": {
+ "postinstall": "cd node_modules/bktree-fast && node-gyp configure"
+ }
+}
diff --git a/test/js/third_party/react-dom/react-dom-server.bun.cjs b/test/js/third_party/react-dom/react-dom-server.bun.cjs
new file mode 100644
index 000000000..f67e54a8c
--- /dev/null
+++ b/test/js/third_party/react-dom/react-dom-server.bun.cjs
@@ -0,0 +1,2670 @@
+/**
+ * @license React
+ * react-dom-server.bun.production.min.js
+ *
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+"use strict";
+var ba = require("react"),
+ ca = require("react-dom");
+function l(a, b) {
+ 0 !== b.length && a.write(b);
+}
+function da(a, b) {
+ "function" === typeof a.error ? a.error(b) : a.close();
+}
+var r = Object.prototype.hasOwnProperty,
+ ea =
+ /^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,
+ fa = {},
+ ha = {};
+function ia(a) {
+ if (r.call(ha, a)) return !0;
+ if (r.call(fa, a)) return !1;
+ if (ea.test(a)) return (ha[a] = !0);
+ fa[a] = !0;
+ return !1;
+}
+function t(a, b, c, d, e, f, g) {
+ this.acceptsBooleans = 2 === b || 3 === b || 4 === b;
+ this.attributeName = d;
+ this.attributeNamespace = e;
+ this.mustUseProperty = c;
+ this.propertyName = a;
+ this.type = b;
+ this.sanitizeURL = f;
+ this.removeEmptyString = g;
+}
+var v = {},
+ ja =
+ "children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(
+ " ",
+ );
+ja.push("innerText", "textContent");
+ja.forEach(function (a) {
+ v[a] = new t(a, 0, !1, a, null, !1, !1);
+});
+[
+ ["acceptCharset", "accept-charset"],
+ ["className", "class"],
+ ["htmlFor", "for"],
+ ["httpEquiv", "http-equiv"],
+].forEach(function (a) {
+ var b = a[0];
+ v[b] = new t(b, 1, !1, a[1], null, !1, !1);
+});
+["contentEditable", "draggable", "spellCheck", "value"].forEach(function (a) {
+ v[a] = new t(a, 2, !1, a.toLowerCase(), null, !1, !1);
+});
+["autoReverse", "externalResourcesRequired", "focusable", "preserveAlpha"].forEach(function (a) {
+ v[a] = new t(a, 2, !1, a, null, !1, !1);
+});
+"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope"
+ .split(" ")
+ .forEach(function (a) {
+ v[a] = new t(a, 3, !1, a.toLowerCase(), null, !1, !1);
+ });
+["checked", "multiple", "muted", "selected"].forEach(function (a) {
+ v[a] = new t(a, 3, !0, a, null, !1, !1);
+});
+["capture", "download"].forEach(function (a) {
+ v[a] = new t(a, 4, !1, a, null, !1, !1);
+});
+["cols", "rows", "size", "span"].forEach(function (a) {
+ v[a] = new t(a, 6, !1, a, null, !1, !1);
+});
+["rowSpan", "start"].forEach(function (a) {
+ v[a] = new t(a, 5, !1, a.toLowerCase(), null, !1, !1);
+});
+var ka = /[\-:]([a-z])/g;
+function la(a) {
+ return a[1].toUpperCase();
+}
+"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height"
+ .split(" ")
+ .forEach(function (a) {
+ var b = a.replace(ka, la);
+ v[b] = new t(b, 1, !1, a, null, !1, !1);
+ });
+"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function (a) {
+ var b = a.replace(ka, la);
+ v[b] = new t(b, 1, !1, a, "http://www.w3.org/1999/xlink", !1, !1);
+});
+["xml:base", "xml:lang", "xml:space"].forEach(function (a) {
+ var b = a.replace(ka, la);
+ v[b] = new t(b, 1, !1, a, "http://www.w3.org/XML/1998/namespace", !1, !1);
+});
+["tabIndex", "crossOrigin"].forEach(function (a) {
+ v[a] = new t(a, 1, !1, a.toLowerCase(), null, !1, !1);
+});
+v.xlinkHref = new t("xlinkHref", 1, !1, "xlink:href", "http://www.w3.org/1999/xlink", !0, !1);
+["src", "href", "action", "formAction"].forEach(function (a) {
+ v[a] = new t(a, 1, !1, a.toLowerCase(), null, !0, !0);
+});
+var ma = {
+ animationIterationCount: !0,
+ aspectRatio: !0,
+ borderImageOutset: !0,
+ borderImageSlice: !0,
+ borderImageWidth: !0,
+ boxFlex: !0,
+ boxFlexGroup: !0,
+ boxOrdinalGroup: !0,
+ columnCount: !0,
+ columns: !0,
+ flex: !0,
+ flexGrow: !0,
+ flexPositive: !0,
+ flexShrink: !0,
+ flexNegative: !0,
+ flexOrder: !0,
+ gridArea: !0,
+ gridRow: !0,
+ gridRowEnd: !0,
+ gridRowSpan: !0,
+ gridRowStart: !0,
+ gridColumn: !0,
+ gridColumnEnd: !0,
+ gridColumnSpan: !0,
+ gridColumnStart: !0,
+ fontWeight: !0,
+ lineClamp: !0,
+ lineHeight: !0,
+ opacity: !0,
+ order: !0,
+ orphans: !0,
+ tabSize: !0,
+ widows: !0,
+ zIndex: !0,
+ zoom: !0,
+ fillOpacity: !0,
+ floodOpacity: !0,
+ stopOpacity: !0,
+ strokeDasharray: !0,
+ strokeDashoffset: !0,
+ strokeMiterlimit: !0,
+ strokeOpacity: !0,
+ strokeWidth: !0,
+ },
+ na = ["Webkit", "ms", "Moz", "O"];
+Object.keys(ma).forEach(function (a) {
+ na.forEach(function (b) {
+ b = b + a.charAt(0).toUpperCase() + a.substring(1);
+ ma[b] = ma[a];
+ });
+});
+var oa = /["'&<>]/;
+function w(a) {
+ if ("boolean" === typeof a || "number" === typeof a) return "" + a;
+ a = "" + a;
+ var b = oa.exec(a);
+ if (b) {
+ var c = "",
+ d,
+ e = 0;
+ for (d = b.index; d < a.length; d++) {
+ switch (a.charCodeAt(d)) {
+ case 34:
+ b = "&quot;";
+ break;
+ case 38:
+ b = "&amp;";
+ break;
+ case 39:
+ b = "&#x27;";
+ break;
+ case 60:
+ b = "&lt;";
+ break;
+ case 62:
+ b = "&gt;";
+ break;
+ default:
+ continue;
+ }
+ e !== d && (c += a.substring(e, d));
+ e = d + 1;
+ c += b;
+ }
+ a = e !== d ? c + a.substring(e, d) : c;
+ }
+ return a;
+}
+var pa = /([A-Z])/g,
+ qa = /^ms-/,
+ ra = Array.isArray,
+ x = Object.assign,
+ y = null,
+ sa = [],
+ va = { preload: ta, preinit: ua };
+function ta(a, b) {
+ if (y) {
+ var c = y;
+ if ("string" === typeof a && a && "object" === typeof b && null !== b) {
+ var d = b.as,
+ e = c.preloadsMap.get(a);
+ e ||
+ (e = z(c, a, d, {
+ href: a,
+ rel: "preload",
+ as: d,
+ crossOrigin: "font" === d ? "" : b.crossOrigin,
+ integrity: b.integrity,
+ }));
+ switch (d) {
+ case "font":
+ c.fontPreloads.add(e);
+ break;
+ case "style":
+ c.explicitStylePreloads.add(e);
+ break;
+ case "script":
+ c.explicitScriptPreloads.add(e);
+ }
+ }
+ }
+}
+function ua(a, b) {
+ if (y) {
+ var c = y;
+ if ("string" === typeof a && a && "object" === typeof b && null !== b)
+ switch (b.as) {
+ case "style":
+ var d = c.stylesMap.get(a);
+ d ||
+ ((d = b.precedence || "default"),
+ (d = wa(c, a, d, {
+ rel: "stylesheet",
+ href: a,
+ "data-precedence": d,
+ crossOrigin: b.crossOrigin,
+ })));
+ d.set.add(d);
+ c.explicitStylePreloads.add(d.hint);
+ break;
+ case "script":
+ (d = c.scriptsMap.get(a)),
+ d ||
+ ((d = xa(c, a, {
+ src: a,
+ async: !0,
+ crossOrigin: b.crossOrigin,
+ integrity: b.integrity,
+ })),
+ c.scripts.add(d));
+ }
+ }
+}
+function ya(a, b) {
+ return {
+ rel: "preload",
+ as: "style",
+ href: a,
+ crossOrigin: b.crossOrigin,
+ integrity: b.integrity,
+ media: b.media,
+ hrefLang: b.hrefLang,
+ referrerPolicy: b.referrerPolicy,
+ };
+}
+function za(a, b) {
+ return {
+ rel: "preload",
+ as: "script",
+ href: a,
+ crossOrigin: b.crossOrigin,
+ integrity: b.integrity,
+ referrerPolicy: b.referrerPolicy,
+ };
+}
+function z(a, b, c, d) {
+ c = { type: "preload", as: c, href: b, flushed: !1, props: d };
+ a.preloadsMap.set(b, c);
+ return c;
+}
+function wa(a, b, c, d) {
+ var e = a.stylesMap,
+ f = a.preloadsMap,
+ g = a.precedences,
+ h = g.get(c);
+ h || ((h = new Set()), g.set(c, h));
+ (f = f.get(b))
+ ? ((a = f.props),
+ null == d.crossOrigin && (d.crossOrigin = a.crossOrigin),
+ null == d.referrerPolicy && (d.referrerPolicy = a.referrerPolicy),
+ null == d.title && (d.title = a.title))
+ : ((f = ya(b, d)), (f = z(a, b, "style", f)), a.explicitStylePreloads.add(f));
+ c = {
+ type: "style",
+ href: b,
+ precedence: c,
+ flushed: !1,
+ inShell: !1,
+ props: d,
+ hint: f,
+ set: h,
+ };
+ e.set(b, c);
+ return c;
+}
+function xa(a, b, c) {
+ var d = a.scriptsMap,
+ e = a.preloadsMap.get(b);
+ e
+ ? ((a = e.props),
+ null == c.crossOrigin && (c.crossOrigin = a.crossOrigin),
+ null == c.referrerPolicy && (c.referrerPolicy = a.referrerPolicy),
+ null == c.integrity && (c.integrity = a.integrity))
+ : ((e = za(b, c)), (e = z(a, b, "script", e)), a.explicitScriptPreloads.add(e));
+ c = { type: "script", src: b, flushed: !1, props: c, hint: e };
+ d.set(b, c);
+ return c;
+}
+function Aa(a, b) {
+ if (!y) throw Error('"currentResources" was expected to exist. This is a bug in React.');
+ var c = y;
+ switch (a) {
+ case "title":
+ var d = b.children;
+ Array.isArray(d) && 1 === d.length && (d = d[0]);
+ if ("string" === typeof d || "number" === typeof d) {
+ var e = "title::" + d;
+ a = c.headsMap.get(e);
+ a ||
+ ((b = x({}, b)),
+ (b.children = d),
+ (a = { type: "title", props: b, flushed: !1 }),
+ c.headsMap.set(e, a),
+ c.headResources.add(a));
+ }
+ return !0;
+ case "meta":
+ if ("string" === typeof b.charSet) e = "charSet";
+ else if ("string" === typeof b.content)
+ if (((a = "::" + b.content), "string" === typeof b.httpEquiv)) e = "httpEquiv::" + b.httpEquiv + a;
+ else if ("string" === typeof b.name) e = "name::" + b.name + a;
+ else if ("string" === typeof b.itemProp) e = "itemProp::" + b.itemProp + a;
+ else if ("string" === typeof b.property) {
+ var f = b.property;
+ e = "property::" + f + a;
+ d = f;
+ a = f.split(":").slice(0, -1).join(":");
+ (a = c.structuredMetaKeys.get(a)) && (e = a.key + "::child::" + e);
+ }
+ e &&
+ !c.headsMap.has(e) &&
+ ((b = { type: "meta", key: e, props: x({}, b), flushed: !1 }),
+ c.headsMap.set(e, b),
+ "charSet" === e ? (c.charset = b) : (d && c.structuredMetaKeys.set(d, b), c.headResources.add(b)));
+ return !0;
+ case "base":
+ return (
+ (e = b.target),
+ (d = b.href),
+ (e =
+ "base" +
+ ("string" === typeof d ? '[href="' + d + '"]' : ":not([href])") +
+ ("string" === typeof e ? '[target="' + e + '"]' : ":not([target])")),
+ c.headsMap.has(e) ||
+ ((b = { type: "base", props: x({}, b), flushed: !1 }), c.headsMap.set(e, b), c.bases.add(b)),
+ !0
+ );
+ }
+ return !1;
+}
+function Ba(a) {
+ if (!y) throw Error('"currentResources" was expected to exist. This is a bug in React.');
+ var b = y,
+ c = a.rel,
+ d = a.href;
+ if (!d || "string" !== typeof d || !c || "string" !== typeof c) return !1;
+ switch (c) {
+ case "stylesheet":
+ var e = a.onLoad,
+ f = a.onError;
+ c = a.precedence;
+ var g = a.disabled;
+ if ("string" !== typeof c || e || f || null != g)
+ return (c = b.preloadsMap.get(d)), c || ((c = z(b, d, "style", ya(d, a))), b.usedStylePreloads.add(c)), !1;
+ e = b.stylesMap.get(d);
+ e ||
+ ((a = x({}, a)),
+ (a.href = d),
+ (a.rel = "stylesheet"),
+ (a["data-precedence"] = c),
+ delete a.precedence,
+ (e = wa(y, d, c, a)),
+ b.usedStylePreloads.add(e.hint));
+ b.boundaryResources ? b.boundaryResources.add(e) : e.set.add(e);
+ return !0;
+ case "preload":
+ switch (((e = a.as), e)) {
+ case "script":
+ case "style":
+ case "font":
+ c = b.preloadsMap.get(d);
+ if (!c)
+ switch (
+ ((a = x({}, a)),
+ (a.href = d),
+ (a.rel = "preload"),
+ (a.as = e),
+ "font" === e && (a.crossOrigin = ""),
+ (c = z(b, d, e, a)),
+ e)
+ ) {
+ case "script":
+ b.explicitScriptPreloads.add(c);
+ break;
+ case "style":
+ b.explicitStylePreloads.add(c);
+ break;
+ case "font":
+ b.fontPreloads.add(c);
+ }
+ return !0;
+ }
+ }
+ if (a.onLoad || a.onError) return !0;
+ d =
+ "rel:" +
+ c +
+ "::href:" +
+ d +
+ "::sizes:" +
+ ("string" === typeof a.sizes ? a.sizes : "") +
+ "::media:" +
+ ("string" === typeof a.media ? a.media : "");
+ e = b.headsMap.get(d);
+ if (!e)
+ switch (((e = { type: "link", props: x({}, a), flushed: !1 }), b.headsMap.set(d, e), c)) {
+ case "preconnect":
+ case "dns-prefetch":
+ b.preconnects.add(e);
+ break;
+ default:
+ b.headResources.add(e);
+ }
+ return !0;
+}
+function Ca(a, b) {
+ var c = a.boundaryResources;
+ c &&
+ (b.forEach(function (a) {
+ return c.add(a);
+ }),
+ b.clear());
+}
+function Da(a, b) {
+ b.forEach(function (a) {
+ return a.set.add(a);
+ });
+ b.clear();
+}
+var Ea = ca.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Dispatcher,
+ Fa = /(<\/|<)(s)(cript)/gi;
+function Ga(a, b, c, d) {
+ return "" + b + ("s" === c ? "\\u0073" : "\\u0053") + d;
+}
+function Ha(a, b, c, d, e) {
+ a = void 0 === a ? "" : a;
+ b = void 0 === b ? "<script>" : '<script nonce="' + w(b) + '">';
+ var f = [];
+ void 0 !== c && f.push(b, ("" + c).replace(Fa, Ga), "\x3c/script>");
+ if (void 0 !== d)
+ for (c = 0; c < d.length; c++) {
+ var g = d[c],
+ h = "string" === typeof g ? void 0 : g.integrity;
+ f.push('<script src="', w("string" === typeof g ? g : g.src));
+ h && f.push('" integrity="', w(h));
+ f.push('" async="">\x3c/script>');
+ }
+ if (void 0 !== e)
+ for (d = 0; d < e.length; d++)
+ (c = e[d]),
+ (g = "string" === typeof c ? void 0 : c.integrity),
+ f.push('<script type="module" src="', w("string" === typeof c ? c : c.src)),
+ g && f.push('" integrity="', w(g)),
+ f.push('" async="">\x3c/script>');
+ return {
+ bootstrapChunks: f,
+ startInlineScript: b,
+ placeholderPrefix: a + "P:",
+ segmentPrefix: a + "S:",
+ boundaryPrefix: a + "B:",
+ idPrefix: a,
+ nextSuspenseID: 0,
+ sentCompleteSegmentFunction: !1,
+ sentCompleteBoundaryFunction: !1,
+ sentClientRenderFunction: !1,
+ sentStyleInsertionFunction: !1,
+ };
+}
+function A(a, b, c) {
+ return { insertionMode: a, selectedValue: b, noscriptTagInScope: c };
+}
+function Ia(a) {
+ return A("http://www.w3.org/2000/svg" === a ? 2 : "http://www.w3.org/1998/Math/MathML" === a ? 3 : 0, null, !1);
+}
+function Ja(a, b, c) {
+ switch (b) {
+ case "noscript":
+ return A(1, null, !0);
+ case "select":
+ return A(1, null != c.value ? c.value : c.defaultValue, a.noscriptTagInScope);
+ case "svg":
+ return A(2, null, a.noscriptTagInScope);
+ case "math":
+ return A(3, null, a.noscriptTagInScope);
+ case "foreignObject":
+ return A(1, null, a.noscriptTagInScope);
+ case "table":
+ return A(4, null, a.noscriptTagInScope);
+ case "thead":
+ case "tbody":
+ case "tfoot":
+ return A(5, null, a.noscriptTagInScope);
+ case "colgroup":
+ return A(7, null, a.noscriptTagInScope);
+ case "tr":
+ return A(6, null, a.noscriptTagInScope);
+ }
+ return 4 <= a.insertionMode || 0 === a.insertionMode ? A(1, null, a.noscriptTagInScope) : a;
+}
+function Ka(a, b, c, d) {
+ if ("" === b) return d;
+ d && a.push("\x3c!-- --\x3e");
+ a.push(w(b));
+ return !0;
+}
+var La = new Map();
+function Ma(a, b, c) {
+ if ("object" !== typeof c)
+ throw Error(
+ "The `style` prop expects a mapping from style properties to values, not a string. For example, style={{marginRight: spacing + 'em'}} when using JSX.",
+ );
+ b = !0;
+ for (var d in c)
+ if (r.call(c, d)) {
+ var e = c[d];
+ if (null != e && "boolean" !== typeof e && "" !== e) {
+ if (0 === d.indexOf("--")) {
+ var f = w(d);
+ e = w(("" + e).trim());
+ } else {
+ f = d;
+ var g = La.get(f);
+ void 0 !== g
+ ? (f = g)
+ : ((g = w(f.replace(pa, "-$1").toLowerCase().replace(qa, "-ms-"))), La.set(f, g), (f = g));
+ e = "number" === typeof e ? (0 === e || r.call(ma, d) ? "" + e : e + "px") : w(("" + e).trim());
+ }
+ b ? ((b = !1), a.push(' style="', f, ":", e)) : a.push(";", f, ":", e);
+ }
+ }
+ b || a.push('"');
+}
+function C(a, b, c, d) {
+ switch (c) {
+ case "style":
+ Ma(a, b, d);
+ return;
+ case "defaultValue":
+ case "defaultChecked":
+ case "innerHTML":
+ case "suppressContentEditableWarning":
+ case "suppressHydrationWarning":
+ return;
+ }
+ if (!(2 < c.length) || ("o" !== c[0] && "O" !== c[0]) || ("n" !== c[1] && "N" !== c[1]))
+ if (((b = v.hasOwnProperty(c) ? v[c] : null), null !== b)) {
+ switch (typeof d) {
+ case "function":
+ case "symbol":
+ return;
+ case "boolean":
+ if (!b.acceptsBooleans) return;
+ }
+ c = b.attributeName;
+ switch (b.type) {
+ case 3:
+ d && a.push(" ", c, '=""');
+ break;
+ case 4:
+ !0 === d ? a.push(" ", c, '=""') : !1 !== d && a.push(" ", c, '="', w(d), '"');
+ break;
+ case 5:
+ isNaN(d) || a.push(" ", c, '="', w(d), '"');
+ break;
+ case 6:
+ !isNaN(d) && 1 <= d && a.push(" ", c, '="', w(d), '"');
+ break;
+ default:
+ b.sanitizeURL && (d = "" + d), a.push(" ", c, '="', w(d), '"');
+ }
+ } else if (ia(c)) {
+ switch (typeof d) {
+ case "function":
+ case "symbol":
+ return;
+ case "boolean":
+ if (((b = c.toLowerCase().slice(0, 5)), "data-" !== b && "aria-" !== b)) return;
+ }
+ a.push(" ", c, '="', w(d), '"');
+ }
+}
+function D(a, b, c) {
+ if (null != b) {
+ if (null != c) throw Error("Can only set one of `children` or `props.dangerouslySetInnerHTML`.");
+ if ("object" !== typeof b || !("__html" in b))
+ throw Error(
+ "`props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. Please visit https://reactjs.org/link/dangerously-set-inner-html for more information.",
+ );
+ b = b.__html;
+ null !== b && void 0 !== b && a.push("" + b);
+ }
+}
+function Na(a) {
+ var b = "";
+ ba.Children.forEach(a, function (a) {
+ null != a && (b += a);
+ });
+ return b;
+}
+function F(a, b, c) {
+ var d = "stylesheet" === b.rel;
+ a.push(G("link"));
+ for (var e in b)
+ if (r.call(b, e)) {
+ var f = b[e];
+ if (null != f)
+ switch (e) {
+ case "children":
+ case "dangerouslySetInnerHTML":
+ throw Error(
+ "link is a self-closing tag and must neither have `children` nor use `dangerouslySetInnerHTML`.",
+ );
+ case "precedence":
+ if (d) continue;
+ default:
+ C(a, c, e, f);
+ }
+ }
+ a.push("/>");
+ return null;
+}
+function I(a, b, c, d) {
+ a.push(G(c));
+ for (var e in b)
+ if (r.call(b, e)) {
+ var f = b[e];
+ if (null != f)
+ switch (e) {
+ case "children":
+ case "dangerouslySetInnerHTML":
+ throw Error(
+ c + " is a self-closing tag and must neither have `children` nor use `dangerouslySetInnerHTML`.",
+ );
+ default:
+ C(a, d, e, f);
+ }
+ }
+ a.push("/>");
+ return null;
+}
+function Oa(a, b, c) {
+ a.push(G("title"));
+ var d = null,
+ e;
+ for (e in b)
+ if (r.call(b, e)) {
+ var f = b[e];
+ if (null != f)
+ switch (e) {
+ case "children":
+ d = f;
+ break;
+ case "dangerouslySetInnerHTML":
+ throw Error("`dangerouslySetInnerHTML` does not make sense on <title>.");
+ default:
+ C(a, c, e, f);
+ }
+ }
+ a.push(">");
+ b = Array.isArray(d) && 2 > d.length ? d[0] || null : d;
+ ("string" !== typeof b && "number" !== typeof b) || a.push(w(b));
+ a.push("</", "title", ">");
+ return null;
+}
+function Pa(a, b, c) {
+ a.push(G("script"));
+ var d = null,
+ e = null,
+ f;
+ for (f in b)
+ if (r.call(b, f)) {
+ var g = b[f];
+ if (null != g)
+ switch (f) {
+ case "children":
+ d = g;
+ break;
+ case "dangerouslySetInnerHTML":
+ e = g;
+ break;
+ default:
+ C(a, c, f, g);
+ }
+ }
+ a.push(">");
+ D(a, e, d);
+ "string" === typeof d && a.push(w(d));
+ a.push("</", "script", ">");
+ return null;
+}
+function J(a, b, c, d) {
+ a.push(G(c));
+ var e = (c = null),
+ f;
+ for (f in b)
+ if (r.call(b, f)) {
+ var g = b[f];
+ if (null != g)
+ switch (f) {
+ case "children":
+ c = g;
+ break;
+ case "dangerouslySetInnerHTML":
+ e = g;
+ break;
+ default:
+ C(a, d, f, g);
+ }
+ }
+ a.push(">");
+ D(a, e, c);
+ return "string" === typeof c ? (a.push(w(c)), null) : c;
+}
+var Qa = /^[a-zA-Z][a-zA-Z:_\.\-\d]*$/,
+ Ra = new Map();
+function G(a) {
+ var b = Ra.get(a);
+ if (void 0 === b) {
+ if (!Qa.test(a)) throw Error("Invalid tag: " + a);
+ b = "<" + a;
+ Ra.set(a, b);
+ }
+ return b;
+}
+function Sa(a, b, c, d, e, f, g) {
+ switch (c) {
+ case "select":
+ a.push(G("select"));
+ var h = (g = null),
+ m;
+ for (m in d)
+ if (r.call(d, m)) {
+ var k = d[m];
+ if (null != k)
+ switch (m) {
+ case "children":
+ g = k;
+ break;
+ case "dangerouslySetInnerHTML":
+ h = k;
+ break;
+ case "defaultValue":
+ case "value":
+ break;
+ default:
+ C(a, e, m, k);
+ }
+ }
+ a.push(">");
+ D(a, h, g);
+ return g;
+ case "option":
+ g = f.selectedValue;
+ a.push(G("option"));
+ var p = (m = k = null),
+ n = null;
+ for (h in d)
+ if (r.call(d, h)) {
+ var q = d[h];
+ if (null != q)
+ switch (h) {
+ case "children":
+ k = q;
+ break;
+ case "selected":
+ p = q;
+ break;
+ case "dangerouslySetInnerHTML":
+ n = q;
+ break;
+ case "value":
+ m = q;
+ default:
+ C(a, e, h, q);
+ }
+ }
+ if (null != g)
+ if (((d = null !== m ? "" + m : Na(k)), ra(g)))
+ for (e = 0; e < g.length; e++) {
+ if ("" + g[e] === d) {
+ a.push(' selected=""');
+ break;
+ }
+ }
+ else "" + g === d && a.push(' selected=""');
+ else p && a.push(' selected=""');
+ a.push(">");
+ D(a, n, k);
+ return k;
+ case "textarea":
+ a.push(G("textarea"));
+ k = h = g = null;
+ for (n in d)
+ if (r.call(d, n) && ((m = d[n]), null != m))
+ switch (n) {
+ case "children":
+ k = m;
+ break;
+ case "value":
+ g = m;
+ break;
+ case "defaultValue":
+ h = m;
+ break;
+ case "dangerouslySetInnerHTML":
+ throw Error("`dangerouslySetInnerHTML` does not make sense on <textarea>.");
+ default:
+ C(a, e, n, m);
+ }
+ null === g && null !== h && (g = h);
+ a.push(">");
+ if (null != k) {
+ if (null != g) throw Error("If you supply `defaultValue` on a <textarea>, do not pass children.");
+ if (ra(k) && 1 < k.length) throw Error("<textarea> can only have at most one child.");
+ g = "" + k;
+ }
+ "string" === typeof g && "\n" === g[0] && a.push("\n");
+ null !== g && a.push(w("" + g));
+ return null;
+ case "input":
+ a.push(G("input"));
+ m = n = h = g = null;
+ for (k in d)
+ if (r.call(d, k) && ((p = d[k]), null != p))
+ switch (k) {
+ case "children":
+ case "dangerouslySetInnerHTML":
+ throw Error(
+ "input is a self-closing tag and must neither have `children` nor use `dangerouslySetInnerHTML`.",
+ );
+ case "defaultChecked":
+ m = p;
+ break;
+ case "defaultValue":
+ h = p;
+ break;
+ case "checked":
+ n = p;
+ break;
+ case "value":
+ g = p;
+ break;
+ default:
+ C(a, e, k, p);
+ }
+ null !== n ? C(a, e, "checked", n) : null !== m && C(a, e, "checked", m);
+ null !== g ? C(a, e, "value", g) : null !== h && C(a, e, "value", h);
+ a.push("/>");
+ return null;
+ case "menuitem":
+ a.push(G("menuitem"));
+ for (var u in d)
+ if (r.call(d, u) && ((g = d[u]), null != g))
+ switch (u) {
+ case "children":
+ case "dangerouslySetInnerHTML":
+ throw Error("menuitems cannot have `children` nor `dangerouslySetInnerHTML`.");
+ default:
+ C(a, e, u, g);
+ }
+ a.push(">");
+ return null;
+ case "title":
+ return (a = 2 !== f.insertionMode && !f.noscriptTagInScope && Aa("title", d) ? null : Oa(a, d, e)), a;
+ case "link":
+ return !f.noscriptTagInScope && Ba(d) ? (g && a.push("\x3c!-- --\x3e"), (a = null)) : (a = F(a, d, e)), a;
+ case "script":
+ if ((h = !f.noscriptTagInScope)) {
+ if (!y) throw Error('"currentResources" was expected to exist. This is a bug in React.');
+ h = y;
+ k = d.src;
+ n = d.onLoad;
+ m = d.onError;
+ k && "string" === typeof k
+ ? d.async
+ ? (n || m
+ ? ((n = h.preloadsMap.get(k)), n || ((n = z(h, k, "script", za(k, d))), h.usedScriptPreloads.add(n)))
+ : ((n = h.scriptsMap.get(k)), n || ((n = x({}, d)), (n.src = k), (n = xa(h, k, n)), h.scripts.add(n))),
+ (h = !0))
+ : (h = !1)
+ : (h = !1);
+ }
+ h ? (g && a.push("\x3c!-- --\x3e"), (a = null)) : (a = Pa(a, d, e));
+ return a;
+ case "meta":
+ return (
+ !f.noscriptTagInScope && Aa("meta", d) ? (g && a.push("\x3c!-- --\x3e"), (a = null)) : (a = I(a, d, "meta", e)),
+ a
+ );
+ case "base":
+ return (
+ !f.noscriptTagInScope && Aa("base", d) ? (g && a.push("\x3c!-- --\x3e"), (a = null)) : (a = I(a, d, "base", e)),
+ a
+ );
+ case "listing":
+ case "pre":
+ a.push(G(c));
+ h = g = null;
+ for (p in d)
+ if (r.call(d, p) && ((k = d[p]), null != k))
+ switch (p) {
+ case "children":
+ g = k;
+ break;
+ case "dangerouslySetInnerHTML":
+ h = k;
+ break;
+ default:
+ C(a, e, p, k);
+ }
+ a.push(">");
+ if (null != h) {
+ if (null != g) throw Error("Can only set one of `children` or `props.dangerouslySetInnerHTML`.");
+ if ("object" !== typeof h || !("__html" in h))
+ throw Error(
+ "`props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. Please visit https://reactjs.org/link/dangerously-set-inner-html for more information.",
+ );
+ d = h.__html;
+ null !== d &&
+ void 0 !== d &&
+ ("string" === typeof d && 0 < d.length && "\n" === d[0] ? a.push("\n", d) : a.push("" + d));
+ }
+ "string" === typeof g && "\n" === g[0] && a.push("\n");
+ return g;
+ case "area":
+ case "br":
+ case "col":
+ case "embed":
+ case "hr":
+ case "img":
+ case "keygen":
+ case "param":
+ case "source":
+ case "track":
+ case "wbr":
+ return I(a, d, c, e);
+ case "annotation-xml":
+ case "color-profile":
+ case "font-face":
+ case "font-face-src":
+ case "font-face-uri":
+ case "font-face-format":
+ case "font-face-name":
+ case "missing-glyph":
+ return J(a, d, c, e);
+ case "head":
+ return J(b, d, c, e);
+ case "html":
+ return 0 === f.insertionMode && b.push("<!DOCTYPE html>"), J(b, d, c, e);
+ default:
+ if (-1 === c.indexOf("-") && "string" !== typeof d.is) return J(a, d, c, e);
+ a.push(G(c));
+ h = g = null;
+ for (q in d)
+ if (r.call(d, q) && ((k = d[q]), null != k && "function" !== typeof k && "object" !== typeof k && !1 !== k))
+ switch ((!0 === k && (k = ""), "className" === q && (q = "class"), q)) {
+ case "children":
+ g = k;
+ break;
+ case "dangerouslySetInnerHTML":
+ h = k;
+ break;
+ case "style":
+ Ma(a, e, k);
+ break;
+ case "suppressContentEditableWarning":
+ case "suppressHydrationWarning":
+ break;
+ default:
+ ia(q) && "function" !== typeof k && "symbol" !== typeof k && a.push(" ", q, '="', w(k), '"');
+ }
+ a.push(">");
+ D(a, h, g);
+ return g;
+ }
+}
+function Ta(a, b, c) {
+ switch (c) {
+ case "title":
+ case "script":
+ case "area":
+ case "base":
+ case "br":
+ case "col":
+ case "embed":
+ case "hr":
+ case "img":
+ case "input":
+ case "keygen":
+ case "link":
+ case "meta":
+ case "param":
+ case "source":
+ case "track":
+ case "wbr":
+ return;
+ case "body":
+ b.unshift("</", c, ">");
+ return;
+ case "html":
+ b.push("</", c, ">");
+ return;
+ }
+ a.push("</", c, ">");
+}
+function Ua(a, b, c) {
+ l(a, '\x3c!--$?--\x3e<template id="');
+ if (null === c) throw Error("An ID must have been assigned before we can complete the boundary.");
+ l(a, c);
+ return !!a.write('"></template>');
+}
+function Va(a, b, c, d) {
+ switch (c.insertionMode) {
+ case 0:
+ case 1:
+ return l(a, '<div hidden id="'), l(a, b.segmentPrefix), l(a, d.toString(16)), !!a.write('">');
+ case 2:
+ return (
+ l(a, '<svg aria-hidden="true" style="display:none" id="'),
+ l(a, b.segmentPrefix),
+ l(a, d.toString(16)),
+ !!a.write('">')
+ );
+ case 3:
+ return (
+ l(a, '<math aria-hidden="true" style="display:none" id="'),
+ l(a, b.segmentPrefix),
+ l(a, d.toString(16)),
+ !!a.write('">')
+ );
+ case 4:
+ return l(a, '<table hidden id="'), l(a, b.segmentPrefix), l(a, d.toString(16)), !!a.write('">');
+ case 5:
+ return l(a, '<table hidden><tbody id="'), l(a, b.segmentPrefix), l(a, d.toString(16)), !!a.write('">');
+ case 6:
+ return l(a, '<table hidden><tr id="'), l(a, b.segmentPrefix), l(a, d.toString(16)), !!a.write('">');
+ case 7:
+ return l(a, '<table hidden><colgroup id="'), l(a, b.segmentPrefix), l(a, d.toString(16)), !!a.write('">');
+ default:
+ throw Error("Unknown insertion mode. This is a bug in React.");
+ }
+}
+function Wa(a, b) {
+ switch (b.insertionMode) {
+ case 0:
+ case 1:
+ return !!a.write("</div>");
+ case 2:
+ return !!a.write("</svg>");
+ case 3:
+ return !!a.write("</math>");
+ case 4:
+ return !!a.write("</table>");
+ case 5:
+ return !!a.write("</tbody></table>");
+ case 6:
+ return !!a.write("</tr></table>");
+ case 7:
+ return !!a.write("</colgroup></table>");
+ default:
+ throw Error("Unknown insertion mode. This is a bug in React.");
+ }
+}
+var Xa = /[<\u2028\u2029]/g;
+function Ya(a) {
+ return JSON.stringify(a).replace(Xa, function (a) {
+ switch (a) {
+ case "<":
+ return "\\u003c";
+ case "\u2028":
+ return "\\u2028";
+ case "\u2029":
+ return "\\u2029";
+ default:
+ throw Error(
+ "escapeJSStringsForInstructionScripts encountered a match it does not know how to replace. this means the match regex and the replacement characters are no longer in sync. This is a bug in React",
+ );
+ }
+ });
+}
+var Za = /[&><\u2028\u2029]/g;
+function K(a) {
+ return JSON.stringify(a).replace(Za, function (a) {
+ switch (a) {
+ case "&":
+ return "\\u0026";
+ case ">":
+ return "\\u003e";
+ case "<":
+ return "\\u003c";
+ case "\u2028":
+ return "\\u2028";
+ case "\u2029":
+ return "\\u2029";
+ default:
+ throw Error(
+ "escapeJSObjectForInstructionScripts encountered a match it does not know how to replace. this means the match regex and the replacement characters are no longer in sync. This is a bug in React",
+ );
+ }
+ });
+}
+function $a(a, b, c) {
+ function d(a) {
+ a.flushed || (F(e, a.props, c), (a.flushed = !0));
+ }
+ var e = [],
+ f = b.charset,
+ g = b.bases,
+ h = b.preconnects,
+ m = b.fontPreloads,
+ k = b.precedences,
+ p = b.usedStylePreloads,
+ n = b.scripts,
+ q = b.usedScriptPreloads,
+ u = b.explicitStylePreloads,
+ H = b.explicitScriptPreloads,
+ B = b.headResources;
+ f && (I(e, f.props, "meta", c), (f.flushed = !0), (b.charset = null));
+ g.forEach(function (a) {
+ I(e, a.props, "base", c);
+ a.flushed = !0;
+ });
+ g.clear();
+ h.forEach(function (a) {
+ F(e, a.props, c);
+ a.flushed = !0;
+ });
+ h.clear();
+ m.forEach(function (a) {
+ F(e, a.props, c);
+ a.flushed = !0;
+ });
+ m.clear();
+ k.forEach(function (a, b) {
+ a.size
+ ? (a.forEach(function (a) {
+ F(e, a.props, c);
+ a.flushed = !0;
+ a.inShell = !0;
+ a.hint.flushed = !0;
+ }),
+ a.clear())
+ : e.push('<style data-precedence="', w(b), '"></style>');
+ });
+ p.forEach(d);
+ p.clear();
+ n.forEach(function (a) {
+ Pa(e, a.props, c);
+ a.flushed = !0;
+ a.hint.flushed = !0;
+ });
+ n.clear();
+ q.forEach(d);
+ q.clear();
+ u.forEach(d);
+ u.clear();
+ H.forEach(d);
+ H.clear();
+ B.forEach(function (a) {
+ switch (a.type) {
+ case "title":
+ Oa(e, a.props, c);
+ break;
+ case "meta":
+ I(e, a.props, "meta", c);
+ break;
+ case "link":
+ F(e, a.props, c);
+ }
+ a.flushed = !0;
+ });
+ B.clear();
+ f = !0;
+ for (b = 0; b < e.length - 1; b++) l(a, e[b]);
+ b < e.length && (f = !!a.write(e[b]));
+ return f;
+}
+function ab(a, b, c) {
+ function d(a) {
+ a.flushed || (F(e, a.props, c), (a.flushed = !0));
+ }
+ var e = [],
+ f = b.charset,
+ g = b.preconnects,
+ h = b.fontPreloads,
+ m = b.usedStylePreloads,
+ k = b.scripts,
+ p = b.usedScriptPreloads,
+ n = b.explicitStylePreloads,
+ q = b.explicitScriptPreloads,
+ u = b.headResources;
+ f && (I(e, f.props, "meta", c), (f.flushed = !0), (b.charset = null));
+ g.forEach(function (a) {
+ F(e, a.props, c);
+ a.flushed = !0;
+ });
+ g.clear();
+ h.forEach(function (a) {
+ F(e, a.props, c);
+ a.flushed = !0;
+ });
+ h.clear();
+ m.forEach(d);
+ m.clear();
+ k.forEach(function (a) {
+ J(e, a.props, "script", c);
+ Ta(e, e, "script", a.props);
+ a.flushed = !0;
+ a.hint.flushed = !0;
+ });
+ k.clear();
+ p.forEach(d);
+ p.clear();
+ n.forEach(d);
+ n.clear();
+ q.forEach(d);
+ q.clear();
+ u.forEach(function (a) {
+ switch (a.type) {
+ case "title":
+ Oa(e, a.props, c);
+ break;
+ case "meta":
+ I(e, a.props, "meta", c);
+ break;
+ case "link":
+ F(e, a.props, c);
+ }
+ a.flushed = !0;
+ });
+ u.clear();
+ f = !0;
+ for (b = 0; b < e.length - 1; b++) l(a, e[b]);
+ b < e.length && (f = !!a.write(e[b]));
+ return f;
+}
+function bb(a, b) {
+ l(a, "[");
+ var c = "[";
+ b.forEach(function (b) {
+ if (!b.inShell)
+ if (b.flushed) l(a, c), l(a, K("" + b.href)), l(a, "]"), (c = ",[");
+ else {
+ l(a, c);
+ var d = b.precedence,
+ f = b.props;
+ l(a, K("" + b.href));
+ d = "" + d;
+ l(a, ",");
+ l(a, K(d));
+ for (var g in f)
+ if (r.call(f, g)) {
+ var h = f[g];
+ if (null != h)
+ switch (g) {
+ case "href":
+ case "rel":
+ case "precedence":
+ case "data-precedence":
+ break;
+ case "children":
+ case "dangerouslySetInnerHTML":
+ throw Error(
+ "link is a self-closing tag and must neither have `children` nor use `dangerouslySetInnerHTML`.",
+ );
+ default:
+ a: {
+ d = a;
+ var m = g,
+ k = m.toLowerCase();
+ switch (typeof h) {
+ case "function":
+ case "symbol":
+ break a;
+ }
+ switch (m) {
+ case "innerHTML":
+ case "dangerouslySetInnerHTML":
+ case "suppressContentEditableWarning":
+ case "suppressHydrationWarning":
+ case "style":
+ break a;
+ case "className":
+ k = "class";
+ break;
+ case "hidden":
+ if (!1 === h) break a;
+ break;
+ case "src":
+ case "href":
+ break;
+ default:
+ if (!ia(m)) break a;
+ }
+ if (!(2 < m.length) || ("o" !== m[0] && "O" !== m[0]) || ("n" !== m[1] && "N" !== m[1]))
+ (h = "" + h), l(d, ","), l(d, K(k)), l(d, ","), l(d, K(h));
+ }
+ }
+ }
+ l(a, "]");
+ c = ",[";
+ b.flushed = !0;
+ b.hint.flushed = !0;
+ }
+ });
+ l(a, "]");
+}
+var cb = Symbol.for("react.element"),
+ db = Symbol.for("react.portal"),
+ eb = Symbol.for("react.fragment"),
+ fb = Symbol.for("react.strict_mode"),
+ gb = Symbol.for("react.profiler"),
+ hb = Symbol.for("react.provider"),
+ ib = Symbol.for("react.context"),
+ jb = Symbol.for("react.server_context"),
+ kb = Symbol.for("react.forward_ref"),
+ lb = Symbol.for("react.suspense"),
+ mb = Symbol.for("react.suspense_list"),
+ nb = Symbol.for("react.memo"),
+ ob = Symbol.for("react.lazy"),
+ pb = Symbol.for("react.scope"),
+ qb = Symbol.for("react.debug_trace_mode"),
+ rb = Symbol.for("react.offscreen"),
+ sb = Symbol.for("react.legacy_hidden"),
+ tb = Symbol.for("react.cache"),
+ ub = Symbol.for("react.default_value"),
+ vb = Symbol.for("react.memo_cache_sentinel"),
+ wb = Symbol.iterator;
+function xb(a) {
+ if (null == a) return null;
+ if ("function" === typeof a) return a.displayName || a.name || null;
+ if ("string" === typeof a) return a;
+ switch (a) {
+ case eb:
+ return "Fragment";
+ case db:
+ return "Portal";
+ case gb:
+ return "Profiler";
+ case fb:
+ return "StrictMode";
+ case lb:
+ return "Suspense";
+ case mb:
+ return "SuspenseList";
+ case tb:
+ return "Cache";
+ }
+ if ("object" === typeof a)
+ switch (a.$$typeof) {
+ case ib:
+ return (a.displayName || "Context") + ".Consumer";
+ case hb:
+ return (a._context.displayName || "Context") + ".Provider";
+ case kb:
+ var b = a.render;
+ a = a.displayName;
+ a || ((a = b.displayName || b.name || ""), (a = "" !== a ? "ForwardRef(" + a + ")" : "ForwardRef"));
+ return a;
+ case nb:
+ return (b = a.displayName || null), null !== b ? b : xb(a.type) || "Memo";
+ case ob:
+ b = a._payload;
+ a = a._init;
+ try {
+ return xb(a(b));
+ } catch (c) {
+ break;
+ }
+ case jb:
+ return (a.displayName || a._globalName) + ".Provider";
+ }
+ return null;
+}
+var yb = ba.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
+ zb = {};
+function Ab(a, b) {
+ a = a.contextTypes;
+ if (!a) return zb;
+ var c = {},
+ d;
+ for (d in a) c[d] = b[d];
+ return c;
+}
+var L = null;
+function Bb(a, b) {
+ if (a !== b) {
+ a.context._currentValue = a.parentValue;
+ a = a.parent;
+ var c = b.parent;
+ if (null === a) {
+ if (null !== c) throw Error("The stacks must reach the root at the same time. This is a bug in React.");
+ } else {
+ if (null === c) throw Error("The stacks must reach the root at the same time. This is a bug in React.");
+ Bb(a, c);
+ }
+ b.context._currentValue = b.value;
+ }
+}
+function Cb(a) {
+ a.context._currentValue = a.parentValue;
+ a = a.parent;
+ null !== a && Cb(a);
+}
+function Db(a) {
+ var b = a.parent;
+ null !== b && Db(b);
+ a.context._currentValue = a.value;
+}
+function Eb(a, b) {
+ a.context._currentValue = a.parentValue;
+ a = a.parent;
+ if (null === a)
+ throw Error("The depth must equal at least at zero before reaching the root. This is a bug in React.");
+ a.depth === b.depth ? Bb(a, b) : Eb(a, b);
+}
+function Fb(a, b) {
+ var c = b.parent;
+ if (null === c)
+ throw Error("The depth must equal at least at zero before reaching the root. This is a bug in React.");
+ a.depth === c.depth ? Bb(a, c) : Fb(a, c);
+ b.context._currentValue = b.value;
+}
+function Gb(a) {
+ var b = L;
+ b !== a &&
+ (null === b ? Db(a) : null === a ? Cb(b) : b.depth === a.depth ? Bb(b, a) : b.depth > a.depth ? Eb(b, a) : Fb(b, a),
+ (L = a));
+}
+var Hb = {
+ isMounted: function () {
+ return !1;
+ },
+ enqueueSetState: function (a, b) {
+ a = a._reactInternals;
+ null !== a.queue && a.queue.push(b);
+ },
+ enqueueReplaceState: function (a, b) {
+ a = a._reactInternals;
+ a.replace = !0;
+ a.queue = [b];
+ },
+ enqueueForceUpdate: function () {},
+};
+function Ib(a, b, c, d) {
+ var e = void 0 !== a.state ? a.state : null;
+ a.updater = Hb;
+ a.props = c;
+ a.state = e;
+ var f = { queue: [], replace: !1 };
+ a._reactInternals = f;
+ var g = b.contextType;
+ a.context = "object" === typeof g && null !== g ? g._currentValue : d;
+ g = b.getDerivedStateFromProps;
+ "function" === typeof g && ((g = g(c, e)), (e = null === g || void 0 === g ? e : x({}, e, g)), (a.state = e));
+ if (
+ "function" !== typeof b.getDerivedStateFromProps &&
+ "function" !== typeof a.getSnapshotBeforeUpdate &&
+ ("function" === typeof a.UNSAFE_componentWillMount || "function" === typeof a.componentWillMount)
+ )
+ if (
+ ((b = a.state),
+ "function" === typeof a.componentWillMount && a.componentWillMount(),
+ "function" === typeof a.UNSAFE_componentWillMount && a.UNSAFE_componentWillMount(),
+ b !== a.state && Hb.enqueueReplaceState(a, a.state, null),
+ null !== f.queue && 0 < f.queue.length)
+ )
+ if (((b = f.queue), (g = f.replace), (f.queue = null), (f.replace = !1), g && 1 === b.length)) a.state = b[0];
+ else {
+ f = g ? b[0] : a.state;
+ e = !0;
+ for (g = g ? 1 : 0; g < b.length; g++) {
+ var h = b[g];
+ h = "function" === typeof h ? h.call(a, f, c, d) : h;
+ null != h && (e ? ((e = !1), (f = x({}, f, h))) : x(f, h));
+ }
+ a.state = f;
+ }
+ else f.queue = null;
+}
+var Jb = { id: 1, overflow: "" };
+function Kb(a, b, c) {
+ var d = a.id;
+ a = a.overflow;
+ var e = 32 - Lb(d) - 1;
+ d &= ~(1 << e);
+ c += 1;
+ var f = 32 - Lb(b) + e;
+ if (30 < f) {
+ var g = e - (e % 5);
+ f = (d & ((1 << g) - 1)).toString(32);
+ d >>= g;
+ e -= g;
+ return { id: (1 << (32 - Lb(b) + e)) | (c << e) | d, overflow: f + a };
+ }
+ return { id: (1 << f) | (c << e) | d, overflow: a };
+}
+var Lb = Math.clz32 ? Math.clz32 : Mb,
+ Nb = Math.log,
+ Ob = Math.LN2;
+function Mb(a) {
+ a >>>= 0;
+ return 0 === a ? 32 : (31 - ((Nb(a) / Ob) | 0)) | 0;
+}
+var Pb = Error(
+ "Suspense Exception: This is not a real error! It's an implementation detail of `use` to interrupt the current render. You must either rethrow it immediately, or move the `use` call outside of the `try/catch` block. Capturing without rethrowing will lead to unexpected behavior.\n\nTo handle async errors, wrap your component in an error boundary, or call the promise's `.catch` method and pass the result to `use`",
+);
+function Qb() {}
+function Rb(a, b, c) {
+ c = a[c];
+ void 0 === c ? a.push(b) : c !== b && (b.then(Qb, Qb), (b = c));
+ switch (b.status) {
+ case "fulfilled":
+ return b.value;
+ case "rejected":
+ throw b.reason;
+ default:
+ if ("string" !== typeof b.status)
+ switch (
+ ((a = b),
+ (a.status = "pending"),
+ a.then(
+ function (a) {
+ if ("pending" === b.status) {
+ var c = b;
+ c.status = "fulfilled";
+ c.value = a;
+ }
+ },
+ function (a) {
+ if ("pending" === b.status) {
+ var c = b;
+ c.status = "rejected";
+ c.reason = a;
+ }
+ },
+ ),
+ b.status)
+ ) {
+ case "fulfilled":
+ return b.value;
+ case "rejected":
+ throw b.reason;
+ }
+ Sb = b;
+ throw Pb;
+ }
+}
+var Sb = null;
+function Tb() {
+ if (null === Sb) throw Error("Expected a suspended thenable. This is a bug in React. Please file an issue.");
+ var a = Sb;
+ Sb = null;
+ return a;
+}
+function Ub(a, b) {
+ return (a === b && (0 !== a || 1 / a === 1 / b)) || (a !== a && b !== b);
+}
+var Vb = "function" === typeof Object.is ? Object.is : Ub,
+ M = null,
+ Wb = null,
+ Xb = null,
+ N = null,
+ O = !1,
+ Yb = !1,
+ Q = 0,
+ R = 0,
+ S = null,
+ T = null,
+ Zb = 0;
+function U() {
+ if (null === M)
+ throw Error(
+ "Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:\n1. You might have mismatching versions of React and the renderer (such as React DOM)\n2. You might be breaking the Rules of Hooks\n3. You might have more than one copy of React in the same app\nSee https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.",
+ );
+ return M;
+}
+function $b() {
+ if (0 < Zb) throw Error("Rendered more hooks than during the previous render");
+ return { memoizedState: null, queue: null, next: null };
+}
+function ac() {
+ null === N
+ ? null === Xb
+ ? ((O = !1), (Xb = N = $b()))
+ : ((O = !0), (N = Xb))
+ : null === N.next
+ ? ((O = !1), (N = N.next = $b()))
+ : ((O = !0), (N = N.next));
+ return N;
+}
+function bc(a, b, c, d) {
+ for (; Yb; ) (Yb = !1), (R = Q = 0), (Zb += 1), (N = null), (c = a(b, d));
+ cc();
+ return c;
+}
+function dc() {
+ var a = S;
+ S = null;
+ return a;
+}
+function cc() {
+ Wb = M = null;
+ Yb = !1;
+ Xb = null;
+ Zb = 0;
+ N = T = null;
+}
+function ec(a, b) {
+ return "function" === typeof b ? b(a) : b;
+}
+function fc(a, b, c) {
+ M = U();
+ N = ac();
+ if (O) {
+ var d = N.queue;
+ b = d.dispatch;
+ if (null !== T && ((c = T.get(d)), void 0 !== c)) {
+ T.delete(d);
+ d = N.memoizedState;
+ do (d = a(d, c.action)), (c = c.next);
+ while (null !== c);
+ N.memoizedState = d;
+ return [d, b];
+ }
+ return [N.memoizedState, b];
+ }
+ a = a === ec ? ("function" === typeof b ? b() : b) : void 0 !== c ? c(b) : b;
+ N.memoizedState = a;
+ a = N.queue = { last: null, dispatch: null };
+ a = a.dispatch = gc.bind(null, M, a);
+ return [N.memoizedState, a];
+}
+function hc(a, b) {
+ M = U();
+ N = ac();
+ b = void 0 === b ? null : b;
+ if (null !== N) {
+ var c = N.memoizedState;
+ if (null !== c && null !== b) {
+ var d = c[1];
+ a: if (null === d) d = !1;
+ else {
+ for (var e = 0; e < d.length && e < b.length; e++)
+ if (!Vb(b[e], d[e])) {
+ d = !1;
+ break a;
+ }
+ d = !0;
+ }
+ if (d) return c[0];
+ }
+ }
+ a = a();
+ N.memoizedState = [a, b];
+ return a;
+}
+function gc(a, b, c) {
+ if (25 <= Zb) throw Error("Too many re-renders. React limits the number of renders to prevent an infinite loop.");
+ if (a === M)
+ if (((Yb = !0), (a = { action: c, next: null }), null === T && (T = new Map()), (c = T.get(b)), void 0 === c))
+ T.set(b, a);
+ else {
+ for (b = c; null !== b.next; ) b = b.next;
+ b.next = a;
+ }
+}
+function ic() {
+ throw Error("A function wrapped in useEvent can't be called during rendering.");
+}
+function jc() {
+ throw Error("startTransition cannot be called during server rendering.");
+}
+function kc() {
+ throw Error("Cache cannot be refreshed during server rendering.");
+}
+function lc() {}
+var nc = {
+ readContext: function (a) {
+ return a._currentValue;
+ },
+ useContext: function (a) {
+ U();
+ return a._currentValue;
+ },
+ useMemo: hc,
+ useReducer: fc,
+ useRef: function (a) {
+ M = U();
+ N = ac();
+ var b = N.memoizedState;
+ return null === b ? ((a = { current: a }), (N.memoizedState = a)) : b;
+ },
+ useState: function (a) {
+ return fc(ec, a);
+ },
+ useInsertionEffect: lc,
+ useLayoutEffect: function () {},
+ useCallback: function (a, b) {
+ return hc(function () {
+ return a;
+ }, b);
+ },
+ useImperativeHandle: lc,
+ useEffect: lc,
+ useDebugValue: lc,
+ useDeferredValue: function (a) {
+ U();
+ return a;
+ },
+ useTransition: function () {
+ U();
+ return [!1, jc];
+ },
+ useId: function () {
+ var a = Wb.treeContext;
+ var b = a.overflow;
+ a = a.id;
+ a = (a & ~(1 << (32 - Lb(a) - 1))).toString(32) + b;
+ var c = mc;
+ if (null === c)
+ throw Error("Invalid hook call. Hooks can only be called inside of the body of a function component.");
+ b = Q++;
+ a = ":" + c.idPrefix + "R" + a;
+ 0 < b && (a += "H" + b.toString(32));
+ return a + ":";
+ },
+ useMutableSource: function (a, b) {
+ U();
+ return b(a._source);
+ },
+ useSyncExternalStore: function (a, b, c) {
+ if (void 0 === c)
+ throw Error(
+ "Missing getServerSnapshot, which is required for server-rendered content. Will revert to client rendering.",
+ );
+ return c();
+ },
+ useCacheRefresh: function () {
+ return kc;
+ },
+ useEvent: function () {
+ return ic;
+ },
+ useMemoCache: function (a) {
+ for (var b = Array(a), c = 0; c < a; c++) b[c] = vb;
+ return b;
+ },
+ use: function (a) {
+ if (null !== a && "object" === typeof a) {
+ if ("function" === typeof a.then) {
+ var b = R;
+ R += 1;
+ null === S && (S = []);
+ return Rb(S, a, b);
+ }
+ if (a.$$typeof === ib || a.$$typeof === jb) return a._currentValue;
+ }
+ throw Error("An unsupported type was passed to use(): " + String(a));
+ },
+ },
+ mc = null,
+ oc = {
+ getCacheSignal: function () {
+ throw Error("Not implemented.");
+ },
+ getCacheForType: function () {
+ throw Error("Not implemented.");
+ },
+ },
+ pc = yb.ReactCurrentDispatcher,
+ qc = yb.ReactCurrentCache;
+function rc(a) {
+ console.error(a);
+ return null;
+}
+function W() {}
+function sc(a, b, c, d, e, f, g, h, m) {
+ var k = [],
+ p = new Set(),
+ n = {
+ preloadsMap: new Map(),
+ stylesMap: new Map(),
+ scriptsMap: new Map(),
+ headsMap: new Map(),
+ charset: null,
+ bases: new Set(),
+ preconnects: new Set(),
+ fontPreloads: new Set(),
+ precedences: new Map(),
+ usedStylePreloads: new Set(),
+ scripts: new Set(),
+ usedScriptPreloads: new Set(),
+ explicitStylePreloads: new Set(),
+ explicitScriptPreloads: new Set(),
+ headResources: new Set(),
+ structuredMetaKeys: new Map(),
+ boundaryResources: null,
+ };
+ b = {
+ destination: null,
+ responseState: b,
+ progressiveChunkSize: void 0 === d ? 12800 : d,
+ status: 0,
+ fatalError: null,
+ nextSegmentId: 0,
+ allPendingTasks: 0,
+ pendingRootTasks: 0,
+ resources: n,
+ completedRootSegment: null,
+ abortableTasks: p,
+ pingedTasks: k,
+ clientRenderedBoundaries: [],
+ completedBoundaries: [],
+ partialBoundaries: [],
+ preamble: [],
+ postamble: [],
+ onError: void 0 === e ? rc : e,
+ onAllReady: void 0 === f ? W : f,
+ onShellReady: void 0 === g ? W : g,
+ onShellError: void 0 === h ? W : h,
+ onFatalError: void 0 === m ? W : m,
+ };
+ c = tc(b, 0, null, c, !1, !1);
+ c.parentFlushed = !0;
+ a = uc(b, null, a, null, c, p, zb, null, Jb);
+ k.push(a);
+ return b;
+}
+function uc(a, b, c, d, e, f, g, h, m) {
+ a.allPendingTasks++;
+ null === d ? a.pendingRootTasks++ : d.pendingTasks++;
+ var k = {
+ node: c,
+ ping: function () {
+ var b = a.pingedTasks;
+ b.push(k);
+ 1 === b.length && vc(a);
+ },
+ blockedBoundary: d,
+ blockedSegment: e,
+ abortSet: f,
+ legacyContext: g,
+ context: h,
+ treeContext: m,
+ thenableState: b,
+ };
+ f.add(k);
+ return k;
+}
+function tc(a, b, c, d, e, f) {
+ return {
+ status: 0,
+ id: -1,
+ index: b,
+ parentFlushed: !1,
+ chunks: [],
+ children: [],
+ formatContext: d,
+ boundary: c,
+ lastPushedText: e,
+ textEmbedded: f,
+ };
+}
+function X(a, b) {
+ a = a.onError(b);
+ if (null != a && "string" !== typeof a)
+ throw Error(
+ 'onError returned something with a type other than "string". onError should return a string and may return null or undefined but must not return anything else. It received something of type "' +
+ typeof a +
+ '" instead',
+ );
+ return a;
+}
+function Y(a, b) {
+ var c = a.onShellError;
+ c(b);
+ c = a.onFatalError;
+ c(b);
+ null !== a.destination ? ((a.status = 2), da(a.destination, b)) : ((a.status = 1), (a.fatalError = b));
+}
+function wc(a, b, c, d) {
+ var e = c.render(),
+ f = d.childContextTypes;
+ if (null !== f && void 0 !== f) {
+ var g = b.legacyContext;
+ if ("function" !== typeof c.getChildContext) d = g;
+ else {
+ c = c.getChildContext();
+ for (var h in c)
+ if (!(h in f))
+ throw Error(
+ (xb(d) || "Unknown") + '.getChildContext(): key "' + h + '" is not defined in childContextTypes.',
+ );
+ d = x({}, g, c);
+ }
+ b.legacyContext = d;
+ Z(a, b, null, e);
+ b.legacyContext = g;
+ } else Z(a, b, null, e);
+}
+function xc(a, b) {
+ if (a && a.defaultProps) {
+ b = x({}, b);
+ a = a.defaultProps;
+ for (var c in a) void 0 === b[c] && (b[c] = a[c]);
+ return b;
+ }
+ return b;
+}
+function yc(a, b, c, d, e, f) {
+ if ("function" === typeof d)
+ if (d.prototype && d.prototype.isReactComponent)
+ (c = Ab(d, b.legacyContext)),
+ (f = d.contextType),
+ (f = new d(e, "object" === typeof f && null !== f ? f._currentValue : c)),
+ Ib(f, d, e, c),
+ wc(a, b, f, d);
+ else {
+ f = Ab(d, b.legacyContext);
+ M = {};
+ Wb = b;
+ R = Q = 0;
+ S = c;
+ c = d(e, f);
+ c = bc(d, e, c, f);
+ var g = 0 !== Q;
+ if ("object" === typeof c && null !== c && "function" === typeof c.render && void 0 === c.$$typeof)
+ Ib(c, d, e, f), wc(a, b, c, d);
+ else if (g) {
+ e = b.treeContext;
+ b.treeContext = Kb(e, 1, 0);
+ try {
+ Z(a, b, null, c);
+ } finally {
+ b.treeContext = e;
+ }
+ } else Z(a, b, null, c);
+ }
+ else if ("string" === typeof d)
+ (c = b.blockedSegment),
+ (f = Sa(c.chunks, a.preamble, d, e, a.responseState, c.formatContext, c.lastPushedText)),
+ (c.lastPushedText = !1),
+ (g = c.formatContext),
+ (c.formatContext = Ja(g, d, e)),
+ zc(a, b, f),
+ (c.formatContext = g),
+ Ta(c.chunks, a.postamble, d),
+ (c.lastPushedText = !1);
+ else {
+ switch (d) {
+ case sb:
+ case qb:
+ case fb:
+ case gb:
+ case eb:
+ Z(a, b, null, e.children);
+ return;
+ case rb:
+ "hidden" !== e.mode && Z(a, b, null, e.children);
+ return;
+ case mb:
+ Z(a, b, null, e.children);
+ return;
+ case pb:
+ throw Error("ReactDOMServer does not yet support scope components.");
+ case lb:
+ a: {
+ d = b.blockedBoundary;
+ c = b.blockedSegment;
+ f = e.fallback;
+ e = e.children;
+ g = new Set();
+ var h = {
+ id: null,
+ rootSegmentID: -1,
+ parentFlushed: !1,
+ pendingTasks: 0,
+ forceClientRender: !1,
+ completedSegments: [],
+ byteSize: 0,
+ fallbackAbortableTasks: g,
+ errorDigest: null,
+ resources: new Set(),
+ },
+ m = tc(a, c.chunks.length, h, c.formatContext, !1, !1);
+ c.children.push(m);
+ c.lastPushedText = !1;
+ var k = tc(a, 0, null, c.formatContext, !1, !1);
+ k.parentFlushed = !0;
+ b.blockedBoundary = h;
+ b.blockedSegment = k;
+ a.resources.boundaryResources = h.resources;
+ try {
+ if (
+ (zc(a, b, e),
+ k.lastPushedText && k.textEmbedded && k.chunks.push("\x3c!-- --\x3e"),
+ (k.status = 1),
+ 0 === h.pendingTasks &&
+ (null !== a.completedRootSegment || 0 < a.pendingRootTasks) &&
+ Da(a.resources, h.resources),
+ Ac(h, k),
+ 0 === h.pendingTasks)
+ )
+ break a;
+ } catch (p) {
+ (k.status = 4), (h.forceClientRender = !0), (h.errorDigest = X(a, p));
+ } finally {
+ (a.resources.boundaryResources = d ? d.resources : null), (b.blockedBoundary = d), (b.blockedSegment = c);
+ }
+ b = uc(a, null, f, d, m, g, b.legacyContext, b.context, b.treeContext);
+ a.pingedTasks.push(b);
+ }
+ return;
+ }
+ if ("object" === typeof d && null !== d)
+ switch (d.$$typeof) {
+ case kb:
+ d = d.render;
+ M = {};
+ Wb = b;
+ R = Q = 0;
+ S = c;
+ c = d(e, f);
+ e = bc(d, e, c, f);
+ if (0 !== Q) {
+ d = b.treeContext;
+ b.treeContext = Kb(d, 1, 0);
+ try {
+ Z(a, b, null, e);
+ } finally {
+ b.treeContext = d;
+ }
+ } else Z(a, b, null, e);
+ return;
+ case nb:
+ d = d.type;
+ e = xc(d, e);
+ yc(a, b, c, d, e, f);
+ return;
+ case hb:
+ c = e.children;
+ d = d._context;
+ e = e.value;
+ f = d._currentValue;
+ d._currentValue = e;
+ g = L;
+ L = e = {
+ parent: g,
+ depth: null === g ? 0 : g.depth + 1,
+ context: d,
+ parentValue: f,
+ value: e,
+ };
+ b.context = e;
+ Z(a, b, null, c);
+ a = L;
+ if (null === a) throw Error("Tried to pop a Context at the root of the app. This is a bug in React.");
+ e = a.parentValue;
+ a.context._currentValue = e === ub ? a.context._defaultValue : e;
+ a = L = a.parent;
+ b.context = a;
+ return;
+ case ib:
+ e = e.children;
+ e = e(d._currentValue);
+ Z(a, b, null, e);
+ return;
+ case ob:
+ f = d._init;
+ d = f(d._payload);
+ e = xc(d, e);
+ yc(a, b, c, d, e, void 0);
+ return;
+ }
+ throw Error(
+ "Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: " +
+ ((null == d ? d : typeof d) + "."),
+ );
+ }
+}
+function Z(a, b, c, d) {
+ b.node = d;
+ if ("object" === typeof d && null !== d) {
+ switch (d.$$typeof) {
+ case cb:
+ yc(a, b, c, d.type, d.props, d.ref);
+ return;
+ case db:
+ throw Error(
+ "Portals are not currently supported by the server renderer. Render them conditionally so that they only appear on the client render.",
+ );
+ case ob:
+ c = d._init;
+ d = c(d._payload);
+ Z(a, b, null, d);
+ return;
+ }
+ if (ra(d)) {
+ Bc(a, b, d);
+ return;
+ }
+ null === d || "object" !== typeof d
+ ? (c = null)
+ : ((c = (wb && d[wb]) || d["@@iterator"]), (c = "function" === typeof c ? c : null));
+ if (c && (c = c.call(d))) {
+ d = c.next();
+ if (!d.done) {
+ var e = [];
+ do e.push(d.value), (d = c.next());
+ while (!d.done);
+ Bc(a, b, e);
+ }
+ return;
+ }
+ a = Object.prototype.toString.call(d);
+ throw Error(
+ "Objects are not valid as a React child (found: " +
+ ("[object Object]" === a ? "object with keys {" + Object.keys(d).join(", ") + "}" : a) +
+ "). If you meant to render a collection of children, use an array instead.",
+ );
+ }
+ "string" === typeof d
+ ? ((c = b.blockedSegment), (c.lastPushedText = Ka(b.blockedSegment.chunks, d, a.responseState, c.lastPushedText)))
+ : "number" === typeof d &&
+ ((c = b.blockedSegment),
+ (c.lastPushedText = Ka(b.blockedSegment.chunks, "" + d, a.responseState, c.lastPushedText)));
+}
+function Bc(a, b, c) {
+ for (var d = c.length, e = 0; e < d; e++) {
+ var f = b.treeContext;
+ b.treeContext = Kb(f, d, e);
+ try {
+ zc(a, b, c[e]);
+ } finally {
+ b.treeContext = f;
+ }
+ }
+}
+function zc(a, b, c) {
+ var d = b.blockedSegment.formatContext,
+ e = b.legacyContext,
+ f = b.context;
+ try {
+ return Z(a, b, null, c);
+ } catch (k) {
+ if ((cc(), (c = k === Pb ? Tb() : k), "object" === typeof c && null !== c && "function" === typeof c.then)) {
+ var g = dc(),
+ h = b.blockedSegment,
+ m = tc(a, h.chunks.length, null, h.formatContext, h.lastPushedText, !0);
+ h.children.push(m);
+ h.lastPushedText = !1;
+ a = uc(a, g, b.node, b.blockedBoundary, m, b.abortSet, b.legacyContext, b.context, b.treeContext).ping;
+ c.then(a, a);
+ b.blockedSegment.formatContext = d;
+ b.legacyContext = e;
+ b.context = f;
+ Gb(f);
+ } else throw ((b.blockedSegment.formatContext = d), (b.legacyContext = e), (b.context = f), Gb(f), c);
+ }
+}
+function Cc(a) {
+ var b = a.blockedBoundary;
+ a = a.blockedSegment;
+ a.status = 3;
+ Dc(this, b, a);
+}
+function Ec(a, b, c) {
+ var d = a.blockedBoundary;
+ a.blockedSegment.status = 3;
+ null === d
+ ? (b.allPendingTasks--, 1 !== b.status && 2 !== b.status && (X(b, c), Y(b, c)))
+ : (d.pendingTasks--,
+ d.forceClientRender ||
+ ((d.forceClientRender = !0),
+ (d.errorDigest = b.onError(c)),
+ d.parentFlushed && b.clientRenderedBoundaries.push(d)),
+ d.fallbackAbortableTasks.forEach(function (a) {
+ return Ec(a, b, c);
+ }),
+ d.fallbackAbortableTasks.clear(),
+ b.allPendingTasks--,
+ 0 === b.allPendingTasks && ((a = b.onAllReady), a()));
+}
+function Ac(a, b) {
+ if (0 === b.chunks.length && 1 === b.children.length && null === b.children[0].boundary) {
+ var c = b.children[0];
+ c.id = b.id;
+ c.parentFlushed = !0;
+ 1 === c.status && Ac(a, c);
+ } else a.completedSegments.push(b);
+}
+function Dc(a, b, c) {
+ if (null === b) {
+ if (c.parentFlushed) {
+ if (null !== a.completedRootSegment) throw Error("There can only be one root segment. This is a bug in React.");
+ a.completedRootSegment = c;
+ }
+ a.pendingRootTasks--;
+ 0 === a.pendingRootTasks && ((a.onShellError = W), (b = a.onShellReady), b());
+ } else
+ b.pendingTasks--,
+ b.forceClientRender ||
+ (0 === b.pendingTasks
+ ? (c.parentFlushed && 1 === c.status && Ac(b, c),
+ (null !== a.completedRootSegment || 0 < a.pendingRootTasks) && Da(a.resources, b.resources),
+ b.parentFlushed && a.completedBoundaries.push(b),
+ b.fallbackAbortableTasks.forEach(Cc, a),
+ b.fallbackAbortableTasks.clear())
+ : c.parentFlushed &&
+ 1 === c.status &&
+ (Ac(b, c), 1 === b.completedSegments.length && b.parentFlushed && a.partialBoundaries.push(b)));
+ a.allPendingTasks--;
+ 0 === a.allPendingTasks && ((a = a.onAllReady), a());
+}
+function vc(a) {
+ if (2 !== a.status) {
+ var b = L,
+ c = pc.current;
+ pc.current = nc;
+ var d = qc.current;
+ qc.current = oc;
+ var e = a.resources;
+ sa.push(y);
+ y = e;
+ e = Ea.current;
+ Ea.current = va;
+ var f = mc;
+ mc = a.responseState;
+ try {
+ var g = a.pingedTasks,
+ h;
+ for (h = 0; h < g.length; h++) {
+ var m = g[h];
+ var k = a,
+ p = m.blockedBoundary;
+ k.resources.boundaryResources = p ? p.resources : null;
+ var n = m.blockedSegment;
+ if (0 === n.status) {
+ Gb(m.context);
+ try {
+ var q = m.thenableState;
+ m.thenableState = null;
+ Z(k, m, q, m.node);
+ n.lastPushedText && n.textEmbedded && n.chunks.push("\x3c!-- --\x3e");
+ m.abortSet.delete(m);
+ n.status = 1;
+ Dc(k, m.blockedBoundary, n);
+ } catch (aa) {
+ cc();
+ var u = aa === Pb ? Tb() : aa;
+ if ("object" === typeof u && null !== u && "function" === typeof u.then) {
+ var H = m.ping;
+ u.then(H, H);
+ m.thenableState = dc();
+ } else {
+ m.abortSet.delete(m);
+ n.status = 4;
+ var B = k,
+ E = m.blockedBoundary,
+ V = u,
+ P = X(B, V);
+ null === E
+ ? Y(B, V)
+ : (E.pendingTasks--,
+ E.forceClientRender ||
+ ((E.forceClientRender = !0),
+ (E.errorDigest = P),
+ E.parentFlushed && B.clientRenderedBoundaries.push(E)));
+ B.allPendingTasks--;
+ if (0 === B.allPendingTasks) {
+ var Mc = B.onAllReady;
+ Mc();
+ }
+ }
+ } finally {
+ k.resources.boundaryResources = null;
+ }
+ }
+ }
+ g.splice(0, h);
+ null !== a.destination && Fc(a, a.destination);
+ } catch (aa) {
+ X(a, aa), Y(a, aa);
+ } finally {
+ (mc = f), (pc.current = c), (qc.current = d), (y = sa.pop()), (Ea.current = e), c === nc && Gb(b);
+ }
+ }
+}
+function Gc(a, b, c) {
+ c.parentFlushed = !0;
+ switch (c.status) {
+ case 0:
+ var d = (c.id = a.nextSegmentId++);
+ c.lastPushedText = !1;
+ c.textEmbedded = !1;
+ a = a.responseState;
+ l(b, '<template id="');
+ l(b, a.placeholderPrefix);
+ a = d.toString(16);
+ l(b, a);
+ return !!b.write('"></template>');
+ case 1:
+ c.status = 2;
+ var e = !0;
+ d = c.chunks;
+ var f = 0;
+ c = c.children;
+ for (var g = 0; g < c.length; g++) {
+ for (e = c[g]; f < e.index; f++) l(b, d[f]);
+ e = Hc(a, b, e);
+ }
+ for (; f < d.length - 1; f++) l(b, d[f]);
+ f < d.length && (e = !!b.write(d[f]));
+ return e;
+ default:
+ throw Error(
+ "Aborted, errored or already flushed boundaries should not be flushed again. This is a bug in React.",
+ );
+ }
+}
+function Hc(a, b, c) {
+ var d = c.boundary;
+ if (null === d) return Gc(a, b, c);
+ d.parentFlushed = !0;
+ if (d.forceClientRender)
+ (d = d.errorDigest),
+ b.write("\x3c!--$!--\x3e"),
+ l(b, "<template"),
+ d && (l(b, ' data-dgst="'), l(b, w(d)), l(b, '"')),
+ b.write("></template>"),
+ Gc(a, b, c);
+ else if (0 < d.pendingTasks) {
+ d.rootSegmentID = a.nextSegmentId++;
+ 0 < d.completedSegments.length && a.partialBoundaries.push(d);
+ var e = a.responseState;
+ var f = e.nextSuspenseID++;
+ e = e.boundaryPrefix + f.toString(16);
+ d = d.id = e;
+ Ua(b, a.responseState, d);
+ Gc(a, b, c);
+ } else if (d.byteSize > a.progressiveChunkSize)
+ (d.rootSegmentID = a.nextSegmentId++), a.completedBoundaries.push(d), Ua(b, a.responseState, d.id), Gc(a, b, c);
+ else {
+ Ca(a.resources, d.resources);
+ b.write("\x3c!--$--\x3e");
+ c = d.completedSegments;
+ if (1 !== c.length)
+ throw Error("A previously unvisited boundary must have exactly one root segment. This is a bug in React.");
+ Hc(a, b, c[0]);
+ }
+ return !!b.write("\x3c!--/$--\x3e");
+}
+function Ic(a, b, c) {
+ Va(b, a.responseState, c.formatContext, c.id);
+ Hc(a, b, c);
+ return Wa(b, c.formatContext);
+}
+function Jc(a, b, c) {
+ a.resources.boundaryResources = c.resources;
+ for (var d = c.completedSegments, e = 0; e < d.length; e++) Kc(a, b, c, d[e]);
+ d.length = 0;
+ a = a.responseState;
+ d = c.id;
+ e = c.rootSegmentID;
+ c = c.resources;
+ var f;
+ a: {
+ for (f = c.values(); ; ) {
+ var g = f.next().value;
+ if (!g) break;
+ if (!g.inShell) {
+ f = !0;
+ break a;
+ }
+ }
+ f = !1;
+ }
+ l(b, a.startInlineScript);
+ f
+ ? a.sentCompleteBoundaryFunction
+ ? a.sentStyleInsertionFunction
+ ? l(b, '$RR("')
+ : ((a.sentStyleInsertionFunction = !0),
+ l(
+ b,
+ '$RM=new Map;\n$RR=function(p,q,v){function r(l){this.s=l}for(var t=$RC,u=$RM,m=new Map,n=document,g,e,f=n.querySelectorAll("link[data-precedence],style[data-precedence]"),d=0;e=f[d++];)m.set(e.dataset.precedence,g=e);e=0;f=[];for(var c,h,b,a;c=v[e++];){var k=0;h=c[k++];if(b=u.get(h))"l"!==b.s&&f.push(b);else{a=n.createElement("link");a.href=h;a.rel="stylesheet";for(a.dataset.precedence=d=c[k++];b=c[k++];)a.setAttribute(b,c[k++]);b=a._p=new Promise(function(l,w){a.onload=l;a.onerror=w});b.then(r.bind(b,\n"l"),r.bind(b,"e"));u.set(h,b);f.push(b);c=m.get(d)||g;c===g&&(g=a);m.set(d,a);c?c.parentNode.insertBefore(a,c.nextSibling):(d=n.head,d.insertBefore(a,d.firstChild))}}Promise.all(f).then(t.bind(null,p,q,""),t.bind(null,p,q,"Resource failed to load"))};;$RR("',
+ ))
+ : ((a.sentCompleteBoundaryFunction = !0),
+ (a.sentStyleInsertionFunction = !0),
+ l(
+ b,
+ '$RC=function(b,c,e){c=document.getElementById(c);c.parentNode.removeChild(c);var a=document.getElementById(b);if(a){b=a.previousSibling;if(e)b.data="$!",a.setAttribute("data-dgst",e);else{e=b.parentNode;a=b.nextSibling;var f=0;do{if(a&&8===a.nodeType){var d=a.data;if("/$"===d)if(0===f)break;else f--;else"$"!==d&&"$?"!==d&&"$!"!==d||f++}d=a.nextSibling;e.removeChild(a);a=d}while(a);for(;c.firstChild;)e.insertBefore(c.firstChild,a);b.data="$"}b._reactRetry&&b._reactRetry()}};;$RM=new Map;\n$RR=function(p,q,v){function r(l){this.s=l}for(var t=$RC,u=$RM,m=new Map,n=document,g,e,f=n.querySelectorAll("link[data-precedence],style[data-precedence]"),d=0;e=f[d++];)m.set(e.dataset.precedence,g=e);e=0;f=[];for(var c,h,b,a;c=v[e++];){var k=0;h=c[k++];if(b=u.get(h))"l"!==b.s&&f.push(b);else{a=n.createElement("link");a.href=h;a.rel="stylesheet";for(a.dataset.precedence=d=c[k++];b=c[k++];)a.setAttribute(b,c[k++]);b=a._p=new Promise(function(l,w){a.onload=l;a.onerror=w});b.then(r.bind(b,\n"l"),r.bind(b,"e"));u.set(h,b);f.push(b);c=m.get(d)||g;c===g&&(g=a);m.set(d,a);c?c.parentNode.insertBefore(a,c.nextSibling):(d=n.head,d.insertBefore(a,d.firstChild))}}Promise.all(f).then(t.bind(null,p,q,""),t.bind(null,p,q,"Resource failed to load"))};;$RR("',
+ ))
+ : a.sentCompleteBoundaryFunction
+ ? l(b, '$RC("')
+ : ((a.sentCompleteBoundaryFunction = !0),
+ l(
+ b,
+ '$RC=function(b,c,e){c=document.getElementById(c);c.parentNode.removeChild(c);var a=document.getElementById(b);if(a){b=a.previousSibling;if(e)b.data="$!",a.setAttribute("data-dgst",e);else{e=b.parentNode;a=b.nextSibling;var f=0;do{if(a&&8===a.nodeType){var d=a.data;if("/$"===d)if(0===f)break;else f--;else"$"!==d&&"$?"!==d&&"$!"!==d||f++}d=a.nextSibling;e.removeChild(a);a=d}while(a);for(;c.firstChild;)e.insertBefore(c.firstChild,a);b.data="$"}b._reactRetry&&b._reactRetry()}};;$RC("',
+ ));
+ if (null === d) throw Error("An ID must have been assigned before we can complete the boundary.");
+ e = e.toString(16);
+ l(b, d);
+ l(b, '","');
+ l(b, a.segmentPrefix);
+ l(b, e);
+ f ? (l(b, '",'), bb(b, c)) : l(b, '"');
+ return !!b.write(")\x3c/script>");
+}
+function Kc(a, b, c, d) {
+ if (2 === d.status) return !0;
+ var e = d.id;
+ if (-1 === e) {
+ if (-1 === (d.id = c.rootSegmentID))
+ throw Error("A root segment ID must have been assigned by now. This is a bug in React.");
+ return Ic(a, b, d);
+ }
+ Ic(a, b, d);
+ a = a.responseState;
+ l(b, a.startInlineScript);
+ a.sentCompleteSegmentFunction
+ ? l(b, '$RS("')
+ : ((a.sentCompleteSegmentFunction = !0),
+ l(
+ b,
+ '$RS=function(a,b){a=document.getElementById(a);b=document.getElementById(b);for(a.parentNode.removeChild(a);a.firstChild;)b.parentNode.insertBefore(a.firstChild,b);b.parentNode.removeChild(b)};;$RS("',
+ ));
+ l(b, a.segmentPrefix);
+ e = e.toString(16);
+ l(b, e);
+ l(b, '","');
+ l(b, a.placeholderPrefix);
+ l(b, e);
+ return !!b.write('")\x3c/script>');
+}
+function Fc(a, b) {
+ try {
+ var c,
+ d = a.completedRootSegment;
+ if (null !== d)
+ if (0 === a.pendingRootTasks) {
+ var e = a.preamble;
+ for (c = 0; c < e.length; c++) l(b, e[c]);
+ $a(b, a.resources, a.responseState);
+ Hc(a, b, d);
+ a.completedRootSegment = null;
+ var f = a.responseState.bootstrapChunks;
+ for (d = 0; d < f.length - 1; d++) l(b, f[d]);
+ d < f.length && b.write(f[d]);
+ } else return;
+ else ab(b, a.resources, a.responseState);
+ var g = a.clientRenderedBoundaries;
+ for (c = 0; c < g.length; c++) {
+ var h = g[c];
+ f = b;
+ var m = a.responseState,
+ k = h.id,
+ p = h.errorDigest,
+ n = h.errorMessage,
+ q = h.errorComponentStack;
+ l(f, m.startInlineScript);
+ m.sentClientRenderFunction
+ ? l(f, '$RX("')
+ : ((m.sentClientRenderFunction = !0),
+ l(
+ f,
+ '$RX=function(b,c,d,e){var a=document.getElementById(b);a&&(b=a.previousSibling,b.data="$!",a=a.dataset,c&&(a.dgst=c),d&&(a.msg=d),e&&(a.stck=e),b._reactRetry&&b._reactRetry())};;$RX("',
+ ));
+ if (null === k) throw Error("An ID must have been assigned before we can complete the boundary.");
+ l(f, k);
+ l(f, '"');
+ if (p || n || q) l(f, ","), l(f, Ya(p || ""));
+ if (n || q) l(f, ","), l(f, Ya(n || ""));
+ q && (l(f, ","), l(f, Ya(q)));
+ if (!f.write(")\x3c/script>")) {
+ a.destination = null;
+ c++;
+ g.splice(0, c);
+ return;
+ }
+ }
+ g.splice(0, c);
+ var u = a.completedBoundaries;
+ for (c = 0; c < u.length; c++)
+ if (!Jc(a, b, u[c])) {
+ a.destination = null;
+ c++;
+ u.splice(0, c);
+ return;
+ }
+ u.splice(0, c);
+ var H = a.partialBoundaries;
+ for (c = 0; c < H.length; c++) {
+ var B = H[c];
+ a: {
+ g = a;
+ h = b;
+ g.resources.boundaryResources = B.resources;
+ var E = B.completedSegments;
+ for (m = 0; m < E.length; m++)
+ if (!Kc(g, h, B, E[m])) {
+ m++;
+ E.splice(0, m);
+ var V = !1;
+ break a;
+ }
+ E.splice(0, m);
+ V = !0;
+ }
+ if (!V) {
+ a.destination = null;
+ c++;
+ H.splice(0, c);
+ return;
+ }
+ }
+ H.splice(0, c);
+ var P = a.completedBoundaries;
+ for (c = 0; c < P.length; c++)
+ if (!Jc(a, b, P[c])) {
+ a.destination = null;
+ c++;
+ P.splice(0, c);
+ return;
+ }
+ P.splice(0, c);
+ } finally {
+ if (
+ 0 === a.allPendingTasks &&
+ 0 === a.pingedTasks.length &&
+ 0 === a.clientRenderedBoundaries.length &&
+ 0 === a.completedBoundaries.length
+ ) {
+ a = a.postamble;
+ for (c = 0; c < a.length; c++) l(b, a[c]);
+ b.end();
+ }
+ }
+}
+function Lc(a, b) {
+ try {
+ var c = a.abortableTasks;
+ if (0 < c.size) {
+ var d = void 0 === b ? Error("The render was aborted by the server without a reason.") : b;
+ c.forEach(function (b) {
+ return Ec(b, a, d);
+ });
+ c.clear();
+ }
+ null !== a.destination && Fc(a, a.destination);
+ } catch (e) {
+ X(a, e), Y(a, e);
+ }
+}
+exports.renderToNodeStream = function () {
+ throw Error(
+ "ReactDOMServer.renderToNodeStream(): The Node Stream API is not available in Bun. Use ReactDOMServer.renderToReadableStream() instead.",
+ );
+};
+exports.renderToReadableStream = function (a, b) {
+ return new Promise(function (c, d) {
+ var e,
+ f,
+ g = new Promise(function (a, b) {
+ f = a;
+ e = b;
+ }),
+ h = sc(
+ a,
+ Ha(
+ b ? b.identifierPrefix : void 0,
+ b ? b.nonce : void 0,
+ b ? b.bootstrapScriptContent : void 0,
+ b ? b.bootstrapScripts : void 0,
+ b ? b.bootstrapModules : void 0,
+ b ? b.unstable_externalRuntimeSrc : void 0,
+ ),
+ Ia(b ? b.namespaceURI : void 0),
+ b ? b.progressiveChunkSize : void 0,
+ b ? b.onError : void 0,
+ f,
+ function () {
+ var a = new ReadableStream(
+ {
+ type: "direct",
+ pull: function (a) {
+ if (1 === h.status) (h.status = 2), da(a, h.fatalError);
+ else if (2 !== h.status && null === h.destination) {
+ h.destination = a;
+ try {
+ Fc(h, a);
+ } catch (q) {
+ X(h, q), Y(h, q);
+ }
+ }
+ },
+ cancel: function () {
+ Lc(h);
+ },
+ },
+ { highWaterMark: 2048 },
+ );
+ a.allReady = g;
+ c(a);
+ },
+ function (a) {
+ g.catch(function () {});
+ d(a);
+ },
+ e,
+ );
+ if (b && b.signal) {
+ var m = b.signal;
+ if (m.aborted) Lc(h, m.reason);
+ else {
+ var k = function () {
+ Lc(h, m.reason);
+ m.removeEventListener("abort", k);
+ };
+ m.addEventListener("abort", k);
+ }
+ }
+ vc(h);
+ });
+};
+exports.renderToStaticNodeStream = function () {
+ throw Error(
+ "ReactDOMServer.renderToStaticNodeStream(): The Node Stream API is not available in Bun. Use ReactDOMServer.renderToReadableStream() instead.",
+ );
+};
+exports.version = "18.2.0";
diff --git a/test/js/third_party/react-dom/react-dom.test.tsx b/test/js/third_party/react-dom/react-dom.test.tsx
new file mode 100644
index 000000000..2f1309fb8
--- /dev/null
+++ b/test/js/third_party/react-dom/react-dom.test.tsx
@@ -0,0 +1,285 @@
+import {
+ concatArrayBuffers,
+ readableStreamToArray,
+ readableStreamToArrayBuffer,
+ readableStreamToBlob,
+ readableStreamToText,
+ serve,
+} from "bun";
+import { heapStats } from "bun:jsc";
+import { describe, expect, it } from "bun:test";
+import { renderToReadableStream as renderToReadableStreamBrowser } from "react-dom/server.browser";
+import { gc } from "harness";
+import { renderToReadableStream as renderToReadableStreamBun } from "./react-dom-server.bun.cjs";
+import React from "react";
+
+Object.defineProperty(renderToReadableStreamBrowser, "name", {
+ value: "server.browser",
+});
+Object.defineProperty(renderToReadableStreamBun, "name", {
+ value: "server.bun",
+});
+var port = 8908;
+
+const fixtures = [
+ // Needs at least six variations
+ // - < 8 chars, latin1
+ // - 8+ chars, latin1
+ // - 16+ chars, latin1
+ // - < 8 chars, utf16
+ // - 8+ chars, utf16
+ // - 16+ chars, utf16
+ ["<a>b</a>", <a>b</a>],
+ ["<span>Hello World!</span>", <span>Hello World!</span>],
+ ["<a></a>", <a />],
+ ["<span>😋</span>", <span>😋</span>],
+ ["<a>😋</a>", <a>😋</a>],
+ ["<span>Hello World! 😋</span>", <span>Hello World! 😋</span>],
+ [
+ "<span>Hello World!</span>😋",
+ <>
+ <span>Hello World!</span>😋
+ </>,
+ ],
+ [
+ "<span>😋Hello World!</span>",
+ <>
+ <span>😋Hello World!</span>
+ </>,
+ ],
+ ["😋", <>😋</>],
+ ["l😋l", <>l😋l</>],
+ ["lo😋", <>lo😋</>],
+ ["😋lo", <>😋lo</>],
+ [
+ "😋<span>Hello World!</span>",
+ <>
+ 😋
+ <span>Hello World!</span>
+ </>,
+ ],
+ [
+ "😋😋😋😋<span>Hello World!</span>",
+ <>
+ 😋😋😋😋
+ <span>Hello World!</span>
+ </>,
+ ],
+ ["<span>Hello😋😋😋😋World!</span>", <span>Hello😋😋😋😋World!</span>],
+ [
+ "<span>Hello World!</span>😋😋😋😋",
+ <>
+ <span>Hello World!</span>
+ 😋😋😋😋
+ </>,
+ ],
+ [
+ "😋L😋l😋L😋<span>Alternating latin1 &amp; utf16</span>",
+ <>
+ 😋L😋l😋L😋<span>Alternating latin1 &amp; utf16</span>
+ </>,
+ ],
+ ["<span>Hello😋L😋l😋L😋World!</span>", <span>Hello😋L😋l😋L😋World!</span>],
+ [
+ "<span>Hello World!</span>😋L😋l😋L😋",
+ <>
+ <span>Hello World!</span>
+ 😋L😋l😋L😋
+ </>,
+ ],
+];
+
+describe("React", () => {
+ it("React.createContext works", () => {
+ expect(typeof React.createContext).toBe("function");
+ const pleaseDontThrow = React.createContext({ foo: true });
+ expect((pleaseDontThrow as any).$$typeof.description).toBe("react.context");
+
+ const pleaseDontThrow2 = (React as any).default.createContext({
+ foo: true,
+ });
+ expect(pleaseDontThrow2.$$typeof.description).toBe("react.context");
+ });
+});
+
+describe("ReactDOM", () => {
+ for (let renderToReadableStream of [renderToReadableStreamBun, renderToReadableStreamBrowser]) {
+ for (let [inputString, reactElement] of fixtures) {
+ describe(`${renderToReadableStream.name}(${inputString})`, () => {
+ it("Response.text()", async () => {
+ const stream = await renderToReadableStream(reactElement);
+ gc();
+ const response = new Response(stream);
+ gc();
+ try {
+ const text = await response.text();
+ gc();
+ expect(text.replaceAll("<!-- -->", "")).toBe(inputString);
+ gc();
+ } catch (e: any) {
+ console.log(e.stack);
+ throw e;
+ }
+ });
+ it("Response.arrayBuffer()", async () => {
+ const stream = await renderToReadableStream(reactElement);
+ gc();
+ const response = new Response(stream);
+ gc();
+ const text = new TextDecoder().decode(await response.arrayBuffer());
+ gc();
+ expect(text.replaceAll("<!-- -->", "")).toBe(inputString);
+ gc();
+ });
+ it("Response.blob()", async () => {
+ const stream = await renderToReadableStream(reactElement);
+ gc();
+ const response = new Response(stream);
+ gc();
+ const text = await (await response.blob()).text();
+ gc();
+ expect(text.replaceAll("<!-- -->", "")).toBe(inputString);
+ gc();
+ });
+ it("readableStreamToText(stream)", async () => {
+ const stream = await renderToReadableStream(reactElement);
+ gc();
+ const text = await readableStreamToText(stream);
+ gc();
+ expect(text.replaceAll("<!-- -->", "")).toBe(inputString);
+ gc();
+ });
+ it("readableStreamToBlob(stream)", async () => {
+ try {
+ const stream = await renderToReadableStream(reactElement);
+ gc();
+ const blob = await readableStreamToBlob(stream);
+ const text = await blob.text();
+ gc();
+ expect(text.replaceAll("<!-- -->", "")).toBe(inputString);
+ gc();
+ } catch (e: any) {
+ console.error(e.message);
+ console.error(e.stack);
+ throw e;
+ }
+ });
+ it("readableStreamToArray(stream)", async () => {
+ const stream = await renderToReadableStream(reactElement);
+ gc();
+ const array = await readableStreamToArray(stream);
+ const text =
+ renderToReadableStream === renderToReadableStreamBun
+ ? array.join("")
+ : new TextDecoder().decode(concatArrayBuffers(array as any[]));
+ gc();
+ expect(text.replaceAll("<!-- -->", "")).toBe(inputString);
+ gc();
+ });
+ it("readableStreamToArrayBuffer(stream)", async () => {
+ const stream = await renderToReadableStream(reactElement);
+ gc();
+ const arrayBuffer = await readableStreamToArrayBuffer(stream);
+ const text = new TextDecoder().decode(arrayBuffer);
+ gc();
+ expect(text.replaceAll("<!-- -->", "")).toBe(inputString);
+ gc();
+ });
+ it("for await (chunk of stream)", async () => {
+ const stream = await renderToReadableStream(reactElement);
+ gc();
+ const chunks: any = [];
+ for await (let chunk of stream) {
+ chunks.push(chunk);
+ }
+ const text = await new Response(chunks).text();
+ gc();
+ expect(text.replaceAll("<!-- -->", "")).toBe(inputString);
+ gc();
+ });
+
+ it("for await (chunk of stream) (arrayBuffer)", async () => {
+ const stream = await renderToReadableStream(reactElement);
+ gc();
+ const chunks: any[] = [];
+ for await (let chunk of stream) {
+ chunks.push(chunk);
+ }
+ const text = new TextDecoder().decode(await new Response(chunks as any).arrayBuffer());
+ gc();
+ expect(text.replaceAll("<!-- -->", "")).toBe(inputString);
+ gc();
+ });
+ });
+ }
+ }
+ for (let renderToReadableStream of [renderToReadableStreamBun, renderToReadableStreamBrowser]) {
+ // there is an event loop bug that causes deadlocks
+ // the bug is with `fetch`, not with the HTTP server
+ for (let [inputString, reactElement] of fixtures) {
+ describe(`${renderToReadableStream.name}(${inputString})`, () => {
+ it("http server, 1 request", async () => {
+ await (async function () {
+ var server;
+ try {
+ server = serve({
+ port: port++,
+ async fetch(req) {
+ return new Response(await renderToReadableStream(reactElement));
+ },
+ });
+ const resp = await fetch("http://localhost:" + server.port + "/");
+ expect((await resp.text()).replaceAll("<!-- -->", "")).toBe(inputString);
+ gc();
+ } catch (e) {
+ throw e;
+ } finally {
+ server?.stop();
+ gc();
+ }
+ })();
+ gc();
+ expect(heapStats().objectTypeCounts.ReadableHTTPResponseSinkController ?? 0).toBe(1);
+ });
+ const count = 4;
+ it(`http server, ${count} requests`, async () => {
+ var remain = count;
+ await (async function () {
+ var server;
+ try {
+ server = serve({
+ port: port++,
+ async fetch(req) {
+ return new Response(await renderToReadableStream(reactElement));
+ },
+ });
+ gc();
+ while (remain--) {
+ var attempt = remain + 1;
+ const response = await fetch("http://localhost:" + server.port + "/");
+ gc();
+ const result = await response.text();
+ try {
+ expect(result.replaceAll("<!-- -->", "")).toBe(inputString);
+ } catch (e: any) {
+ e.message += "\nAttempt: " + attempt;
+ throw e;
+ }
+
+ gc();
+ }
+ } catch (e) {
+ throw e;
+ } finally {
+ server.stop();
+ }
+ })();
+
+ const { ReadableHTTPResponseSinkController = 0 } = heapStats().objectTypeCounts;
+ expect(ReadableHTTPResponseSinkController).toBe(1);
+ expect(remain + 1).toBe(0);
+ });
+ });
+ }
+ }
+});
diff --git a/test/js/third_party/svelte/bun-loader-svelte.ts b/test/js/third_party/svelte/bun-loader-svelte.ts
new file mode 100644
index 000000000..f0a6e3419
--- /dev/null
+++ b/test/js/third_party/svelte/bun-loader-svelte.ts
@@ -0,0 +1,18 @@
+import { plugin } from "bun";
+
+await plugin({
+ name: "svelte loader",
+ async setup(builder) {
+ var { compile } = await import("svelte/compiler");
+ var { readFileSync } = await import("fs");
+ await 2;
+ builder.onLoad({ filter: /\.svelte$/ }, ({ path }) => ({
+ contents: compile(readFileSync(path, "utf8"), {
+ filename: path,
+ generate: "ssr",
+ }).js.code,
+ loader: "js",
+ }));
+ await 1;
+ },
+});
diff --git a/test/js/third_party/svelte/hello.svelte b/test/js/third_party/svelte/hello.svelte
new file mode 100644
index 000000000..05dac4294
--- /dev/null
+++ b/test/js/third_party/svelte/hello.svelte
@@ -0,0 +1,5 @@
+<script>
+ let name = "world";
+</script>
+
+<h1>Hello {name}!</h1>
diff --git a/test/js/third_party/svelte/package.json b/test/js/third_party/svelte/package.json
new file mode 100644
index 000000000..ae4958ccc
--- /dev/null
+++ b/test/js/third_party/svelte/package.json
@@ -0,0 +1,4 @@
+{
+ "name": "bun-loader-svelte",
+ "module": "./bun-loader-svelte.ts"
+}
diff --git a/test/js/third_party/svelte/svelte.test.ts b/test/js/third_party/svelte/svelte.test.ts
new file mode 100644
index 000000000..44e36cce2
--- /dev/null
+++ b/test/js/third_party/svelte/svelte.test.ts
@@ -0,0 +1,21 @@
+import { describe, expect, it } from "bun:test";
+import "./bun-loader-svelte";
+
+describe("require", () => {
+ it("SSRs `<h1>Hello world!</h1>` with Svelte", () => {
+ const { default: App } = require("./hello.svelte");
+ const { html } = App.render();
+
+ expect(html).toBe("<h1>Hello world!</h1>");
+ });
+});
+
+describe("dynamic import", () => {
+ it("SSRs `<h1>Hello world!</h1>` with Svelte", async () => {
+ const { default: App }: any = await import("./hello.svelte");
+
+ const { html } = App.render();
+
+ expect(html).toBe("<h1>Hello world!</h1>");
+ });
+});
diff --git a/test/js/web/abort/abort-signal-timeout.test.js b/test/js/web/abort/abort-signal-timeout.test.js
new file mode 100644
index 000000000..7d741b2ad
--- /dev/null
+++ b/test/js/web/abort/abort-signal-timeout.test.js
@@ -0,0 +1,12 @@
+import { expect, test } from "bun:test";
+
+test.skip("AbortSignal.timeout", done => {
+ const abort = AbortSignal.timeout(10);
+ abort.addEventListener("abort", event => {
+ done();
+ });
+
+ // AbortSignal.timeout doesn't keep the event loop / process alive
+ // so we set a no-op timeout
+ setTimeout(() => {}, 11);
+});
diff --git a/test/js/web/console/console-log.expected.txt b/test/js/web/console/console-log.expected.txt
new file mode 100644
index 000000000..97191c8be
--- /dev/null
+++ b/test/js/web/console/console-log.expected.txt
@@ -0,0 +1,46 @@
+Hello World!
+123
+-123
+123.567
+-123.567
+true
+false
+null
+undefined
+Symbol(Symbol Description)
+2000-06-27T02:24:34.304Z
+[ 123, 456, 789 ]
+{
+ name: "foo"
+}
+{
+ a: 123,
+ b: 456,
+ c: 789
+}
+{
+ a: {
+ b: {
+ c: 123
+ },
+ bacon: true
+ },
+ name: "bar"
+}
+Promise { <pending> }
+[Function]
+[Function: Foo]
+{}
+[Function: foooo]
+/FooRegex/
+Is it a bug or a feature that formatting numbers like 123 is colored
+String 123 should be 2nd word, 456 == 456 and percent s %s == What okay
+{
+ foo: {
+ name: "baz"
+ },
+ bar: [Circular]
+} am
+[
+ {}, {}, {}, {}
+]
diff --git a/test/js/web/console/console-log.js b/test/js/web/console/console-log.js
new file mode 100644
index 000000000..e23a3e9cb
--- /dev/null
+++ b/test/js/web/console/console-log.js
@@ -0,0 +1,54 @@
+console.log("Hello World!");
+console.log(123);
+console.log(-123);
+console.log(123.567);
+console.log(-123.567);
+console.log(true);
+console.log(false);
+console.log(null);
+console.log(undefined);
+console.log(Symbol("Symbol Description"));
+console.log(new Date(Math.pow(2, 34) * 56));
+console.log([123, 456, 789]);
+console.log({ name: "foo" });
+console.log({ a: 123, b: 456, c: 789 });
+console.log({
+ a: {
+ b: {
+ c: 123,
+ },
+ bacon: true,
+ },
+ name: "bar",
+});
+
+console.log(new Promise(() => {}));
+
+class Foo {}
+
+console.log(() => {});
+console.log(Foo);
+console.log(new Foo());
+console.log(function foooo() {});
+
+console.log(/FooRegex/);
+
+console.error("uh oh");
+console.time("Check");
+
+console.log("Is it a bug or a feature that formatting numbers like %d is colored", 123);
+//console.log(globalThis);
+
+console.log("String %s should be 2nd word, 456 == %s and percent s %s == %s", "123", "456", "%s", "What", "okay");
+
+const infinteLoop = {
+ foo: {
+ name: "baz",
+ },
+ bar: {},
+};
+
+infinteLoop.bar = infinteLoop;
+console.log(infinteLoop, "am");
+
+console.log(new Array(4).fill({}));
diff --git a/test/js/web/console/console-log.test.ts b/test/js/web/console/console-log.test.ts
new file mode 100644
index 000000000..98c8370de
--- /dev/null
+++ b/test/js/web/console/console-log.test.ts
@@ -0,0 +1,20 @@
+import { file, spawn } from "bun";
+import { expect, it } from "bun:test";
+import { bunExe } from "harness";
+
+it("should log to console correctly", async () => {
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), import.meta.dir + "/console-log.js"],
+ stdin: null,
+ stdout: "pipe",
+ stderr: "pipe",
+ env: {
+ BUN_DEBUG_QUIET_LOGS: "1",
+ },
+ });
+ expect(await exited).toBe(0);
+ expect(await new Response(stderr).text()).toBe("uh oh\n");
+ expect(await new Response(stdout).text()).toBe(
+ await new Response(file(import.meta.dir + "/console-log.expected.txt")).text(),
+ );
+});
diff --git a/test/js/web/crypto/web-crypto.test.ts b/test/js/web/crypto/web-crypto.test.ts
new file mode 100644
index 000000000..250282b96
--- /dev/null
+++ b/test/js/web/crypto/web-crypto.test.ts
@@ -0,0 +1,91 @@
+import { describe, expect, it } from "bun:test";
+
+describe("Web Crypto", () => {
+ it("has globals", () => {
+ expect(crypto.subtle !== undefined).toBe(true);
+ expect(CryptoKey.name).toBe("CryptoKey");
+ expect(SubtleCrypto.name).toBe("SubtleCrypto");
+ });
+ it("should encrypt and decrypt", async () => {
+ const key = await crypto.subtle.generateKey(
+ {
+ name: "AES-GCM",
+ length: 256,
+ },
+ true,
+ ["encrypt", "decrypt"],
+ );
+ const iv = crypto.getRandomValues(new Uint8Array(12));
+ const data = new TextEncoder().encode("Hello World!");
+ const encrypted = await crypto.subtle.encrypt(
+ {
+ name: "AES-GCM",
+ iv,
+ },
+ key,
+ data,
+ );
+ const decrypted = await crypto.subtle.decrypt(
+ {
+ name: "AES-GCM",
+ iv,
+ },
+ key,
+ encrypted,
+ );
+ expect(new TextDecoder().decode(decrypted)).toBe("Hello World!");
+ });
+
+ it("should verify and sign", async () => {
+ async function importKey(secret) {
+ return await crypto.subtle.importKey(
+ "raw",
+ new TextEncoder().encode(secret),
+ { name: "HMAC", hash: "SHA-256" },
+ false,
+ ["sign", "verify"],
+ );
+ }
+
+ async function signResponse(message, secret) {
+ const key = await importKey(secret);
+ const signature = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(message));
+
+ // Convert ArrayBuffer to Base64
+ return btoa(String.fromCharCode(...new Uint8Array(signature)));
+ }
+
+ async function verifySignature(message, signature, secret) {
+ const key = await importKey(secret);
+
+ // Convert Base64 to Uint8Array
+ const sigBuf = Uint8Array.from(atob(signature), c => c.charCodeAt(0));
+
+ return await crypto.subtle.verify("HMAC", key, sigBuf, new TextEncoder().encode(message));
+ }
+
+ const msg = `hello world`;
+ const SECRET = "secret";
+ const signature = await signResponse(msg, SECRET);
+
+ const isSigValid = await verifySignature(msg, signature, SECRET);
+ expect(isSigValid).toBe(true);
+ });
+});
+
+describe("Ed25519", () => {
+ describe("generateKey", () => {
+ it("should return CryptoKeys without namedCurve in algorithm field", async () => {
+ const { publicKey, privateKey } = (await crypto.subtle.generateKey("Ed25519", true, [
+ "sign",
+ "verify",
+ ])) as CryptoKeyPair;
+ expect(publicKey.algorithm!.name).toBe("Ed25519");
+ // @ts-ignore
+ expect(publicKey.algorithm!.namedCurve).toBe(undefined);
+ expect(privateKey.algorithm!.name).toBe("Ed25519");
+ // @ts-ignore
+ expect(privateKey.algorithm!.namedCurve).toBe(undefined);
+ });
+ });
+});
diff --git a/test/js/web/encoding/text-decoder.test.js b/test/js/web/encoding/text-decoder.test.js
new file mode 100644
index 000000000..abd4c2a72
--- /dev/null
+++ b/test/js/web/encoding/text-decoder.test.js
@@ -0,0 +1,243 @@
+import { expect, it, describe } from "bun:test";
+import { gc as gcTrace, withoutAggressiveGC } from "harness";
+
+const getByteLength = str => {
+ // returns the byte length of an utf8 string
+ var s = str.length;
+ for (var i = str.length - 1; i >= 0; i--) {
+ var code = str.charCodeAt(i);
+ if (code > 0x7f && code <= 0x7ff) s++;
+ else if (code > 0x7ff && code <= 0xffff) s += 2;
+ if (code >= 0xdc00 && code <= 0xdfff) i--; //trail surrogate
+ }
+ return s;
+};
+
+describe("TextDecoder", () => {
+ it("should not crash on empty text", () => {
+ const decoder = new TextDecoder();
+ gcTrace(true);
+ const fixtures = [new Uint8Array(), new Uint8Array([]), new Buffer(0), new ArrayBuffer(0), new Uint16Array(0)];
+
+ for (let input of fixtures) {
+ expect(decoder.decode(input)).toBe("");
+ }
+
+ // Cause a de-opt
+ try {
+ decoder.decode([NaN, Symbol("s")]);
+ } catch (e) {}
+
+ // DOMJIT test
+ for (let i = 0; i < 90000; i++) {
+ decoder.decode(fixtures[0]);
+ }
+
+ gcTrace(true);
+ });
+ it("should decode ascii text", () => {
+ const decoder = new TextDecoder("latin1");
+ gcTrace(true);
+ expect(decoder.encoding).toBe("windows-1252");
+ gcTrace(true);
+ expect(decoder.decode(new Uint8Array([0x41, 0x42, 0x43]))).toBe("ABC");
+ gcTrace(true);
+
+ // hit the SIMD code path
+ const result = [
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33,
+ ];
+ gcTrace(true);
+ expect(decoder.decode(Uint8Array.from(result))).toBe(String.fromCharCode(...result));
+ gcTrace(true);
+ });
+
+ it("should decode unicode text", () => {
+ const decoder = new TextDecoder();
+ gcTrace(true);
+ const inputBytes = [226, 157, 164, 239, 184, 143, 32, 82, 101, 100, 32, 72, 101, 97, 114, 116];
+ for (var repeat = 1; repeat < 100; repeat++) {
+ var text = `❤️ Red Heart`.repeat(repeat);
+
+ var bytes = Array.from({ length: repeat }, () => inputBytes).flat();
+ var decoded = decoder.decode(Uint8Array.from(bytes));
+ expect(decoder.encoding).toBe("utf-8");
+ expect(decoded).toBe(text);
+ gcTrace(true);
+ }
+ });
+
+ describe("typedArrays", () => {
+ var text = `ABC DEF GHI JKL MNO PQR STU VWX YZ ABC DEF GHI JKL MNO PQR STU V`;
+ var bytes = new TextEncoder().encode(text);
+ var decoder = new TextDecoder();
+ for (let TypedArray of [
+ Uint8Array,
+ Uint16Array,
+ Uint32Array,
+ Int8Array,
+ Int16Array,
+ Int32Array,
+ Float32Array,
+ Float64Array,
+ DataView,
+ BigInt64Array,
+ BigUint64Array,
+ ]) {
+ it(`should decode ${TypedArray.name}`, () => {
+ const decoded = decoder.decode(new TypedArray(bytes.buffer));
+ expect(decoded).toBe(text);
+ });
+ }
+
+ it("DOMJIT call", () => {
+ const array = new Uint8Array(bytes.buffer);
+ withoutAggressiveGC(() => {
+ for (let i = 0; i < 100_000; i++) {
+ const decoded = decoder.decode(array);
+ expect(decoded).toBe(text);
+ }
+ });
+ });
+ });
+
+ it("should decode unicode text with multiple consecutive emoji", () => {
+ const decoder = new TextDecoder();
+ const encoder = new TextEncoder();
+ gcTrace(true);
+ var text = `❤️❤️❤️❤️❤️❤️ Red Heart`;
+
+ text += ` ✨ Sparkles 🔥 Fire 😀 😃 😄 😁 😆 😅 😂 🤣 🥲 ☺️ 😊 😇 🙂 🙃 😉 😌 😍 🥰 😘 😗 😙 😚 😋 😛 😝 😜 🤪 🤨 🧐 🤓 😎 🥸 🤩 🥳 😏 😒 😞 😔 😟 😕 🙁 ☹️ 😣 😖 😫 😩 🥺 😢 😭 😤 😠 😡 🤬 🤯 😳 🥵 🥶 😱 😨 😰`;
+ gcTrace(true);
+ expect(decoder.decode(encoder.encode(text))).toBe(text);
+ gcTrace(true);
+ const bytes = new Uint8Array(getByteLength(text) * 8);
+ gcTrace(true);
+ const amount = encoder.encodeInto(text, bytes);
+ gcTrace(true);
+ expect(decoder.decode(bytes.subarray(0, amount.written))).toBe(text);
+ gcTrace(true);
+ });
+});
+
+it("truncated sequences", () => {
+ const assert_equals = (a, b) => expect(a).toBe(b);
+
+ // Truncated sequences
+ assert_equals(new TextDecoder().decode(new Uint8Array([0xf0])), "\uFFFD");
+ assert_equals(new TextDecoder().decode(new Uint8Array([0xf0, 0x9f])), "\uFFFD");
+ assert_equals(new TextDecoder().decode(new Uint8Array([0xf0, 0x9f, 0x92])), "\uFFFD");
+
+ // Errors near end-of-queue
+ assert_equals(new TextDecoder().decode(new Uint8Array([0xf0, 0x9f, 0x41])), "\uFFFDA");
+ assert_equals(new TextDecoder().decode(new Uint8Array([0xf0, 0x41, 0x42])), "\uFFFDAB");
+ assert_equals(new TextDecoder().decode(new Uint8Array([0xf0, 0x41, 0xf0])), "\uFFFDA\uFFFD");
+ assert_equals(new TextDecoder().decode(new Uint8Array([0xf0, 0x8f, 0x92])), "\uFFFD\uFFFD\uFFFD");
+});
diff --git a/test/js/web/encoding/text-encoder.test.js b/test/js/web/encoding/text-encoder.test.js
new file mode 100644
index 000000000..3d271026d
--- /dev/null
+++ b/test/js/web/encoding/text-encoder.test.js
@@ -0,0 +1,281 @@
+import { expect, it, describe } from "bun:test";
+import { gc as gcTrace, withoutAggressiveGC } from "harness";
+
+const getByteLength = str => {
+ // returns the byte length of an utf8 string
+ var s = str.length;
+ for (var i = str.length - 1; i >= 0; i--) {
+ var code = str.charCodeAt(i);
+ if (code > 0x7f && code <= 0x7ff) s++;
+ else if (code > 0x7ff && code <= 0xffff) s += 2;
+ if (code >= 0xdc00 && code <= 0xdfff) i--; //trail surrogate
+ }
+ return s;
+};
+
+describe("TextEncoder", () => {
+ it("should encode latin1 text with non-ascii latin1 characters", () => {
+ var text = "H©ell©o Wor©ld!";
+
+ gcTrace(true);
+ const encoder = new TextEncoder();
+ const encoded = encoder.encode(text);
+ gcTrace(true);
+ const into = new Uint8Array(100);
+ const out = encoder.encodeInto(text, into);
+ gcTrace(true);
+ expect(out.read).toBe(text.length);
+
+ expect(encoded instanceof Uint8Array).toBe(true);
+ const result = [72, 194, 169, 101, 108, 108, 194, 169, 111, 32, 87, 111, 114, 194, 169, 108, 100, 33];
+ for (let i = 0; i < result.length; i++) {
+ expect(encoded[i]).toBe(result[i]);
+ expect(into[i]).toBe(result[i]);
+ }
+ expect(encoded.length).toBe(result.length);
+ expect(out.written).toBe(result.length);
+
+ const repeatCOunt = 16;
+ text = "H©ell©o Wor©ld!".repeat(repeatCOunt);
+ const byteLength = getByteLength(text);
+ const encoded2 = encoder.encode(text);
+ expect(encoded2.length).toBe(byteLength);
+ const into2 = new Uint8Array(byteLength);
+ const out2 = encoder.encodeInto(text, into2);
+ expect(out2.read).toBe(text.length);
+ expect(out2.written).toBe(byteLength);
+ expect(into2).toEqual(encoded2);
+ const repeatedResult = new Uint8Array(byteLength);
+ for (let i = 0; i < repeatCOunt; i++) {
+ repeatedResult.set(result, i * result.length);
+ }
+ expect(into2).toEqual(repeatedResult);
+ });
+
+ it("should encode latin1 text", async () => {
+ gcTrace(true);
+ const text = "Hello World!";
+ const encoder = new TextEncoder();
+ gcTrace(true);
+ const encoded = encoder.encode(text);
+ gcTrace(true);
+ expect(encoded instanceof Uint8Array).toBe(true);
+ expect(encoded.length).toBe(text.length);
+ gcTrace(true);
+ const result = [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33];
+ for (let i = 0; i < result.length; i++) {
+ expect(encoded[i]).toBe(result[i]);
+ }
+
+ let t = [
+ {
+ str: "\u009c\u0097",
+ expected: [194, 156, 194, 151],
+ },
+ {
+ str: "世",
+ expected: [228, 184, 150],
+ },
+ // Less than 0, out of range.
+ {
+ str: -1,
+ expected: [45, 49],
+ },
+ // Greater than 0x10FFFF, out of range.
+ {
+ str: 0x110000,
+ expected: [49, 49, 49, 52, 49, 49, 50],
+ },
+ // The Unicode replacement character.
+ {
+ str: "\uFFFD",
+ expected: [239, 191, 189],
+ },
+ ];
+ for (let { str, expected } of t) {
+ let utf8 = new TextEncoder().encode(str);
+ expect([...utf8]).toEqual(expected);
+ }
+
+ expect([...new TextEncoder().encode(String.fromCodePoint(0))]).toEqual([0]);
+
+ const fixture = new Uint8Array(await Bun.file(import.meta.dir + "/utf8-encoding-fixture.bin").arrayBuffer());
+ const length = 0x110000;
+ let textEncoder = new TextEncoder();
+ let textDecoder = new TextDecoder();
+ let encodeOut = new Uint8Array(length * 4);
+ let encodeIntoOut = new Uint8Array(length * 4);
+ let encodeIntoBuffer = new Uint8Array(4);
+ let encodeDecodedOut = new Uint8Array(length * 4);
+ for (let i = 0, offset = 0; i < length; i++, offset += 4) {
+ const s = String.fromCodePoint(i);
+ const u = textEncoder.encode(s);
+ encodeOut.set(u, offset);
+
+ textEncoder.encodeInto(s, encodeIntoBuffer);
+ encodeIntoOut.set(encodeIntoBuffer, offset);
+
+ const decoded = textDecoder.decode(encodeIntoBuffer);
+ const encoded = textEncoder.encode(decoded);
+ encodeDecodedOut.set(encoded, offset);
+ }
+
+ expect(encodeOut).toEqual(fixture);
+ expect(encodeIntoOut).toEqual(fixture);
+ expect(encodeOut).toEqual(encodeIntoOut);
+ expect(encodeDecodedOut).toEqual(encodeOut);
+ expect(encodeDecodedOut).toEqual(encodeIntoOut);
+ expect(encodeDecodedOut).toEqual(fixture);
+
+ expect(() => textEncoder.encode(String.fromCodePoint(length + 1))).toThrow();
+ });
+
+ it("should encode long latin1 text", async () => {
+ const text = "Hello World!".repeat(1000);
+ const encoder = new TextEncoder();
+ gcTrace(true);
+ const encoded = encoder.encode(text);
+ gcTrace(true);
+ expect(encoded instanceof Uint8Array).toBe(true);
+ expect(encoded.length).toBe(text.length);
+ gcTrace(true);
+ const decoded = new TextDecoder().decode(encoded);
+ expect(decoded).toBe(text);
+ gcTrace();
+ await new Promise(resolve => setTimeout(resolve, 1));
+ gcTrace();
+ expect(decoded).toBe(text);
+ });
+
+ it("should encode latin1 rope text", () => {
+ var text = "Hello";
+ text += " ";
+ text += "World!";
+
+ gcTrace(true);
+ const encoder = new TextEncoder();
+ const encoded = encoder.encode(text);
+ gcTrace(true);
+ const into = new Uint8Array(100);
+ const out = encoder.encodeInto(text, into);
+ gcTrace(true);
+ expect(out.read).toBe(text.length);
+ expect(out.written).toBe(encoded.length);
+ expect(encoded instanceof Uint8Array).toBe(true);
+ const result = [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33];
+ for (let i = 0; i < result.length; i++) {
+ expect(encoded[i]).toBe(result[i]);
+ expect(encoded[i]).toBe(into[i]);
+ }
+ expect(encoded.length).toBe(getByteLength(text));
+ });
+
+ it("should encode latin1 rope text with non-ascii latin1 characters", () => {
+ var text = "H©ell©o";
+ text += " ";
+ text += "Wor©ld!";
+
+ gcTrace(true);
+ const encoder = new TextEncoder();
+ const encoded = encoder.encode(text);
+ gcTrace(true);
+ const into = new Uint8Array(100);
+ const out = encoder.encodeInto(text, into);
+ gcTrace(true);
+ expect(out.read).toBe(text.length);
+
+ expect(encoded instanceof Uint8Array).toBe(true);
+ const result = [72, 194, 169, 101, 108, 108, 194, 169, 111, 32, 87, 111, 114, 194, 169, 108, 100, 33];
+
+ for (let i = 0; i < result.length; i++) {
+ expect(encoded[i]).toBe(into[i]);
+ expect(encoded[i]).toBe(result[i]);
+ }
+ expect(encoded.length).toBe(result.length);
+ expect(out.written).toBe(encoded.length);
+
+ withoutAggressiveGC(() => {
+ for (let i = 0; i < 10_000; i++) {
+ expect(encoder.encodeInto(text, into)).toEqual(out);
+ }
+ });
+ });
+
+ it("should encode utf-16 text", () => {
+ var text = `❤️ Red Heart
+ ✨ Sparkles
+ 🔥 Fire
+ `;
+ var encoder = new TextEncoder();
+ var decoder = new TextDecoder();
+ gcTrace(true);
+ expect(decoder.decode(encoder.encode(text))).toBe(text);
+ gcTrace(true);
+ });
+
+ // this test is from a web platform test in WebKit
+ describe("should use a unicode replacement character for invalid surrogate pairs", () => {
+ var bad = [
+ {
+ encoding: "utf-16le",
+ input: [0x00, 0xd8],
+ expected: "\uFFFD",
+ name: "lone surrogate lead",
+ },
+ {
+ encoding: "utf-16le",
+ input: [0x00, 0xdc],
+ expected: "\uFFFD",
+ name: "lone surrogate trail",
+ },
+ {
+ encoding: "utf-16le",
+ input: [0x00, 0xd8, 0x00, 0x00],
+ expected: "\uFFFD\u0000",
+ name: "unmatched surrogate lead",
+ },
+ {
+ encoding: "utf-16le",
+ input: [0x00, 0xdc, 0x00, 0x00],
+ expected: "\uFFFD\u0000",
+ name: "unmatched surrogate trail",
+ },
+ {
+ encoding: "utf-16le",
+ input: [0x00, 0xdc, 0x00, 0xd8],
+ expected: "\uFFFD\uFFFD",
+ name: "swapped surrogate pair",
+ },
+ ];
+
+ bad.forEach(function (t) {
+ it(t.encoding + " - " + t.name, () => {
+ gcTrace(true);
+ expect(new TextDecoder(t.encoding).decode(new Uint8Array(t.input))).toBe(t.expected);
+ expect(new TextDecoder(t.encoding).decode(new Uint16Array(new Uint8Array(t.input).buffer))).toBe(t.expected);
+ gcTrace(true);
+ });
+ // test(function () {
+ // assert_throws_js(TypeError, function () {
+ // new TextDecoder(t.encoding, { fatal: true }).decode(
+ // new Uint8Array(t.input)
+ // );
+ // });
+ // }, t.encoding + " - " + t.name + " (fatal flag set)");
+ });
+ });
+
+ it("should encode utf-16 rope text", () => {
+ gcTrace(true);
+ var textReal = `❤️ Red Heart ✨ Sparkles 🔥 Fire`;
+
+ var a = textReal.split("");
+ var text = "";
+ for (let j of a) {
+ text += j;
+ }
+
+ var encoder = new TextEncoder();
+ expect(new TextDecoder().decode(encoder.encode(text))).toBe(textReal);
+ });
+});
diff --git a/test/js/web/encoding/utf8-encoding-fixture.bin b/test/js/web/encoding/utf8-encoding-fixture.bin
new file mode 100644
index 000000000..1f9ecf34f
--- /dev/null
+++ b/test/js/web/encoding/utf8-encoding-fixture.bin
Binary files differ
diff --git a/test/js/web/fetch/body-mixin-errors.test.ts b/test/js/web/fetch/body-mixin-errors.test.ts
new file mode 100644
index 000000000..f57bbc56c
--- /dev/null
+++ b/test/js/web/fetch/body-mixin-errors.test.ts
@@ -0,0 +1,17 @@
+import { it, describe, expect } from "bun:test";
+
+describe("body-mixin-errors", () => {
+ it("should fail when bodyUsed", async () => {
+ var res = new Response("a");
+ expect(res.bodyUsed).toBe(false);
+ await res.text();
+ expect(res.bodyUsed).toBe(true);
+
+ try {
+ await res.text();
+ throw new Error("should not get here");
+ } catch (e: any) {
+ expect(e.message).toBe("Body already used");
+ }
+ });
+});
diff --git a/test/js/web/fetch/body-stream.test.ts b/test/js/web/fetch/body-stream.test.ts
new file mode 100644
index 000000000..1cd932ed9
--- /dev/null
+++ b/test/js/web/fetch/body-stream.test.ts
@@ -0,0 +1,451 @@
+// @ts-nocheck
+import { file, gc, serve, ServeOptions } from "bun";
+import { afterAll, afterEach, describe, expect, it, test } from "bun:test";
+import { readFileSync } from "fs";
+
+var port = 0;
+
+{
+ const BodyMixin = [
+ Request.prototype.arrayBuffer,
+ Request.prototype.blob,
+ Request.prototype.text,
+ Request.prototype.json,
+ ];
+ const useRequestObjectValues = [true, false];
+
+ for (let RequestPrototypeMixin of BodyMixin) {
+ for (let useRequestObject of useRequestObjectValues) {
+ describe(`Request.prototoype.${RequestPrototypeMixin.name}() ${
+ useRequestObject ? "fetch(req)" : "fetch(url)"
+ }`, () => {
+ const inputFixture = [
+ [JSON.stringify("Hello World"), JSON.stringify("Hello World")],
+ [JSON.stringify("Hello World 123"), Buffer.from(JSON.stringify("Hello World 123")).buffer],
+ [JSON.stringify("Hello World 456"), Buffer.from(JSON.stringify("Hello World 456"))],
+ [
+ JSON.stringify("EXTREMELY LONG VERY LONG STRING WOW SO LONG YOU WONT BELIEVE IT! ".repeat(100)),
+ Buffer.from(
+ JSON.stringify("EXTREMELY LONG VERY LONG STRING WOW SO LONG YOU WONT BELIEVE IT! ".repeat(100)),
+ ),
+ ],
+ [
+ JSON.stringify("EXTREMELY LONG 🔥 UTF16 🔥 VERY LONG STRING WOW SO LONG YOU WONT BELIEVE IT! ".repeat(100)),
+ Buffer.from(
+ JSON.stringify(
+ "EXTREMELY LONG 🔥 UTF16 🔥 VERY LONG STRING WOW SO LONG YOU WONT BELIEVE IT! ".repeat(100),
+ ),
+ ),
+ ],
+ ];
+
+ for (const [name, input] of inputFixture) {
+ test(`${name.slice(0, Math.min(name.length ?? name.byteLength, 64))}`, async () => {
+ await runInServer(
+ {
+ async fetch(req) {
+ var result = await RequestPrototypeMixin.call(req);
+ if (RequestPrototypeMixin === Request.prototype.json) {
+ result = JSON.stringify(result);
+ }
+ if (typeof result === "string") {
+ expect(result.length).toBe(name.length);
+ expect(result).toBe(name);
+ } else if (result && result instanceof Blob) {
+ expect(result.size).toBe(new TextEncoder().encode(name).byteLength);
+ expect(await result.text()).toBe(name);
+ } else {
+ expect(result.byteLength).toBe(Buffer.from(input).byteLength);
+ expect(Bun.SHA1.hash(result, "base64")).toBe(Bun.SHA1.hash(input, "base64"));
+ }
+ return new Response(result, {
+ headers: req.headers,
+ });
+ },
+ },
+ async url => {
+ var response;
+
+ // once, then batch of 5
+
+ if (useRequestObject) {
+ response = await fetch(
+ new Request({
+ body: input,
+ method: "POST",
+ url: url,
+ headers: {
+ "content-type": "text/plain",
+ },
+ }),
+ );
+ } else {
+ response = await fetch(url, {
+ body: input,
+ method: "POST",
+ headers: {
+ "content-type": "text/plain",
+ },
+ });
+ }
+
+ expect(response.status).toBe(200);
+ expect(response.headers.get("content-length")).toBe(String(Buffer.from(input).byteLength));
+ expect(response.headers.get("content-type")).toBe("text/plain");
+ expect(await response.text()).toBe(name);
+
+ var promises = new Array(5);
+ for (let i = 0; i < 5; i++) {
+ if (useRequestObject) {
+ promises[i] = await fetch(
+ new Request({
+ body: input,
+ method: "POST",
+ url: url,
+ headers: {
+ "content-type": "text/plain",
+ "x-counter": i,
+ },
+ }),
+ );
+ } else {
+ promises[i] = await fetch(url, {
+ body: input,
+ method: "POST",
+ headers: {
+ "content-type": "text/plain",
+ "x-counter": i,
+ },
+ });
+ }
+ }
+
+ const results = await Promise.all(promises);
+ for (let i = 0; i < 5; i++) {
+ const response = results[i];
+ expect(response.status).toBe(200);
+ expect(response.headers.get("content-length")).toBe(String(Buffer.from(input).byteLength));
+ expect(response.headers.get("content-type")).toBe("text/plain");
+ expect(response.headers.get("x-counter")).toBe(String(i));
+ expect(await response.text()).toBe(name);
+ }
+ },
+ );
+ });
+ }
+ });
+ }
+ }
+}
+
+var existingServer;
+async function runInServer(opts: ServeOptions, cb: (url: string) => void | Promise<void>) {
+ var server;
+ const handler = {
+ ...opts,
+ port: port++,
+ fetch(req) {
+ try {
+ return opts.fetch(req);
+ } catch (e) {
+ console.error(e.message);
+ console.log(e.stack);
+ throw e;
+ }
+ },
+ error(err) {
+ console.log(err.message);
+ console.log(err.stack);
+ throw err;
+ },
+ };
+
+ if (!existingServer) {
+ existingServer = server = Bun.serve(handler);
+ } else {
+ server = existingServer;
+ server.reload(handler);
+ }
+
+ try {
+ await cb(`http://${server.hostname}:${server.port}`);
+ } catch (e) {
+ throw e;
+ } finally {
+ }
+}
+
+afterAll(() => {
+ existingServer && existingServer.close();
+ existingServer = null;
+});
+
+function fillRepeating(dstBuffer, start, end) {
+ let len = dstBuffer.length,
+ sLen = end - start,
+ p = sLen;
+ while (p < len) {
+ if (p + sLen > len) sLen = len - p;
+ dstBuffer.copyWithin(p, start, sLen);
+ p += sLen;
+ sLen <<= 1;
+ }
+}
+
+function gc() {
+ Bun.gc(true);
+}
+
+describe("reader", function () {
+ try {
+ // - empty
+ // - 1 byte
+ // - less than the InlineBlob limit
+ // - multiple chunks
+ // - backpressure
+ for (let inputLength of [0, 1, 2, 12, 95, 1024, 1024 * 1024, 1024 * 1024 * 2]) {
+ var bytes = new Uint8Array(inputLength);
+ {
+ const chunk = Math.min(bytes.length, 256);
+ for (var i = 0; i < chunk; i++) {
+ bytes[i] = 255 - i;
+ }
+ }
+
+ if (bytes.length > 255) fillRepeating(bytes, 0, bytes.length);
+
+ for (const huge_ of [
+ bytes,
+ bytes.buffer,
+ new DataView(bytes.buffer),
+ new Int8Array(bytes),
+ new Blob([bytes]),
+
+ new Uint16Array(bytes),
+ new Uint32Array(bytes),
+ new Float64Array(bytes),
+
+ new Int16Array(bytes),
+ new Int32Array(bytes),
+ new Float32Array(bytes),
+
+ // make sure we handle subarray() as expected when reading
+ // typed arrays from native code
+ new Int16Array(bytes).subarray(1),
+ new Int16Array(bytes).subarray(0, new Int16Array(bytes).byteLength - 1),
+ new Int32Array(bytes).subarray(1),
+ new Int32Array(bytes).subarray(0, new Int32Array(bytes).byteLength - 1),
+ new Float32Array(bytes).subarray(1),
+ new Float32Array(bytes).subarray(0, new Float32Array(bytes).byteLength - 1),
+ new Int16Array(bytes).subarray(0, 1),
+ new Int32Array(bytes).subarray(0, 1),
+ new Float32Array(bytes).subarray(0, 1),
+ ]) {
+ gc();
+ const thisArray = huge_;
+ it(`works with ${thisArray.constructor.name}(${
+ thisArray.byteLength ?? thisArray.size
+ }:${inputLength}) via req.body.getReader() in chunks`, async () => {
+ var huge = thisArray;
+ var called = false;
+ gc();
+
+ const expectedHash =
+ huge instanceof Blob
+ ? Bun.SHA1.hash(new Uint8Array(await huge.arrayBuffer()), "base64")
+ : Bun.SHA1.hash(huge, "base64");
+ const expectedSize = huge instanceof Blob ? huge.size : huge.byteLength;
+
+ const out = await runInServer(
+ {
+ async fetch(req) {
+ try {
+ expect(req.headers.get("x-custom")).toBe("hello");
+ expect(req.headers.get("content-type")).toBe("text/plain");
+ expect(req.headers.get("user-agent")).toBe(navigator.userAgent);
+
+ gc();
+ expect(req.headers.get("x-custom")).toBe("hello");
+ expect(req.headers.get("content-type")).toBe("text/plain");
+ expect(req.headers.get("user-agent")).toBe(navigator.userAgent);
+
+ var reader = req.body.getReader();
+ called = true;
+ var buffers = [];
+ while (true) {
+ var { done, value } = await reader.read();
+ if (done) break;
+ buffers.push(value);
+ }
+ const out = new Blob(buffers);
+ gc();
+ expect(out.size).toBe(expectedSize);
+ expect(Bun.SHA1.hash(await out.arrayBuffer(), "base64")).toBe(expectedHash);
+ expect(req.headers.get("x-custom")).toBe("hello");
+ expect(req.headers.get("content-type")).toBe("text/plain");
+ expect(req.headers.get("user-agent")).toBe(navigator.userAgent);
+ gc();
+ return new Response(out, {
+ headers: req.headers,
+ });
+ } catch (e) {
+ console.error(e);
+ throw e;
+ }
+ },
+ },
+ async url => {
+ gc();
+ const response = await fetch(url, {
+ body: huge,
+ method: "POST",
+ headers: {
+ "content-type": "text/plain",
+ "x-custom": "hello",
+ "x-typed-array": thisArray.constructor.name,
+ },
+ });
+ huge = undefined;
+ expect(response.status).toBe(200);
+ const response_body = new Uint8Array(await response.arrayBuffer());
+
+ expect(response_body.byteLength).toBe(expectedSize);
+ expect(Bun.SHA1.hash(response_body, "base64")).toBe(expectedHash);
+
+ gc();
+ expect(response.headers.get("content-type")).toBe("text/plain");
+ gc();
+ },
+ );
+ expect(called).toBe(true);
+ gc();
+ return out;
+ });
+
+ for (let isDirectStream of [true, false]) {
+ const positions = ["begin", "end"];
+ const inner = thisArray => {
+ for (let position of positions) {
+ it(`streaming back ${thisArray.constructor.name}(${
+ thisArray.byteLength ?? thisArray.size
+ }:${inputLength}) starting request.body.getReader() at ${position}`, async () => {
+ var huge = thisArray;
+ var called = false;
+ gc();
+
+ const expectedHash =
+ huge instanceof Blob
+ ? Bun.SHA1.hash(new Uint8Array(await huge.arrayBuffer()), "base64")
+ : Bun.SHA1.hash(huge, "base64");
+ const expectedSize = huge instanceof Blob ? huge.size : huge.byteLength;
+
+ const out = await runInServer(
+ {
+ async fetch(req) {
+ try {
+ var reader;
+
+ if (position === "begin") {
+ reader = req.body.getReader();
+ }
+
+ if (position === "end") {
+ await 1;
+ reader = req.body.getReader();
+ }
+
+ expect(req.headers.get("x-custom")).toBe("hello");
+ expect(req.headers.get("content-type")).toBe("text/plain");
+ expect(req.headers.get("user-agent")).toBe(navigator.userAgent);
+
+ gc();
+ expect(req.headers.get("x-custom")).toBe("hello");
+ expect(req.headers.get("content-type")).toBe("text/plain");
+ expect(req.headers.get("user-agent")).toBe(navigator.userAgent);
+
+ const direct = {
+ type: "direct",
+ async pull(controller) {
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) {
+ called = true;
+ controller.end();
+
+ return;
+ }
+ controller.write(value);
+ }
+ },
+ };
+
+ const web = {
+ async pull(controller) {
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) {
+ called = true;
+ controller.close();
+ return;
+ }
+ controller.enqueue(value);
+ }
+ },
+ };
+
+ return new Response(new ReadableStream(isDirectStream ? direct : web), {
+ headers: req.headers,
+ });
+ } catch (e) {
+ console.error(e);
+ throw e;
+ }
+ },
+ },
+ async url => {
+ gc();
+ const response = await fetch(url, {
+ body: huge,
+ method: "POST",
+ headers: {
+ "content-type": "text/plain",
+ "x-custom": "hello",
+ "x-typed-array": thisArray.constructor.name,
+ },
+ });
+ huge = undefined;
+ expect(response.status).toBe(200);
+ const response_body = new Uint8Array(await response.arrayBuffer());
+
+ expect(response_body.byteLength).toBe(expectedSize);
+ expect(Bun.SHA1.hash(response_body, "base64")).toBe(expectedHash);
+
+ gc();
+ if (!response.headers.has("content-type")) {
+ console.error(Object.fromEntries(response.headers.entries()));
+ }
+
+ expect(response.headers.get("content-type")).toBe("text/plain");
+ gc();
+ },
+ );
+ expect(called).toBe(true);
+ gc();
+ return out;
+ });
+ }
+ };
+
+ if (isDirectStream) {
+ describe(" direct stream", () => inner(thisArray));
+ } else {
+ describe("default stream", () => inner(thisArray));
+ }
+ }
+ }
+ }
+ } catch (e) {
+ console.error(e);
+ throw e;
+ }
+});
diff --git a/test/js/web/fetch/fetch-gzip.test.ts b/test/js/web/fetch/fetch-gzip.test.ts
new file mode 100644
index 000000000..01eedc54a
--- /dev/null
+++ b/test/js/web/fetch/fetch-gzip.test.ts
@@ -0,0 +1,181 @@
+import { concatArrayBuffers } from "bun";
+import { it, describe, expect } from "bun:test";
+import fs from "fs";
+import { gc, gcTick } from "harness";
+
+it("fetch() with a buffered gzip response works (one chunk)", async () => {
+ var server = Bun.serve({
+ port: 6025,
+
+ async fetch(req) {
+ gcTick(true);
+ return new Response(require("fs").readFileSync(import.meta.dir + "/fixture.html.gz"), {
+ headers: {
+ "Content-Encoding": "gzip",
+ "Content-Type": "text/html; charset=utf-8",
+ },
+ });
+ },
+ });
+ gcTick(true);
+
+ const res = await fetch(`http://${server.hostname}:${server.port}`, { verbose: true });
+ gcTick(true);
+ const arrayBuffer = await res.arrayBuffer();
+ const clone = new Buffer(arrayBuffer);
+ gcTick(true);
+ await (async function () {
+ const second = new Buffer(await Bun.file(import.meta.dir + "/fixture.html").arrayBuffer());
+ gcTick(true);
+ expect(second.equals(clone)).toBe(true);
+ })();
+ gcTick(true);
+ server.stop();
+});
+
+it("fetch() with a redirect that returns a buffered gzip response works (one chunk)", async () => {
+ var server = Bun.serve({
+ port: 6020,
+
+ async fetch(req) {
+ if (req.url.endsWith("/redirect"))
+ return new Response(await Bun.file(import.meta.dir + "/fixture.html.gz").arrayBuffer(), {
+ headers: {
+ "Content-Encoding": "gzip",
+ "Content-Type": "text/html; charset=utf-8",
+ },
+ });
+
+ return Response.redirect("/redirect");
+ },
+ });
+
+ const res = await fetch(`http://${server.hostname}:${server.port}/hey`, { verbose: true });
+ const arrayBuffer = await res.arrayBuffer();
+ expect(
+ new Buffer(arrayBuffer).equals(new Buffer(await Bun.file(import.meta.dir + "/fixture.html").arrayBuffer())),
+ ).toBe(true);
+ server.stop();
+});
+
+it("fetch() with a protocol-relative redirect that returns a buffered gzip response works (one chunk)", async () => {
+ const server = Bun.serve({
+ port: 5018,
+
+ async fetch(req, server) {
+ if (req.url.endsWith("/redirect"))
+ return new Response(await Bun.file(import.meta.dir + "/fixture.html.gz").arrayBuffer(), {
+ headers: {
+ "Content-Encoding": "gzip",
+ "Content-Type": "text/html; charset=utf-8",
+ },
+ });
+
+ return Response.redirect(`://${server.hostname}:${server.port}/redirect`);
+ },
+ });
+
+ const res = await fetch(`http://${server.hostname}:${server.port}/hey`, { verbose: true });
+ expect(res.url).toBe(`http://${server.hostname}:${server.port}/redirect`);
+ expect(res.redirected).toBe(true);
+ expect(res.status).toBe(200);
+ const arrayBuffer = await res.arrayBuffer();
+ expect(
+ new Buffer(arrayBuffer).equals(new Buffer(await Bun.file(import.meta.dir + "/fixture.html").arrayBuffer())),
+ ).toBe(true);
+
+ server.stop();
+});
+
+it("fetch() with a gzip response works (one chunk, streamed, with a delay", async () => {
+ var server = Bun.serve({
+ port: 6081,
+
+ fetch(req) {
+ return new Response(
+ new ReadableStream({
+ type: "direct",
+ async pull(controller) {
+ await 2;
+
+ const buffer = await Bun.file(import.meta.dir + "/fixture.html.gz").arrayBuffer();
+ controller.write(buffer);
+ controller.close();
+ },
+ }),
+ {
+ headers: {
+ "Content-Encoding": "gzip",
+ "Content-Type": "text/html; charset=utf-8",
+ "Content-Length": "1",
+ },
+ },
+ );
+ },
+ });
+
+ const res = await fetch(`http://${server.hostname}:${server.port}`, {});
+ const arrayBuffer = await res.arrayBuffer();
+ expect(
+ new Buffer(arrayBuffer).equals(new Buffer(await Bun.file(import.meta.dir + "/fixture.html").arrayBuffer())),
+ ).toBe(true);
+ server.stop();
+});
+
+it("fetch() with a gzip response works (multiple chunks, TCP server", async done => {
+ const compressed = await Bun.file(import.meta.dir + "/fixture.html.gz").arrayBuffer();
+ var socketToClose;
+ const server = Bun.listen({
+ port: 4024,
+ hostname: "0.0.0.0",
+ socket: {
+ async open(socket) {
+ socketToClose = socket;
+
+ var corked: any[] = [];
+ var cork = true;
+ async function write(chunk) {
+ await new Promise<void>((resolve, reject) => {
+ if (cork) {
+ corked.push(chunk);
+ }
+
+ if (!cork && corked.length) {
+ socket.write(corked.join(""));
+ corked.length = 0;
+ }
+
+ if (!cork) {
+ socket.write(chunk);
+ }
+
+ resolve();
+ });
+ }
+ await write("HTTP/1.1 200 OK\r\n");
+ await write("Content-Encoding: gzip\r\n");
+ await write("Content-Type: text/html; charset=utf-8\r\n");
+ await write("Content-Length: " + compressed.byteLength + "\r\n");
+ await write("X-WTF: " + "lol".repeat(1000) + "\r\n");
+ await write("\r\n");
+ for (var i = 100; i < compressed.byteLength; i += 100) {
+ cork = false;
+ await write(compressed.slice(i - 100, i));
+ }
+ await write(compressed.slice(i - 100));
+ socket.flush();
+ },
+ drain(socket) {},
+ },
+ });
+ await 1;
+
+ const res = await fetch(`http://${server.hostname}:${server.port}`, {});
+ const arrayBuffer = await res.arrayBuffer();
+ expect(
+ new Buffer(arrayBuffer).equals(new Buffer(await Bun.file(import.meta.dir + "/fixture.html").arrayBuffer())),
+ ).toBe(true);
+ socketToClose.end();
+ server.stop();
+ done();
+});
diff --git a/test/js/web/fetch/fetch.js.txt b/test/js/web/fetch/fetch.js.txt
new file mode 100644
index 000000000..5a9b52fcf
--- /dev/null
+++ b/test/js/web/fetch/fetch.js.txt
@@ -0,0 +1,46 @@
+<!doctype html>
+<html>
+<head>
+ <title>Example Domain</title>
+
+ <meta charset="utf-8" />
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <style type="text/css">
+ body {
+ background-color: #f0f0f2;
+ margin: 0;
+ padding: 0;
+ font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+
+ }
+ div {
+ width: 600px;
+ margin: 5em auto;
+ padding: 2em;
+ background-color: #fdfdff;
+ border-radius: 0.5em;
+ box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);
+ }
+ a:link, a:visited {
+ color: #38488f;
+ text-decoration: none;
+ }
+ @media (max-width: 700px) {
+ div {
+ margin: 0 auto;
+ width: auto;
+ }
+ }
+ </style>
+</head>
+
+<body>
+<div>
+ <h1>Example Domain</h1>
+ <p>This domain is for use in illustrative examples in documents. You may use this
+ domain in literature without prior coordination or asking for permission.</p>
+ <p><a href="https://www.iana.org/domains/example">More information...</a></p>
+</div>
+</body>
+</html>
diff --git a/test/js/web/fetch/fetch.test.ts b/test/js/web/fetch/fetch.test.ts
new file mode 100644
index 000000000..1185dbd55
--- /dev/null
+++ b/test/js/web/fetch/fetch.test.ts
@@ -0,0 +1,935 @@
+import { serve, sleep } from "bun";
+import { afterAll, afterEach, beforeAll, describe, expect, it, beforeEach } from "bun:test";
+import { chmodSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "fs";
+import { mkfifo } from "mkfifo";
+import { tmpdir } from "os";
+import { join } from "path";
+import { gc, withoutAggressiveGC } from "harness";
+
+const tmp_dir = mkdtempSync(join(realpathSync(tmpdir()), "fetch.test"));
+
+const fixture = readFileSync(join(import.meta.dir, "fetch.js.txt"), "utf8");
+
+let server;
+function startServer({ fetch, ...options }) {
+ server = serve({
+ ...options,
+ fetch,
+ port: 0,
+ });
+}
+
+afterEach(() => {
+ server?.stop?.(true);
+});
+
+afterAll(() => {
+ rmSync(tmp_dir, { force: true, recursive: true });
+});
+
+const payload = new Uint8Array(1024 * 1024 * 2);
+crypto.getRandomValues(payload);
+
+describe("AbortSignal", () => {
+ beforeEach(() => {
+ startServer({
+ async fetch(request) {
+ if (request.url.endsWith("/nodelay")) {
+ return new Response("Hello");
+ }
+ if (request.url.endsWith("/stream")) {
+ const reader = request.body.getReader();
+ const body = new ReadableStream({
+ async pull(controller) {
+ if (!reader) controller.close();
+ const { done, value } = await reader.read();
+ // When no more data needs to be consumed, close the stream
+ if (done) {
+ controller.close();
+ return;
+ }
+ // Enqueue the next data chunk into our target stream
+ controller.enqueue(value);
+ },
+ });
+ return new Response(body);
+ }
+ if (request.method.toUpperCase() === "POST") {
+ const body = await request.text();
+ return new Response(body);
+ }
+ await sleep(15);
+ return new Response("Hello");
+ },
+ });
+ });
+ afterEach(() => {
+ server?.stop?.(true);
+ });
+
+ it("AbortError", async () => {
+ const controller = new AbortController();
+ const signal = controller.signal;
+
+ expect(async () => {
+ async function manualAbort() {
+ await sleep(1);
+ controller.abort();
+ }
+ await Promise.all([
+ fetch(`http://127.0.0.1:${server.port}`, { signal: signal }).then(res => res.text()),
+ manualAbort(),
+ ]);
+ }).toThrow(new DOMException("The operation was aborted."));
+ });
+
+ it("AbortAfterFinish", async () => {
+ const controller = new AbortController();
+ const signal = controller.signal;
+
+ await fetch(`http://127.0.0.1:${server.port}/nodelay`, { signal: signal }).then(async res =>
+ expect(await res.text()).toBe("Hello"),
+ );
+ controller.abort();
+ });
+
+ it("AbortErrorWithReason", async () => {
+ const controller = new AbortController();
+ const signal = controller.signal;
+
+ expect(async () => {
+ async function manualAbort() {
+ await sleep(10);
+ controller.abort(new Error("My Reason"));
+ }
+ await Promise.all([
+ fetch(`http://127.0.0.1:${server.port}`, { signal: signal }).then(res => res.text()),
+ manualAbort(),
+ ]);
+ }).toThrow("My Reason");
+ });
+
+ it("AbortErrorEventListener", async () => {
+ const controller = new AbortController();
+ const signal = controller.signal;
+ signal.addEventListener("abort", ev => {
+ const target = ev.currentTarget;
+ expect(target).toBeDefined();
+ expect(target.aborted).toBe(true);
+ expect(target.reason).toBeDefined();
+ expect(target.reason.name).toBe("AbortError");
+ });
+
+ expect(async () => {
+ async function manualAbort() {
+ await sleep(10);
+ controller.abort();
+ }
+ await Promise.all([
+ fetch(`http://127.0.0.1:${server.port}`, { signal: signal }).then(res => res.text()),
+ manualAbort(),
+ ]);
+ }).toThrow(new DOMException("The operation was aborted."));
+ });
+
+ it("AbortErrorWhileUploading", async () => {
+ const controller = new AbortController();
+
+ expect(async () => {
+ await fetch(`http://localhost:${server.port}`, {
+ method: "POST",
+ body: new ReadableStream({
+ pull(event_controller) {
+ event_controller.enqueue(new Uint8Array([1, 2, 3, 4]));
+ //this will abort immediately should abort before connected
+ controller.abort();
+ },
+ }),
+ signal: controller.signal,
+ });
+ }).toThrow(new DOMException("The operation was aborted."));
+ });
+
+ it("TimeoutError", async () => {
+ const signal = AbortSignal.timeout(10);
+
+ try {
+ await fetch(`http://127.0.0.1:${server.port}`, { signal: signal }).then(res => res.text());
+ expect(() => {}).toThrow();
+ } catch (ex: any) {
+ expect(ex.name).toBe("TimeoutError");
+ }
+ });
+
+ it("Request", async () => {
+ const controller = new AbortController();
+ const signal = controller.signal;
+ async function manualAbort() {
+ await sleep(10);
+ controller.abort();
+ }
+
+ try {
+ const request = new Request(`http://127.0.0.1:${server.port}`, { signal });
+ await Promise.all([fetch(request).then(res => res.text()), manualAbort()]);
+ expect(() => {}).toThrow();
+ } catch (ex: any) {
+ expect(ex.name).toBe("AbortError");
+ }
+ });
+});
+
+describe("Headers", () => {
+ it(".toJSON", () => {
+ const headers = new Headers({
+ "content-length": "123",
+ "content-type": "text/plain",
+ "x-another-custom-header": "Hello World",
+ "x-custom-header": "Hello World",
+ });
+ expect(JSON.stringify(headers.toJSON(), null, 2)).toBe(
+ JSON.stringify(Object.fromEntries(headers.entries()), null, 2),
+ );
+ });
+
+ it(".getSetCookie() with object", () => {
+ const headers = new Headers({
+ "content-length": "123",
+ "content-type": "text/plain",
+ "x-another-custom-header": "Hello World",
+ "x-custom-header": "Hello World",
+ "Set-Cookie": "foo=bar; Path=/; HttpOnly",
+ });
+ expect(headers.count).toBe(5);
+ expect(headers.getAll("set-cookie")).toEqual(["foo=bar; Path=/; HttpOnly"]);
+ });
+
+ it(".getSetCookie() with array", () => {
+ const headers = new Headers([
+ ["content-length", "123"],
+ ["content-type", "text/plain"],
+ ["x-another-custom-header", "Hello World"],
+ ["x-custom-header", "Hello World"],
+ ["Set-Cookie", "foo=bar; Path=/; HttpOnly"],
+ ["Set-Cookie", "foo2=bar2; Path=/; HttpOnly"],
+ ]);
+ expect(headers.count).toBe(6);
+ expect(headers.getAll("set-cookie")).toEqual(["foo=bar; Path=/; HttpOnly", "foo2=bar2; Path=/; HttpOnly"]);
+ });
+
+ it("Set-Cookies init", () => {
+ const headers = new Headers([
+ ["Set-Cookie", "foo=bar"],
+ ["Set-Cookie", "bar=baz"],
+ ["X-bun", "abc"],
+ ["X-bun", "def"],
+ ]);
+ const actual = [...headers];
+ expect(actual).toEqual([
+ ["set-cookie", "foo=bar"],
+ ["set-cookie", "bar=baz"],
+ ["x-bun", "abc, def"],
+ ]);
+ expect([...headers.values()]).toEqual(["foo=bar", "bar=baz", "abc, def"]);
+ });
+
+ it("Headers append multiple", () => {
+ const headers = new Headers([
+ ["Set-Cookie", "foo=bar"],
+ ["X-bun", "foo"],
+ ]);
+ headers.append("Set-Cookie", "bar=baz");
+ headers.append("x-bun", "bar");
+ const actual = [...headers];
+
+ // we do not preserve the order
+ // which is kind of bad
+ expect(actual).toEqual([
+ ["set-cookie", "foo=bar"],
+ ["set-cookie", "bar=baz"],
+ ["x-bun", "foo, bar"],
+ ]);
+ });
+
+ it("append duplicate set cookie key", () => {
+ const headers = new Headers([["Set-Cookie", "foo=bar"]]);
+ headers.append("set-Cookie", "foo=baz");
+ headers.append("Set-cookie", "baz=bar");
+ const actual = [...headers];
+ expect(actual).toEqual([
+ ["set-cookie", "foo=baz"],
+ ["set-cookie", "baz=bar"],
+ ]);
+ });
+
+ it("set duplicate cookie key", () => {
+ const headers = new Headers([["Set-Cookie", "foo=bar"]]);
+ headers.set("set-Cookie", "foo=baz");
+ headers.set("set-cookie", "bar=qat");
+ const actual = [...headers];
+ expect(actual).toEqual([
+ ["set-cookie", "foo=baz"],
+ ["set-cookie", "bar=qat"],
+ ]);
+ });
+});
+
+describe("fetch", () => {
+ const urls = [
+ "https://example.com",
+ "http://example.com",
+ new URL("https://example.com"),
+ new Request({ url: "https://example.com" }),
+ { toString: () => "https://example.com" },
+ ];
+ for (let url of urls) {
+ gc();
+ let name;
+ if (url instanceof URL) {
+ name = "URL: " + url;
+ } else if (url instanceof Request) {
+ name = "Request: " + url.url;
+ } else if (url.hasOwnProperty("toString")) {
+ name = "Object: " + url.toString();
+ } else {
+ name = url;
+ }
+ it(name, async () => {
+ gc();
+ const response = await fetch(url, { verbose: true });
+ gc();
+ const text = await response.text();
+ gc();
+ expect(fixture).toBe(text);
+ });
+ }
+
+ it('redirect: "manual"', async () => {
+ startServer({
+ fetch(req) {
+ return new Response(null, {
+ status: 302,
+ headers: {
+ Location: "https://example.com",
+ },
+ });
+ },
+ });
+ const response = await fetch(`http://${server.hostname}:${server.port}`, {
+ redirect: "manual",
+ });
+ expect(response.status).toBe(302);
+ expect(response.headers.get("location")).toBe("https://example.com");
+ expect(response.redirected).toBe(true);
+ });
+
+ it('redirect: "follow"', async () => {
+ startServer({
+ fetch(req) {
+ return new Response(null, {
+ status: 302,
+ headers: {
+ Location: "https://example.com",
+ },
+ });
+ },
+ });
+ const response = await fetch(`http://${server.hostname}:${server.port}`, {
+ redirect: "follow",
+ });
+ expect(response.status).toBe(200);
+ expect(response.headers.get("location")).toBe(null);
+ expect(response.redirected).toBe(true);
+ });
+
+ it("provide body", async () => {
+ startServer({
+ fetch(req) {
+ return new Response(req.body);
+ },
+ host: "localhost",
+ });
+
+ // POST with body
+ const url = `http://${server.hostname}:${server.port}`;
+ const response = await fetch(url, { method: "POST", body: "buntastic" });
+ expect(response.status).toBe(200);
+ expect(await response.text()).toBe("buntastic");
+ });
+
+ ["GET", "HEAD", "OPTIONS"].forEach(method =>
+ it(`fail on ${method} with body`, async () => {
+ const url = `http://${server.hostname}:${server.port}`;
+ expect(async () => {
+ await fetch(url, { body: "buntastic" });
+ }).toThrow("fetch() request with GET/HEAD/OPTIONS method cannot have body.");
+ }),
+ );
+});
+
+it("simultaneous HTTPS fetch", async () => {
+ const urls = ["https://example.com", "https://www.example.com"];
+ for (let batch = 0; batch < 4; batch++) {
+ const promises = new Array(20);
+ for (let i = 0; i < 20; i++) {
+ promises[i] = fetch(urls[i % 2]);
+ }
+ const result = await Promise.all(promises);
+ expect(result.length).toBe(20);
+ for (let i = 0; i < 20; i++) {
+ expect(result[i].status).toBe(200);
+ expect(await result[i].text()).toBe(fixture);
+ }
+ }
+});
+
+it("website with tlsextname", async () => {
+ // irony
+ await fetch("https://bun.sh", { method: "HEAD" });
+});
+
+function testBlobInterface(blobbyConstructor, hasBlobFn?) {
+ for (let withGC of [false, true]) {
+ for (let jsonObject of [
+ { hello: true },
+ {
+ hello: "😀 😃 😄 😁 😆 😅 😂 🤣 🥲 ☺️ 😊 😇 🙂 🙃 😉 😌 😍 🥰 😘 😗 😙 😚 😋 😛 😝 😜 🤪 🤨 🧐 🤓 😎 🥸 🤩 🥳",
+ },
+ ]) {
+ it(`${jsonObject.hello === true ? "latin1" : "utf16"} json${withGC ? " (with gc) " : ""}`, async () => {
+ if (withGC) gc();
+ var response = blobbyConstructor(JSON.stringify(jsonObject));
+ if (withGC) gc();
+ expect(JSON.stringify(await response.json())).toBe(JSON.stringify(jsonObject));
+ if (withGC) gc();
+ });
+
+ it(`${jsonObject.hello === true ? "latin1" : "utf16"} arrayBuffer -> json${
+ withGC ? " (with gc) " : ""
+ }`, async () => {
+ if (withGC) gc();
+ var response = blobbyConstructor(new TextEncoder().encode(JSON.stringify(jsonObject)));
+ if (withGC) gc();
+ expect(JSON.stringify(await response.json())).toBe(JSON.stringify(jsonObject));
+ if (withGC) gc();
+ });
+
+ it(`${jsonObject.hello === true ? "latin1" : "utf16"} arrayBuffer -> invalid json${
+ withGC ? " (with gc) " : ""
+ }`, async () => {
+ if (withGC) gc();
+ var response = blobbyConstructor(
+ new TextEncoder().encode(JSON.stringify(jsonObject) + " NOW WE ARE INVALID JSON"),
+ );
+ if (withGC) gc();
+ var failed = false;
+ try {
+ await response.json();
+ } catch (e) {
+ failed = true;
+ }
+ expect(failed).toBe(true);
+ if (withGC) gc();
+ });
+
+ it(`${jsonObject.hello === true ? "latin1" : "utf16"} text${withGC ? " (with gc) " : ""}`, async () => {
+ if (withGC) gc();
+ var response = blobbyConstructor(JSON.stringify(jsonObject));
+ if (withGC) gc();
+ expect(await response.text()).toBe(JSON.stringify(jsonObject));
+ if (withGC) gc();
+ });
+
+ it(`${jsonObject.hello === true ? "latin1" : "utf16"} arrayBuffer -> text${
+ withGC ? " (with gc) " : ""
+ }`, async () => {
+ if (withGC) gc();
+ var response = blobbyConstructor(new TextEncoder().encode(JSON.stringify(jsonObject)));
+ if (withGC) gc();
+ expect(await response.text()).toBe(JSON.stringify(jsonObject));
+ if (withGC) gc();
+ });
+
+ it(`${jsonObject.hello === true ? "latin1" : "utf16"} arrayBuffer${withGC ? " (with gc) " : ""}`, async () => {
+ if (withGC) gc();
+
+ var response = blobbyConstructor(JSON.stringify(jsonObject));
+ if (withGC) gc();
+
+ const bytes = new TextEncoder().encode(JSON.stringify(jsonObject));
+ if (withGC) gc();
+
+ const compare = new Uint8Array(await response.arrayBuffer());
+ if (withGC) gc();
+
+ withoutAggressiveGC(() => {
+ for (let i = 0; i < compare.length; i++) {
+ if (withGC) gc();
+
+ expect(compare[i]).toBe(bytes[i]);
+ if (withGC) gc();
+ }
+ });
+ if (withGC) gc();
+ });
+
+ it(`${jsonObject.hello === true ? "latin1" : "utf16"} arrayBuffer -> arrayBuffer${
+ withGC ? " (with gc) " : ""
+ }`, async () => {
+ if (withGC) gc();
+
+ var response = blobbyConstructor(new TextEncoder().encode(JSON.stringify(jsonObject)));
+ if (withGC) gc();
+
+ const bytes = new TextEncoder().encode(JSON.stringify(jsonObject));
+ if (withGC) gc();
+
+ const compare = new Uint8Array(await response.arrayBuffer());
+ if (withGC) gc();
+
+ withoutAggressiveGC(() => {
+ for (let i = 0; i < compare.length; i++) {
+ if (withGC) gc();
+
+ expect(compare[i]).toBe(bytes[i]);
+ if (withGC) gc();
+ }
+ });
+ if (withGC) gc();
+ });
+
+ hasBlobFn &&
+ it(`${jsonObject.hello === true ? "latin1" : "utf16"} blob${withGC ? " (with gc) " : ""}`, async () => {
+ if (withGC) gc();
+ const text = JSON.stringify(jsonObject);
+ var response = blobbyConstructor(text);
+ if (withGC) gc();
+ const size = new TextEncoder().encode(text).byteLength;
+ if (withGC) gc();
+ const blobed = await response.blob();
+ if (withGC) gc();
+ expect(blobed instanceof Blob).toBe(true);
+ if (withGC) gc();
+ expect(blobed.size).toBe(size);
+ if (withGC) gc();
+ blobed.type = "";
+ if (withGC) gc();
+ expect(blobed.type).toBe("");
+ if (withGC) gc();
+ blobed.type = "application/json";
+ if (withGC) gc();
+ expect(blobed.type).toBe("application/json");
+ if (withGC) gc();
+ const out = await blobed.text();
+ expect(out).toBe(text);
+ if (withGC) gc();
+ await new Promise(resolve => setTimeout(resolve, 1));
+ if (withGC) gc();
+ expect(out).toBe(text);
+ const first = await blobed.arrayBuffer();
+ const initial = first[0];
+ first[0] = 254;
+ const second = await blobed.arrayBuffer();
+ expect(second[0]).toBe(initial);
+ expect(first[0]).toBe(254);
+ });
+ }
+ }
+}
+
+describe("Bun.file", () => {
+ let count = 0;
+ testBlobInterface(data => {
+ const blob = new Blob([data]);
+ const buffer = Bun.peek(blob.arrayBuffer());
+ const path = join(tmp_dir, `tmp-${count++}.bytes`);
+ writeFileSync(path, buffer);
+ const file = Bun.file(path);
+ expect(blob.size).toBe(file.size);
+ return file;
+ });
+
+ it("size is Infinity on a fifo", () => {
+ const path = join(tmp_dir, "test-fifo");
+ mkfifo(path);
+ const { size } = Bun.file(path);
+ expect(size).toBe(Infinity);
+ });
+
+ function forEachMethod(fn, skip?) {
+ const method = ["arrayBuffer", "text", "json"];
+ for (const m of method) {
+ (skip ? it.skip : it)(m, fn(m));
+ }
+ }
+
+ describe("bad permissions throws", () => {
+ const path = join(tmp_dir, "my-new-file");
+ beforeAll(async () => {
+ await Bun.write(path, "hey");
+ chmodSync(path, 0o000);
+ });
+
+ forEachMethod(
+ m => () => {
+ const file = Bun.file(path);
+ expect(async () => await file[m]()).toThrow("Permission denied");
+ },
+ () => {
+ try {
+ readFileSync(path);
+ } catch {
+ return false;
+ }
+ return true;
+ },
+ );
+ });
+
+ describe("non-existent file throws", () => {
+ const path = join(tmp_dir, "does-not-exist");
+
+ forEachMethod(m => async () => {
+ const file = Bun.file(path);
+ expect(async () => await file[m]()).toThrow("No such file or directory");
+ });
+ });
+});
+
+describe("Blob", () => {
+ testBlobInterface(data => new Blob([data]));
+
+ var blobConstructorValues = [
+ ["123", "456"],
+ ["123", 456],
+ ["123", "456", "789"],
+ ["123", 456, 789],
+ [1, 2, 3, 4, 5, 6, 7, 8, 9],
+ [Uint8Array.from([1, 2, 3, 4, 5, 6, 7, 9])],
+ [Uint8Array.from([1, 2, 3, 4]), "5678", 9],
+ [new Blob([Uint8Array.from([1, 2, 3, 4])]), "5678", 9],
+ [
+ new Blob([
+ new TextEncoder().encode(
+ "😀 😃 😄 😁 😆 😅 😂 🤣 🥲 ☺️ 😊 😇 🙂 🙃 😉 😌 😍 🥰 😘 😗 😙 😚 😋 😛 😝 😜 🤪 🤨 🧐 🤓 😎 🥸 🤩 🥳",
+ ),
+ ]),
+ ],
+ [
+ new TextEncoder().encode(
+ "😀 😃 😄 😁 😆 😅 😂 🤣 🥲 ☺️ 😊 😇 🙂 🙃 😉 😌 😍 🥰 😘 😗 😙 😚 😋 😛 😝 😜 🤪 🤨 🧐 🤓 😎 🥸 🤩 🥳",
+ ),
+ ],
+ ];
+
+ var expected = [
+ "123456",
+ "123456",
+ "123456789",
+ "123456789",
+ "123456789",
+ "\x01\x02\x03\x04\x05\x06\x07\t",
+ "\x01\x02\x03\x0456789",
+ "\x01\x02\x03\x0456789",
+ "😀 😃 😄 😁 😆 😅 😂 🤣 🥲 ☺️ 😊 😇 🙂 🙃 😉 😌 😍 🥰 😘 😗 😙 😚 😋 😛 😝 😜 🤪 🤨 🧐 🤓 😎 🥸 🤩 🥳",
+ "😀 😃 😄 😁 😆 😅 😂 🤣 🥲 ☺️ 😊 😇 🙂 🙃 😉 😌 😍 🥰 😘 😗 😙 😚 😋 😛 😝 😜 🤪 🤨 🧐 🤓 😎 🥸 🤩 🥳",
+ ];
+
+ it(`blobConstructorValues`, async () => {
+ for (let i = 0; i < blobConstructorValues.length; i++) {
+ var response = new Blob(blobConstructorValues[i]);
+ const res = await response.text();
+ if (res !== expected[i]) {
+ throw new Error(
+ `Failed: ${expected[i].split("").map(a => a.charCodeAt(0))}, received: ${res
+ .split("")
+ .map(a => a.charCodeAt(0))}`,
+ );
+ }
+
+ expect(res).toBe(expected[i]);
+ }
+ });
+
+ for (let withGC of [false, true]) {
+ it(`Blob.slice() ${withGC ? " with gc" : ""}`, async () => {
+ var parts = ["hello", " ", "world"];
+ if (withGC) gc();
+ var str = parts.join("");
+ if (withGC) gc();
+ var combined = new Blob(parts);
+ if (withGC) gc();
+ for (let part of parts) {
+ if (withGC) gc();
+ expect(await combined.slice(str.indexOf(part), str.indexOf(part) + part.length).text()).toBe(part);
+ if (withGC) gc();
+ }
+ if (withGC) gc();
+ for (let part of parts) {
+ if (withGC) gc();
+ expect(await combined.slice(str.indexOf(part), str.indexOf(part) + part.length).text()).toBe(part);
+ if (withGC) gc();
+ }
+ });
+ }
+});
+
+{
+ const sample = new TextEncoder().encode("Hello World!");
+ const typedArrays = [
+ Uint8Array,
+ Uint8ClampedArray,
+ Int8Array,
+ Uint16Array,
+ Int16Array,
+ Uint32Array,
+ Int32Array,
+ Float32Array,
+ Float64Array,
+ ];
+ const Constructors = [Blob, Response, Request];
+
+ for (let withGC of [false, true]) {
+ for (let TypedArray of typedArrays) {
+ for (let Constructor of Constructors) {
+ it(`${Constructor.name} arrayBuffer() with ${TypedArray.name}${withGC ? " with gc" : ""}`, async () => {
+ const data = new TypedArray(sample);
+ if (withGC) gc();
+ const input = Constructor === Blob ? [data] : Constructor === Request ? { body: data } : data;
+ if (withGC) gc();
+ const blob = new Constructor(input);
+ if (withGC) gc();
+ const out = await blob.arrayBuffer();
+ if (withGC) gc();
+ expect(out instanceof ArrayBuffer).toBe(true);
+ if (withGC) gc();
+ expect(out.byteLength).toBe(data.byteLength);
+ if (withGC) gc();
+ });
+ }
+ }
+ }
+}
+
+describe("Response", () => {
+ describe("Response.json", () => {
+ it("works", async () => {
+ const inputs = ["hellooo", [[123], 456, 789], { hello: "world" }, { ok: "😉 😌 😍 🥰 😘 " }];
+ for (let input of inputs) {
+ const output = JSON.stringify(input);
+ expect(await Response.json(input).text()).toBe(output);
+ }
+ // JSON.stringify() returns undefined
+ expect(await Response.json().text()).toBe("");
+ // JSON.stringify("") returns '""'
+ expect(await Response.json("").text()).toBe('""');
+ });
+ it("sets the content-type header", () => {
+ let response = Response.json("hello");
+ expect(response.type).toBe("basic");
+ expect(response.headers.get("content-type")).toBe("application/json;charset=utf-8");
+ expect(response.status).toBe(200);
+ });
+ it("supports number status code", () => {
+ let response = Response.json("hello", 407);
+ expect(response.type).toBe("basic");
+ expect(response.headers.get("content-type")).toBe("application/json;charset=utf-8");
+ expect(response.status).toBe(407);
+ });
+
+ it("supports headers", () => {
+ var response = Response.json("hello", {
+ headers: {
+ "content-type": "potato",
+ "x-hello": "world",
+ },
+ status: 408,
+ });
+
+ expect(response.headers.get("x-hello")).toBe("world");
+ expect(response.status).toBe(408);
+ });
+ });
+ describe("Response.redirect", () => {
+ it("works", () => {
+ const inputs = [
+ "http://example.com",
+ "http://example.com/",
+ "http://example.com/hello",
+ "http://example.com/hello/",
+ "http://example.com/hello/world",
+ "http://example.com/hello/world/",
+ ];
+ for (let input of inputs) {
+ expect(Response.redirect(input).headers.get("Location")).toBe(input);
+ }
+ });
+
+ it("supports headers", () => {
+ var response = Response.redirect("https://example.com", {
+ headers: {
+ "content-type": "potato",
+ "x-hello": "world",
+ Location: "https://wrong.com",
+ },
+ status: 408,
+ });
+ expect(response.headers.get("x-hello")).toBe("world");
+ expect(response.headers.get("Location")).toBe("https://example.com");
+ expect(response.status).toBe(302);
+ expect(response.type).toBe("basic");
+ expect(response.ok).toBe(false);
+ });
+ });
+ describe("Response.error", () => {
+ it("works", () => {
+ expect(Response.error().type).toBe("error");
+ expect(Response.error().ok).toBe(false);
+ expect(Response.error().status).toBe(0);
+ });
+ });
+ it("clone", async () => {
+ gc();
+ var body = new Response("<div>hello</div>", {
+ headers: {
+ "content-type": "text/html; charset=utf-8",
+ },
+ });
+ gc();
+ var clone = body.clone();
+ gc();
+ body.headers.set("content-type", "text/plain");
+ gc();
+ expect(clone.headers.get("content-type")).toBe("text/html; charset=utf-8");
+ gc();
+ expect(body.headers.get("content-type")).toBe("text/plain");
+ gc();
+ expect(await clone.text()).toBe("<div>hello</div>");
+ gc();
+ });
+ it("invalid json", async () => {
+ gc();
+ var body = new Response("<div>hello</div>", {
+ headers: {
+ "content-type": "text/html; charset=utf-8",
+ },
+ });
+ try {
+ await body.json();
+ expect(false).toBe(true);
+ } catch (exception) {
+ expect(exception instanceof SyntaxError).toBe(true);
+ }
+ });
+
+ testBlobInterface(data => new Response(data), true);
+});
+
+describe("Request", () => {
+ it("clone", async () => {
+ gc();
+ var body = new Request("https://hello.com", {
+ headers: {
+ "content-type": "text/html; charset=utf-8",
+ },
+ body: "<div>hello</div>",
+ });
+ gc();
+ expect(body.signal).toBeDefined();
+ gc();
+ expect(body.headers.get("content-type")).toBe("text/html; charset=utf-8");
+ gc();
+ var clone = body.clone();
+ gc();
+ expect(clone.signal).toBeDefined();
+ gc();
+ body.headers.set("content-type", "text/plain");
+ gc();
+ expect(clone.headers.get("content-type")).toBe("text/html; charset=utf-8");
+ gc();
+ expect(body.headers.get("content-type")).toBe("text/plain");
+ gc();
+ expect(await clone.text()).toBe("<div>hello</div>");
+ });
+
+ it("signal", async () => {
+ gc();
+ const controller = new AbortController();
+ const req = new Request("https://hello.com", { signal: controller.signal });
+ expect(req.signal.aborted).toBe(false);
+ gc();
+ controller.abort();
+ gc();
+ expect(req.signal.aborted).toBe(true);
+ });
+
+ it("cloned signal", async () => {
+ gc();
+ const controller = new AbortController();
+ const req = new Request("https://hello.com", { signal: controller.signal });
+ expect(req.signal.aborted).toBe(false);
+ gc();
+ controller.abort();
+ gc();
+ expect(req.signal.aborted).toBe(true);
+ gc();
+ const cloned = req.clone();
+ expect(cloned.signal.aborted).toBe(true);
+ });
+
+ testBlobInterface(data => new Request("https://hello.com", { body: data }), true);
+});
+
+describe("Headers", () => {
+ it("writes", async () => {
+ var headers = new Headers({
+ "content-type": "text/html; charset=utf-8",
+ });
+ gc();
+ expect(headers.get("content-type")).toBe("text/html; charset=utf-8");
+ gc();
+ headers.delete("content-type");
+ gc();
+ expect(headers.get("content-type")).toBe(null);
+ gc();
+ headers.append("content-type", "text/plain");
+ gc();
+ expect(headers.get("content-type")).toBe("text/plain");
+ gc();
+ headers.append("content-type", "text/plain");
+ gc();
+ expect(headers.get("content-type")).toBe("text/plain, text/plain");
+ gc();
+ headers.set("content-type", "text/html; charset=utf-8");
+ gc();
+ expect(headers.get("content-type")).toBe("text/html; charset=utf-8");
+
+ headers.delete("content-type");
+ gc();
+ expect(headers.get("content-type")).toBe(null);
+ gc();
+ });
+});
+
+it("body nullable", async () => {
+ gc();
+ {
+ const req = new Request("https://hello.com", { body: null });
+ expect(req.body).toBeNull();
+ }
+ gc();
+ {
+ const req = new Request("https://hello.com", { body: undefined });
+ expect(req.body).toBeNull();
+ }
+ gc();
+ {
+ const req = new Request("https://hello.com");
+ expect(req.body).toBeNull();
+ }
+ gc();
+ {
+ const req = new Request("https://hello.com", { body: "" });
+ expect(req.body).not.toBeNull();
+ }
+});
diff --git a/test/js/web/fetch/fetch_headers.test.js b/test/js/web/fetch/fetch_headers.test.js
new file mode 100644
index 000000000..cd2786c08
--- /dev/null
+++ b/test/js/web/fetch/fetch_headers.test.js
@@ -0,0 +1,66 @@
+import { describe, it, expect, beforeAll, afterAll } from "bun:test";
+const port = 3009;
+const url = `http://localhost:${port}`;
+let server;
+
+describe("Headers", async () => {
+ // Start up a single server and reuse it between tests
+ beforeAll(() => {
+ server = Bun.serve({
+ fetch(req) {
+ const hdr = req.headers.get("x-test");
+ return new Response(hdr);
+ },
+ port: port,
+ });
+ });
+ afterAll(() => {
+ server.stop();
+ });
+
+ it("Headers should work", async () => {
+ expect(await fetchContent({ "x-test": "header 1" })).toBe("header 1");
+ });
+
+ it("Header names must be valid", async () => {
+ expect(() => fetch(url, { headers: { "a\tb:c": "foo" } })).toThrow("Invalid header name: 'a\tb:c'");
+ expect(() => fetch(url, { headers: { "❤️": "foo" } })).toThrow("Invalid header name: '❤️'");
+ });
+
+ it("Header values must be valid", async () => {
+ expect(() => fetch(url, { headers: { "x-test": "\0" } })).toThrow("Header 'x-test' has invalid value: '\0'");
+ expect(() => fetch(url, { headers: { "x-test": "❤️" } })).toThrow("Header 'x-test' has invalid value: '❤️'");
+ });
+
+ it("repro 1602", async () => {
+ const origString = "😂1234".slice(3);
+
+ var encoder = new TextEncoder();
+ var decoder = new TextDecoder();
+ const roundTripString = decoder.decode(encoder.encode(origString));
+
+ expect(roundTripString).toBe(origString);
+
+ // This one will pass
+ expect(await fetchContent({ "x-test": roundTripString })).toBe(roundTripString);
+ // This would hang
+ expect(await fetchContent({ "x-test": origString })).toBe(origString);
+ });
+
+ describe("toJSON()", () => {
+ it("should provide lowercase header names", () => {
+ const headers1 = new Headers({ "X-Test": "yep", "Content-Type": "application/json" });
+ expect(headers1.toJSON()).toEqual({ "x-test": "yep", "content-type": "application/json" });
+
+ const headers2 = new Headers();
+ headers2.append("X-Test", "yep");
+ headers2.append("Content-Type", "application/json");
+ expect(headers2.toJSON()).toEqual({ "x-test": "yep", "content-type": "application/json" });
+ });
+ });
+});
+
+async function fetchContent(headers) {
+ const res = await fetch(url, { headers: headers }, { verbose: true });
+ return await res.text();
+}
diff --git a/test/js/web/fetch/fixture.html b/test/js/web/fetch/fixture.html
new file mode 100644
index 000000000..081040506
--- /dev/null
+++ b/test/js/web/fetch/fixture.html
@@ -0,0 +1,1428 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <meta property="og:title" content="Bun is a fast all-in-one JavaScript runtime" />
+ <title>Bun is a fast all-in-one JavaScript runtime</title>
+ <meta
+ property="og:description"
+ content="Bundle, transpile, install and run JavaScript &amp; TypeScript
+ projects – all in Bun. Bun is a new JavaScript runtime with
+ a native bundler, transpiler, task runner and npm client built-in."
+ />
+ <meta name="og:locale" content="en_US" />
+ <meta name="twitter:site" content="@jarredsumner" />
+ <meta name="twitter:card" content="summary_large_image" />
+ <meta property="og:image" content="https://bun.sh/share.png" />
+ <meta
+ name="description"
+ content="Bundle, transpile, install and run JavaScript &amp; TypeScript
+ projects – all in Bun. Bun is a new JavaScript runtime with
+ a native bundler, transpiler, task runner and npm client built-in."
+ />
+ <meta name="theme-color" content="#fbf0df" />
+ <link rel="manifest" href="manifest.json" />
+ <link rel="icon" type="image/png" sizes="256x256" href="/logo-square.png" />
+ <link rel="icon" type="image/png" sizes="32x32" href="/logo-square@32px.png" />
+ <link rel="icon" type="image/png" sizes="16x16" href="/logo-square@16px.png" />
+ <style>
+ :root {
+ --black: #0b0a08;
+ --blue: #00a6e1;
+ --orange: #f89b4b;
+ --orange-light: #d4d3d2;
+ --monospace-font: "Fira Code", "Hack", "Source Code Pro", "SF Mono", "Inconsolata", monospace;
+ --dark-border: rgba(200, 200, 25, 0.2);
+ --max-width: 1152px;
+ --system-font: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell,
+ "Open Sans", "Helvetica Neue", sans-serif;
+ --horizontal-padding: 3rem;
+ --vertical-padding: 4rem;
+ --line-height: 1.4;
+ }
+ * {
+ box-sizing: border-box;
+ }
+ head,
+ body,
+ :root {
+ margin: 0 auto;
+ padding: 0;
+ font-family: var(--system-font);
+ }
+ body {
+ background-color: #fbf0df;
+ }
+ a {
+ color: inherit;
+ text-decoration: none;
+ transition: transform 0.1s linear;
+ }
+ a:visited {
+ color: inherit;
+ }
+ a:hover {
+ text-decoration: underline;
+ transform: scale(1.06);
+ transform-origin: middle center;
+ }
+ #header-wrap,
+ #pitch {
+ background-color: var(--black);
+ color: #fbf0df;
+ width: 100%;
+ }
+ #logo-link {
+ width: fit-content;
+ display: flex;
+ gap: 24px;
+ align-items: center;
+ }
+ main {
+ width: auto;
+ margin: 0 auto;
+ max-width: var(--max-width);
+ display: grid;
+ grid-template-columns: auto auto;
+ overflow-y: hidden;
+ }
+ main,
+ header,
+ #explain-section {
+ margin: 0 auto;
+ max-width: var(--max-width);
+ padding: 0 var(--horizontal-padding);
+ }
+ #cards-wrap,
+ #usecases,
+ main,
+ header {
+ padding: var(--vertical-padding) var(--horizontal-padding);
+ }
+ #pitch-content {
+ max-width: 600px;
+ }
+ .tagline {
+ margin-top: 0;
+ line-height: 1;
+ font-size: 36pt;
+ }
+ .subtitle {
+ font-size: 1.2rem;
+ }
+ .Navigation ul {
+ white-space: nowrap;
+ display: flex;
+ gap: 2rem;
+ list-style: none;
+ }
+ .NavText {
+ color: #fbf0df;
+ display: block;
+ font-weight: 500;
+ font-size: 1.2rem;
+ }
+ #HeaderInstallButton {
+ margin-left: 2.4rem;
+ }
+ #pitch main {
+ gap: 2rem;
+ }
+ #logo {
+ max-width: 70px;
+ margin: auto 0;
+ }
+ #logo-text {
+ max-width: 96px;
+ }
+ header {
+ display: grid;
+ grid-template-columns: auto max-content;
+ background-color: var(--black);
+ padding: 1.5rem 3rem;
+ align-items: center;
+ color: #fff;
+ }
+ #HeaderInstallButton:hover {
+ cursor: pointer;
+ transform: scale(1.06);
+ }
+ #HeaderInstallButton {
+ transition: transform 0.1s linear;
+ background: #00a6e1;
+ padding: 8px 16px;
+ border-radius: 100px;
+ color: #000;
+ font-weight: 500;
+ }
+ .InstallBox {
+ margin-top: 2rem;
+ background: #15140e;
+ padding: 24px;
+ border-radius: 24px;
+ user-select: none;
+ -webkit-user-select: none;
+ -webkit-user-drag: none;
+ -moz-user-select: none;
+ }
+ .InstallBox-label-heading {
+ font-size: 1.4rem;
+ margin-bottom: 1rem;
+ font-weight: 500;
+ }
+ .InstallBox-label-subtitle {
+ font-size: 0.9rem;
+ color: var(--orange-light);
+ }
+ #usecases-section {
+ background: linear-gradient(12deg, rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.2)),
+ conic-gradient(
+ from 6.27deg at 46.95% 50.05%,
+ #ff8181 0deg,
+ #e5f067 75deg,
+ #6dd9ba 155.62deg,
+ #67f0ae 168.75deg,
+ #8b67f0 243.75deg,
+ #f067e2 300deg,
+ #e967e3 334.49deg,
+ #f06767 348.9deg,
+ #ff8181 360deg
+ );
+ color: #fff;
+ font-family: var(--monospace-font);
+ contain: paint;
+ font-size: 24pt;
+ font-weight: 700;
+ }
+ #usecases-section {
+ padding: 0;
+ margin: 0;
+ }
+ #usecases {
+ padding-top: 1rem;
+ padding-bottom: 1rem;
+ }
+ #usecases-section h1 {
+ background: linear-gradient(90deg, #ff0000 0%, #faff00 50.52%, #0500ff 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+ text-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
+ font-family: Helvetica;
+ margin: 0;
+ padding: 0;
+ }
+ .InstallBox-code-box {
+ background-color: #252420;
+ padding: 4px 16px;
+ position: relative;
+ border-radius: 8px;
+ text-align: center;
+ align-items: center;
+ border: 1px solid var(--orange);
+ margin-top: 1rem;
+ display: flex;
+ justify-content: space-between;
+ align-content: center;
+ white-space: nowrap;
+ margin-bottom: 1rem;
+ font-family: var(--monospace-font);
+ }
+ .InstallBox-curl {
+ user-select: all;
+ -webkit-user-select: text;
+ pointer-events: auto;
+ white-space: nowrap;
+ cursor: text;
+ display: inline-flex;
+ padding: 12px 8px;
+ gap: 2ch;
+ }
+ .InstallBox-curl:before {
+ display: block;
+ content: "$" / "";
+ color: var(--orange);
+ pointer-events: none;
+ width: 1ch;
+ height: 1ch;
+ }
+ .InstallBox-view-source-link {
+ color: var(--orange-light);
+ }
+ .InstallBox-copy {
+ height: 100%;
+ display: flex;
+ align-items: center;
+ color: var(--orange-light);
+ transition: transform 0.05s linear;
+ transition-property: color, transform;
+ transform-origin: center center;
+ cursor: pointer;
+ background: transparent;
+ border: none;
+ font-size: inherit;
+ font-family: inherit;
+ }
+ .InstallBox-copy:hover {
+ color: var(--blue);
+ transform: scale(1.06);
+ }
+ .InstallBox-copy:active {
+ transform: scale(1.12);
+ }
+ .Tabs {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ margin-left: auto;
+ margin-right: auto;
+ justify-content: center;
+ align-items: center;
+ width: min-content;
+ white-space: nowrap;
+ padding: 0;
+ }
+ .Tab {
+ width: min-content;
+ border: none;
+ background-color: transparent;
+ font-family: var(--monospace-font);
+ text-align: center;
+ border-bottom: 1px solid #ccc;
+ cursor: pointer;
+ padding: 16px;
+ color: inherit;
+ font-size: inherit;
+ }
+ .Tab:hover,
+ .Graphs--active-react .Tab[data-tab="react"],
+ .Graphs--active-sqlite .Tab[data-tab="sqlite"],
+ .Graphs--active-websocket .Tab[data-tab="websocket"] {
+ border-bottom-color: #7fffd4;
+ background-color: #82d8f71a;
+ border-right-color: #7fffd4;
+ border-left-color: #7fffd4;
+ }
+ .BarGraph {
+ padding: 24px;
+ display: flex;
+ flex-direction: column;
+ }
+ .BarGraph-heading {
+ font-weight: 500;
+ font-size: 1.5rem;
+ margin: 0;
+ }
+ .BarGraphList {
+ flex: 1;
+ position: relative;
+ list-style-type: none;
+ padding: 0;
+ }
+ .BarGraph,
+ .ActiveTab,
+ .Graphs {
+ height: auto;
+ }
+ .BarGraph-subheading {
+ font-size: 0.9rem;
+ color: #878686;
+ margin: 0;
+ }
+ .BarGraphList {
+ margin-top: 1rem;
+ display: grid;
+ grid-template-columns: repeat(var(--count), 1fr);
+ font-variant-numeric: tabular-nums;
+ font-family: var(--monospace-font);
+ justify-content: center;
+ align-items: flex-start;
+ height: 100%;
+ background-color: #080808;
+ }
+ .BarGraphKey {
+ display: grid;
+ text-align: center;
+ margin-top: 1rem;
+ grid-template-columns: repeat(var(--count), 1fr);
+ }
+ .BarGraphBar {
+ --primary: 70px;
+ --opposite: 100%;
+ }
+ .BarGraph,
+ .BarGraphBar-label,
+ .BarGraphItem {
+ --level: calc(var(--amount) / var(--max));
+ --inverse: calc(1 / var(--level));
+ }
+ .BarGraphBar {
+ margin: 0 auto;
+ width: var(--primary);
+ height: var(--opposite);
+ background-color: #5d5986;
+ position: relative;
+ height: calc(200px * var(--level));
+ }
+ .BarGraphItem {
+ border-right: 1px dashed var(--dark-border);
+ border-top: 1px dashed var(--dark-border);
+ border-bottom: 1px dashed var(--dark-border);
+ min-height: 200px;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-end;
+ }
+ .BarGraphItem--deno {
+ border-right-color: transparent;
+ }
+ .BarGraph--vertical .BarGraphBar {
+ max-width: 90%;
+ }
+ .BarGraphBar-label {
+ color: #fff;
+ font-variant-numeric: tabular-nums;
+ font-family: var(--monospace-font);
+ width: 100%;
+ text-align: center;
+ position: relative;
+ display: flex;
+ justify-content: center;
+ }
+ .CardContent {
+ position: relative;
+ }
+ .BarGraph--vertical .BarGraphBar-label {
+ transform: scaleX(var(--inverse));
+ bottom: 0;
+ right: 0;
+ }
+ .BarGraph--horizontal .BarGraphBar-label {
+ top: -22px;
+ }
+ .BarGraphItem--bun .BarGraphBar {
+ background-color: #f9f1e1;
+ box-shadow: inset 1px 1px 3px #ccc6bb;
+ background-image: url(data:image/svg+xml;base64,PHN2ZyBpZD0iQnVuIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA4MCA3MCI+PHRpdGxlPkJ1biBMb2dvPC90aXRsZT48cGF0aCBpZD0iU2hhZG93IiBkPSJNNzEuMDksMjAuNzRjLS4xNi0uMTctLjMzLS4zNC0uNS0uNXMtLjMzLS4zNC0uNS0uNS0uMzMtLjM0LS41LS41LS4zMy0uMzQtLjUtLjUtLjMzLS4zNC0uNS0uNS0uMzMtLjM0LS41LS41LS4zMy0uMzQtLjUtLjVBMjYuNDYsMjYuNDYsMCwwLDEsNzUuNSwzNS43YzAsMTYuNTctMTYuODIsMzAuMDUtMzcuNSwzMC4wNS0xMS41OCwwLTIxLjk0LTQuMjMtMjguODMtMTAuODZsLjUuNS41LjUuNS41LjUuNS41LjUuNS41LjUuNUMxOS41NSw2NS4zLDMwLjE0LDY5Ljc1LDQyLDY5Ljc1YzIwLjY4LDAsMzcuNS0xMy40OCwzNy41LTMwQzc5LjUsMzIuNjksNzYuNDYsMjYsNzEuMDksMjAuNzRaIi8+PGcgaWQ9IkJvZHkiPjxwYXRoIGlkPSJCYWNrZ3JvdW5kIiBkPSJNNzMsMzUuN2MwLDE1LjIxLTE1LjY3LDI3LjU0LTM1LDI3LjU0UzMsNTAuOTEsMywzNS43QzMsMjYuMjcsOSwxNy45NCwxOC4yMiwxM1MzMy4xOCwzLDM4LDNzOC45NCw0LjEzLDE5Ljc4LDEwQzY3LDE3Ljk0LDczLDI2LjI3LDczLDM1LjdaIiBzdHlsZT0iZmlsbDojZmJmMGRmIi8+PHBhdGggaWQ9IkJvdHRvbV9TaGFkb3ciIGRhdGEtbmFtZT0iQm90dG9tIFNoYWRvdyIgZD0iTTczLDM1LjdhMjEuNjcsMjEuNjcsMCwwLDAtLjgtNS43OGMtMi43MywzMy4zLTQzLjM1LDM0LjktNTkuMzIsMjQuOTRBNDAsNDAsMCwwLDAsMzgsNjMuMjRDNTcuMyw2My4yNCw3Myw1MC44OSw3MywzNS43WiIgc3R5bGU9ImZpbGw6I2Y2ZGVjZSIvPjxwYXRoIGlkPSJMaWdodF9TaGluZSIgZGF0YS1uYW1lPSJMaWdodCBTaGluZSIgZD0iTTI0LjUzLDExLjE3QzI5LDguNDksMzQuOTQsMy40Niw0MC43OCwzLjQ1QTkuMjksOS4yOSwwLDAsMCwzOCwzYy0yLjQyLDAtNSwxLjI1LTguMjUsMy4xMy0xLjEzLjY2LTIuMywxLjM5LTMuNTQsMi4xNS0yLjMzLDEuNDQtNSwzLjA3LTgsNC43QzguNjksMTguMTMsMywyNi42MiwzLDM1LjdjMCwuNCwwLC44LDAsMS4xOUM5LjA2LDE1LjQ4LDIwLjA3LDEzLjg1LDI0LjUzLDExLjE3WiIgc3R5bGU9ImZpbGw6I2ZmZmVmYyIvPjxwYXRoIGlkPSJUb3AiIGQ9Ik0zNS4xMiw1LjUzQTE2LjQxLDE2LjQxLDAsMCwxLDI5LjQ5LDE4Yy0uMjguMjUtLjA2LjczLjMuNTksMy4zNy0xLjMxLDcuOTItNS4yMyw2LTEzLjE0QzM1LjcxLDUsMzUuMTIsNS4xMiwzNS4xMiw1LjUzWm0yLjI3LDBBMTYuMjQsMTYuMjQsMCwwLDEsMzksMTljLS4xMi4zNS4zMS42NS41NS4zNkM0MS43NCwxNi41Niw0My42NSwxMSwzNy45Myw1LDM3LjY0LDQuNzQsMzcuMTksNS4xNCwzNy4zOSw1LjQ5Wm0yLjc2LS4xN0ExNi40MiwxNi40MiwwLDAsMSw0NywxNy4xMmEuMzMuMzMsMCwwLDAsLjY1LjExYy45Mi0zLjQ5LjQtOS40NC03LjE3LTEyLjUzQzQwLjA4LDQuNTQsMzkuODIsNS4wOCw0MC4xNSw1LjMyWk0yMS42OSwxNS43NmExNi45NCwxNi45NCwwLDAsMCwxMC40Ny05Yy4xOC0uMzYuNzUtLjIyLjY2LjE4LTEuNzMsOC03LjUyLDkuNjctMTEuMTIsOS40NUMyMS4zMiwxNi40LDIxLjMzLDE1Ljg3LDIxLjY5LDE1Ljc2WiIgc3R5bGU9ImZpbGw6I2NjYmVhNztmaWxsLXJ1bGU6ZXZlbm9kZCIvPjxwYXRoIGlkPSJPdXRsaW5lIiBkPSJNMzgsNjUuNzVDMTcuMzIsNjUuNzUuNSw1Mi4yNy41LDM1LjdjMC0xMCw2LjE4LTE5LjMzLDE2LjUzLTI0LjkyLDMtMS42LDUuNTctMy4yMSw3Ljg2LTQuNjIsMS4yNi0uNzgsMi40NS0xLjUxLDMuNi0yLjE5QzMyLDEuODksMzUsLjUsMzgsLjVzNS42MiwxLjIsOC45LDMuMTRjMSwuNTcsMiwxLjE5LDMuMDcsMS44NywyLjQ5LDEuNTQsNS4zLDMuMjgsOSw1LjI3QzY5LjMyLDE2LjM3LDc1LjUsMjUuNjksNzUuNSwzNS43LDc1LjUsNTIuMjcsNTguNjgsNjUuNzUsMzgsNjUuNzVaTTM4LDNjLTIuNDIsMC01LDEuMjUtOC4yNSwzLjEzLTEuMTMuNjYtMi4zLDEuMzktMy41NCwyLjE1LTIuMzMsMS40NC01LDMuMDctOCw0LjdDOC42OSwxOC4xMywzLDI2LjYyLDMsMzUuNywzLDUwLjg5LDE4LjcsNjMuMjUsMzgsNjMuMjVTNzMsNTAuODksNzMsMzUuN0M3MywyNi42Miw2Ny4zMSwxOC4xMyw1Ny43OCwxMyw1NCwxMSw1MS4wNSw5LjEyLDQ4LjY2LDcuNjRjLTEuMDktLjY3LTIuMDktMS4yOS0zLTEuODRDNDIuNjMsNCw0MC40MiwzLDM4LDNaIi8+PC9nPjxnIGlkPSJNb3V0aCI+PGcgaWQ9IkJhY2tncm91bmQtMiIgZGF0YS1uYW1lPSJCYWNrZ3JvdW5kIj48cGF0aCBkPSJNNDUuMDUsNDNhOC45Myw4LjkzLDAsMCwxLTIuOTIsNC43MSw2LjgxLDYuODEsMCwwLDEtNCwxLjg4QTYuODQsNi44NCwwLDAsMSwzNCw0Ny43MSw4LjkzLDguOTMsMCwwLDEsMzEuMTIsNDNhLjcyLjcyLDAsMCwxLC44LS44MUg0NC4yNkEuNzIuNzIsMCwwLDEsNDUuMDUsNDNaIiBzdHlsZT0iZmlsbDojYjcxNDIyIi8+PC9nPjxnIGlkPSJUb25ndWUiPjxwYXRoIGlkPSJCYWNrZ3JvdW5kLTMiIGRhdGEtbmFtZT0iQmFja2dyb3VuZCIgZD0iTTM0LDQ3Ljc5YTYuOTEsNi45MSwwLDAsMCw0LjEyLDEuOSw2LjkxLDYuOTEsMCwwLDAsNC4xMS0xLjksMTAuNjMsMTAuNjMsMCwwLDAsMS0xLjA3LDYuODMsNi44MywwLDAsMC00LjktMi4zMSw2LjE1LDYuMTUsMCwwLDAtNSwyLjc4QzMzLjU2LDQ3LjQsMzMuNzYsNDcuNiwzNCw0Ny43OVoiIHN0eWxlPSJmaWxsOiNmZjYxNjQiLz48cGF0aCBpZD0iT3V0bGluZS0yIiBkYXRhLW5hbWU9Ik91dGxpbmUiIGQ9Ik0zNC4xNiw0N2E1LjM2LDUuMzYsMCwwLDEsNC4xOS0yLjA4LDYsNiwwLDAsMSw0LDEuNjljLjIzLS4yNS40NS0uNTEuNjYtLjc3YTcsNywwLDAsMC00LjcxLTEuOTMsNi4zNiw2LjM2LDAsMCwwLTQuODksMi4zNkE5LjUzLDkuNTMsMCwwLDAsMzQuMTYsNDdaIi8+PC9nPjxwYXRoIGlkPSJPdXRsaW5lLTMiIGRhdGEtbmFtZT0iT3V0bGluZSIgZD0iTTM4LjA5LDUwLjE5YTcuNDIsNy40MiwwLDAsMS00LjQ1LTIsOS41Miw5LjUyLDAsMCwxLTMuMTEtNS4wNSwxLjIsMS4yLDAsMCwxLC4yNi0xLDEuNDEsMS40MSwwLDAsMSwxLjEzLS41MUg0NC4yNmExLjQ0LDEuNDQsMCwwLDEsMS4xMy41MSwxLjE5LDEuMTksMCwwLDEsLjI1LDFoMGE5LjUyLDkuNTIsMCwwLDEtMy4xMSw1LjA1QTcuNDIsNy40MiwwLDAsMSwzOC4wOSw1MC4xOVptLTYuMTctNy40Yy0uMTYsMC0uMi4wNy0uMjEuMDlhOC4yOSw4LjI5LDAsMCwwLDIuNzMsNC4zN0E2LjIzLDYuMjMsMCwwLDAsMzguMDksNDlhNi4yOCw2LjI4LDAsMCwwLDMuNjUtMS43Myw4LjMsOC4zLDAsMCwwLDIuNzItNC4zNy4yMS4yMSwwLDAsMC0uMi0uMDlaIi8+PC9nPjxnIGlkPSJGYWNlIj48ZWxsaXBzZSBpZD0iUmlnaHRfQmx1c2giIGRhdGEtbmFtZT0iUmlnaHQgQmx1c2giIGN4PSI1My4yMiIgY3k9IjQwLjE4IiByeD0iNS44NSIgcnk9IjMuNDQiIHN0eWxlPSJmaWxsOiNmZWJiZDAiLz48ZWxsaXBzZSBpZD0iTGVmdF9CbHVjaCIgZGF0YS1uYW1lPSJMZWZ0IEJsdWNoIiBjeD0iMjIuOTUiIGN5PSI0MC4xOCIgcng9IjUuODUiIHJ5PSIzLjQ0IiBzdHlsZT0iZmlsbDojZmViYmQwIi8+PHBhdGggaWQ9IkV5ZXMiIGQ9Ik0yNS43LDM4LjhhNS41MSw1LjUxLDAsMSwwLTUuNS01LjUxQTUuNTEsNS41MSwwLDAsMCwyNS43LDM4LjhabTI0Ljc3LDBBNS41MSw1LjUxLDAsMSwwLDQ1LDMzLjI5LDUuNSw1LjUsMCwwLDAsNTAuNDcsMzguOFoiIHN0eWxlPSJmaWxsLXJ1bGU6ZXZlbm9kZCIvPjxwYXRoIGlkPSJJcmlzIiBkPSJNMjQsMzMuNjRhMi4wNywyLjA3LDAsMSwwLTIuMDYtMi4wN0EyLjA3LDIuMDcsMCwwLDAsMjQsMzMuNjRabTI0Ljc3LDBhMi4wNywyLjA3LDAsMSwwLTIuMDYtMi4wN0EyLjA3LDIuMDcsMCwwLDAsNDguNzUsMzMuNjRaIiBzdHlsZT0iZmlsbDojZmZmO2ZpbGwtcnVsZTpldmVub2RkIi8+PC9nPjwvc3ZnPg==);
+ background-repeat: no-repeat;
+ background-size: 56px 48.8px;
+ background-position: 6px 20%;
+ }
+ .BarGraph--vertical .BarGraphItem--bun {
+ border-top-right-radius: 12px;
+ border-bottom-right-radius: 12px;
+ }
+ .BarGraph--horizontal .BarGraphItem--bun {
+ border-top-left-radius: 12px;
+ border-top-right-radius: 12px;
+ }
+ .BarGraph--vertical .BarGraphBar {
+ height: var(--primary);
+ width: var(--opposite);
+ transform: scaleX(var(--level));
+ transform-origin: bottom left;
+ max-height: 40px;
+ margin-top: 1rem;
+ margin-bottom: 1rem;
+ }
+ .BarGraph--vertical .BarGraphList,
+ .BarGraph--vertical .BarGraphKey--vertical {
+ grid-template-columns: 1fr;
+ grid-template-rows: repeat(var(--count), 1fr);
+ }
+ .BarGraph--vertical .BarGraphList {
+ direction: rtl;
+ }
+ .BarGraphKeyItem-label {
+ color: #fff;
+ }
+ .BarGraphKeyItem-value {
+ color: #7a7a7a;
+ margin-top: 0.5rem;
+ }
+ .BarGraphKeyItem-viewSource {
+ margin-top: 0.5rem;
+ color: #7a7a7a;
+ text-transform: lowercase;
+ font-weight: thin;
+ font-size: 0.8rem;
+ }
+ .BarGraphKeyItem:hover {
+ text-decoration: none;
+ }
+ .BarGraphKeyItem:hover .BarGraphKeyItem-viewSource {
+ color: var(--orange-light);
+ }
+ .DemphasizedLabel {
+ text-transform: uppercase;
+ white-space: nowrap;
+ }
+ #frameworks {
+ display: flex;
+ }
+ .FrameworksGroup {
+ display: grid;
+ grid-template-rows: auto 40px;
+ gap: 0.5rem;
+ }
+ .DemphasizedLabel {
+ color: #7a7a7a;
+ font-weight: 300;
+ }
+ .FrameworksList {
+ display: grid;
+ grid-template-columns: repeat(2, 40px);
+ gap: 3.5rem;
+ align-items: center;
+ }
+ #cards {
+ display: grid;
+ }
+ #explain ul {
+ font-size: 1.2rem;
+ }
+ #explain li {
+ margin-bottom: 1rem;
+ line-height: var(--line-height);
+ }
+ .Tag {
+ --background: rgba(31, 31, 132, 0.15);
+ background-color: var(--background);
+ border-radius: 8px;
+ padding: 3px 8px;
+ color: #000;
+ text-decoration: none !important;
+ display: inline-block;
+ font-family: var(--monospace-font) !important;
+ }
+ .mono {
+ font-family: var(--monospace-font);
+ }
+ .Tag--Command {
+ --background: #111;
+ font-weight: medium;
+ color: #a3ff85;
+ }
+ .Tag--Command:before {
+ content: "\276f"/ "";
+ color: #ffffff59;
+ margin-top: auto;
+ margin-bottom: auto;
+ margin-right: 1ch;
+ margin-left: 0.5ch;
+ display: inline-block;
+ transform: translateY(-1px);
+ }
+ .Tag--WebAPI {
+ --background: #29b6f6;
+ box-shadow: inset -1px -1px 3px #e7bb49;
+ }
+ .Tag--NodeJS {
+ --background: rgb(130, 172, 108);
+ }
+ .Tag--TypeScript {
+ --background: rgb(69, 119, 192);
+ color: #fff;
+ }
+ .Tag--React {
+ color: #82d8f7;
+ --background: #333;
+ }
+ .Tag--React:before {
+ color: #82d8f780;
+ content: "<" / "";
+ }
+ .Tag--React:after {
+ color: #82d8f780;
+ content: ">" / "";
+ }
+ .Tag--Bun {
+ --background: #e600e5;
+ color: #fff;
+ }
+ .mono {
+ font-family: var(--monospace-font);
+ border-radius: 6px;
+ color: #006713;
+ }
+ @media (min-width: 931px) {
+ .InstallBox--mobile {
+ display: none;
+ }
+ }
+ #explain {
+ max-width: 650px;
+ margin: 0 auto;
+ }
+ @media (max-width: 930px) {
+ header {
+ padding: 24px 16px;
+ }
+ .InstallBox--desktop {
+ display: none;
+ }
+ #logo {
+ width: 48px;
+ }
+ :root {
+ --max-width: 100%;
+ --horizontal-padding: 16px;
+ --vertical-padding: 2rem;
+ --line-height: 1.6;
+ }
+ main {
+ grid-template-columns: auto;
+ grid-template-rows: auto auto auto;
+ }
+ #explain li {
+ line-height: var(--line-height);
+ margin-bottom: 1.5rem;
+ }
+ ul {
+ padding: 0;
+ list-style: none;
+ }
+ .Tabs {
+ margin-left: 0;
+ }
+ .Graphs,
+ .BarGraph,
+ .BarGraphList {
+ max-width: auto;
+ }
+ .BarGraph {
+ padding: 24px 0;
+ }
+ #pitch-content {
+ max-width: auto;
+ }
+ #pitch main {
+ gap: 1rem;
+ }
+ .InstallBox {
+ margin-top: 0;
+ }
+ .tagline {
+ font-size: 32pt;
+ }
+ #logo-text,
+ #HeaderInstallButton {
+ display: none;
+ }
+ }
+ .InstallBox--mobile {
+ border-radius: 0;
+ }
+ @media (max-width: 599px) {
+ .InstallBox-copy {
+ display: none;
+ }
+ .InstallBox-code-box {
+ font-size: 0.8rem;
+ }
+ }
+ @media (max-width: 360px) {
+ .tagline {
+ font-size: 22pt;
+ }
+ }
+ #explain p {
+ line-height: var(--line-height);
+ font-size: 1.2rem;
+ }
+ #explain p a {
+ text-decoration: underline;
+ }
+ .Zig {
+ transform: translateY(15%);
+ }
+ .CodeBlock .shiki {
+ padding: 1rem;
+ border-radius: 8px;
+ font-family: var(--monospace-font);
+ font-size: 1rem;
+ }
+ .Identifier {
+ font-family: var(--monospace-font);
+ font-size: 1rem;
+ color: #50fa7b !important;
+ background-color: #282a36;
+ padding: 0.25rem;
+ border-radius: 8px;
+ text-decoration: none !important;
+ }
+ .PerformanceClaim {
+ text-decoration: dashed underline 2px #000 !important;
+ text-decoration-skip-ink: auto !important;
+ }
+ .BarGraph--react,
+ .BarGraph--websocket,
+ .BarGraph--sqlite {
+ display: none;
+ }
+ .Graphs--active-react .BarGraph--react,
+ .Graphs--active-websocket .BarGraph--websocket,
+ .Graphs--active-sqlite .BarGraph--sqlite {
+ display: block;
+ }
+ @media (min-width: 930px) {
+ .Graphs {
+ margin-left: auto;
+ }
+ .BarGraph-subheading,
+ .BarGraph-heading {
+ text-align: center;
+ }
+ .BarGraph-heading {
+ margin-bottom: 0.25rem;
+ }
+ .BarGraphKeyItem-label {
+ width: 130px;
+ }
+ }
+ @media (max-width: 929px) {
+ .InstallBox-code-box {
+ width: fit-content;
+ }
+ .CodeBlock .shiki {
+ padding: 24px 16px;
+ margin: calc(-1 * var(--horizontal-padding));
+ width: calc(100vw - var(--horizontal-padding) - var(--horizontal-padding) -2px);
+ white-space: pre-wrap;
+ box-sizing: border-box;
+ border-radius: 0;
+ font-size: 0.8rem;
+ }
+ .logo-link {
+ gap: 0;
+ }
+ header {
+ grid-template-columns: min-content auto;
+ gap: 2rem;
+ }
+ .tagline,
+ .subtitle,
+ .BarGraph-heading,
+ .BarGraph-subheading {
+ padding: 0 var(--horizontal-padding);
+ }
+ main {
+ padding-left: 0;
+ padding-right: 0;
+ text-align: left;
+ }
+ .InstallBox {
+ padding: 24px 16px;
+ margin-bottom: -32px;
+ }
+ .tagline {
+ font-size: 30pt;
+ }
+ .Tag--Command {
+ display: block;
+ width: fit-content;
+ margin-bottom: 1rem;
+ }
+ .Tabs {
+ margin: 0;
+ gap: 0rem;
+ width: 100%;
+ border-top: 1px solid rgba(200, 200, 200, 0.1);
+ }
+ .Tab {
+ width: 100%;
+ border-bottom-color: #333;
+ }
+ #pitch-content {
+ max-width: 100%;
+ }
+ .Graphs--active-react .Tab[data-tab="react"],
+ .Graphs--active-sqlite .Tab[data-tab="sqlite"],
+ .Graphs--active-websocket .Tab[data-tab="websocket"] {
+ background-color: #6464641a;
+ }
+ }
+ #explain p > code {
+ white-space: pre;
+ padding: 1px 2px;
+ }
+ .Group {
+ display: block;
+ }
+ .Tag--Command {
+ display: block;
+ width: fit-content;
+ margin-bottom: 0.5rem;
+ padding: 8px 12px;
+ }
+ .Label-replace {
+ font-weight: 500;
+ }
+ .Label-text {
+ margin-top: 0.5rem;
+ margin-bottom: 1rem;
+ }
+ #batteries {
+ padding-left: 0;
+ }
+ .Group {
+ margin-bottom: 2rem;
+ }
+ .Group strong {
+ display: block;
+ }
+ .Built {
+ text-align: center;
+ margin-top: 4rem;
+ margin-bottom: 2rem;
+ color: #333;
+ }
+ img {
+ object-fit: contain;
+ }
+ .visually-hidden {
+ clip: rect(0 0 0 0);
+ clip-path: inset(50%);
+ height: 1px;
+ overflow: hidden;
+ position: absolute;
+ white-space: nowrap;
+ width: 1px;
+ }
+ </style>
+ </head>
+ <body>
+ <div id="header-wrap">
+ <header>
+ <a href="/" id="logo-link" aria-label="home"
+ ><img
+ height="61px"
+ src="data:image/svg+xml;base64, PHN2ZyBpZD0iQnVuIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA4MCA3MCI+PHRpdGxlPkJ1biBMb2dvPC90aXRsZT48cGF0aCBpZD0iU2hhZG93IiBkPSJNNzEuMDksMjAuNzRjLS4xNi0uMTctLjMzLS4zNC0uNS0uNXMtLjMzLS4zNC0uNS0uNS0uMzMtLjM0LS41LS41LS4zMy0uMzQtLjUtLjUtLjMzLS4zNC0uNS0uNS0uMzMtLjM0LS41LS41LS4zMy0uMzQtLjUtLjVBMjYuNDYsMjYuNDYsMCwwLDEsNzUuNSwzNS43YzAsMTYuNTctMTYuODIsMzAuMDUtMzcuNSwzMC4wNS0xMS41OCwwLTIxLjk0LTQuMjMtMjguODMtMTAuODZsLjUuNS41LjUuNS41LjUuNS41LjUuNS41LjUuNUMxOS41NSw2NS4zLDMwLjE0LDY5Ljc1LDQyLDY5Ljc1YzIwLjY4LDAsMzcuNS0xMy40OCwzNy41LTMwQzc5LjUsMzIuNjksNzYuNDYsMjYsNzEuMDksMjAuNzRaIi8+PGcgaWQ9IkJvZHkiPjxwYXRoIGlkPSJCYWNrZ3JvdW5kIiBkPSJNNzMsMzUuN2MwLDE1LjIxLTE1LjY3LDI3LjU0LTM1LDI3LjU0UzMsNTAuOTEsMywzNS43QzMsMjYuMjcsOSwxNy45NCwxOC4yMiwxM1MzMy4xOCwzLDM4LDNzOC45NCw0LjEzLDE5Ljc4LDEwQzY3LDE3Ljk0LDczLDI2LjI3LDczLDM1LjdaIiBzdHlsZT0iZmlsbDojZmJmMGRmIi8+PHBhdGggaWQ9IkJvdHRvbV9TaGFkb3ciIGRhdGEtbmFtZT0iQm90dG9tIFNoYWRvdyIgZD0iTTczLDM1LjdhMjEuNjcsMjEuNjcsMCwwLDAtLjgtNS43OGMtMi43MywzMy4zLTQzLjM1LDM0LjktNTkuMzIsMjQuOTRBNDAsNDAsMCwwLDAsMzgsNjMuMjRDNTcuMyw2My4yNCw3Myw1MC44OSw3MywzNS43WiIgc3R5bGU9ImZpbGw6I2Y2ZGVjZSIvPjxwYXRoIGlkPSJMaWdodF9TaGluZSIgZGF0YS1uYW1lPSJMaWdodCBTaGluZSIgZD0iTTI0LjUzLDExLjE3QzI5LDguNDksMzQuOTQsMy40Niw0MC43OCwzLjQ1QTkuMjksOS4yOSwwLDAsMCwzOCwzYy0yLjQyLDAtNSwxLjI1LTguMjUsMy4xMy0xLjEzLjY2LTIuMywxLjM5LTMuNTQsMi4xNS0yLjMzLDEuNDQtNSwzLjA3LTgsNC43QzguNjksMTguMTMsMywyNi42MiwzLDM1LjdjMCwuNCwwLC44LDAsMS4xOUM5LjA2LDE1LjQ4LDIwLjA3LDEzLjg1LDI0LjUzLDExLjE3WiIgc3R5bGU9ImZpbGw6I2ZmZmVmYyIvPjxwYXRoIGlkPSJUb3AiIGQ9Ik0zNS4xMiw1LjUzQTE2LjQxLDE2LjQxLDAsMCwxLDI5LjQ5LDE4Yy0uMjguMjUtLjA2LjczLjMuNTksMy4zNy0xLjMxLDcuOTItNS4yMyw2LTEzLjE0QzM1LjcxLDUsMzUuMTIsNS4xMiwzNS4xMiw1LjUzWm0yLjI3LDBBMTYuMjQsMTYuMjQsMCwwLDEsMzksMTljLS4xMi4zNS4zMS42NS41NS4zNkM0MS43NCwxNi41Niw0My42NSwxMSwzNy45Myw1LDM3LjY0LDQuNzQsMzcuMTksNS4xNCwzNy4zOSw1LjQ5Wm0yLjc2LS4xN0ExNi40MiwxNi40MiwwLDAsMSw0NywxNy4xMmEuMzMuMzMsMCwwLDAsLjY1LjExYy45Mi0zLjQ5LjQtOS40NC03LjE3LTEyLjUzQzQwLjA4LDQuNTQsMzkuODIsNS4wOCw0MC4xNSw1LjMyWk0yMS42OSwxNS43NmExNi45NCwxNi45NCwwLDAsMCwxMC40Ny05Yy4xOC0uMzYuNzUtLjIyLjY2LjE4LTEuNzMsOC03LjUyLDkuNjctMTEuMTIsOS40NUMyMS4zMiwxNi40LDIxLjMzLDE1Ljg3LDIxLjY5LDE1Ljc2WiIgc3R5bGU9ImZpbGw6I2NjYmVhNztmaWxsLXJ1bGU6ZXZlbm9kZCIvPjxwYXRoIGlkPSJPdXRsaW5lIiBkPSJNMzgsNjUuNzVDMTcuMzIsNjUuNzUuNSw1Mi4yNy41LDM1LjdjMC0xMCw2LjE4LTE5LjMzLDE2LjUzLTI0LjkyLDMtMS42LDUuNTctMy4yMSw3Ljg2LTQuNjIsMS4yNi0uNzgsMi40NS0xLjUxLDMuNi0yLjE5QzMyLDEuODksMzUsLjUsMzgsLjVzNS42MiwxLjIsOC45LDMuMTRjMSwuNTcsMiwxLjE5LDMuMDcsMS44NywyLjQ5LDEuNTQsNS4zLDMuMjgsOSw1LjI3QzY5LjMyLDE2LjM3LDc1LjUsMjUuNjksNzUuNSwzNS43LDc1LjUsNTIuMjcsNTguNjgsNjUuNzUsMzgsNjUuNzVaTTM4LDNjLTIuNDIsMC01LDEuMjUtOC4yNSwzLjEzLTEuMTMuNjYtMi4zLDEuMzktMy41NCwyLjE1LTIuMzMsMS40NC01LDMuMDctOCw0LjdDOC42OSwxOC4xMywzLDI2LjYyLDMsMzUuNywzLDUwLjg5LDE4LjcsNjMuMjUsMzgsNjMuMjVTNzMsNTAuODksNzMsMzUuN0M3MywyNi42Miw2Ny4zMSwxOC4xMyw1Ny43OCwxMyw1NCwxMSw1MS4wNSw5LjEyLDQ4LjY2LDcuNjRjLTEuMDktLjY3LTIuMDktMS4yOS0zLTEuODRDNDIuNjMsNCw0MC40MiwzLDM4LDNaIi8+PC9nPjxnIGlkPSJNb3V0aCI+PGcgaWQ9IkJhY2tncm91bmQtMiIgZGF0YS1uYW1lPSJCYWNrZ3JvdW5kIj48cGF0aCBkPSJNNDUuMDUsNDNhOC45Myw4LjkzLDAsMCwxLTIuOTIsNC43MSw2LjgxLDYuODEsMCwwLDEtNCwxLjg4QTYuODQsNi44NCwwLDAsMSwzNCw0Ny43MSw4LjkzLDguOTMsMCwwLDEsMzEuMTIsNDNhLjcyLjcyLDAsMCwxLC44LS44MUg0NC4yNkEuNzIuNzIsMCwwLDEsNDUuMDUsNDNaIiBzdHlsZT0iZmlsbDojYjcxNDIyIi8+PC9nPjxnIGlkPSJUb25ndWUiPjxwYXRoIGlkPSJCYWNrZ3JvdW5kLTMiIGRhdGEtbmFtZT0iQmFja2dyb3VuZCIgZD0iTTM0LDQ3Ljc5YTYuOTEsNi45MSwwLDAsMCw0LjEyLDEuOSw2LjkxLDYuOTEsMCwwLDAsNC4xMS0xLjksMTAuNjMsMTAuNjMsMCwwLDAsMS0xLjA3LDYuODMsNi44MywwLDAsMC00LjktMi4zMSw2LjE1LDYuMTUsMCwwLDAtNSwyLjc4QzMzLjU2LDQ3LjQsMzMuNzYsNDcuNiwzNCw0Ny43OVoiIHN0eWxlPSJmaWxsOiNmZjYxNjQiLz48cGF0aCBpZD0iT3V0bGluZS0yIiBkYXRhLW5hbWU9Ik91dGxpbmUiIGQ9Ik0zNC4xNiw0N2E1LjM2LDUuMzYsMCwwLDEsNC4xOS0yLjA4LDYsNiwwLDAsMSw0LDEuNjljLjIzLS4yNS40NS0uNTEuNjYtLjc3YTcsNywwLDAsMC00LjcxLTEuOTMsNi4zNiw2LjM2LDAsMCwwLTQuODksMi4zNkE5LjUzLDkuNTMsMCwwLDAsMzQuMTYsNDdaIi8+PC9nPjxwYXRoIGlkPSJPdXRsaW5lLTMiIGRhdGEtbmFtZT0iT3V0bGluZSIgZD0iTTM4LjA5LDUwLjE5YTcuNDIsNy40MiwwLDAsMS00LjQ1LTIsOS41Miw5LjUyLDAsMCwxLTMuMTEtNS4wNSwxLjIsMS4yLDAsMCwxLC4yNi0xLDEuNDEsMS40MSwwLDAsMSwxLjEzLS41MUg0NC4yNmExLjQ0LDEuNDQsMCwwLDEsMS4xMy41MSwxLjE5LDEuMTksMCwwLDEsLjI1LDFoMGE5LjUyLDkuNTIsMCwwLDEtMy4xMSw1LjA1QTcuNDIsNy40MiwwLDAsMSwzOC4wOSw1MC4xOVptLTYuMTctNy40Yy0uMTYsMC0uMi4wNy0uMjEuMDlhOC4yOSw4LjI5LDAsMCwwLDIuNzMsNC4zN0E2LjIzLDYuMjMsMCwwLDAsMzguMDksNDlhNi4yOCw2LjI4LDAsMCwwLDMuNjUtMS43Myw4LjMsOC4zLDAsMCwwLDIuNzItNC4zNy4yMS4yMSwwLDAsMC0uMi0uMDlaIi8+PC9nPjxnIGlkPSJGYWNlIj48ZWxsaXBzZSBpZD0iUmlnaHRfQmx1c2giIGRhdGEtbmFtZT0iUmlnaHQgQmx1c2giIGN4PSI1My4yMiIgY3k9IjQwLjE4IiByeD0iNS44NSIgcnk9IjMuNDQiIHN0eWxlPSJmaWxsOiNmZWJiZDAiLz48ZWxsaXBzZSBpZD0iTGVmdF9CbHVjaCIgZGF0YS1uYW1lPSJMZWZ0IEJsdWNoIiBjeD0iMjIuOTUiIGN5PSI0MC4xOCIgcng9IjUuODUiIHJ5PSIzLjQ0IiBzdHlsZT0iZmlsbDojZmViYmQwIi8+PHBhdGggaWQ9IkV5ZXMiIGQ9Ik0yNS43LDM4LjhhNS41MSw1LjUxLDAsMSwwLTUuNS01LjUxQTUuNTEsNS41MSwwLDAsMCwyNS43LDM4LjhabTI0Ljc3LDBBNS41MSw1LjUxLDAsMSwwLDQ1LDMzLjI5LDUuNSw1LjUsMCwwLDAsNTAuNDcsMzguOFoiIHN0eWxlPSJmaWxsLXJ1bGU6ZXZlbm9kZCIvPjxwYXRoIGlkPSJJcmlzIiBkPSJNMjQsMzMuNjRhMi4wNywyLjA3LDAsMSwwLTIuMDYtMi4wN0EyLjA3LDIuMDcsMCwwLDAsMjQsMzMuNjRabTI0Ljc3LDBhMi4wNywyLjA3LDAsMSwwLTIuMDYtMi4wN0EyLjA3LDIuMDcsMCwwLDAsNDguNzUsMzMuNjRaIiBzdHlsZT0iZmlsbDojZmZmO2ZpbGwtcnVsZTpldmVub2RkIi8+PC9nPjwvc3ZnPg=="
+ alt="Bun logo"
+ id="logo" /><img
+ alt="Bun"
+ id="logo-text"
+ height="31.65px"
+ src="data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAALkAAAA9CAYAAADxjMiSAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAi1SURBVHgB7Z3vddpIEMBnBPhdPgUqiLgUYFJBSAXxVRC7gjgVxK4gdgVxKohdQUgFRxo4SAXgT753Ejs3s5JsQiQQsCsJaX/v+R+SkZBmZ2fnnxAKgGYTf/W1AKCL/JW2fxtgGv86x15/Dg7HHmDeHVlQRSC7IYBP/AWxkKKHL/R2osfX9N/RPqZOcsrvN0XEqVL0g/8e80AYuwHgyMMvQi6CHETCOfBADQi9F7zDgCLB7UL1GCOp7wTebafXH4HDkcKjkAfzyTf+MYBqCvNGRNvzt1FLwSVr+Ck4HDHLQk5QExDhxgm7I8GDGkIEpwuEb//NJqfgaDxayONFZa2QhS9r9M/B/eQjOBqNFvJ/D9QOzwXBRXg/+QyOxlJLc2UVMV9Yo38CRyNphJBrCM6D2WQIjsbRhgYhNjr/6IOjckhUnGM0Q/61yzEaH7zW88dtin6SRL93DAJqF6LWcOyNgCZA8GZT4Ii9MhIv0DGD5IJzRDcJiKWmI9ByKgLifOXGTHd1Z2aciy/bMCWqnHEeU4kjbPrcImgLPpaKPqO/Es1ORaLQoBb3CrypF0Wm53kDc+LwCD14j2xObhkhH7EJ+qXDP/Nc18YJOd+Um/Zz/yxr+wPf6DbCBEwek29Gu9t/AzsQzKd8X2gIBmBhOmv3+jdg+VibjqOPJV4vNiFhX6cHwkVbwfU67d4cmzyGtdIJOFJByNbY26EGWVtEiQSzf8bi9QITXj1+H46J/J2WBJhg3SaPwu04SqZNHlVzmdKSTMMkG1G+FNCJh/jaZHJXCl25IC4aao8FeH7a62KeSJCOAH0wiMhLyIIeziYyg9yubrcl5HM+8jW/+c2WwqRPUCKVHsJHW8Ie1DkuUAFaCMdprwdAVwjogx26hPCVZefVUa8/Xt5gx1xhAefFx8Wu2pJP8ibgBSL/OgYLYEOFXBX0uUU5rUbRRXHxeugdWAZlplgxXSprkz/jARIS/AUyKxiG1rxnq8YDYNPgNjlzBivvJTMzFEM3jFzFT8eWb5Zt4J0RQeeV+hcwTOfJzfYbTdXypvEit6dGtHjBMjZcDvwdgndlBGZxFUWF8ORhYRPiPRTN0sxReSFXa7TuLrBdaMXOd/xK4mF5iOzjARTPozavvJCbNh9aii7BYZ3Ew8JrnNLiEghKH7vyQm60IJqjnU32jydh+iKQ+yaalE0V6x6VzHNATx+78glasb/cCE6LZ/OwJmK4M+WninRloFVakwf3P40FhIjgg4tyNg+Wn0ElNXmcnfYRSJ2DCUhdHvVeXoGjbMSrNeWop/ZuEZD17hAs5MNKCblEqli434WSnUaGPjwLeKf38iLv7rLQrU3bgoqAUWrsZVoKbjibnPAOn2z50WUBbEfI2S8aziepCw7KcAlK/rJ05wJzEjYXE4U1+M02/xTnUjsMoDufRWm3o6x94oSqWxb2K7LgT5fBY0uTdyl7GvIh/WSMIV4UWWQ6G7w8RMBbBG/y3gMW9vNgPj02lTu/TK3yyWValMofKYpwAl4uinZo7kR2vF+1EnJF6q5tOEJaPnS4bfu2JLLZcQSGqZcmR+9TiDAJ5pOZ9HbUiUE2/L8F0rSEMST6AYaxZJPjLRHdZWzM7EvOYdguR6lew/65DvL+Em0bLlijBPeTm87zvgsEHQBKEujALHaEnEfj0YZC1nVI9K0FdGEiyZ4ij80Fe3tO8yyE4vI8R0mgBXOzkuaK5JEf9f48ZeE0pn1F2BcpVSMp+7k03BIJmyLkCVJCx2aPsaKJJUF3hREN4gAWnmg0HK8ru73sUiwbmoRctVGpVF7I48prsyYEwXnB2twHR2kchAsRLdjJIaQnfz2zE0Tqxu3eHCVwEEJuZbqPXJXpmyyYLEmViqN4Ki/ksQa0YVoMszZY8bCg935bE0kS/qvaSeGQOABNTmZyytPeOcOdSATGo24Q9QP5mnfnuAlr7v0d2Xjxt0r6hkWLF9F1KQVbFf3DYD5Z25xSP0tVnooRlY45r4wBdMTT9PSMHuxdMFumJov7bNtiEOfX3PKM8R2ern0XEd6GYka5kKtRrIT15Rk9HEYXIR2Rgi/bPB0gskPpFAqo8s4K8bcARiFY54SF2i1GC8Ba+VucMyLCeioCw5pL+7vlyQTSxnl53zgxS7T/EHQCVyG1OZkmiQxIHqRTt+irB0XWeGo/MYfpYVWGqYT1L5L6vnYHgjsoo71ZDF8iyeTsA+IxGGJVuSzzByugAmavUmjckyYSWuCtTReguFd6WbQIPpDhlIZ11Lk/ZNLVtlGZd5jjgUpxZXlZ12Ucn58tL0+j0EK+aJiQs5Y8y7UjwTWUAMXH7dSulK8cmmeukMpdYMsLFjEXClcAnbhdtZgQ6AR9b5ol5EQ/tmo0xEJGBgs3ch1zpSkpGax5bGpBSGOEXDRiG3AIW3LU61/ZqCDPYrUpKbtTp2CITZHtus4ajRByWWiyHf5qVw9CSHRWiACkmFLKLT73Rgv5sxo34mF/+LU8DXkfF5lcH2XpIV0JMhAzTCkn5HtSW02uNa900+q9NJLFKBVKocwGNjQ6292taBD9hkkPCzV0EVtHIdeNPll79ztrGk3ugmh0eb6oyeJqPdPwWiFrpinSw1LXheljWJ+1VL+lc0doiFEo+aDKteI+iNdpj502SWzanT7MJhf79IZJ2hnzTDPauLM8QMDDt/zT5z/8LXJq5lHpII6BFj85yjtdexi+fh7iW+JjQHSMrVJ9o8HIC2WUZp80hR2Q9IIFD3zA1jFFLfJ82CHlGKNMUsmVym5WpBvhs6BLp34PlC8H3fIC20Zu4Jht5btO9HjzUrSQfl68Vg5quOYaRc3n+YIrxW5MA+cbVxnpm588Rr2zpIlNNTyV3Pcgo+uZCFF8zLnt65/k4IcZ8rfUA/O3c9kp3U8XM0TtmX3UX6rLN1iyCLsy+pJtYIjlUSlCIsK9Tfquo9lYz2ld1TiYc+pJRqZrwezYl/8BpjOlthQ26tQAAAAASUVORK5CYII="
+ /></a>
+ <nav class="Navigation">
+ <ul>
+ <li>
+ <a class="NavText" href="https://github.com/oven-sh/bun#Reference">Docs</a>
+ </li>
+ <li>
+ <a class="NavText" href="https://bun.sh/discord">Discord</a>
+ </li>
+ <li>
+ <a class="NavText" href="https://github.com/oven-sh/bun">GitHub</a>
+ </li>
+ </ul>
+ </nav>
+ </header>
+ </div>
+ <div id="pitch">
+ <main>
+ <div id="pitch-content">
+ <h1 class="tagline">Bun is a fast all-in-one JavaScript runtime</h1>
+ <p class="subtitle">
+ Bundle, transpile, install and run JavaScript &amp; TypeScript projects — all in Bun. Bun is a new
+ JavaScript runtime with a native bundler, transpiler, task runner and npm client built-in.
+ </p>
+ <div class="InstallBox InstallBox--desktop">
+ <div class="InstallBox-label">
+ <div class="InstallBox-label-heading">
+ Install Bun CLI
+ <!-- -->0.2.1<!-- -->
+ (beta)
+ </div>
+ <div class="InstallBox-label-subtitle">
+ macOS x64 &amp; Silicon, Linux x64, Windows Subsystem for Linux
+ </div>
+ </div>
+ <div class="InstallBox-code-box">
+ <div class="InstallBox-curl">curl https://bun.sh/install | bash</div>
+ <button class="InstallBox-copy" aria-label="Copy installation script">copy</button>
+ </div>
+ <a class="InstallBox-view-source-link" target="_blank" href="https://bun.sh/install">Show script source</a>
+ </div>
+ </div>
+ <div class="Graphs Graphs--active-react">
+ <div class="Tabs" role="tablist">
+ <button
+ data-tab="react"
+ id="tab-react"
+ aria-controls="react-tab-content"
+ class="Tab"
+ role="tab"
+ aria-selected="true"
+ tabindex="0"
+ >
+ Bun.serve</button
+ ><button
+ data-tab="websocket"
+ id="tab-websocket"
+ aria-controls="websocket-tab-content"
+ class="Tab"
+ role="tab"
+ tabindex="-1"
+ >
+ WebSocket</button
+ ><button
+ data-tab="sqlite"
+ id="tab-sqlite"
+ aria-controls="sqlite-tab-content"
+ class="Tab"
+ role="tab"
+ tabindex="-1"
+ >
+ bun:sqlite
+ </button>
+ </div>
+ <div id="active-tab" class="ActiveTab">
+ <div
+ role="tabpanel"
+ tabindex="0"
+ id="react-tab-content"
+ aria-labelledby="tab-react"
+ class="BarGraph BarGraph--react BarGraph--horizontal BarGraph--dark"
+ >
+ <h2 class="BarGraph-heading">Server-side rendering React</h2>
+ <p class="BarGraph-subheading">HTTP requests per second (Linux x64)</p>
+ <ul style="--count: 3" class="BarGraphList">
+ <li class="BarGraphItem BarGraphItem--bun" style="--amount: 69845; --max: 87306.25">
+ <div class="visually-hidden">bun: 69,845 requests per second</div>
+ <div style="--amount: 69845; --max: 87306.25" class="BarGraphBar" aria-hidden="true">
+ <div style="--amount: 69845; --max: 87306.25" class="BarGraphBar-label">69,845</div>
+ </div>
+ </li>
+ <li class="BarGraphItem BarGraphItem--node" style="--amount: 16288; --max: 87306.25">
+ <div class="visually-hidden">node: 16,288 requests per second</div>
+ <div style="--amount: 16288; --max: 87306.25" class="BarGraphBar" aria-hidden="true">
+ <div style="--amount: 16288; --max: 87306.25" class="BarGraphBar-label">16,288</div>
+ </div>
+ </li>
+ <li class="BarGraphItem BarGraphItem--deno" style="--amount: 12926; --max: 87306.25">
+ <div class="visually-hidden">deno: 12,926 requests per second</div>
+ <div style="--amount: 12926; --max: 87306.25" class="BarGraphBar" aria-hidden="true">
+ <div style="--amount: 12926; --max: 87306.25" class="BarGraphBar-label">12,926</div>
+ </div>
+ </li>
+ </ul>
+ <div style="--count: 3" class="BarGraphKey">
+ <a
+ href="https://github.com/oven-sh/bun/blob/b0a7f8df926e91d3b2f0b3b8833ddaf55073f30e/bench/react-hello-world/react-hello-world.jsx#L27"
+ target="_blank"
+ class="BarGraphKeyItem"
+ aria-label="bun benchmark source"
+ ><div class="BarGraphKeyItem-label">bun</div>
+ <div class="BarGraphKeyItem-value">v0.2.0</div>
+ <div class="BarGraphKeyItem-viewSource">View source</div></a
+ ><a
+ href="https://github.com/oven-sh/bun/blob/e55d6eed2bf9a5db30250fdd8b9be063dc949054/bench/react-hello-world/react-hello-world.node.jsx"
+ target="_blank"
+ class="BarGraphKeyItem"
+ aria-label="node benchmark source"
+ ><div class="BarGraphKeyItem-label">node</div>
+ <div class="BarGraphKeyItem-value">v18.1.0</div>
+ <div class="BarGraphKeyItem-viewSource">View source</div></a
+ ><a
+ href="https://github.com/oven-sh/bun/blob/af033c02c5fbaade201abfe332f376879d9e6885/bench/react-hello-world/react-hello-world.deno.jsx"
+ target="_blank"
+ class="BarGraphKeyItem"
+ aria-label="Deno.serve() benchmark source"
+ ><div class="BarGraphKeyItem-label">Deno.serve()</div>
+ <div class="BarGraphKeyItem-value">v1.26.0</div>
+ <div class="BarGraphKeyItem-viewSource">View source</div></a
+ >
+ </div>
+ </div>
+ <div
+ role="tabpanel"
+ tabindex="-1"
+ id="websocket-tab-content"
+ aria-labelledby="tab-websocket"
+ class="BarGraph BarGraph--websocket BarGraph--horizontal BarGraph--dark"
+ >
+ <h2 class="BarGraph-heading">WebSocket server chat</h2>
+ <p class="BarGraph-subheading">Messages sent per second (Linux x64, 16 clients)</p>
+ <ul style="--count: 3" class="BarGraphList">
+ <li class="BarGraphItem BarGraphItem--bun" style="--amount: 737280; --max: 921600">
+ <div class="visually-hidden">bun: 737,280 messages sent per second</div>
+ <div style="--amount: 737280; --max: 921600" class="BarGraphBar" aria-hidden="true">
+ <div style="--amount: 737280; --max: 921600" class="BarGraphBar-label">737,280</div>
+ </div>
+ </li>
+ <li class="BarGraphItem BarGraphItem--node" style="--amount: 107457; --max: 921600">
+ <div class="visually-hidden">node: 107,457 messages sent per second</div>
+ <div style="--amount: 107457; --max: 921600" class="BarGraphBar" aria-hidden="true">
+ <div style="--amount: 107457; --max: 921600" class="BarGraphBar-label">107,457</div>
+ </div>
+ </li>
+ <li class="BarGraphItem BarGraphItem--deno" style="--amount: 82097; --max: 921600">
+ <div class="visually-hidden">deno: 82,097 messages sent per second</div>
+ <div style="--amount: 82097; --max: 921600" class="BarGraphBar" aria-hidden="true">
+ <div style="--amount: 82097; --max: 921600" class="BarGraphBar-label">82,097</div>
+ </div>
+ </li>
+ </ul>
+ <div style="--count: 3" class="BarGraphKey">
+ <a
+ href="https://github.com/oven-sh/bun/blob/9c7eb75a9ac845d92bfdfd6cc574dc8f39bde293/bench/websocket-server/chat-server.bun.js#L1"
+ target="_blank"
+ class="BarGraphKeyItem"
+ aria-label="Bun.serve() benchmark source"
+ ><div class="BarGraphKeyItem-label">Bun.serve()</div>
+ <div class="BarGraphKeyItem-value">v0.2.1</div>
+ <div class="BarGraphKeyItem-viewSource">View source</div></a
+ ><a
+ href="https://github.com/oven-sh/bun/blob/9c7eb75a9ac845d92bfdfd6cc574dc8f39bde293/bench/websocket-server/chat-server.node.mjs#L1"
+ target="_blank"
+ class="BarGraphKeyItem"
+ aria-label="ws (Node.js) benchmark source"
+ ><div class="BarGraphKeyItem-label">ws (Node.js)</div>
+ <div class="BarGraphKeyItem-value">node v18.10.0</div>
+ <div class="BarGraphKeyItem-viewSource">View source</div></a
+ ><a
+ href="https://github.com/oven-sh/bun/blob/9c7eb75a9ac845d92bfdfd6cc574dc8f39bde293/bench/websocket-server/chat-server.deno.mjs#L1"
+ target="_blank"
+ class="BarGraphKeyItem"
+ aria-label="Deno.serve() benchmark source"
+ ><div class="BarGraphKeyItem-label">Deno.serve()</div>
+ <div class="BarGraphKeyItem-value">v1.26.2</div>
+ <div class="BarGraphKeyItem-viewSource">View source</div></a
+ >
+ </div>
+ </div>
+ <div
+ role="tabpanel"
+ tabindex="-1"
+ id="sqlite-tab-content"
+ aria-labelledby="tab-sqlite"
+ class="BarGraph--sqlite BarGraph BarGraph--horizontal BarGraph--dark"
+ >
+ <h2 class="BarGraph-heading">Load a huge table</h2>
+ <p class="BarGraph-subheading">Average queries per second</p>
+ <ul style="--count: 3" class="BarGraphList">
+ <li class="BarGraphItem BarGraphItem--bun" style="--amount: 70.32; --max: 88">
+ <div class="visually-hidden">bun: 70.32 queries per second</div>
+ <div style="--amount: 70.32; --max: 88" class="BarGraphBar" aria-hidden="true">
+ <div style="--amount: 70.32; --max: 88" class="BarGraphBar-label">70.32</div>
+ </div>
+ </li>
+ <li class="BarGraphItem BarGraphItem--deno" style="--amount: 36.54; --max: 88">
+ <div class="visually-hidden">deno: 36.54 queries per second</div>
+ <div style="--amount: 36.54; --max: 88" class="BarGraphBar" aria-hidden="true">
+ <div style="--amount: 36.54; --max: 88" class="BarGraphBar-label">36.54</div>
+ </div>
+ </li>
+ <li class="BarGraphItem BarGraphItem--better-sqlite3" style="--amount: 23.28; --max: 88">
+ <div class="visually-hidden">better-sqlite3: 23.28 queries per second</div>
+ <div style="--amount: 23.28; --max: 88" class="BarGraphBar" aria-hidden="true">
+ <div style="--amount: 23.28; --max: 88" class="BarGraphBar-label">23.28</div>
+ </div>
+ </li>
+ </ul>
+ <div style="--count: 3" class="BarGraphKey">
+ <a
+ href="https://github.com/oven-sh/bun/blob/b0a7f8df926e91d3b2f0b3b8833ddaf55073f30e/bench/sqlite/bun.js#L9"
+ target="_blank"
+ class="BarGraphKeyItem"
+ aria-label="bun:sqlite benchmark source"
+ ><div class="BarGraphKeyItem-label">bun:sqlite</div>
+ <div class="BarGraphKeyItem-value">v0.2.0</div>
+ <div class="BarGraphKeyItem-viewSource">View source</div></a
+ ><a
+ href="https://github.com/oven-sh/bun/blob/6223030360c121e272aad98c7d1c14a009c5fc1c/bench/sqlite/deno.js#L9"
+ target="_blank"
+ class="BarGraphKeyItem"
+ aria-label="deno (x/sqlite3) benchmark source"
+ ><div class="BarGraphKeyItem-label">deno (x/sqlite3)</div>
+ <div class="BarGraphKeyItem-value">v1.26.1</div>
+ <div class="BarGraphKeyItem-viewSource">View source</div></a
+ ><a
+ href="https://github.com/oven-sh/bun/blob/e55d6eed2bf9a5db30250fdd8b9be063dc949054/bench/sqlite/node.mjs"
+ target="_blank"
+ class="BarGraphKeyItem"
+ aria-label="better-sqlite3 benchmark source"
+ ><div class="BarGraphKeyItem-label">better-sqlite3</div>
+ <div class="BarGraphKeyItem-value">node v18.2.0</div>
+ <div class="BarGraphKeyItem-viewSource">View source</div></a
+ >
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="InstallBox InstallBox--mobile">
+ <div class="InstallBox-label">
+ <div class="InstallBox-label-heading">
+ Install Bun CLI
+ <!-- -->0.2.1<!-- -->
+ (beta)
+ </div>
+ <div class="InstallBox-label-subtitle">macOS x64 &amp; Silicon, Linux x64, Windows Subsystem for Linux</div>
+ </div>
+ <div class="InstallBox-code-box">
+ <div class="InstallBox-curl">curl https://bun.sh/install | bash</div>
+ <button class="InstallBox-copy" aria-label="Copy installation script">copy</button>
+ </div>
+ <a class="InstallBox-view-source-link" target="_blank" href="https://bun.sh/install">Show script source</a>
+ </div>
+ </main>
+ </div>
+ <section id="explain-section">
+ <div id="explain">
+ <h2>Tell me more about Bun</h2>
+ <p>
+ Bun is a modern JavaScript runtime like Node or Deno. It was built from scratch to focus on three main things:
+ </p>
+ <ul>
+ <li>Start fast (it has the edge in mind).</li>
+ <li>New levels of performance (extending JavaScriptCore, the engine).</li>
+ <li>Being a great and complete tool (bundler, transpiler, package manager).</li>
+ </ul>
+ <p>
+ Bun is designed as a drop-in replacement for your current JavaScript &amp; TypeScript apps or scripts — on
+ your local computer, server or on the edge. Bun natively implements hundreds of Node.js and Web APIs,
+ including ~90% of<!-- -->
+ <a href="https://nodejs.org/api/n-api.html" target="_blank">Node-API</a>
+ <!-- -->functions (native modules), fs, path, Buffer and more.
+ </p>
+ <p>
+ The goal of Bun is to run most of the world&#x27;s JavaScript outside of browsers, bringing performance and
+ complexity enhancements to your future infrastructure, as well as developer productivity through better,
+ simpler tooling.
+ </p>
+ <h2>Batteries included</h2>
+ <ul id="batteries">
+ <li>
+ Web APIs like<!-- -->
+ <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/fetch" class="Tag Tag--WebAPI"
+ >fetch</a
+ >,<!-- -->
+ <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket" class="Tag Tag--WebAPI"
+ >WebSocket</a
+ >, and<!-- -->
+ <a
+ target="_blank"
+ href="https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream"
+ class="Tag Tag--WebAPI"
+ >ReadableStream</a
+ >
+ <!-- -->are built-in
+ </li>
+ <li>
+ <span target="_blank" class="Tag Tag--NodeJS">node_modules</span>
+ bun implements Node.js&#x27; module resolution algorithm, so you can use npm packages in Bun. ESM and
+ CommonJS are supported, but Bun internally uses ESM
+ </li>
+ <li>
+ In Bun, every file is transpiled.<!-- -->
+ <span target="_blank" class="Tag Tag--TypeScript">TypeScript</span>
+ &amp; <span target="_blank" class="Tag Tag--React">JSX</span> just work
+ </li>
+ <li>
+ Bun supports <code>&quot;paths&quot;</code>, <code>&quot;jsxImportSource&quot;</code>and more from
+ <span target="_blank" class="Tag Tag--TypeScript">tsconfig.json</span>
+ files
+ </li>
+ <li>
+ <span target="_blank" class="Tag Tag--Bun">Bun.Transpiler</span>
+ Bun&#x27;s JSX &amp; TypeScript transpiler is available as an API in Bun
+ </li>
+ <li>
+ use the fastest system calls available with
+ <span target="_blank" class="Tag Tag--Bun">Bun.write</span>
+ <!-- -->to write, copy, pipe, send and clone files
+ </li>
+ <li>
+ Bun automatically loads environment variables from
+ <span target="_blank" class="Tag Tag--Bun">.env</span>
+ <!-- -->files. No more<!-- -->
+ <code class="mono">require(&quot;dotenv&quot;).config()</code>
+ </li>
+ <li>
+ Bun ships with a fast SQLite3 client built-in<!-- -->
+ <span target="_blank" class="Tag Tag--Bun">bun:sqlite</span>
+ </li>
+ <li>
+ <a target="_blank" href="https://github.com/oven-sh/bun/issues/158" class="Tag Tag--NodeJS">Node-API</a>
+ <!-- -->Bun implements most of<!-- -->
+ <a href="https://nodejs.org/api/n-api.html#node-api" target="_blank">Node-API (N-API)</a>. Many Node.js
+ native modules just work
+ </li>
+ <li>
+ <span target="_blank" class="Tag Tag--Bun">bun:ffi</span> call native code from JavaScript with Bun&#x27;s
+ low-overhead foreign function interface
+ </li>
+ <li>
+ <span target="_blank" class="Tag Tag--NodeJS">node:fs</span>
+ <span target="_blank" class="Tag Tag--NodeJS">node:path</span> Bun natively supports a growing list of
+ Node.js core modules along with globals like Buffer and process
+ </li>
+ </ul>
+ <h2>How does Bun work?</h2>
+ <p>
+ Bun uses the<!-- -->
+ <a href="https://github.com/WebKit/WebKit/tree/main/Source/JavaScriptCore">JavaScriptCore</a>
+ <!-- -->engine, which tends<!-- -->
+ <a target="blank" href="https://twitter.com/jarredsumner/status/1499225725492076544">to start</a>
+ <!-- -->and perform a little faster than more traditional choices like V8. Bun is written in<!-- -->
+ <a href="https://ziglang.org/"
+ ><svg xmlns="http://www.w3.org/2000/svg" height="1.2rem" class="Zig" viewBox="0 0 400 140">
+ <title>Zig</title>
+ <g fill="#F7A41D">
+ <g>
+ <polygon points="46,22 28,44 19,30"></polygon>
+ <polygon
+ points="46,22 33,33 28,44 22,44 22,95 31,95 20,100 12,117 0,117 0,22"
+ shape-rendering="crispEdges"
+ ></polygon>
+ <polygon points="31,95 12,117 4,106"></polygon>
+ </g>
+ <g>
+ <polygon points="56,22 62,36 37,44"></polygon>
+ <polygon points="56,22 111,22 111,44 37,44 56,32" shape-rendering="crispEdges"></polygon>
+ <polygon points="116,95 97,117 90,104"></polygon>
+ <polygon points="116,95 100,104 97,117 42,117 42,95" shape-rendering="crispEdges"></polygon>
+ <polygon points="150,0 52,117 3,140 101,22"></polygon>
+ </g>
+ <g>
+ <polygon points="141,22 140,40 122,45"></polygon>
+ <polygon
+ points="153,22 153,117 106,117 120,105 125,95 131,95 131,45 122,45 132,36 141,22"
+ shape-rendering="crispEdges"
+ ></polygon>
+ <polygon points="125,95 130,110 106,117"></polygon>
+ </g>
+ </g>
+ <g fill="#121212">
+ <g>
+ <polygon points="260,22 260,37 229,40 177,40 177,22" shape-rendering="crispEdges"></polygon>
+ <polygon points="260,37 207,99 207,103 176,103 229,40 229,37"></polygon>
+ <polygon points="261,99 261,117 176,117 176,103 206,99" shape-rendering="crispEdges"></polygon>
+ </g>
+ <rect x="272" y="22" shape-rendering="crispEdges" width="22" height="95"></rect>
+ <g>
+ <polygon points="394,67 394,106 376,106 376,81 360,70 346,67" shape-rendering="crispEdges"></polygon>
+ <polygon points="360,68 376,81 346,67"></polygon>
+ <path
+ d="M394,106c-10.2,7.3-24,12-37.7,12c-29,0-51.1-20.8-51.1-48.3c0-27.3,22.5-48.1,52-48.1
+ c14.3,0,29.2,5.5,38.9,14l-13,15c-7.1-6.3-16.8-10-25.9-10c-17,0-30.2,12.9-30.2,29.5c0,16.8,13.3,29.6,30.3,29.6
+ c5.7,0,12.8-2.3,19-5.5L394,106z"
+ ></path>
+ </g>
+ </g></svg></a
+ >, a low-level programming language with manual memory management.<br /><br />Most of Bun is written from
+ scratch including the JSX/TypeScript transpiler, npm client, bundler, SQLite client, HTTP client, WebSocket
+ client and more.
+ </p>
+ <h2>Why is Bun fast?</h2>
+ <p>
+ An enormous amount of time spent profiling, benchmarking and optimizing things. The answer is different for
+ every part of Bun, but one general theme:<!-- -->
+ <a href="https://ziglang.org/"
+ ><svg xmlns="http://www.w3.org/2000/svg" height="1.2rem" class="Zig" viewBox="0 0 400 140">
+ <title>Zig</title>
+ <g fill="#F7A41D">
+ <g>
+ <polygon points="46,22 28,44 19,30"></polygon>
+ <polygon
+ points="46,22 33,33 28,44 22,44 22,95 31,95 20,100 12,117 0,117 0,22"
+ shape-rendering="crispEdges"
+ ></polygon>
+ <polygon points="31,95 12,117 4,106"></polygon>
+ </g>
+ <g>
+ <polygon points="56,22 62,36 37,44"></polygon>
+ <polygon points="56,22 111,22 111,44 37,44 56,32" shape-rendering="crispEdges"></polygon>
+ <polygon points="116,95 97,117 90,104"></polygon>
+ <polygon points="116,95 100,104 97,117 42,117 42,95" shape-rendering="crispEdges"></polygon>
+ <polygon points="150,0 52,117 3,140 101,22"></polygon>
+ </g>
+ <g>
+ <polygon points="141,22 140,40 122,45"></polygon>
+ <polygon
+ points="153,22 153,117 106,117 120,105 125,95 131,95 131,45 122,45 132,36 141,22"
+ shape-rendering="crispEdges"
+ ></polygon>
+ <polygon points="125,95 130,110 106,117"></polygon>
+ </g>
+ </g>
+ <g fill="#121212">
+ <g>
+ <polygon points="260,22 260,37 229,40 177,40 177,22" shape-rendering="crispEdges"></polygon>
+ <polygon points="260,37 207,99 207,103 176,103 229,40 229,37"></polygon>
+ <polygon points="261,99 261,117 176,117 176,103 206,99" shape-rendering="crispEdges"></polygon>
+ </g>
+ <rect x="272" y="22" shape-rendering="crispEdges" width="22" height="95"></rect>
+ <g>
+ <polygon points="394,67 394,106 376,106 376,81 360,70 346,67" shape-rendering="crispEdges"></polygon>
+ <polygon points="360,68 376,81 346,67"></polygon>
+ <path
+ d="M394,106c-10.2,7.3-24,12-37.7,12c-29,0-51.1-20.8-51.1-48.3c0-27.3,22.5-48.1,52-48.1
+ c14.3,0,29.2,5.5,38.9,14l-13,15c-7.1-6.3-16.8-10-25.9-10c-17,0-30.2,12.9-30.2,29.5c0,16.8,13.3,29.6,30.3,29.6
+ c5.7,0,12.8-2.3,19-5.5L394,106z"
+ ></path>
+ </g>
+ </g></svg></a
+ >&#x27;s low-level control over memory and lack of hidden control flow makes it much simpler to write fast
+ software.<!-- -->
+ <a href="https://github.com/sponsors/ziglang">Sponsor the Zig Software Foundation</a>.
+ </p>
+ <h2>Getting started</h2>
+ <p>
+ To install Bun, run this<!-- -->
+ <a target="_blank" href="https://bun.sh/install">install script</a>
+ <!-- -->in your terminal. It downloads Bun from GitHub.
+ </p>
+ <div class="CodeBlock">
+ <pre
+ class="shiki"
+ style="background-color: #282a36"
+ ><code><span class="line"><span style="color: #F8F8F2">curl https://bun.sh/install </span><span style="color: #FF79C6">|</span><span style="color: #F8F8F2"> bash</span></span></code></pre>
+ </div>
+ <p>
+ <!-- -->Bun&#x27;s HTTP server is built on web standards like<!-- -->
+ <a class="Identifier" href="https://developer.mozilla.org/en-US/docs/Web/API/Request">Request</a>
+ <!-- -->and<!-- -->
+ <a class="Identifier" href="https://developer.mozilla.org/en-US/docs/Web/API/Response">Response</a>
+ </p>
+ <div class="CodeBlock">
+ <pre
+ class="shiki"
+ style="background-color: #282a36"
+ ><code><span class="line"><span style="color: #6272A4">// http.js</span></span>
+<span class="line"><span style="color: #FF79C6">export</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">default</span><span style="color: #F8F8F2"> {</span></span>
+<span class="line"><span style="color: #F8F8F2"> port</span><span style="color: #FF79C6">:</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">3000</span><span style="color: #F8F8F2">,</span></span>
+<span class="line"><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">fetch</span><span style="color: #F8F8F2">(</span><span style="color: #FFB86C; font-style: italic">request</span><span style="color: #F8F8F2">) {</span></span>
+<span class="line"><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">return</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6; font-weight: bold">new</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">Response</span><span style="color: #F8F8F2">(</span><span style="color: #E9F284">&quot;</span><span style="color: #F1FA8C">Welcome to Bun!</span><span style="color: #E9F284">&quot;</span><span style="color: #F8F8F2">);</span></span>
+<span class="line"><span style="color: #F8F8F2"> },</span></span>
+<span class="line"><span style="color: #F8F8F2">};</span></span></code></pre>
+ </div>
+ <p>Run it with Bun:</p>
+ <div class="CodeBlock">
+ <pre
+ class="shiki"
+ style="background-color: #282a36"
+ ><code><span class="line"><span style="color: #F8F8F2">bun run http.js</span></span></code></pre>
+ </div>
+ <p>
+ Then open<!-- -->
+ <a target="_blank" href="http://localhost:3000">http://localhost:3000</a>
+ <!-- -->in your browser.<br /><br />See<!-- -->
+ <a href="https://github.com/oven-sh/bun/tree/main/examples">more examples</a>
+ <!-- -->and check out<!-- -->
+ <a href="https://github.com/oven-sh/bun#Reference">the docs</a>. If you have any questions or want help, join<!-- -->
+ <a href="https://bun.sh/discord">Bun&#x27;s Discord</a>.
+ </p>
+ <h2>Bun CLI</h2>
+ <div class="Group">
+ <span target="_blank" class="Tag Tag--Command">bun run</span>
+ <p>
+ The same command for running JavaScript &amp; TypeScript files with bun&#x27;s JavaScript runtime also runs
+ package.json<!-- -->
+ <code class="mono">&quot;scripts&quot;</code>.
+ </p>
+ <strong
+ >Replace <code class="mono">npm run</code> with<!-- -->
+ <code class="mono">bun run</code> and save 160ms on every run.</strong
+ ><br />
+ <div>
+ Bun runs package.json scripts<!-- -->
+ <a
+ href="https://twitter.com/jarredsumner/status/1454218996983623685"
+ target="_blank"
+ class="PerformanceClaim"
+ >30x faster than <code class="mono">npm run</code></a
+ >
+ </div>
+ </div>
+ <div class="Group">
+ <span target="_blank" class="Tag Tag--Command">bun install</span>
+ <p>
+ <code classsName="mono">bun install</code> is an npm-compatible package manager. You probably will be
+ surprised by how much faster copying files can get.
+ </p>
+ <strong
+ >Replace <code class="mono">yarn</code> with<!-- -->
+ <code class="mono">bun install</code> and get 20x faster package installs.</strong
+ ><br />
+ <div><code class="mono">bun install</code> uses the fastest system calls available to copy files.</div>
+ </div>
+ <div class="Group">
+ <span target="_blank" class="Tag Tag--Command">bun wiptest</span>
+ <p>A Jest-like test runner for JavaScript &amp; TypeScript projects built-in to Bun.</p>
+ <div class="Label">
+ <a
+ href="https://twitter.com/jarredsumner/status/1542824445810642946"
+ target="_blank"
+ class="PerformanceClaim"
+ >You&#x27;ve never seen a JavaScript test runner this fast</a
+ >
+ <!-- -->(or incomplete).
+ </div>
+ </div>
+ <h2>What is the license?</h2>
+ <p>MIT License, excluding dependencies which have various licenses.</p>
+ <h2>How do I see the source code?</h2>
+ <p>Bun is on <a href="https://github.com/oven-sh/bun">GitHub</a>.</p>
+ </div>
+ </section>
+ <section id="explain-section"><div id="explain"></div></section>
+ <script>
+ [...document.querySelectorAll(".Tab")].map(el => {
+ el.addEventListener("click", function (e) {
+ var tab = e.srcElement.getAttribute("data-tab");
+ [...document.querySelectorAll(".Tab")].map(el => {
+ var active = el.getAttribute("data-tab") === tab;
+ el.setAttribute("tabindex", active ? 0 : -1);
+ el.setAttribute("aria-selected", active);
+ });
+ [...document.querySelectorAll(".BarGraph")].map(el => {
+ var active = el.id === tab + "-tab-content";
+ el.setAttribute("tabindex", active ? 0 : -1);
+ });
+ document.querySelector(".Graphs").setAttribute("class", "Graphs Graphs--active-" + tab);
+ });
+
+ el.addEventListener("keydown", e => {
+ var tabs = [...document.querySelectorAll(".Tab")];
+ var activeTabEl = document.querySelector(".Tab[aria-selected='true']");
+ var activeTabIndex = tabs.indexOf(activeTabEl);
+ if (e.key === "ArrowRight" || e.key === "ArrowDown") {
+ e.preventDefault();
+ activeTabIndex = (activeTabIndex + 1) % tabs.length;
+ tabs[activeTabIndex].click();
+ tabs[activeTabIndex].focus();
+ }
+ if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
+ e.preventDefault();
+ activeTabIndex = (activeTabIndex + tabs.length - 1) % tabs.length;
+ tabs[activeTabIndex].click();
+ tabs[activeTabIndex].focus();
+ }
+ if (e.key === "Home") {
+ e.preventDefault();
+ tabs[0].click();
+ tabs[0].focus();
+ }
+ if (e.key === "End") {
+ e.preventDefault();
+ tabs[tabs.length - 1].click();
+ tabs[tabs.length - 1].focus();
+ }
+ });
+ });
+
+ for (const el of document.querySelectorAll(".InstallBox-copy")) {
+ el.addEventListener("click", async e => {
+ await navigator.clipboard.writeText("curl https://bun.sh/install | bash");
+ });
+ }
+ </script>
+ <div class="Built">
+ Built with Bun
+ <!-- -->0.2.1
+ </div>
+ </body>
+</html>
diff --git a/test/js/web/fetch/fixture.html.gz b/test/js/web/fetch/fixture.html.gz
new file mode 100644
index 000000000..0bb85d4cb
--- /dev/null
+++ b/test/js/web/fetch/fixture.html.gz
Binary files differ
diff --git a/test/js/web/html/FormData.test.ts b/test/js/web/html/FormData.test.ts
new file mode 100644
index 000000000..9d0db4361
--- /dev/null
+++ b/test/js/web/html/FormData.test.ts
@@ -0,0 +1,410 @@
+import { afterAll, beforeAll, describe, expect, it, test } from "bun:test";
+import fs, { chmodSync, unlinkSync } from "fs";
+import { mkfifo } from "mkfifo";
+import { gc, withoutAggressiveGC } from "../../gc";
+
+describe("FormData", () => {
+ it("should be able to append a string", () => {
+ const formData = new FormData();
+ formData.append("foo", "bar");
+ expect(formData.get("foo")).toBe("bar");
+ expect(formData.getAll("foo")[0]).toBe("bar");
+ });
+
+ it("should be able to append a Blob", async () => {
+ const formData = new FormData();
+ formData.append("foo", new Blob(["bar"]));
+ expect(await formData.get("foo").text()).toBe("bar");
+ expect(formData.getAll("foo")[0] instanceof Blob).toBe(true);
+ });
+
+ it("should be able to set a Blob", async () => {
+ const formData = new FormData();
+ formData.set("foo", new Blob(["bar"]));
+ expect(await formData.get("foo").text()).toBe("bar");
+ expect(formData.getAll("foo")[0] instanceof Blob).toBe(true);
+ });
+
+ it("should be able to set a string", async () => {
+ const formData = new FormData();
+ formData.set("foo", "bar");
+ expect(formData.get("foo")).toBe("bar");
+ expect(formData.getAll("foo")[0]).toBe("bar");
+ });
+
+ const multipartFormDataFixturesRawBody = [
+ {
+ name: "simple",
+ body: '--foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbar\r\n--foo--\r\n',
+ headers: {
+ "Content-Type": "multipart/form-data; boundary=foo",
+ },
+ expected: {
+ foo: "bar",
+ },
+ },
+ {
+ name: "simple with trailing CRLF",
+ body: '--foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbar\r\n--foo--\r\n\r\n',
+ headers: {
+ "Content-Type": "multipart/form-data; boundary=foo",
+ },
+ expected: {
+ foo: "bar",
+ },
+ },
+ {
+ name: "simple with trailing CRLF and extra CRLF",
+ body: '--foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbar\r\n--foo--\r\n\r\n\r\n',
+ headers: {
+ "Content-Type": "multipart/form-data; boundary=foo",
+ },
+ expected: {
+ foo: "bar",
+ },
+ },
+ {
+ name: "advanced",
+ body: '--foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbar\r\n--foo\r\nContent-Disposition: form-data; name="baz"\r\n\r\nqux\r\n--foo--\r\n',
+ headers: {
+ "Content-Type": "multipart/form-data; boundary=foo",
+ },
+ expected: {
+ foo: "bar",
+ baz: "qux",
+ },
+ },
+ {
+ name: "advanced with multiple values",
+ body: '--foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbar\r\n--foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbaz\r\n--foo--\r\n',
+ headers: {
+ "Content-Type": "multipart/form-data; boundary=foo",
+ },
+ expected: {
+ foo: ["bar", "baz"],
+ },
+ },
+ {
+ name: "advanced with multiple values and trailing CRLF",
+ body: '--foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbar\r\n--foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbaz\r\n--foo--\r\n\r\n',
+ headers: {
+ "Content-Type": "multipart/form-data; boundary=foo",
+ },
+ expected: {
+ foo: ["bar", "baz"],
+ },
+ },
+ {
+ name: "extremely advanced",
+ body: '--foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbar\r\n--foo\r\nContent-Disposition: form-data; name="baz"\r\n\r\nqux\r\n--foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbaz\r\n--foo--\r\n',
+ headers: {
+ "Content-Type": "multipart/form-data; boundary=foo",
+ },
+ expected: {
+ foo: ["bar", "baz"],
+ baz: "qux",
+ },
+ },
+ {
+ name: "with name and filename",
+ body: '--foo\r\nContent-Disposition: form-data; name="foo"; filename="bar"\r\n\r\nbaz\r\n--foo--\r\n',
+ headers: {
+ "Content-Type": "multipart/form-data; boundary=foo",
+ },
+ expected: {
+ foo: new Blob(["baz"]),
+ },
+ },
+ {
+ name: "with name and filename and trailing CRLF",
+ body: '--foo\r\nContent-Disposition: form-data; name="foo"; filename="bar"\r\n\r\nbaz\r\n--foo--\r\n\r\n',
+ headers: {
+ "Content-Type": "multipart/form-data; boundary=foo",
+ },
+ expected: {
+ foo: new Blob(["baz"]),
+ },
+ },
+ ];
+
+ for (const { name, body, headers, expected: expected_ } of multipartFormDataFixturesRawBody) {
+ const Class = [Response, Request] as const;
+ for (const C of Class) {
+ it(`should parse multipart/form-data (${name}) with ${C.name}`, async () => {
+ const response = C === Response ? new Response(body, { headers }) : new Request({ headers, body });
+ const formData = await response.formData();
+ expect(formData instanceof FormData).toBe(true);
+ const entry = {};
+ const expected = Object.assign({}, expected_);
+
+ for (const key of formData.keys()) {
+ const values = formData.getAll(key);
+ if (values.length > 1) {
+ entry[key] = values;
+ } else {
+ entry[key] = values[0];
+ if (entry[key] instanceof Blob) {
+ expect(expected[key] instanceof Blob).toBe(true);
+
+ entry[key] = await entry[key].text();
+ expected[key] = await expected[key].text();
+ } else {
+ expect(typeof entry[key]).toBe(typeof expected[key]);
+ expect(expected[key] instanceof Blob).toBe(false);
+ }
+ }
+ }
+
+ expect(entry).toEqual(expected);
+ });
+
+ it(`should roundtrip multipart/form-data (${name}) with ${C.name}`, async () => {
+ const response = C === Response ? new Response(body, { headers }) : new Request({ headers, body });
+ const formData = await response.formData();
+ expect(formData instanceof FormData).toBe(true);
+
+ const request = await new Response(formData).formData();
+ expect(request instanceof FormData).toBe(true);
+
+ const aKeys = Array.from(formData.keys());
+ const bKeys = Array.from(request.keys());
+ expect(aKeys).toEqual(bKeys);
+
+ for (const key of aKeys) {
+ const aValues = formData.getAll(key);
+ const bValues = request.getAll(key);
+ for (let i = 0; i < aValues.length; i++) {
+ const a = aValues[i];
+ const b = bValues[i];
+ if (a instanceof Blob) {
+ expect(b instanceof Blob).toBe(true);
+ expect(await a.text()).toBe(await b.text());
+ } else {
+ expect(a).toBe(b);
+ }
+ }
+ }
+
+ // Test that it also works with Blob.
+ const c = await new Blob([body], { type: headers["Content-Type"] }).formData();
+ expect(c instanceof FormData).toBe(true);
+ const cKeys = Array.from(c.keys());
+ expect(cKeys).toEqual(bKeys);
+ for (const key of cKeys) {
+ const cValues = c.getAll(key);
+ const bValues = request.getAll(key);
+ for (let i = 0; i < cValues.length; i++) {
+ const c = cValues[i];
+ const b = bValues[i];
+ if (c instanceof Blob) {
+ expect(b instanceof Blob).toBe(true);
+ expect(await c.text()).toBe(await b.text());
+ } else {
+ expect(c).toBe(b);
+ }
+ }
+ }
+ });
+ }
+ }
+
+ it("should throw on missing final boundary", async () => {
+ const response = new Response('-foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbar\r\n', {
+ headers: {
+ "Content-Type": "multipart/form-data; boundary=foo",
+ },
+ });
+ try {
+ await response.formData();
+ throw "should have thrown";
+ } catch (e) {
+ expect(typeof e.message).toBe("string");
+ }
+ });
+
+ it("should throw on bad boundary", async () => {
+ const response = new Response('foo\r\nContent-Disposition: form-data; name="foo"\r\n\r\nbar\r\n', {
+ headers: {
+ "Content-Type": "multipart/form-data; boundary=foo",
+ },
+ });
+ try {
+ await response.formData();
+ throw "should have thrown";
+ } catch (e) {
+ expect(typeof e.message).toBe("string");
+ }
+ });
+
+ it("should throw on bad header", async () => {
+ const response = new Response('foo\r\nContent-Disposition: form-data; name"foo"\r\n\r\nbar\r\n', {
+ headers: {
+ "Content-Type": "multipart/form-data; boundary=foo",
+ },
+ });
+ try {
+ await response.formData();
+ throw "should have thrown";
+ } catch (e) {
+ expect(typeof e.message).toBe("string");
+ }
+ });
+
+ it("file upload on HTTP server (receive)", async () => {
+ const server = Bun.serve({
+ port: 0,
+ development: false,
+ async fetch(req) {
+ const formData = await req.formData();
+ return new Response(formData.get("foo"));
+ },
+ });
+
+ const reqBody = new Request(`http://${server.hostname}:${server.port}`, {
+ body: '--foo\r\nContent-Disposition: form-data; name="foo"; filename="bar"\r\n\r\nbaz\r\n--foo--\r\n\r\n',
+ headers: {
+ "Content-Type": "multipart/form-data; boundary=foo",
+ },
+ method: "POST",
+ });
+
+ const res = await fetch(reqBody);
+ const body = await res.text();
+ expect(body).toBe("baz");
+ server.stop(true);
+ });
+
+ it("file send on HTTP server (receive)", async () => {
+ const server = Bun.serve({
+ port: 0,
+ development: false,
+ async fetch(req) {
+ const formData = await req.formData();
+ return new Response(formData);
+ },
+ });
+
+ const reqBody = new Request(`http://${server.hostname}:${server.port}`, {
+ body: '--foo\r\nContent-Disposition: form-data; name="foo"; filename="bar"\r\n\r\nbaz\r\n--foo--\r\n\r\n',
+ headers: {
+ "Content-Type": "multipart/form-data; boundary=foo",
+ },
+ method: "POST",
+ });
+
+ const res = await fetch(reqBody);
+ const body = await res.formData();
+ expect(await (body.get("foo") as Blob).text()).toBe("baz");
+ server.stop(true);
+ });
+
+ describe("Bun.file support", () => {
+ describe("roundtrip", () => {
+ const path = import.meta.dir + "/form-data-fixture.txt";
+ for (const C of [Request, Response]) {
+ it(`with ${C.name}`, async () => {
+ await Bun.write(path, "foo!");
+ const formData = new FormData();
+ formData.append("foo", Bun.file(path));
+ const response = C === Response ? new Response(formData) : new Request({ body: formData });
+ expect(response.headers.get("content-type")?.startsWith("multipart/form-data;")).toBe(true);
+
+ const formData2 = await response.formData();
+ expect(formData2 instanceof FormData).toBe(true);
+ expect(formData2.get("foo") instanceof Blob).toBe(true);
+ expect(await (formData2.get("foo") as Blob).text()).toBe("foo!");
+ });
+ }
+ });
+
+ it("doesnt crash when file is missing", async () => {
+ const formData = new FormData();
+ formData.append("foo", Bun.file("missing"));
+ expect(() => new Response(formData)).toThrow();
+ });
+ });
+
+ it("Bun.inspect", () => {
+ const formData = new FormData();
+ formData.append("foo", "bar");
+ formData.append("foo", new Blob(["bar"]));
+ formData.append("bar", "baz");
+ formData.append("boop", Bun.file("missing"));
+ expect(Bun.inspect(formData).length > 0).toBe(true);
+ });
+
+ describe("URLEncoded", () => {
+ test("should parse URL encoded", async () => {
+ const response = new Response("foo=bar&baz=qux", {
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ },
+ });
+ const formData = await response.formData();
+ expect(formData instanceof FormData).toBe(true);
+ expect(formData.get("foo")).toBe("bar");
+ expect(formData.get("baz")).toBe("qux");
+ });
+
+ test("should parse URLSearchParams", async () => {
+ const searchParams = new URLSearchParams("foo=bar&baz=qux");
+ const response = new Response(searchParams);
+ expect(response.headers.get("Content-Type")).toBe("application/x-www-form-urlencoded;charset=UTF-8");
+
+ expect(searchParams instanceof URLSearchParams).toBe(true);
+ expect(searchParams.get("foo")).toBe("bar");
+
+ const formData = await response.formData();
+ expect(formData instanceof FormData).toBe(true);
+ expect(formData.get("foo")).toBe("bar");
+ expect(formData.get("baz")).toBe("qux");
+ });
+
+ test("should parse URL encoded with charset", async () => {
+ const response = new Response("foo=bar&baz=qux", {
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
+ },
+ });
+ const formData = await response.formData();
+ expect(formData instanceof FormData).toBe(true);
+ expect(formData.get("foo")).toBe("bar");
+ expect(formData.get("baz")).toBe("qux");
+ });
+
+ test("should parse URL encoded with charset and space", async () => {
+ const response = new Response("foo=bar&baz=qux+quux", {
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
+ },
+ });
+ const formData = await response.formData();
+ expect(formData instanceof FormData).toBe(true);
+ expect(formData.get("foo")).toBe("bar");
+ expect(formData.get("baz")).toBe("qux quux");
+ });
+
+ test("should parse URL encoded with charset and plus", async () => {
+ const response = new Response("foo=bar&baz=qux+quux", {
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
+ },
+ });
+ const formData = await response.formData();
+ expect(formData instanceof FormData).toBe(true);
+ expect(formData.get("foo")).toBe("bar");
+ expect(formData.get("baz")).toBe("qux quux");
+ });
+
+ it("should handle multiple values", async () => {
+ const response = new Response("foo=bar&foo=baz", {
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ },
+ });
+ const formData = await response.formData();
+ expect(formData instanceof FormData).toBe(true);
+ expect(formData.getAll("foo")).toEqual(["bar", "baz"]);
+ });
+ });
+});
diff --git a/test/js/web/html/form-data-fixture.txt b/test/js/web/html/form-data-fixture.txt
new file mode 100644
index 000000000..a4d20dd78
--- /dev/null
+++ b/test/js/web/html/form-data-fixture.txt
@@ -0,0 +1 @@
+foo! \ No newline at end of file
diff --git a/test/js/web/streams/bun-streams-test-fifo.sh b/test/js/web/streams/bun-streams-test-fifo.sh
new file mode 100644
index 000000000..57650ba1d
--- /dev/null
+++ b/test/js/web/streams/bun-streams-test-fifo.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+
+echoerr() { echo "$@" 1>&2; }
+
+echoerr "bun-streams-test-fifo.sh: starting"
+echo -e "$FIFO_TEST" >>${@: -1}
+echoerr "bun-streams-test-fifo.sh: ending"
+exit 0
diff --git a/test/js/web/streams/fetch.js.txt b/test/js/web/streams/fetch.js.txt
new file mode 100644
index 000000000..5a9b52fcf
--- /dev/null
+++ b/test/js/web/streams/fetch.js.txt
@@ -0,0 +1,46 @@
+<!doctype html>
+<html>
+<head>
+ <title>Example Domain</title>
+
+ <meta charset="utf-8" />
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <style type="text/css">
+ body {
+ background-color: #f0f0f2;
+ margin: 0;
+ padding: 0;
+ font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+
+ }
+ div {
+ width: 600px;
+ margin: 5em auto;
+ padding: 2em;
+ background-color: #fdfdff;
+ border-radius: 0.5em;
+ box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);
+ }
+ a:link, a:visited {
+ color: #38488f;
+ text-decoration: none;
+ }
+ @media (max-width: 700px) {
+ div {
+ margin: 0 auto;
+ width: auto;
+ }
+ }
+ </style>
+</head>
+
+<body>
+<div>
+ <h1>Example Domain</h1>
+ <p>This domain is for use in illustrative examples in documents. You may use this
+ domain in literature without prior coordination or asking for permission.</p>
+ <p><a href="https://www.iana.org/domains/example">More information...</a></p>
+</div>
+</body>
+</html>
diff --git a/test/js/web/streams/streams.test.js b/test/js/web/streams/streams.test.js
new file mode 100644
index 000000000..c4af85e4f
--- /dev/null
+++ b/test/js/web/streams/streams.test.js
@@ -0,0 +1,630 @@
+import { file, readableStreamToArrayBuffer, readableStreamToArray, readableStreamToText } from "bun";
+import { expect, it, beforeEach, afterEach, describe } from "bun:test";
+import { mkfifo } from "mkfifo";
+import { realpathSync, unlinkSync, writeFileSync } from "node:fs";
+import { join } from "node:path";
+import { tmpdir } from "os";
+import { gc } from "harness";
+
+beforeEach(() => gc());
+afterEach(() => gc());
+
+describe("WritableStream", () => {
+ it("works", async () => {
+ try {
+ var chunks = [];
+ var writable = new WritableStream({
+ write(chunk, controller) {
+ chunks.push(chunk);
+ },
+ close(er) {
+ console.log("closed");
+ console.log(er);
+ },
+ abort(reason) {
+ console.log("aborted!");
+ console.log(reason);
+ },
+ });
+
+ var writer = writable.getWriter();
+
+ writer.write(new Uint8Array([1, 2, 3]));
+
+ writer.write(new Uint8Array([4, 5, 6]));
+
+ await writer.close();
+
+ expect(JSON.stringify(Array.from(Buffer.concat(chunks)))).toBe(JSON.stringify([1, 2, 3, 4, 5, 6]));
+ } catch (e) {
+ console.log(e);
+ console.log(e.stack);
+ throw e;
+ }
+ });
+
+ it("pipeTo", async () => {
+ const rs = new ReadableStream({
+ start(controller) {
+ controller.enqueue("hello world");
+ controller.close();
+ },
+ });
+
+ let received;
+ const ws = new WritableStream({
+ write(chunk, controller) {
+ received = chunk;
+ },
+ });
+ await rs.pipeTo(ws);
+ expect(received).toBe("hello world");
+ });
+});
+
+describe("ReadableStream.prototype.tee", () => {
+ it("class", () => {
+ const [a, b] = new ReadableStream().tee();
+ expect(a instanceof ReadableStream).toBe(true);
+ expect(b instanceof ReadableStream).toBe(true);
+ });
+
+ describe("default stream", () => {
+ it("works", async () => {
+ var [a, b] = new ReadableStream({
+ start(controller) {
+ controller.enqueue("a");
+ controller.enqueue("b");
+ controller.enqueue("c");
+ controller.close();
+ },
+ }).tee();
+
+ expect(await readableStreamToText(a)).toBe("abc");
+ expect(await readableStreamToText(b)).toBe("abc");
+ });
+ });
+
+ describe("direct stream", () => {
+ it("works", async () => {
+ try {
+ var [a, b] = new ReadableStream({
+ pull(controller) {
+ controller.write("a");
+ controller.write("b");
+ controller.write("c");
+ controller.close();
+ },
+ type: "direct",
+ }).tee();
+
+ expect(await readableStreamToText(a)).toBe("abc");
+ expect(await readableStreamToText(b)).toBe("abc");
+ } catch (e) {
+ console.log(e.message);
+ console.log(e.stack);
+ throw e;
+ }
+ });
+ });
+});
+
+it("ReadableStream.prototype[Symbol.asyncIterator]", async () => {
+ const stream = new ReadableStream({
+ start(controller) {
+ controller.enqueue("hello");
+ controller.enqueue("world");
+ controller.close();
+ },
+ cancel(reason) {},
+ });
+
+ const chunks = [];
+ try {
+ for await (const chunk of stream) {
+ chunks.push(chunk);
+ }
+ } catch (e) {
+ console.log(e.message);
+ console.log(e.stack);
+ }
+
+ expect(chunks.join("")).toBe("helloworld");
+});
+
+it("ReadableStream.prototype[Symbol.asyncIterator] pull", async () => {
+ const stream = new ReadableStream({
+ pull(controller) {
+ controller.enqueue("hello");
+ controller.enqueue("world");
+ controller.close();
+ },
+ cancel(reason) {},
+ });
+
+ const chunks = [];
+ for await (const chunk of stream) {
+ chunks.push(chunk);
+ }
+ expect(chunks.join("")).toBe("helloworld");
+});
+
+it("ReadableStream.prototype[Symbol.asyncIterator] direct", async () => {
+ const stream = new ReadableStream({
+ pull(controller) {
+ controller.write("hello");
+ controller.write("world");
+ controller.close();
+ },
+ type: "direct",
+ cancel(reason) {},
+ });
+
+ const chunks = [];
+ try {
+ for await (const chunk of stream) {
+ chunks.push(chunk);
+ }
+ } catch (e) {
+ console.log(e.message);
+ console.log(e.stack);
+ }
+
+ expect(Buffer.concat(chunks).toString()).toBe("helloworld");
+});
+
+it("ReadableStream.prototype.values() cancel", async () => {
+ var cancelled = false;
+ const stream = new ReadableStream({
+ pull(controller) {
+ controller.enqueue("hello");
+ controller.enqueue("world");
+ },
+ cancel(reason) {
+ cancelled = true;
+ },
+ });
+
+ for await (const chunk of stream.values({ preventCancel: false })) {
+ break;
+ }
+ expect(cancelled).toBe(true);
+});
+
+it("ReadableStream.prototype.values() preventCancel", async () => {
+ var cancelled = false;
+ const stream = new ReadableStream({
+ pull(controller) {
+ controller.enqueue("hello");
+ controller.enqueue("world");
+ },
+ cancel(reason) {
+ cancelled = true;
+ },
+ });
+
+ for await (const chunk of stream.values({ preventCancel: true })) {
+ break;
+ }
+ expect(cancelled).toBe(false);
+});
+
+it("ReadableStream.prototype.values", async () => {
+ const stream = new ReadableStream({
+ start(controller) {
+ controller.enqueue("hello");
+ controller.enqueue("world");
+ controller.close();
+ },
+ });
+
+ const chunks = [];
+ for await (const chunk of stream.values()) {
+ chunks.push(chunk);
+ }
+ expect(chunks.join("")).toBe("helloworld");
+});
+
+it("Bun.file() read text from pipe", async () => {
+ try {
+ unlinkSync("/tmp/fifo");
+ } catch (e) {}
+
+ console.log("here");
+ mkfifo("/tmp/fifo", 0o666);
+
+ // 65k so its less than the max on linux
+ const large = "HELLO!".repeat((((1024 * 65) / "HELLO!".length) | 0) + 1);
+
+ const chunks = [];
+
+ const proc = Bun.spawn({
+ cmd: ["bash", join(import.meta.dir + "/", "bun-streams-test-fifo.sh"), "/tmp/fifo"],
+ stderr: "inherit",
+ stdout: null,
+ stdin: null,
+ env: {
+ FIFO_TEST: large,
+ },
+ });
+ const exited = proc.exited;
+ proc.ref();
+
+ const prom = (async function () {
+ while (chunks.length === 0) {
+ var out = Bun.file("/tmp/fifo").stream();
+ for await (const chunk of out) {
+ chunks.push(chunk);
+ }
+ }
+ return Buffer.concat(chunks).toString();
+ })();
+
+ const [status, output] = await Promise.all([exited, prom]);
+ expect(output.length).toBe(large.length + 1);
+ expect(output).toBe(large + "\n");
+ expect(status).toBe(0);
+});
+
+it("exists globally", () => {
+ expect(typeof ReadableStream).toBe("function");
+ expect(typeof ReadableStreamBYOBReader).toBe("function");
+ expect(typeof ReadableStreamBYOBRequest).toBe("function");
+ expect(typeof ReadableStreamDefaultController).toBe("function");
+ expect(typeof ReadableStreamDefaultReader).toBe("function");
+ expect(typeof TransformStream).toBe("function");
+ expect(typeof TransformStreamDefaultController).toBe("function");
+ expect(typeof WritableStream).toBe("function");
+ expect(typeof WritableStreamDefaultController).toBe("function");
+ expect(typeof WritableStreamDefaultWriter).toBe("function");
+ expect(typeof ByteLengthQueuingStrategy).toBe("function");
+ expect(typeof CountQueuingStrategy).toBe("function");
+});
+
+it("new Response(stream).body", async () => {
+ var stream = new ReadableStream({
+ pull(controller) {
+ controller.enqueue("hello");
+ controller.enqueue("world");
+ controller.close();
+ },
+ cancel() {},
+ });
+ var response = new Response(stream);
+ expect(response.body).toBe(stream);
+ expect(await response.text()).toBe("helloworld");
+});
+
+it("new Request({body: stream}).body", async () => {
+ var stream = new ReadableStream({
+ pull(controller) {
+ controller.enqueue("hello");
+ controller.enqueue("world");
+ controller.close();
+ },
+ cancel() {},
+ });
+ var response = new Request({ body: stream });
+ expect(response.body).toBe(stream);
+ expect(await response.text()).toBe("helloworld");
+});
+
+it("ReadableStream (readMany)", async () => {
+ var stream = new ReadableStream({
+ pull(controller) {
+ controller.enqueue("hello");
+ controller.enqueue("world");
+ controller.close();
+ },
+ cancel() {},
+ });
+ var reader = stream.getReader();
+ const chunk = await reader.readMany();
+ expect(chunk.value.join("")).toBe("helloworld");
+ expect((await reader.read()).done).toBe(true);
+});
+
+it("ReadableStream (direct)", async () => {
+ var stream = new ReadableStream({
+ pull(controller) {
+ controller.write("hello");
+ controller.write("world");
+ controller.close();
+ },
+ cancel() {},
+ type: "direct",
+ });
+ var reader = stream.getReader();
+ const chunk = await reader.read();
+ expect(chunk.value.join("")).toBe(Buffer.from("helloworld").join(""));
+ expect((await reader.read()).done).toBe(true);
+ expect((await reader.read()).done).toBe(true);
+});
+
+it("ReadableStream (bytes)", async () => {
+ var stream = new ReadableStream({
+ start(controller) {
+ controller.enqueue(Buffer.from("abdefgh"));
+ },
+ pull(controller) {},
+ cancel() {},
+ type: "bytes",
+ });
+ const chunks = [];
+ const chunk = await stream.getReader().read();
+ chunks.push(chunk.value);
+ expect(chunks[0].join("")).toBe(Buffer.from("abdefgh").join(""));
+});
+
+it("ReadableStream (default)", async () => {
+ var stream = new ReadableStream({
+ start(controller) {
+ controller.enqueue(Buffer.from("abdefgh"));
+ controller.close();
+ },
+ pull(controller) {},
+ cancel() {},
+ });
+ const chunks = [];
+ const chunk = await stream.getReader().read();
+ chunks.push(chunk.value);
+ expect(chunks[0].join("")).toBe(Buffer.from("abdefgh").join(""));
+});
+
+it("readableStreamToArray", async () => {
+ var queue = [Buffer.from("abdefgh")];
+ var stream = new ReadableStream({
+ pull(controller) {
+ var chunk = queue.shift();
+ if (chunk) {
+ controller.enqueue(chunk);
+ } else {
+ controller.close();
+ }
+ },
+ cancel() {},
+ type: "bytes",
+ });
+
+ const chunks = await readableStreamToArray(stream);
+
+ expect(chunks[0].join("")).toBe(Buffer.from("abdefgh").join(""));
+});
+
+it("readableStreamToArrayBuffer (bytes)", async () => {
+ var queue = [Buffer.from("abdefgh")];
+ var stream = new ReadableStream({
+ pull(controller) {
+ var chunk = queue.shift();
+ if (chunk) {
+ controller.enqueue(chunk);
+ } else {
+ controller.close();
+ }
+ },
+ cancel() {},
+ type: "bytes",
+ });
+ const buffer = await readableStreamToArrayBuffer(stream);
+ expect(new TextDecoder().decode(new Uint8Array(buffer))).toBe("abdefgh");
+});
+
+it("readableStreamToArrayBuffer (default)", async () => {
+ var queue = [Buffer.from("abdefgh")];
+ var stream = new ReadableStream({
+ pull(controller) {
+ var chunk = queue.shift();
+ if (chunk) {
+ controller.enqueue(chunk);
+ } else {
+ controller.close();
+ }
+ },
+ cancel() {},
+ });
+
+ const buffer = await readableStreamToArrayBuffer(stream);
+ expect(new TextDecoder().decode(new Uint8Array(buffer))).toBe("abdefgh");
+});
+
+it("ReadableStream for Blob", async () => {
+ var blob = new Blob(["abdefgh", "ijklmnop"]);
+ expect(await blob.text()).toBe("abdefghijklmnop");
+ var stream;
+ try {
+ stream = blob.stream();
+ stream = blob.stream();
+ } catch (e) {
+ console.error(e);
+ console.error(e.stack);
+ }
+ const chunks = [];
+ var reader;
+ reader = stream.getReader();
+
+ while (true) {
+ var chunk;
+ try {
+ chunk = await reader.read();
+ } catch (e) {
+ console.error(e);
+ console.error(e.stack);
+ }
+ if (chunk.done) break;
+ chunks.push(new TextDecoder().decode(chunk.value));
+ }
+ expect(chunks.join("")).toBe(new TextDecoder().decode(Buffer.from("abdefghijklmnop")));
+});
+
+it("ReadableStream for File", async () => {
+ var blob = file(import.meta.dir + "/fetch.js.txt");
+ var stream = blob.stream();
+ const chunks = [];
+ var reader = stream.getReader();
+ stream = undefined;
+ while (true) {
+ const chunk = await reader.read();
+ if (chunk.done) break;
+ chunks.push(chunk.value);
+ }
+ reader = undefined;
+ const output = new Uint8Array(await blob.arrayBuffer()).join("");
+ const input = chunks.map(a => a.join("")).join("");
+ expect(output).toBe(input);
+});
+
+it("ReadableStream for File errors", async () => {
+ try {
+ var blob = file(import.meta.dir + "/fetch.js.txt.notfound");
+ blob.stream().getReader();
+ throw new Error("should not reach here");
+ } catch (e) {
+ expect(e.code).toBe("ENOENT");
+ expect(e.syscall).toBe("open");
+ }
+});
+
+it("ReadableStream for empty blob closes immediately", async () => {
+ var blob = new Blob([]);
+ var stream = blob.stream();
+ const chunks = [];
+ var reader = stream.getReader();
+ while (true) {
+ const chunk = await reader.read();
+ if (chunk.done) break;
+ chunks.push(chunk.value);
+ }
+
+ expect(chunks.length).toBe(0);
+});
+
+it("ReadableStream for empty file closes immediately", async () => {
+ writeFileSync("/tmp/bun-empty-file-123456", "");
+ var blob = file("/tmp/bun-empty-file-123456");
+ var stream;
+ try {
+ stream = blob.stream();
+ } catch (e) {
+ console.error(e.stack);
+ }
+ const chunks = [];
+ var reader = stream.getReader();
+ while (true) {
+ const chunk = await reader.read();
+ if (chunk.done) break;
+ chunks.push(chunk.value);
+ }
+
+ expect(chunks.length).toBe(0);
+});
+
+it("new Response(stream).arrayBuffer() (bytes)", async () => {
+ var queue = [Buffer.from("abdefgh")];
+ var stream = new ReadableStream({
+ pull(controller) {
+ var chunk = queue.shift();
+ if (chunk) {
+ controller.enqueue(chunk);
+ } else {
+ controller.close();
+ }
+ },
+ cancel() {},
+ type: "bytes",
+ });
+ const buffer = await new Response(stream).arrayBuffer();
+ expect(new TextDecoder().decode(new Uint8Array(buffer))).toBe("abdefgh");
+});
+
+it("new Response(stream).arrayBuffer() (default)", async () => {
+ var queue = [Buffer.from("abdefgh")];
+ var stream = new ReadableStream({
+ pull(controller) {
+ var chunk = queue.shift();
+ if (chunk) {
+ controller.enqueue(chunk);
+ } else {
+ controller.close();
+ }
+ },
+ cancel() {},
+ });
+ const buffer = await new Response(stream).arrayBuffer();
+ expect(new TextDecoder().decode(new Uint8Array(buffer))).toBe("abdefgh");
+});
+
+it("new Response(stream).text() (default)", async () => {
+ var queue = [Buffer.from("abdefgh")];
+ var stream = new ReadableStream({
+ pull(controller) {
+ var chunk = queue.shift();
+ if (chunk) {
+ controller.enqueue(chunk);
+ } else {
+ controller.close();
+ }
+ },
+ cancel() {},
+ });
+ const text = await new Response(stream).text();
+ expect(text).toBe("abdefgh");
+});
+
+it("new Response(stream).json() (default)", async () => {
+ var queue = [Buffer.from(JSON.stringify({ hello: true }))];
+ var stream = new ReadableStream({
+ pull(controller) {
+ var chunk = queue.shift();
+ if (chunk) {
+ controller.enqueue(chunk);
+ } else {
+ controller.close();
+ }
+ },
+ cancel() {},
+ });
+ const json = await new Response(stream).json();
+ expect(json.hello).toBe(true);
+});
+
+it("new Response(stream).blob() (default)", async () => {
+ var queue = [Buffer.from(JSON.stringify({ hello: true }))];
+ var stream = new ReadableStream({
+ pull(controller) {
+ var chunk = queue.shift();
+ if (chunk) {
+ controller.enqueue(chunk);
+ } else {
+ controller.close();
+ }
+ },
+ cancel() {},
+ });
+ const response = new Response(stream);
+ const blob = await response.blob();
+ expect(await blob.text()).toBe('{"hello":true}');
+});
+
+it("Blob.stream() -> new Response(stream).text()", async () => {
+ var blob = new Blob(["abdefgh"]);
+ var stream = blob.stream();
+ const text = await new Response(stream).text();
+ expect(text).toBe("abdefgh");
+});
+
+it("Bun.file().stream() read text from large file", async () => {
+ const hugely = "HELLO!".repeat(1024 * 1024 * 10);
+ const tmpfile = join(realpathSync(tmpdir()), "bun-streams-test.txt");
+ writeFileSync(tmpfile, hugely);
+ try {
+ const chunks = [];
+ for await (const chunk of Bun.file(tmpfile).stream()) {
+ chunks.push(chunk);
+ }
+ const output = Buffer.concat(chunks).toString();
+ expect(output).toHaveLength(hugely.length);
+ expect(output).toBe(hugely);
+ } finally {
+ unlinkSync(tmpfile);
+ }
+});
diff --git a/test/js/web/timers/microtask.test.js b/test/js/web/timers/microtask.test.js
new file mode 100644
index 000000000..f41159cfa
--- /dev/null
+++ b/test/js/web/timers/microtask.test.js
@@ -0,0 +1,74 @@
+import { it } from "bun:test";
+
+it("queueMicrotask", async () => {
+ // You can verify this test is correct by copy pasting this into a browser's console and checking it doesn't throw an error.
+ var run = 0;
+
+ await new Promise((resolve, reject) => {
+ queueMicrotask(() => {
+ if (run++ != 0) {
+ reject(new Error("Microtask execution order is wrong: " + run));
+ }
+ queueMicrotask(() => {
+ if (run++ != 3) {
+ reject(new Error("Microtask execution order is wrong: " + run));
+ }
+ });
+ });
+ queueMicrotask(() => {
+ if (run++ != 1) {
+ reject(new Error("Microtask execution order is wrong: " + run));
+ }
+ queueMicrotask(() => {
+ if (run++ != 4) {
+ reject(new Error("Microtask execution order is wrong: " + run));
+ }
+
+ queueMicrotask(() => {
+ if (run++ != 6) {
+ reject(new Error("Microtask execution order is wrong: " + run));
+ }
+ });
+ });
+ });
+ queueMicrotask(() => {
+ if (run++ != 2) {
+ reject(new Error("Microtask execution order is wrong: " + run));
+ }
+ queueMicrotask(() => {
+ if (run++ != 5) {
+ reject(new Error("Microtask execution order is wrong: " + run));
+ }
+
+ queueMicrotask(() => {
+ if (run++ != 7) {
+ reject(new Error("Microtask execution order is wrong: " + run));
+ }
+ resolve(true);
+ });
+ });
+ });
+ });
+
+ {
+ var passed = false;
+ try {
+ queueMicrotask(1234);
+ } catch (exception) {
+ passed = exception instanceof TypeError;
+ }
+
+ if (!passed) throw new Error("queueMicrotask should throw a TypeError if the argument is not a function");
+ }
+
+ {
+ var passed = false;
+ try {
+ queueMicrotask();
+ } catch (exception) {
+ passed = exception instanceof TypeError;
+ }
+
+ if (!passed) throw new Error("queueMicrotask should throw a TypeError if the argument is empty");
+ }
+});
diff --git a/test/js/web/timers/performance.test.js b/test/js/web/timers/performance.test.js
new file mode 100644
index 000000000..dd50c4dc6
--- /dev/null
+++ b/test/js/web/timers/performance.test.js
@@ -0,0 +1,22 @@
+import { expect, it } from "bun:test";
+
+it("performance.now() should be monotonic", () => {
+ const first = performance.now();
+ const second = performance.now();
+ const third = performance.now();
+ const fourth = performance.now();
+ const fifth = performance.now();
+ const sixth = performance.now();
+ expect(first < second).toBe(true);
+ expect(second < third).toBe(true);
+ expect(third < fourth).toBe(true);
+ expect(fourth < fifth).toBe(true);
+ expect(fifth < sixth).toBe(true);
+ expect(Bun.nanoseconds() > 0).toBe(true);
+ expect(Bun.nanoseconds() > sixth).toBe(true);
+ expect(typeof Bun.nanoseconds() === "number").toBe(true);
+});
+
+it("performance.timeOrigin + performance.now() should be similar to Date.now()", () => {
+ expect(Math.abs(performance.timeOrigin + performance.now() - Date.now()) < 1000).toBe(true);
+});
diff --git a/test/js/web/timers/setImmediate.test.js b/test/js/web/timers/setImmediate.test.js
new file mode 100644
index 000000000..9cd6fa1c9
--- /dev/null
+++ b/test/js/web/timers/setImmediate.test.js
@@ -0,0 +1,47 @@
+import { it, expect } from "bun:test";
+
+it("setImmediate", async () => {
+ var lastID = -1;
+ const result = await new Promise((resolve, reject) => {
+ var numbers = [];
+
+ for (let i = 0; i < 10; i++) {
+ const id = setImmediate((...args) => {
+ numbers.push(i);
+ if (i === 9) {
+ resolve(numbers);
+ }
+ try {
+ expect(args.length).toBe(1);
+ expect(args[0]).toBe(i);
+ } catch (err) {
+ reject(err);
+ }
+ }, i);
+ expect(id > lastID).toBe(true);
+ lastID = id;
+ }
+ });
+
+ for (let j = 0; j < result.length; j++) {
+ expect(result[j]).toBe(j);
+ }
+ expect(result.length).toBe(10);
+});
+
+it("clearImmediate", async () => {
+ var called = false;
+ const id = setImmediate(() => {
+ called = true;
+ expect(false).toBe(true);
+ });
+ clearImmediate(id);
+
+ // assert it doesn't crash if you call clearImmediate twice
+ clearImmediate(id);
+
+ await new Promise((resolve, reject) => {
+ setImmediate(resolve);
+ });
+ expect(called).toBe(false);
+});
diff --git a/test/js/web/timers/setInterval.test.js b/test/js/web/timers/setInterval.test.js
new file mode 100644
index 000000000..7b03afba5
--- /dev/null
+++ b/test/js/web/timers/setInterval.test.js
@@ -0,0 +1,61 @@
+import { it, expect } from "bun:test";
+
+it("setInterval", async () => {
+ var counter = 0;
+ var start;
+ const result = await new Promise((resolve, reject) => {
+ start = performance.now();
+
+ var id = setInterval(
+ (...args) => {
+ counter++;
+ if (counter === 10) {
+ resolve(counter);
+ clearInterval(id);
+ }
+ try {
+ expect(args).toStrictEqual(["foo"]);
+ } catch (err) {
+ reject(err);
+ clearInterval(id);
+ }
+ },
+ 1,
+ "foo",
+ );
+ });
+
+ expect(result).toBe(10);
+ expect(performance.now() - start >= 10).toBe(true);
+});
+
+it("clearInterval", async () => {
+ var called = false;
+ const id = setInterval(() => {
+ called = true;
+ expect(false).toBe(true);
+ }, 1);
+ clearInterval(id);
+ await new Promise((resolve, reject) => {
+ setInterval(() => {
+ resolve();
+ }, 10);
+ });
+ expect(called).toBe(false);
+});
+
+it("async setInterval", async () => {
+ var remaining = 5;
+ await new Promise((resolve, reject) => {
+ queueMicrotask(() => {
+ var id = setInterval(async () => {
+ await 1;
+ remaining--;
+ if (remaining === 0) {
+ clearInterval(id);
+ resolve();
+ }
+ }, 1);
+ });
+ });
+});
diff --git a/test/js/web/timers/setTimeout.test.js b/test/js/web/timers/setTimeout.test.js
new file mode 100644
index 000000000..88472adc7
--- /dev/null
+++ b/test/js/web/timers/setTimeout.test.js
@@ -0,0 +1,173 @@
+import { it, expect } from "bun:test";
+
+it("setTimeout", async () => {
+ var lastID = -1;
+ const result = await new Promise((resolve, reject) => {
+ var numbers = [];
+
+ for (let i = 0; i < 10; i++) {
+ const id = setTimeout(
+ (...args) => {
+ numbers.push(i);
+ if (i === 9) {
+ resolve(numbers);
+ }
+ try {
+ expect(args).toStrictEqual(["foo"]);
+ } catch (err) {
+ reject(err);
+ }
+ },
+ i,
+ "foo",
+ );
+ expect(+id > lastID).toBe(true);
+ lastID = id;
+ }
+ });
+
+ for (let j = 0; j < result.length; j++) {
+ expect(result[j]).toBe(j);
+ }
+ expect(result.length).toBe(10);
+});
+
+it("clearTimeout", async () => {
+ var called = false;
+
+ // as object
+ {
+ const id = setTimeout(() => {
+ called = true;
+ expect(false).toBe(true);
+ }, 0);
+ clearTimeout(id);
+
+ // assert it doesn't crash if you call clearTimeout twice
+ clearTimeout(id);
+ }
+
+ // as number
+ {
+ const id = setTimeout(() => {
+ called = true;
+ expect(false).toBe(true);
+ }, 0);
+ clearTimeout(+id);
+
+ // assert it doesn't crash if you call clearTimeout twice
+ clearTimeout(+id);
+ }
+
+ await new Promise((resolve, reject) => {
+ setTimeout(resolve, 10);
+ });
+ expect(called).toBe(false);
+});
+
+it("setTimeout(() => {}, 0)", async () => {
+ var called = false;
+ setTimeout(() => {
+ called = true;
+ }, 0);
+ await new Promise((resolve, reject) => {
+ setTimeout(() => {
+ resolve();
+ }, 10);
+ });
+ expect(called).toBe(true);
+ var ranFirst = -1;
+ setTimeout(() => {
+ if (ranFirst === -1) ranFirst = 1;
+ }, 1);
+ setTimeout(() => {
+ if (ranFirst === -1) ranFirst = 0;
+ }, 0);
+
+ await new Promise((resolve, reject) => {
+ setTimeout(() => {
+ resolve();
+ }, 10);
+ });
+ expect(ranFirst).toBe(0);
+
+ ranFirst = -1;
+
+ const id = setTimeout(() => {
+ ranFirst = 0;
+ }, 0);
+ clearTimeout(id);
+ await new Promise((resolve, reject) => {
+ setTimeout(() => {
+ resolve();
+ }, 10);
+ });
+ expect(ranFirst).toBe(-1);
+});
+
+it("Bun.sleep", async () => {
+ var sleeps = 0;
+ await Bun.sleep(0);
+ const start = performance.now();
+ sleeps++;
+ await Bun.sleep(1);
+ sleeps++;
+ await Bun.sleep(2);
+ sleeps++;
+ const end = performance.now();
+ expect((end - start) * 1000).toBeGreaterThanOrEqual(3);
+
+ expect(sleeps).toBe(3);
+});
+
+it("Bun.sleep propagates exceptions", async () => {
+ try {
+ await Bun.sleep(1).then(a => {
+ throw new Error("TestPassed");
+ });
+ throw "Should not reach here";
+ } catch (err) {
+ expect(err.message).toBe("TestPassed");
+ }
+});
+
+it("Bun.sleep works with a Date object", async () => {
+ var ten_ms = new Date();
+ ten_ms.setMilliseconds(ten_ms.getMilliseconds() + 12);
+ const now = performance.now();
+ await Bun.sleep(ten_ms);
+ expect(performance.now() - now).toBeGreaterThanOrEqual(10);
+});
+
+it("node.js timers/promises setTimeout propagates exceptions", async () => {
+ const { setTimeout } = require("timers/promises");
+ try {
+ await setTimeout(1).then(a => {
+ throw new Error("TestPassed");
+ });
+ throw "Should not reach here";
+ } catch (err) {
+ expect(err.message).toBe("TestPassed");
+ }
+});
+
+it.skip("order of setTimeouts", done => {
+ var nums = [];
+ var maybeDone = cb => {
+ return () => {
+ cb();
+ if (nums.length === 4) {
+ try {
+ expect(nums).toEqual([1, 2, 3, 4]);
+ done();
+ } catch (e) {
+ done(e);
+ }
+ }
+ };
+ };
+ setTimeout(maybeDone(() => nums.push(2)));
+ setTimeout(maybeDone(() => nums.push(3), 0));
+ setTimeout(maybeDone(() => nums.push(4), 1));
+ Promise.resolve().then(maybeDone(() => nums.push(1)));
+});
diff --git a/test/js/web/url/url.test.ts b/test/js/web/url/url.test.ts
new file mode 100644
index 000000000..19e10b262
--- /dev/null
+++ b/test/js/web/url/url.test.ts
@@ -0,0 +1,137 @@
+import { describe, it, expect } from "bun:test";
+
+describe("url", () => {
+ it("prints", () => {
+ expect(Bun.inspect(new URL("https://example.com"))).toBe(`URL {
+ href: "https://example.com/",
+ origin: "https://example.com",
+ protocol: "https:",
+ username: "",
+ password: "",
+ host: "example.com",
+ hostname: "example.com",
+ port: "",
+ pathname: "/",
+ hash: "",
+ search: "",
+ searchParams: URLSearchParams {
+ append: [Function: append],
+ delete: [Function: delete],
+ get: [Function: get],
+ getAll: [Function: getAll],
+ has: [Function: has],
+ set: [Function: set],
+ sort: [Function: sort],
+ entries: [Function: entries],
+ keys: [Function: keys],
+ values: [Function: values],
+ forEach: [Function: forEach],
+ toString: [Function: toString],
+ [Symbol(Symbol.iterator)]: [Function: entries]
+ },
+ toJSON: [Function: toJSON],
+ toString: [Function: toString]
+}`);
+
+ expect(
+ Bun.inspect(
+ new URL("https://github.com/oven-sh/bun/issues/135?hello%20i%20have%20spaces%20thank%20you%20good%20night"),
+ ),
+ ).toBe(`URL {
+ href: "https://github.com/oven-sh/bun/issues/135?hello%20i%20have%20spaces%20thank%20you%20good%20night",
+ origin: "https://github.com",
+ protocol: "https:",
+ username: "",
+ password: "",
+ host: "github.com",
+ hostname: "github.com",
+ port: "",
+ pathname: "/oven-sh/bun/issues/135",
+ hash: "",
+ search: "?hello%20i%20have%20spaces%20thank%20you%20good%20night",
+ searchParams: URLSearchParams {
+ append: [Function: append],
+ delete: [Function: delete],
+ get: [Function: get],
+ getAll: [Function: getAll],
+ has: [Function: has],
+ set: [Function: set],
+ sort: [Function: sort],
+ entries: [Function: entries],
+ keys: [Function: keys],
+ values: [Function: values],
+ forEach: [Function: forEach],
+ toString: [Function: toString],
+ [Symbol(Symbol.iterator)]: [Function: entries]
+ },
+ toJSON: [Function: toJSON],
+ toString: [Function: toString]
+}`);
+ });
+ it("works", () => {
+ const inputs = [
+ [
+ "https://username:password@api.foo.bar.com:9999/baz/okay/i/123?ran=out&of=things#to-use-as-a-placeholder",
+ {
+ hash: "#to-use-as-a-placeholder",
+ host: "api.foo.bar.com:9999",
+ hostname: "api.foo.bar.com",
+ href: "https://username:password@api.foo.bar.com:9999/baz/okay/i/123?ran=out&of=things#to-use-as-a-placeholder",
+ origin: "https://api.foo.bar.com:9999",
+ password: "password",
+ pathname: "/baz/okay/i/123",
+ port: "9999",
+ protocol: "https:",
+ search: "?ran=out&of=things",
+ username: "username",
+ },
+ ],
+ [
+ "https://url.spec.whatwg.org/#url-serializing",
+ {
+ hash: "#url-serializing",
+ host: "url.spec.whatwg.org",
+ hostname: "url.spec.whatwg.org",
+ href: "https://url.spec.whatwg.org/#url-serializing",
+ origin: "https://url.spec.whatwg.org",
+ password: "",
+ pathname: "/",
+ port: "",
+ protocol: "https:",
+ search: "",
+ username: "",
+ },
+ ],
+ [
+ "https://url.spec.whatwg.org#url-serializing",
+ {
+ hash: "#url-serializing",
+ host: "url.spec.whatwg.org",
+ hostname: "url.spec.whatwg.org",
+ href: "https://url.spec.whatwg.org/#url-serializing",
+ origin: "https://url.spec.whatwg.org",
+ password: "",
+ pathname: "/",
+ port: "",
+ protocol: "https:",
+ search: "",
+ username: "",
+ },
+ ],
+ ] as const;
+
+ for (let [url, values] of inputs) {
+ const result = new URL(url);
+ expect(result.hash).toBe(values.hash);
+ expect(result.host).toBe(values.host);
+ expect(result.hostname).toBe(values.hostname);
+ expect(result.href).toBe(values.href);
+ expect(result.password).toBe(values.password);
+ expect(result.pathname).toBe(values.pathname);
+ expect(result.port).toBe(values.port);
+ expect(result.protocol).toBe(values.protocol);
+ expect(result.search).toBe(values.search);
+ expect(result.username).toBe(values.username);
+ }
+ });
+});
diff --git a/test/js/web/util/atob.test.js b/test/js/web/util/atob.test.js
new file mode 100644
index 000000000..4945829e1
--- /dev/null
+++ b/test/js/web/util/atob.test.js
@@ -0,0 +1,77 @@
+import { expect, it } from "bun:test";
+
+function expectInvalidCharacters(val) {
+ try {
+ atob(val);
+ throw new Error("Expected error");
+ } catch (error) {
+ expect(error.message).toBe("The string contains invalid characters.");
+ }
+}
+
+it("atob", () => {
+ expect(atob("YQ==")).toBe("a");
+ expect(atob("YWI=")).toBe("ab");
+ expect(atob("YWJj")).toBe("abc");
+ expect(atob("YWJjZA==")).toBe("abcd");
+ expect(atob("YWJjZGU=")).toBe("abcde");
+ expect(atob("YWJjZGVm")).toBe("abcdef");
+ expect(atob("zzzz")).toBe("Ï<ó");
+ expect(atob("")).toBe("");
+ expect(atob(null)).toBe("žée");
+ expect(atob("6ek=")).toBe("éé");
+ expect(atob("6ek")).toBe("éé");
+ expect(atob("gIE=")).toBe("€");
+ expect(atob("zz")).toBe("Ï");
+ expect(atob("zzz")).toBe("Ï<");
+ expect(atob("zzz=")).toBe("Ï<");
+ expect(atob(" YQ==")).toBe("a");
+ expect(atob("YQ==\u000a")).toBe("a");
+
+ try {
+ atob();
+ } catch (error) {
+ expect(error.name).toBe("TypeError");
+ }
+ expectInvalidCharacters(undefined);
+ expectInvalidCharacters(" abcd===");
+ expectInvalidCharacters("abcd=== ");
+ expectInvalidCharacters("abcd ===");
+ expectInvalidCharacters("тест");
+ expectInvalidCharacters("z");
+ expectInvalidCharacters("zzz==");
+ expectInvalidCharacters("zzz===");
+ expectInvalidCharacters("zzz====");
+ expectInvalidCharacters("zzz=====");
+ expectInvalidCharacters("zzzzz");
+ expectInvalidCharacters("z=zz");
+ expectInvalidCharacters("=");
+ expectInvalidCharacters("==");
+ expectInvalidCharacters("===");
+ expectInvalidCharacters("====");
+ expectInvalidCharacters("=====");
+});
+
+it("btoa", () => {
+ expect(btoa("a")).toBe("YQ==");
+ expect(btoa("ab")).toBe("YWI=");
+ expect(btoa("abc")).toBe("YWJj");
+ expect(btoa("abcd")).toBe("YWJjZA==");
+ expect(btoa("abcde")).toBe("YWJjZGU=");
+ expect(btoa("abcdef")).toBe("YWJjZGVm");
+ expect(typeof btoa).toBe("function");
+ try {
+ btoa();
+ throw new Error("Expected error");
+ } catch (error) {
+ expect(error.name).toBe("TypeError");
+ }
+ var window = "[object Window]";
+ expect(btoa("")).toBe("");
+ expect(btoa(null)).toBe("bnVsbA==");
+ expect(btoa(undefined)).toBe("dW5kZWZpbmVk");
+ expect(btoa(window)).toBe("W29iamVjdCBXaW5kb3dd");
+ expect(btoa("éé")).toBe("6ek=");
+ expect(btoa("\u0080\u0081")).toBe("gIE=");
+ expect(btoa(Bun)).toBe(btoa("[object Bun]"));
+});
diff --git a/test/js/web/web-globals.test.js b/test/js/web/web-globals.test.js
new file mode 100644
index 000000000..b7a243190
--- /dev/null
+++ b/test/js/web/web-globals.test.js
@@ -0,0 +1,156 @@
+import { unsafe } from "bun";
+import { expect, it, test } from "bun:test";
+import { withoutAggressiveGC } from "harness";
+
+test("exists", () => {
+ expect(typeof URL !== "undefined").toBe(true);
+ expect(typeof URLSearchParams !== "undefined").toBe(true);
+ expect(typeof DOMException !== "undefined").toBe(true);
+ expect(typeof Event !== "undefined").toBe(true);
+ expect(typeof EventTarget !== "undefined").toBe(true);
+ expect(typeof AbortController !== "undefined").toBe(true);
+ expect(typeof AbortSignal !== "undefined").toBe(true);
+ expect(typeof CustomEvent !== "undefined").toBe(true);
+ expect(typeof Headers !== "undefined").toBe(true);
+ expect(typeof ErrorEvent !== "undefined").toBe(true);
+ expect(typeof CloseEvent !== "undefined").toBe(true);
+ expect(typeof MessageEvent !== "undefined").toBe(true);
+ expect(typeof TextEncoder !== "undefined").toBe(true);
+ expect(typeof WebSocket !== "undefined").toBe(true);
+ expect(typeof Blob !== "undefined").toBe(true);
+ expect(typeof FormData !== "undefined").toBe(true);
+});
+
+test("CloseEvent", () => {
+ var event = new CloseEvent("close", { reason: "world" });
+ expect(event.type).toBe("close");
+ const target = new EventTarget();
+ var called = false;
+ target.addEventListener("close", ({ type, reason }) => {
+ expect(type).toBe("close");
+ expect(reason).toBe("world");
+ called = true;
+ });
+ target.dispatchEvent(event);
+ expect(called).toBe(true);
+});
+
+test("MessageEvent", () => {
+ var event = new MessageEvent("message", { data: "world" });
+ expect(event.type).toBe("message");
+ const target = new EventTarget();
+ var called = false;
+ target.addEventListener("message", ({ type, data }) => {
+ expect(type).toBe("message");
+ expect(data).toBe("world");
+ called = true;
+ });
+ target.dispatchEvent(event);
+ expect(called).toBe(true);
+});
+
+it("crypto.getRandomValues", () => {
+ var foo = new Uint8Array(32);
+
+ // run it once buffered and unbuffered
+ {
+ var array = crypto.getRandomValues(foo);
+ expect(array).toBe(foo);
+ expect(array.reduce((sum, a) => (sum += a === 0), 0) != foo.length).toBe(true);
+ }
+
+ // disable it for this block because it tends to get stuck here running the GC forever
+ withoutAggressiveGC(() => {
+ // run it again to check that the fast path works
+ for (var i = 0; i < 9000; i++) {
+ var array = crypto.getRandomValues(foo);
+ expect(array).toBe(foo);
+ }
+ });
+
+ // run it on a large input
+ expect(!!crypto.getRandomValues(new Uint8Array(8096)).find(a => a > 0)).toBe(true);
+
+ {
+ // any additional input into getRandomValues() makes it unbuffered
+ var array = crypto.getRandomValues(foo, "unbuffered");
+ expect(array).toBe(foo);
+ expect(array.reduce((sum, a) => (sum += a === 0), 0) != foo.length).toBe(true);
+ }
+});
+
+// not actually a web global
+it("crypto.timingSafeEqual", () => {
+ const crypto = import.meta.require("node:crypto");
+ var uuidStr = crypto.randomUUID();
+ expect(uuidStr.length).toBe(36);
+ expect(uuidStr[8]).toBe("-");
+ expect(uuidStr[13]).toBe("-");
+ expect(uuidStr[18]).toBe("-");
+ expect(uuidStr[23]).toBe("-");
+ const uuid = Buffer.from(uuidStr);
+
+ expect(crypto.timingSafeEqual(uuid, uuid)).toBe(true);
+ expect(crypto.timingSafeEqual(uuid, uuid.slice())).toBe(true);
+ try {
+ crypto.timingSafeEqual(uuid, uuid.slice(1));
+ expect(false).toBe(true);
+ } catch (e) {}
+
+ try {
+ crypto.timingSafeEqual(uuid, uuid.slice(0, uuid.length - 2));
+ expect(false).toBe(true);
+ } catch (e) {
+ expect(e.message).toBe("Input buffers must have the same length");
+ }
+
+ try {
+ expect(crypto.timingSafeEqual(uuid, crypto.randomUUID())).toBe(false);
+ expect(false).toBe(true);
+ } catch (e) {
+ expect(e.name).toBe("TypeError");
+ }
+
+ var shorter = uuid.slice(0, 1);
+ for (let i = 0; i < 9000; i++) {
+ if (!crypto.timingSafeEqual(shorter, shorter)) throw new Error("fail");
+ }
+});
+
+it("crypto.randomUUID", () => {
+ var uuid = crypto.randomUUID();
+ expect(uuid.length).toBe(36);
+ expect(uuid[8]).toBe("-");
+ expect(uuid[13]).toBe("-");
+ expect(uuid[18]).toBe("-");
+ expect(uuid[23]).toBe("-");
+
+ withoutAggressiveGC(() => {
+ // check that the fast path works
+ for (let i = 0; i < 9000; i++) {
+ var uuid2 = crypto.randomUUID();
+ expect(uuid2.length).toBe(36);
+ expect(uuid2[8]).toBe("-");
+ expect(uuid2[13]).toBe("-");
+ expect(uuid2[18]).toBe("-");
+ expect(uuid2[23]).toBe("-");
+ }
+ });
+});
+
+it("URL.prototype.origin", () => {
+ const url = new URL("https://html.spec.whatwg.org/");
+ const { origin, host, hostname } = url;
+
+ expect(hostname).toBe("html.spec.whatwg.org");
+ expect(host).toBe("html.spec.whatwg.org");
+ expect(origin).toBe("https://html.spec.whatwg.org");
+});
+
+test("navigator", () => {
+ expect(globalThis.navigator !== undefined).toBe(true);
+ const version = process.versions.bun;
+ const userAgent = `Bun/${version}`;
+ expect(navigator.hardwareConcurrency > 0).toBe(true);
+ expect(navigator.userAgent).toBe(userAgent);
+});
diff --git a/test/js/web/websocket/websocket-subprocess.ts b/test/js/web/websocket/websocket-subprocess.ts
new file mode 100644
index 000000000..fd25b7fd5
--- /dev/null
+++ b/test/js/web/websocket/websocket-subprocess.ts
@@ -0,0 +1,13 @@
+const host = process.argv[2];
+
+const ws = new WebSocket(host);
+
+ws.onmessage = message => {
+ if (message.data === "hello websocket") {
+ ws.send("hello");
+ } else if (message.data === "timeout") {
+ setTimeout(() => {
+ ws.send("close");
+ }, 300);
+ }
+};
diff --git a/test/js/web/websocket/websocket.test.js b/test/js/web/websocket/websocket.test.js
new file mode 100644
index 000000000..f0f29c1c3
--- /dev/null
+++ b/test/js/web/websocket/websocket.test.js
@@ -0,0 +1,263 @@
+import { describe, it, expect } from "bun:test";
+import { unsafe, spawn, readableStreamToText } from "bun";
+import { bunExe, bunEnv, gc } from "harness";
+
+const TEST_WEBSOCKET_HOST = process.env.TEST_WEBSOCKET_HOST || "wss://ws.postman-echo.com/raw";
+
+describe("WebSocket", () => {
+ it("should connect", async () => {
+ const ws = new WebSocket(TEST_WEBSOCKET_HOST);
+ await new Promise((resolve, reject) => {
+ ws.onopen = resolve;
+ ws.onerror = reject;
+ });
+ var closed = new Promise((resolve, reject) => {
+ ws.onclose = resolve;
+ });
+ ws.close();
+ await closed;
+ });
+
+ it("should connect over https", async () => {
+ const ws = new WebSocket(TEST_WEBSOCKET_HOST.replaceAll("wss:", "https:"));
+ await new Promise((resolve, reject) => {
+ ws.onopen = resolve;
+ ws.onerror = reject;
+ });
+ var closed = new Promise((resolve, reject) => {
+ ws.onclose = resolve;
+ });
+ ws.close();
+ await closed;
+ });
+
+ it("supports headers", done => {
+ const server = Bun.serve({
+ port: 8024,
+ fetch(req, server) {
+ expect(req.headers.get("X-Hello")).toBe("World");
+ expect(req.headers.get("content-type")).toBe("lolwut");
+ server.stop();
+ done();
+ return new Response();
+ },
+ websocket: {
+ open(ws) {
+ ws.close();
+ },
+ },
+ });
+ const ws = new WebSocket(`ws://${server.hostname}:${server.port}`, {
+ headers: {
+ "X-Hello": "World",
+ "content-type": "lolwut",
+ },
+ });
+ });
+
+ it("should connect over http", done => {
+ const server = Bun.serve({
+ port: 8025,
+ fetch(req, server) {
+ server.stop();
+ done();
+ return new Response();
+ },
+ websocket: {
+ open(ws) {
+ ws.close();
+ },
+ },
+ });
+ const ws = new WebSocket(`http://${server.hostname}:${server.port}`, {});
+ });
+
+ it("should send and receive messages", async () => {
+ const ws = new WebSocket(TEST_WEBSOCKET_HOST);
+ await new Promise((resolve, reject) => {
+ ws.onopen = resolve;
+ ws.onerror = reject;
+ ws.onclose = () => {
+ reject("WebSocket closed");
+ };
+ });
+ const count = 10;
+
+ // 10 messages in burst
+ var promise = new Promise((resolve, reject) => {
+ var remain = count;
+ ws.onmessage = event => {
+ gc(true);
+ expect(event.data).toBe("Hello World!");
+ remain--;
+
+ if (remain <= 0) {
+ ws.onmessage = () => {};
+ resolve();
+ }
+ };
+ ws.onerror = reject;
+ });
+
+ for (let i = 0; i < count; i++) {
+ ws.send("Hello World!");
+ gc(true);
+ }
+
+ await promise;
+ var echo = 0;
+
+ // 10 messages one at a time
+ function waitForEcho() {
+ return new Promise((resolve, reject) => {
+ gc(true);
+ const msg = `Hello World! ${echo++}`;
+ ws.onmessage = event => {
+ expect(event.data).toBe(msg);
+ resolve();
+ };
+ ws.onerror = reject;
+ ws.onclose = reject;
+ ws.send(msg);
+ gc(true);
+ });
+ }
+ gc(true);
+ for (let i = 0; i < count; i++) await waitForEcho();
+ ws.onclose = () => {};
+ ws.onerror = () => {};
+ ws.close();
+ gc(true);
+ });
+});
+
+describe("websocket in subprocess", () => {
+ var port = 8765;
+ it("should exit", async () => {
+ let messageReceived = false;
+ const server = Bun.serve({
+ port: port++,
+ fetch(req, server) {
+ if (server.upgrade(req)) {
+ return;
+ }
+
+ return new Response("http response");
+ },
+ websocket: {
+ open(ws) {
+ ws.send("hello websocket");
+ },
+ message(ws) {
+ messageReceived = true;
+ ws.close();
+ },
+ close(ws) {},
+ },
+ });
+ const subprocess = Bun.spawn({
+ cmd: [bunExe(), import.meta.dir + "/websocket-subprocess.ts", `http://${server.hostname}:${server.port}`],
+ stderr: "pipe",
+ stdin: "pipe",
+ stdout: "pipe",
+ env: bunEnv,
+ });
+
+ expect(await subprocess.exited).toBe(0);
+ expect(messageReceived).toBe(true);
+ server.stop(true);
+ });
+
+ it("should exit after killed", async () => {
+ const subprocess = Bun.spawn({
+ cmd: [bunExe(), import.meta.dir + "/websocket-subprocess.ts", TEST_WEBSOCKET_HOST],
+ stderr: "pipe",
+ stdin: "pipe",
+ stdout: "pipe",
+ env: bunEnv,
+ });
+
+ subprocess.kill();
+
+ expect(await subprocess.exited).toBe("SIGHUP");
+ });
+
+ it("should exit with invalid url", async () => {
+ const subprocess = Bun.spawn({
+ cmd: [bunExe(), import.meta.dir + "/websocket-subprocess.ts", "invalid url"],
+ stderr: "pipe",
+ stdin: "pipe",
+ stdout: "pipe",
+ env: bunEnv,
+ });
+
+ expect(await subprocess.exited).toBe(1);
+ });
+
+ it("should exit after timeout", async () => {
+ let messageReceived = false;
+ let start = 0;
+ const server = Bun.serve({
+ port: port++,
+ fetch(req, server) {
+ if (server.upgrade(req)) {
+ return;
+ }
+
+ return new Response("http response");
+ },
+ websocket: {
+ open(ws) {
+ start = performance.now();
+ ws.send("timeout");
+ },
+ message(ws, message) {
+ messageReceived = true;
+ expect(performance.now() - start >= 300).toBe(true);
+ ws.close();
+ },
+ close(ws) {},
+ },
+ });
+ const subprocess = Bun.spawn({
+ cmd: [bunExe(), import.meta.dir + "/websocket-subprocess.ts", `http://${server.hostname}:${server.port}`],
+ stderr: "pipe",
+ stdin: "pipe",
+ stdout: "pipe",
+ env: bunEnv,
+ });
+
+ expect(await subprocess.exited).toBe(0);
+ expect(messageReceived).toBe(true);
+ server.stop(true);
+ });
+
+ it("should exit after server stop and 0 messages", async () => {
+ const server = Bun.serve({
+ port: port++,
+ fetch(req, server) {
+ if (server.upgrade(req)) {
+ return;
+ }
+
+ return new Response("http response");
+ },
+ websocket: {
+ open(ws) {},
+ message(ws, message) {},
+ close(ws) {},
+ },
+ });
+
+ const subprocess = Bun.spawn({
+ cmd: [bunExe(), import.meta.dir + "/websocket-subprocess.ts", `http://${server.hostname}:${server.port}`],
+ stderr: "pipe",
+ stdin: "pipe",
+ stdout: "pipe",
+ env: bunEnv,
+ });
+
+ server.stop(true);
+ expect(await subprocess.exited).toBe(0);
+ });
+});
diff --git a/test/js/workerd/html-rewriter.test.js b/test/js/workerd/html-rewriter.test.js
new file mode 100644
index 000000000..b5db3fde2
--- /dev/null
+++ b/test/js/workerd/html-rewriter.test.js
@@ -0,0 +1,303 @@
+import { describe, it, expect } from "bun:test";
+import { gcTick } from "../gc";
+
+var setTimeoutAsync = (fn, delay) => {
+ return new Promise((resolve, reject) => {
+ setTimeout(() => {
+ try {
+ resolve(fn());
+ } catch (e) {
+ reject(e);
+ }
+ }, delay);
+ });
+};
+
+describe("HTMLRewriter", () => {
+ it("HTMLRewriter: async replacement", async () => {
+ await gcTick();
+ const res = new HTMLRewriter()
+ .on("div", {
+ async element(element) {
+ await setTimeoutAsync(() => {
+ element.setInnerContent("<span>replace</span>", { html: true });
+ }, 5);
+ },
+ })
+ .transform(new Response("<div>example.com</div>"));
+ await gcTick();
+ expect(await res.text()).toBe("<div><span>replace</span></div>");
+ await gcTick();
+ });
+
+ it("supports element handlers", async () => {
+ var rewriter = new HTMLRewriter();
+ rewriter.on("div", {
+ element(element) {
+ element.setInnerContent("<blink>it worked!</blink>", { html: true });
+ },
+ });
+ var input = new Response("<div>hello</div>");
+ var output = rewriter.transform(input);
+ expect(await output.text()).toBe("<div><blink>it worked!</blink></div>");
+ });
+
+ it("(from file) supports element handlers", async () => {
+ var rewriter = new HTMLRewriter();
+ rewriter.on("div", {
+ element(element) {
+ element.setInnerContent("<blink>it worked!</blink>", { html: true });
+ },
+ });
+ await Bun.write("/tmp/html-rewriter.txt.js", "<div>hello</div>");
+ var input = new Response(Bun.file("/tmp/html-rewriter.txt.js"));
+ var output = rewriter.transform(input);
+ expect(await output.text()).toBe("<div><blink>it worked!</blink></div>");
+ });
+
+ it("supports attribute iterator", async () => {
+ var rewriter = new HTMLRewriter();
+ var expected = [
+ ["first", ""],
+ ["second", "alrihgt"],
+ ["third", "123"],
+ ["fourth", "5"],
+ ["fifth", "helloooo"],
+ ];
+ rewriter.on("div", {
+ element(element2) {
+ for (let attr of element2.attributes) {
+ const stack = expected.shift();
+ expect(stack[0]).toBe(attr[0]);
+ expect(stack[1]).toBe(attr[1]);
+ }
+ },
+ });
+ var input = new Response('<div first second="alrihgt" third="123" fourth=5 fifth=helloooo>hello</div>');
+ var output = rewriter.transform(input);
+ expect(await output.text()).toBe('<div first second="alrihgt" third="123" fourth=5 fifth=helloooo>hello</div>');
+ expect(expected.length).toBe(0);
+ });
+
+ it("handles element specific mutations", async () => {
+ // prepend/append
+ let res = new HTMLRewriter()
+ .on("p", {
+ element(element) {
+ element.prepend("<span>prepend</span>");
+ element.prepend("<span>prepend html</span>", { html: true });
+ element.append("<span>append</span>");
+ element.append("<span>append html</span>", { html: true });
+ },
+ })
+ .transform(new Response("<p>test</p>"));
+ expect(await res.text()).toBe(
+ [
+ "<p>",
+ "<span>prepend html</span>",
+ "&lt;span&gt;prepend&lt;/span&gt;",
+ "test",
+ "&lt;span&gt;append&lt;/span&gt;",
+ "<span>append html</span>",
+ "</p>",
+ ].join(""),
+ );
+
+ // setInnerContent
+ res = new HTMLRewriter()
+ .on("p", {
+ element(element) {
+ element.setInnerContent("<span>replace</span>");
+ },
+ })
+ .transform(new Response("<p>test</p>"));
+ expect(await res.text()).toBe("<p>&lt;span&gt;replace&lt;/span&gt;</p>");
+ res = new HTMLRewriter()
+ .on("p", {
+ element(element) {
+ element.setInnerContent("<span>replace</span>", { html: true });
+ },
+ })
+ .transform(new Response("<p>test</p>"));
+ expect(await res.text()).toBe("<p><span>replace</span></p>");
+
+ // removeAndKeepContent
+ res = new HTMLRewriter()
+ .on("p", {
+ element(element) {
+ element.removeAndKeepContent();
+ },
+ })
+ .transform(new Response("<p>test</p>"));
+ expect(await res.text()).toBe("test");
+ });
+
+ it("handles element class properties", async () => {
+ class Handler {
+ constructor(content) {
+ this.content = content;
+ }
+
+ // noinspection JSUnusedGlobalSymbols
+ element(element) {
+ element.setInnerContent(this.content);
+ }
+ }
+ const res = new HTMLRewriter().on("p", new Handler("new")).transform(new Response("<p>test</p>"));
+ expect(await res.text()).toBe("<p>new</p>");
+ });
+
+ const commentsMutationsInput = "<p><!--test--></p>";
+ const commentsMutationsExpected = {
+ beforeAfter: [
+ "<p>",
+ "&lt;span&gt;before&lt;/span&gt;",
+ "<span>before html</span>",
+ "<!--test-->",
+ "<span>after html</span>",
+ "&lt;span&gt;after&lt;/span&gt;",
+ "</p>",
+ ].join(""),
+ replace: "<p>&lt;span&gt;replace&lt;/span&gt;</p>",
+ replaceHtml: "<p><span>replace</span></p>",
+ remove: "<p></p>",
+ };
+
+ const commentPropertiesMacro = async func => {
+ const res = func(new HTMLRewriter(), comment => {
+ expect(comment.removed).toBe(false);
+ expect(comment.text).toBe("test");
+ comment.text = "new";
+ expect(comment.text).toBe("new");
+ }).transform(new Response("<p><!--test--></p>"));
+ expect(await res.text()).toBe("<p><!--new--></p>");
+ };
+
+ it("HTMLRewriter: handles comment properties", () =>
+ commentPropertiesMacro((rw, comments) => {
+ rw.on("p", { comments });
+ return rw;
+ }));
+
+ it("selector tests", async () => {
+ const checkSelector = async (selector, input, expected) => {
+ const res = new HTMLRewriter()
+ .on(selector, {
+ element(element) {
+ element.setInnerContent("new");
+ },
+ })
+ .transform(new Response(input));
+ expect(await res.text()).toBe(expected);
+ };
+
+ await checkSelector("*", "<h1>1</h1><p>2</p>", "<h1>new</h1><p>new</p>");
+ await checkSelector("p", "<h1>1</h1><p>2</p>", "<h1>1</h1><p>new</p>");
+ await checkSelector(
+ "p:nth-child(2)",
+ "<div><p>1</p><p>2</p><p>3</p></div>",
+ "<div><p>1</p><p>new</p><p>3</p></div>",
+ );
+ await checkSelector(
+ "p:first-child",
+ "<div><p>1</p><p>2</p><p>3</p></div>",
+ "<div><p>new</p><p>2</p><p>3</p></div>",
+ );
+ await checkSelector(
+ "p:nth-of-type(2)",
+ "<div><p>1</p><h1>2</h1><p>3</p><h1>4</h1><p>5</p></div>",
+ "<div><p>1</p><h1>2</h1><p>new</p><h1>4</h1><p>5</p></div>",
+ );
+ await checkSelector(
+ "p:first-of-type",
+ "<div><h1>1</h1><p>2</p><p>3</p></div>",
+ "<div><h1>1</h1><p>new</p><p>3</p></div>",
+ );
+ await checkSelector(
+ "p:not(:first-child)",
+ "<div><p>1</p><p>2</p><p>3</p></div>",
+ "<div><p>1</p><p>new</p><p>new</p></div>",
+ );
+ await checkSelector("p.red", '<p class="red">1</p><p>2</p>', '<p class="red">new</p><p>2</p>');
+ await checkSelector("h1#header", '<h1 id="header">1</h1><h1>2</h1>', '<h1 id="header">new</h1><h1>2</h1>');
+ await checkSelector("p[data-test]", "<p data-test>1</p><p>2</p>", "<p data-test>new</p><p>2</p>");
+ await checkSelector(
+ 'p[data-test="one"]',
+ '<p data-test="one">1</p><p data-test="two">2</p>',
+ '<p data-test="one">new</p><p data-test="two">2</p>',
+ );
+ await checkSelector(
+ 'p[data-test="one" i]',
+ '<p data-test="one">1</p><p data-test="OnE">2</p><p data-test="two">3</p>',
+ '<p data-test="one">new</p><p data-test="OnE">new</p><p data-test="two">3</p>',
+ );
+ await checkSelector(
+ 'p[data-test="one" s]',
+ '<p data-test="one">1</p><p data-test="OnE">2</p><p data-test="two">3</p>',
+ '<p data-test="one">new</p><p data-test="OnE">2</p><p data-test="two">3</p>',
+ );
+ await checkSelector(
+ 'p[data-test~="two"]',
+ '<p data-test="one two three">1</p><p data-test="one two">2</p><p data-test="one">3</p>',
+ '<p data-test="one two three">new</p><p data-test="one two">new</p><p data-test="one">3</p>',
+ );
+ await checkSelector(
+ 'p[data-test^="a"]',
+ '<p data-test="a1">1</p><p data-test="a2">2</p><p data-test="b1">3</p>',
+ '<p data-test="a1">new</p><p data-test="a2">new</p><p data-test="b1">3</p>',
+ );
+ await checkSelector(
+ 'p[data-test$="1"]',
+ '<p data-test="a1">1</p><p data-test="a2">2</p><p data-test="b1">3</p>',
+ '<p data-test="a1">new</p><p data-test="a2">2</p><p data-test="b1">new</p>',
+ );
+ await checkSelector(
+ 'p[data-test*="b"]',
+ '<p data-test="abc">1</p><p data-test="ab">2</p><p data-test="a">3</p>',
+ '<p data-test="abc">new</p><p data-test="ab">new</p><p data-test="a">3</p>',
+ );
+ await checkSelector(
+ 'p[data-test|="a"]',
+ '<p data-test="a">1</p><p data-test="a-1">2</p><p data-test="a2">3</p>',
+ '<p data-test="a">new</p><p data-test="a-1">new</p><p data-test="a2">3</p>',
+ );
+ await checkSelector(
+ "div span",
+ "<div><h1><span>1</span></h1><span>2</span><b>3</b></div>",
+ "<div><h1><span>new</span></h1><span>new</span><b>3</b></div>",
+ );
+ await checkSelector(
+ "div > span",
+ "<div><h1><span>1</span></h1><span>2</span><b>3</b></div>",
+ "<div><h1><span>1</span></h1><span>new</span><b>3</b></div>",
+ );
+ });
+
+ it("supports deleting innerContent", async () => {
+ expect(
+ await new HTMLRewriter()
+ .on("div", {
+ element(elem) {
+ // https://github.com/oven-sh/bun/issues/2323
+ elem.setInnerContent("");
+ },
+ })
+ .transform(new Response("<div>content</div>"))
+ .text(),
+ ).toEqual("<div></div>");
+ });
+
+ it("supports deleting innerHTML", async () => {
+ expect(
+ await new HTMLRewriter()
+ .on("div", {
+ element(elem) {
+ // https://github.com/oven-sh/bun/issues/2323
+ elem.setInnerContent("", { html: true });
+ },
+ })
+ .transform(new Response("<div><span>content</span></div>"))
+ .text(),
+ ).toEqual("<div></div>");
+ });
+});