From 0423a178e94eecc8e507650aec9f450191caeb46 Mon Sep 17 00:00:00 2001 From: Muki Kiboigo Date: Tue, 9 Sep 2025 13:09:03 -0700 Subject: [PATCH] migrate fetch tests to htmlRunner --- src/browser/fetch/Headers.zig | 87 +++++++++------------------- src/browser/fetch/Request.zig | 60 +++++-------------- src/browser/fetch/Response.zig | 19 +++--- src/browser/fetch/fetch.zig | 6 +- src/tests/fetch/headers.html | 102 +++++++++++++++++++++++++++++++++ src/tests/fetch/request.html | 22 +++++++ src/tests/fetch/response.html | 38 ++++++++++++ 7 files changed, 219 insertions(+), 115 deletions(-) create mode 100644 src/tests/fetch/headers.html create mode 100644 src/tests/fetch/request.html create mode 100644 src/tests/fetch/response.html diff --git a/src/browser/fetch/Headers.zig b/src/browser/fetch/Headers.zig index 2ea412cf..f7d83c3f 100644 --- a/src/browser/fetch/Headers.zig +++ b/src/browser/fetch/Headers.zig @@ -17,9 +17,12 @@ // along with this program. If not, see . const std = @import("std"); +const log = @import("../../log.zig"); const URL = @import("../../url.zig").URL; const Page = @import("../page.zig").Page; +const iterator = @import("../iterator/iterator.zig"); + const v8 = @import("v8"); const Env = @import("../env.zig").Env; @@ -108,7 +111,8 @@ pub fn constructor(_init: ?HeadersInit, page: *Page) !Headers { } pub fn append(self: *Headers, name: []const u8, value: []const u8, allocator: std.mem.Allocator) !void { - const gop = try self.headers.getOrPut(allocator, name); + const key = try allocator.dupe(u8, name); + const gop = try self.headers.getOrPut(allocator, key); if (gop.found_existing) { // If we found it, append the value. @@ -129,13 +133,13 @@ pub fn _delete(self: *Headers, name: []const u8) void { _ = self.headers.remove(name); } -pub const HeaderEntryIterator = struct { +pub const HeadersEntryIterator = struct { slot: [2][]const u8, iter: HeaderHashMap.Iterator, // TODO: these SHOULD be in lexigraphical order but I'm not sure how actually // important that is. - pub fn _next(self: *HeaderEntryIterator) ?[2][]const u8 { + pub fn _next(self: *HeadersEntryIterator) ?[2][]const u8 { if (self.iter.next()) |entry| { self.slot[0] = entry.key_ptr.*; self.slot[1] = entry.value_ptr.*; @@ -146,10 +150,12 @@ pub const HeaderEntryIterator = struct { } }; -pub fn _entries(self: *const Headers) HeaderEntryIterator { +pub fn _entries(self: *const Headers) HeadersEntryIterable { return .{ - .slot = undefined, - .iter = self.headers.iterator(), + .inner = .{ + .slot = undefined, + .iter = self.headers.iterator(), + }, }; } @@ -171,10 +177,10 @@ pub fn _has(self: *const Headers, name: []const u8) bool { return self.headers.contains(name); } -pub const HeaderKeyIterator = struct { +pub const HeadersKeyIterator = struct { iter: HeaderHashMap.KeyIterator, - pub fn _next(self: *HeaderKeyIterator) ?[]const u8 { + pub fn _next(self: *HeadersKeyIterator) ?[]const u8 { if (self.iter.next()) |key| { return key.*; } else { @@ -183,21 +189,22 @@ pub const HeaderKeyIterator = struct { } }; -pub fn _keys(self: *const Headers) HeaderKeyIterator { - return .{ .iter = self.headers.keyIterator() }; +pub fn _keys(self: *const Headers) HeadersKeyIterable { + return .{ .inner = .{ .iter = self.headers.keyIterator() } }; } pub fn _set(self: *Headers, name: []const u8, value: []const u8, page: *Page) !void { const arena = page.arena; - const gop = try self.headers.getOrPut(arena, name); + const key = try arena.dupe(u8, name); + const gop = try self.headers.getOrPut(arena, key); gop.value_ptr.* = try arena.dupe(u8, value); } -pub const HeaderValueIterator = struct { +pub const HeadersValueIterator = struct { iter: HeaderHashMap.ValueIterator, - pub fn _next(self: *HeaderValueIterator) ?[]const u8 { + pub fn _next(self: *HeadersValueIterator) ?[]const u8 { if (self.iter.next()) |value| { return value.*; } else { @@ -206,53 +213,15 @@ pub const HeaderValueIterator = struct { } }; -pub fn _values(self: *const Headers) HeaderValueIterator { - return .{ .iter = self.headers.valueIterator() }; +pub fn _values(self: *const Headers) HeadersValueIterable { + return .{ .inner = .{ .iter = self.headers.valueIterator() } }; } +pub const HeadersKeyIterable = iterator.Iterable(HeadersKeyIterator, "HeadersKeyIterator"); +pub const HeadersValueIterable = iterator.Iterable(HeadersValueIterator, "HeadersValueIterator"); +pub const HeadersEntryIterable = iterator.Iterable(HeadersEntryIterator, "HeadersEntryIterator"); + const testing = @import("../../testing.zig"); -test "fetch: headers" { - var runner = try testing.jsRunner(testing.tracking_allocator, .{ .url = "https://lightpanda.io" }); - defer runner.deinit(); - - try runner.testCases(&.{ - .{ "let emptyHeaders = new Headers()", "undefined" }, - }, .{}); - - try runner.testCases(&.{ - .{ "let headers = new Headers({'Set-Cookie': 'name=world'})", "undefined" }, - .{ "headers.get('set-cookie')", "name=world" }, - }, .{}); - - // adapted from the mdn examples - try runner.testCases(&.{ - .{ "const myHeaders = new Headers();", "undefined" }, - .{ "myHeaders.append('Content-Type', 'image/jpeg')", "undefined" }, - .{ "myHeaders.has('Picture-Type')", "false" }, - .{ "myHeaders.get('Content-Type')", "image/jpeg" }, - .{ "myHeaders.append('Content-Type', 'image/png')", "undefined" }, - .{ "myHeaders.get('Content-Type')", "image/jpeg, image/png" }, - .{ "myHeaders.delete('Content-Type')", "undefined" }, - .{ "myHeaders.get('Content-Type')", "null" }, - .{ "myHeaders.set('Picture-Type', 'image/svg')", "undefined" }, - .{ "myHeaders.get('Picture-Type')", "image/svg" }, - .{ "myHeaders.has('Picture-Type')", "true" }, - }, .{}); - - try runner.testCases(&.{ - .{ "const originalHeaders = new Headers([['Content-Type', 'application/json'], ['Authorization', 'Bearer token123']])", "undefined" }, - .{ "originalHeaders.get('Content-Type')", "application/json" }, - .{ "originalHeaders.get('Authorization')", "Bearer token123" }, - .{ "const newHeaders = new Headers(originalHeaders)", "undefined" }, - .{ "newHeaders.get('Content-Type')", "application/json" }, - .{ "newHeaders.get('Authorization')", "Bearer token123" }, - .{ "newHeaders.has('Content-Type')", "true" }, - .{ "newHeaders.has('Authorization')", "true" }, - .{ "newHeaders.has('X-Custom')", "false" }, - // Verify that modifying the new headers doesn't affect the original - .{ "newHeaders.set('X-Custom', 'test-value')", "undefined" }, - .{ "newHeaders.get('X-Custom')", "test-value" }, - .{ "originalHeaders.get('X-Custom')", "null" }, - .{ "originalHeaders.has('X-Custom')", "false" }, - }, .{}); +test "fetch: Headers" { + try testing.htmlRunner("fetch/headers.html"); } diff --git a/src/browser/fetch/Request.zig b/src/browser/fetch/Request.zig index 67dc7cab..7cfc77c2 100644 --- a/src/browser/fetch/Request.zig +++ b/src/browser/fetch/Request.zig @@ -54,6 +54,10 @@ pub const RequestCache = enum { return null; } } + + pub fn toString(self: RequestCache) []const u8 { + return @tagName(self); + } }; pub const RequestCredentials = enum { @@ -70,6 +74,10 @@ pub const RequestCredentials = enum { return null; } } + + pub fn toString(self: RequestCredentials) []const u8 { + return @tagName(self); + } }; // https://developer.mozilla.org/en-US/docs/Web/API/RequestInit @@ -154,6 +162,10 @@ pub fn get_cache(self: *const Request) RequestCache { return self.cache; } +pub fn get_credentials(self: *const Request) RequestCredentials { + return self.credentials; +} + pub fn get_headers(self: *Request) *Headers { return &self.headers; } @@ -249,50 +261,6 @@ pub fn _text(self: *Response, page: *Page) !Env.Promise { } const testing = @import("../../testing.zig"); -test "fetch: request" { - var runner = try testing.jsRunner(testing.tracking_allocator, .{ .url = "https://lightpanda.io" }); - defer runner.deinit(); - - try runner.testCases(&.{ - .{ "let request = new Request('flower.png')", "undefined" }, - .{ "request.url", "https://lightpanda.io/flower.png" }, - .{ "request.method", "GET" }, - }, .{}); - - try runner.testCases(&.{ - .{ "let request2 = new Request('https://google.com', { method: 'POST', body: 'Hello, World' })", "undefined" }, - .{ "request2.url", "https://google.com" }, - .{ "request2.method", "POST" }, - }, .{}); -} - -test "fetch: Browser.fetch" { - var runner = try testing.jsRunner(testing.tracking_allocator, .{}); - defer runner.deinit(); - - try runner.testCases(&.{ - .{ - \\ var ok = false; - \\ const request = new Request("http://127.0.0.1:9582/loader"); - \\ fetch(request).then((response) => { ok = response.ok; }); - \\ false; - , - "false", - }, - // all events have been resolved. - .{ "ok", "true" }, - }, .{}); - - try runner.testCases(&.{ - .{ - \\ var ok2 = false; - \\ const request2 = new Request("http://127.0.0.1:9582/loader"); - \\ (async function () { resp = await fetch(request2); ok2 = resp.ok; }()); - \\ false; - , - "false", - }, - // all events have been resolved. - .{ "ok2", "true" }, - }, .{}); +test "fetch: Request" { + try testing.htmlRunner("fetch/request.html"); } diff --git a/src/browser/fetch/Response.zig b/src/browser/fetch/Response.zig index be7cc8f1..af1b615e 100644 --- a/src/browser/fetch/Response.zig +++ b/src/browser/fetch/Response.zig @@ -36,7 +36,8 @@ const Page = @import("../page.zig").Page; // https://developer.mozilla.org/en-US/docs/Web/API/Response const Response = @This(); -status: u16 = 0, +status: u16 = 200, +status_text: []const u8 = "", headers: Headers, mime: ?Mime = null, url: []const u8 = "", @@ -50,7 +51,7 @@ const ResponseBody = union(enum) { const ResponseOptions = struct { status: u16 = 200, - statusText: []const u8 = "", + statusText: ?[]const u8 = null, headers: ?HeadersInit = null, }; @@ -72,10 +73,13 @@ pub fn constructor(_input: ?ResponseBody, _options: ?ResponseOptions, page: *Pag }; const headers: Headers = if (options.headers) |hdrs| try Headers.constructor(hdrs, page) else .{}; + const status_text = if (options.statusText) |st| try arena.dupe(u8, st) else ""; return .{ .body = body, .headers = headers, + .status = options.status, + .status_text = status_text, }; } @@ -105,6 +109,10 @@ pub fn get_status(self: *const Response) u16 { return self.status; } +pub fn get_statusText(self: *const Response) []const u8 { + return self.status_text; +} + pub fn get_url(self: *const Response) []const u8 { return self.url; } @@ -183,9 +191,6 @@ pub fn _text(self: *Response, page: *Page) !Env.Promise { } const testing = @import("../../testing.zig"); -test "fetch: response" { - var runner = try testing.jsRunner(testing.tracking_allocator, .{ .url = "https://lightpanda.io" }); - defer runner.deinit(); - - try runner.testCases(&.{}, .{}); +test "fetch: Response" { + try testing.htmlRunner("fetch/response.html"); } diff --git a/src/browser/fetch/fetch.zig b/src/browser/fetch/fetch.zig index d3008d7b..b2c676df 100644 --- a/src/browser/fetch/fetch.zig +++ b/src/browser/fetch/fetch.zig @@ -36,9 +36,9 @@ const Response = @import("Response.zig"); pub const Interfaces = .{ @import("Headers.zig"), - @import("Headers.zig").HeaderEntryIterator, - @import("Headers.zig").HeaderKeyIterator, - @import("Headers.zig").HeaderValueIterator, + @import("Headers.zig").HeadersEntryIterable, + @import("Headers.zig").HeadersKeyIterable, + @import("Headers.zig").HeadersValueIterable, @import("Request.zig"), @import("Response.zig"), }; diff --git a/src/tests/fetch/headers.html b/src/tests/fetch/headers.html new file mode 100644 index 00000000..57d6ce2e --- /dev/null +++ b/src/tests/fetch/headers.html @@ -0,0 +1,102 @@ + + + + + + + + + diff --git a/src/tests/fetch/request.html b/src/tests/fetch/request.html new file mode 100644 index 00000000..7bfdfe56 --- /dev/null +++ b/src/tests/fetch/request.html @@ -0,0 +1,22 @@ + + + diff --git a/src/tests/fetch/response.html b/src/tests/fetch/response.html new file mode 100644 index 00000000..8002e8b3 --- /dev/null +++ b/src/tests/fetch/response.html @@ -0,0 +1,38 @@ + + +