/**
* @lazarv/rsc - Flight Streaming Tests
*
* Tests for async streaming, Promise handling, TEXT rows, and progressive reveal
*/
import { describe, expect, it } from "vitest";
import { createFromReadableStream } from "../client/index.mjs";
import { renderToReadableStream } from "../server/index.mjs";
// Helper to delay for async tests
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
// Helper to decode a stream chunk value to string.
// Reconstructed text ReadableStreams yield strings (not Uint8Array).
function decodeChunk(value) {
return typeof value === "string" ? value : new TextDecoder().decode(value);
}
// Helper to collect stream content
async function streamToString(stream) {
const reader = stream.getReader();
const decoder = new TextDecoder();
let result = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
result += decoder.decode(value, { stream: true });
}
result += decoder.decode();
return result;
}
// Helper to collect stream chunks for timing analysis
async function collectChunks(stream) {
const reader = stream.getReader();
const decoder = new TextDecoder();
const chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push({
time: Date.now(),
data: decoder.decode(value, { stream: true }),
});
}
return chunks;
}
describe("Flight Streaming - Promise Handling", () => {
it("should stream Promise that resolves later", async () => {
const promise = new Promise((resolve) => {
setTimeout(() => resolve("delayed value"), 10);
});
const stream = renderToReadableStream(promise);
const result = await createFromReadableStream(stream);
expect(await result).toBe("delayed value");
});
it("should stream Promise with complex value", async () => {
const promise = Promise.resolve({
users: [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
],
total: 2,
});
const stream = renderToReadableStream(promise);
const result = await createFromReadableStream(stream);
const value = await result;
expect(value.users).toHaveLength(2);
expect(value.total).toBe(2);
});
it("should stream nested Promises", async () => {
const data = {
immediate: "now",
later: Promise.resolve("soon"),
evenLater: new Promise((resolve) =>
setTimeout(() => resolve("delayed"), 10)
),
};
const stream = renderToReadableStream(data);
const result = await createFromReadableStream(stream);
expect(result.immediate).toBe("now");
expect(await result.later).toBe("soon");
expect(await result.evenLater).toBe("delayed");
});
it("should stream array of Promises", async () => {
const promises = [
Promise.resolve(1),
Promise.resolve(2),
Promise.resolve(3),
];
const stream = renderToReadableStream(promises);
const result = await createFromReadableStream(stream);
const values = await Promise.all(result);
expect(values).toEqual([1, 2, 3]);
});
});
describe("Flight Streaming - Async Iterables", () => {
it("should stream async iterable values", async () => {
async function* asyncGen() {
yield 1;
yield 2;
yield 3;
}
const stream = renderToReadableStream(asyncGen());
const result = await createFromReadableStream(stream);
const values = [];
for await (const value of result) {
values.push(value);
}
expect(values).toEqual([1, 2, 3]);
});
it("should stream async iterable with delays", async () => {
async function* asyncGen() {
yield "first";
await delay(5);
yield "second";
await delay(5);
yield "third";
}
const stream = renderToReadableStream(asyncGen());
const result = await createFromReadableStream(stream);
const values = [];
for await (const value of result) {
values.push(value);
}
expect(values).toEqual(["first", "second", "third"]);
});
it("should stream async iterable with complex values", async () => {
async function* asyncGen() {
yield { id: 1, data: "a" };
yield { id: 2, data: "b" };
}
const stream = renderToReadableStream(asyncGen());
const result = await createFromReadableStream(stream);
const values = [];
for await (const value of result) {
values.push(value);
}
expect(values).toEqual([
{ id: 1, data: "a" },
{ id: 2, data: "b" },
]);
});
});
describe("Flight Streaming - ReadableStream Transfer", () => {
it("should transfer ReadableStream values", async () => {
const textStream = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode("hello"));
controller.enqueue(new TextEncoder().encode(" world"));
controller.close();
},
});
const stream = renderToReadableStream(textStream);
const result = await createFromReadableStream(stream);
const reader = result.getReader();
const chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(new TextDecoder().decode(value));
}
expect(chunks.join("")).toBe("hello world");
});
it("should transfer ReadableStream with binary data", async () => {
const binaryStream = new ReadableStream({
start(controller) {
controller.enqueue(new Uint8Array([1, 2, 3]));
controller.enqueue(new Uint8Array([4, 5, 6]));
controller.close();
},
});
const stream = renderToReadableStream(binaryStream);
const result = await createFromReadableStream(stream);
const reader = result.getReader();