mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 02:48:50 +00:00
306 lines
9.4 KiB
TypeScript
306 lines
9.4 KiB
TypeScript
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 "./gc";
|
|
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 & utf16</span>",
|
|
<>
|
|
😋L😋l😋L😋<span>Alternating latin1 & 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);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
});
|