htmlRunner for ReadableStream tests, fix ReadableStream enqueue

This commit is contained in:
Muki Kiboigo
2025-09-16 12:17:05 -07:00
parent c553a2cd38
commit 2a0964f66b
4 changed files with 123 additions and 164 deletions

View File

@@ -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");
}

View File

@@ -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();
}

View File

@@ -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();

View File

@@ -0,0 +1,117 @@
<script src="../testing.js"></script>
<script id=readable_stream>
const stream = new ReadableStream({
start(controller) {
controller.enqueue("hello");
controller.enqueue("world");
controller.close();
}
});
const reader = stream.getReader();
testing.async(reader.read(), (data) => {
testing.expectEqual("hello", data.value);
testing.expectEqual(false, data.done);
});
</script>
<script id=readable_stream_close>
var closeResult;
const stream1 = new ReadableStream({
start(controller) {
controller.enqueue("first");
controller.enqueue("second");
controller.close();
}
});
const reader1 = stream1.getReader();
testing.async(reader1.read(), (data) => {
testing.expectEqual("first", data.value);
testing.expectEqual(false, data.done);
});
testing.async(reader1.read(), (data) => {
testing.expectEqual("second", data.value);
testing.expectEqual(false, data.done);
});
testing.async(reader1.read(), (data) => {
testing.expectEqual(undefined, data.value);
testing.expectEqual(true, data.done);
});
</script>
<script id=readable_stream_cancel>
var readResult;
var cancelResult;
var closeResult;
const stream2 = new ReadableStream({
start(controller) {
controller.enqueue("data1");
controller.enqueue("data2");
controller.enqueue("data3");
},
cancel(reason) {
closeResult = `Stream cancelled: ${reason}`;
}
});
const reader2 = stream2.getReader();
testing.async(reader2.read(), (data) => {
testing.expectEqual("data1", data.value);
testing.expectEqual(false, data.done);
});
testing.async(reader2.cancel("user requested"), (result) => {
testing.expectEqual(undefined, result);
testing.expectEqual("Stream cancelled: user requested", closeResult);
});
</script>
<script id=readable_stream_cancel_no_reason>
var closeResult2;
const stream3 = new ReadableStream({
start(controller) {
controller.enqueue("test");
},
cancel(reason) {
closeResult2 = reason === undefined ? "no reason" : reason;
}
});
const reader3 = stream3.getReader();
testing.async(reader3.cancel(), (result) => {
testing.expectEqual(undefined, result);
testing.expectEqual("no reason", closeResult2);
});
</script>
<script id=readable_stream_read_after_cancel>
var readAfterCancelResult;
const stream4 = new ReadableStream({
start(controller) {
controller.enqueue("before_cancel");
},
});
const reader4 = stream4.getReader();
testing.async(reader4.cancel("test cancel"), (cancelResult) => {
testing.expectEqual(undefined, cancelResult);
});
testing.async(reader4.read(), (data) => {
testing.expectEqual(undefined, data.value);
testing.expectEqual(true, data.done);
});
</script>