diff --git a/src/browser/streams/ReadableStream.zig b/src/browser/streams/ReadableStream.zig
index 4b27ba3c..216152da 100644
--- a/src/browser/streams/ReadableStream.zig
+++ b/src/browser/streams/ReadableStream.zig
@@ -173,166 +173,5 @@ pub fn _getReader(self: *ReadableStream, _options: ?GetReaderOptions) !ReadableS
const testing = @import("../../testing.zig");
test "streams: ReadableStream" {
- var runner = try testing.jsRunner(testing.tracking_allocator, .{ .url = "https://lightpanda.io" });
- defer runner.deinit();
-
- try runner.testCases(&.{
- .{ "var readResult;", "undefined" },
- .{
- \\ const stream = new ReadableStream({
- \\ start(controller) {
- \\ controller.enqueue("hello");
- \\ controller.enqueue("world");
- \\ controller.close();
- \\ }
- \\ });
- ,
- undefined,
- },
- .{
- \\ const reader = stream.getReader();
- \\ (async function () { readResult = await reader.read() }());
- \\ false;
- ,
- "false",
- },
- .{ "reader", "[object ReadableStreamDefaultReader]" },
- .{ "readResult.value", "hello" },
- .{ "readResult.done", "false" },
- }, .{});
-}
-
-test "streams: ReadableStream cancel and close" {
- var runner = try testing.jsRunner(testing.tracking_allocator, .{ .url = "https://lightpanda.io" });
- defer runner.deinit();
- try runner.testCases(&.{
- .{ "var readResult; var cancelResult; var closeResult;", "undefined" },
-
- // Test 1: Stream with controller.close()
- .{
- \\ const stream1 = new ReadableStream({
- \\ start(controller) {
- \\ controller.enqueue("first");
- \\ controller.enqueue("second");
- \\ controller.close();
- \\ }
- \\ });
- ,
- undefined,
- },
- .{ "const reader1 = stream1.getReader();", undefined },
- .{
- \\ (async function () {
- \\ readResult = await reader1.read();
- \\ }());
- \\ false;
- ,
- "false",
- },
- .{ "readResult.value", "first" },
- .{ "readResult.done", "false" },
-
- // Read second chunk
- .{
- \\ (async function () {
- \\ readResult = await reader1.read();
- \\ }());
- \\ false;
- ,
- "false",
- },
- .{ "readResult.value", "second" },
- .{ "readResult.done", "false" },
-
- // Read after close - should get done: true
- .{
- \\ (async function () {
- \\ readResult = await reader1.read();
- \\ }());
- \\ false;
- ,
- "false",
- },
- .{ "readResult.value", "undefined" },
- .{ "readResult.done", "true" },
-
- // Test 2: Stream with reader.cancel()
- .{
- \\ const stream2 = new ReadableStream({
- \\ start(controller) {
- \\ controller.enqueue("data1");
- \\ controller.enqueue("data2");
- \\ controller.enqueue("data3");
- \\ },
- \\ cancel(reason) {
- \\ closeResult = `Stream cancelled: ${reason}`;
- \\ }
- \\ });
- ,
- undefined,
- },
- .{ "const reader2 = stream2.getReader();", undefined },
-
- // Read one chunk before canceling
- .{
- \\ (async function () {
- \\ readResult = await reader2.read();
- \\ }());
- \\ false;
- ,
- "false",
- },
- .{ "readResult.value", "data1" },
- .{ "readResult.done", "false" },
-
- // Cancel the stream
- .{
- \\ (async function () {
- \\ cancelResult = await reader2.cancel("user requested");
- \\ }());
- \\ false;
- ,
- "false",
- },
- .{ "cancelResult", "undefined" },
- .{ "closeResult", "Stream cancelled: user requested" },
-
- // Try to read after cancel - should throw or return done
- .{
- \\ try {
- \\ (async function () {
- \\ readResult = await reader2.read();
- \\ }());
- \\ } catch(e) {
- \\ readResult = { error: e.name };
- \\ }
- \\ false;
- ,
- "false",
- },
-
- // Test 3: Cancel without reason
- .{
- \\ const stream3 = new ReadableStream({
- \\ start(controller) {
- \\ controller.enqueue("test");
- \\ },
- \\ cancel(reason) {
- \\ closeResult = reason === undefined ? "no reason" : reason;
- \\ }
- \\ });
- ,
- undefined,
- },
- .{ "const reader3 = stream3.getReader();", undefined },
- .{
- \\ (async function () {
- \\ await reader3.cancel();
- \\ }());
- \\ false;
- ,
- "false",
- },
- .{ "closeResult", "no reason" },
- }, .{});
+ try testing.htmlRunner("streams/readable_stream.html");
}
diff --git a/src/browser/streams/ReadableStreamDefaultController.zig b/src/browser/streams/ReadableStreamDefaultController.zig
index 84bb5073..c83a13a5 100644
--- a/src/browser/streams/ReadableStreamDefaultController.zig
+++ b/src/browser/streams/ReadableStreamDefaultController.zig
@@ -59,13 +59,15 @@ pub fn _enqueue(self: *ReadableStreamDefaultController, chunk: []const u8, page:
return error.TypeError;
}
+ const duped_chunk = try page.arena.dupe(u8, chunk);
+
if (self.stream.reader_resolver) |*rr| {
defer rr.deinit();
- try rr.resolve(ReadableStreamReadResult{ .value = .{ .data = chunk }, .done = false });
+ try rr.resolve(ReadableStreamReadResult{ .value = .{ .data = duped_chunk }, .done = false });
self.stream.reader_resolver = null;
}
- try self.stream.queue.append(page.arena, chunk);
+ try self.stream.queue.append(page.arena, duped_chunk);
try self.stream.pullIf();
}
diff --git a/src/browser/streams/ReadableStreamDefaultReader.zig b/src/browser/streams/ReadableStreamDefaultReader.zig
index 5ad863ec..0708eff2 100644
--- a/src/browser/streams/ReadableStreamDefaultReader.zig
+++ b/src/browser/streams/ReadableStreamDefaultReader.zig
@@ -48,6 +48,7 @@ pub fn _read(self: *const ReadableStreamDefaultReader, page: *Page) !Env.Promise
if (stream.queue.items.len > 0) {
const data = self.stream.queue.orderedRemove(0);
const resolver = page.main_context.createPromiseResolver();
+
try resolver.resolve(ReadableStreamReadResult{ .value = .{ .data = data }, .done = false });
try self.stream.pullIf();
return resolver.promise();
diff --git a/src/tests/streams/readable_stream.html b/src/tests/streams/readable_stream.html
new file mode 100644
index 00000000..a8d71d66
--- /dev/null
+++ b/src/tests/streams/readable_stream.html
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+