mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-12-14 23:38:57 +00:00
Pass Headers legacy tests
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<script src="../testing.js"></script>
|
||||
|
||||
<script id=headers>
|
||||
@@ -48,9 +49,9 @@
|
||||
}
|
||||
|
||||
testing.expectEqual(3, keys.length);
|
||||
testing.expectEqual(true, keys.includes("Content-Type"));
|
||||
testing.expectEqual(true, keys.includes("Authorization"));
|
||||
testing.expectEqual(true, keys.includes("X-Custom"));
|
||||
testing.expectEqual(true, keys.includes("content-type"));
|
||||
testing.expectEqual(true, keys.includes("authorization"));
|
||||
testing.expectEqual(true, keys.includes("x-custom"));
|
||||
</script>
|
||||
|
||||
<script id=values>
|
||||
@@ -84,15 +85,15 @@
|
||||
testing.expectEqual(3, entries.length);
|
||||
|
||||
const entryMap = new Map(entries);
|
||||
testing.expectEqual("application/json", entryMap.get("Content-Type"));
|
||||
testing.expectEqual("Bearer token123", entryMap.get("Authorization"));
|
||||
testing.expectEqual("test-value", entryMap.get("X-Custom"));
|
||||
testing.expectEqual("application/json", entryMap.get("content-type"));
|
||||
testing.expectEqual("Bearer token123", entryMap.get("authorization"));
|
||||
testing.expectEqual("test-value", entryMap.get("x-custom"));
|
||||
|
||||
const entryKeys = Array.from(entryMap.keys());
|
||||
testing.expectEqual(3, entryKeys.length);
|
||||
testing.expectEqual(true, entryKeys.includes("Content-Type"));
|
||||
testing.expectEqual(true, entryKeys.includes("Authorization"));
|
||||
testing.expectEqual(true, entryKeys.includes("X-Custom"));
|
||||
testing.expectEqual(true, entryKeys.includes("content-type"));
|
||||
testing.expectEqual(true, entryKeys.includes("authorization"));
|
||||
testing.expectEqual(true, entryKeys.includes("x-custom"));
|
||||
|
||||
const entryValues = Array.from(entryMap.values());
|
||||
testing.expectEqual(3, entryValues.length);
|
||||
|
||||
@@ -72,6 +72,137 @@
|
||||
testing.expectEqual('authorization', entries[0][0]);
|
||||
testing.expectEqual('Bearer token2', entries[0][1]);
|
||||
}
|
||||
|
||||
{
|
||||
const headers = new Headers({"Set-Cookie": "name=world"});
|
||||
testing.expectEqual("name=world", headers.get("set-cookie"));
|
||||
}
|
||||
|
||||
// Test object initialization with case normalization
|
||||
{
|
||||
const headers = new Headers({
|
||||
"Content-Type": "application/json",
|
||||
"AUTHORIZATION": "Bearer token",
|
||||
"x-CuStOm": "mixed-case"
|
||||
});
|
||||
|
||||
// All headers should be normalized to lowercase
|
||||
testing.expectEqual("application/json", headers.get("content-type"));
|
||||
testing.expectEqual("Bearer token", headers.get("authorization"));
|
||||
testing.expectEqual("mixed-case", headers.get("x-custom"));
|
||||
|
||||
// Verify via keys iterator that names are normalized
|
||||
const keys = Array.from(headers.keys());
|
||||
testing.expectEqual(3, keys.length);
|
||||
testing.expectEqual(true, keys.includes("content-type"));
|
||||
testing.expectEqual(true, keys.includes("authorization"));
|
||||
testing.expectEqual(true, keys.includes("x-custom"));
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=array-initialization>
|
||||
// Test array initialization
|
||||
{
|
||||
const headers = new Headers([
|
||||
["Content-Type", "application/json"],
|
||||
["Authorization", "Bearer token123"]
|
||||
]);
|
||||
|
||||
testing.expectEqual("application/json", headers.get("Content-Type"));
|
||||
testing.expectEqual("Bearer token123", headers.get("Authorization"));
|
||||
testing.expectEqual(true, headers.has("Content-Type"));
|
||||
testing.expectEqual(true, headers.has("Authorization"));
|
||||
}
|
||||
|
||||
// Test array initialization with case normalization
|
||||
{
|
||||
const headers = new Headers([
|
||||
["Content-Type", "text/html"],
|
||||
["AUTHORIZATION", "Bearer abc"],
|
||||
["x-CuStOm-HeAdEr", "value123"]
|
||||
]);
|
||||
|
||||
// All header names should be normalized to lowercase
|
||||
testing.expectEqual("text/html", headers.get("content-type"));
|
||||
testing.expectEqual("Bearer abc", headers.get("authorization"));
|
||||
testing.expectEqual("value123", headers.get("x-custom-header"));
|
||||
|
||||
// Verify case-insensitive access works
|
||||
testing.expectEqual("text/html", headers.get("CONTENT-TYPE"));
|
||||
testing.expectEqual("Bearer abc", headers.get("Authorization"));
|
||||
testing.expectEqual("value123", headers.get("X-CUSTOM-HEADER"));
|
||||
|
||||
// Verify keys are normalized
|
||||
const keys = Array.from(headers.keys());
|
||||
testing.expectEqual(3, keys.length);
|
||||
testing.expectEqual("content-type", keys[0]);
|
||||
testing.expectEqual("authorization", keys[1]);
|
||||
testing.expectEqual("x-custom-header", keys[2]);
|
||||
}
|
||||
|
||||
// Test array initialization with multiple values
|
||||
{
|
||||
const headers = new Headers([
|
||||
["Accept", "application/json"],
|
||||
["Accept", "text/html"],
|
||||
["Content-Type", "text/plain"]
|
||||
]);
|
||||
|
||||
const entries = Array.from(headers.entries());
|
||||
testing.expectEqual(3, entries.length);
|
||||
|
||||
// All Accept headers should be present
|
||||
const acceptValues = entries.filter(e => e[0] === 'accept').map(e => e[1]);
|
||||
testing.expectEqual(2, acceptValues.length);
|
||||
testing.expectEqual("application/json", acceptValues[0]);
|
||||
testing.expectEqual("text/html", acceptValues[1]);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=copy-constructor>
|
||||
// Test creating Headers from another Headers object
|
||||
{
|
||||
const original = new Headers();
|
||||
original.set("Content-Type", "application/json");
|
||||
original.set("Authorization", "Bearer token123");
|
||||
original.set("X-Custom", "test-value");
|
||||
|
||||
const copy = new Headers(original);
|
||||
|
||||
// Copy should have all the same headers
|
||||
testing.expectEqual("application/json", copy.get("Content-Type"));
|
||||
testing.expectEqual("Bearer token123", copy.get("Authorization"));
|
||||
testing.expectEqual("test-value", copy.get("X-Custom"));
|
||||
|
||||
// Copy should be independent
|
||||
copy.set("X-Modified", "new-value");
|
||||
testing.expectEqual("new-value", copy.get("X-Modified"));
|
||||
testing.expectEqual(null, original.get("X-Modified"));
|
||||
|
||||
// Modifying copy shouldn't affect original
|
||||
copy.set("Content-Type", "text/html");
|
||||
testing.expectEqual("text/html", copy.get("Content-Type"));
|
||||
testing.expectEqual("application/json", original.get("Content-Type"));
|
||||
}
|
||||
|
||||
// Test copy constructor with mixed-case headers
|
||||
{
|
||||
const original = new Headers([
|
||||
["Content-TYPE", "application/json"],
|
||||
["AUTHORIZATION", "Bearer xyz"]
|
||||
]);
|
||||
|
||||
const copy = new Headers(original);
|
||||
|
||||
// Headers should be normalized in copy
|
||||
testing.expectEqual("application/json", copy.get("content-type"));
|
||||
testing.expectEqual("Bearer xyz", copy.get("authorization"));
|
||||
|
||||
const keys = Array.from(copy.keys());
|
||||
testing.expectEqual(2, keys.length);
|
||||
testing.expectEqual("content-type", keys[0]);
|
||||
testing.expectEqual("authorization", keys[1]);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=iterators>
|
||||
@@ -159,3 +290,47 @@
|
||||
testing.expectEqual('text/plain', values[2]);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=get-concatenation>
|
||||
// Test that get() returns concatenated values for headers with multiple values
|
||||
{
|
||||
const headers = new Headers();
|
||||
headers.append('Accept', 'application/json');
|
||||
headers.append('Accept', 'text/html');
|
||||
headers.append('Accept', 'text/plain');
|
||||
|
||||
// get() should return comma-separated concatenated values
|
||||
const acceptValue = headers.get('Accept');
|
||||
testing.expectEqual('application/json, text/html, text/plain', acceptValue);
|
||||
}
|
||||
|
||||
// Test concatenation with case-insensitive header names
|
||||
{
|
||||
const headers = new Headers();
|
||||
headers.append('Content-Type', 'image/jpeg');
|
||||
headers.append('CONTENT-TYPE', 'image/png');
|
||||
headers.append('content-type', 'image/svg+xml');
|
||||
|
||||
const contentType = headers.get('Content-Type');
|
||||
testing.expectEqual('image/jpeg, image/png, image/svg+xml', contentType);
|
||||
}
|
||||
|
||||
// Test that set() replaces all values, not concatenates
|
||||
{
|
||||
const headers = new Headers();
|
||||
headers.append('Authorization', 'Bearer token1');
|
||||
headers.append('Authorization', 'Bearer token2');
|
||||
|
||||
// Before set, should have both values
|
||||
testing.expectEqual('Bearer token1, Bearer token2', headers.get('Authorization'));
|
||||
|
||||
// set() should replace all values
|
||||
headers.set('Authorization', 'Bearer new-token');
|
||||
testing.expectEqual('Bearer new-token', headers.get('Authorization'));
|
||||
|
||||
// Should only have one entry now
|
||||
const entries = Array.from(headers.entries());
|
||||
const authEntries = entries.filter(e => e[0] === 'authorization');
|
||||
testing.expectEqual(1, authEntries.length);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -33,6 +33,7 @@ pub fn registerTypes() []const type {
|
||||
};
|
||||
}
|
||||
|
||||
const Normalizer = *const fn([]const u8, *Page) []const u8;
|
||||
pub const KeyValueList = @This();
|
||||
|
||||
_entries: std.ArrayListUnmanaged(Entry) = .empty,
|
||||
@@ -50,7 +51,7 @@ pub fn copy(arena: Allocator, original: KeyValueList) !KeyValueList {
|
||||
return list;
|
||||
}
|
||||
|
||||
pub fn fromJsObject(arena: Allocator, js_obj: js.Object) !KeyValueList {
|
||||
pub fn fromJsObject(arena: Allocator, js_obj: js.Object, comptime normalizer: ?Normalizer, page: *Page) !KeyValueList {
|
||||
var it = js_obj.nameIterator();
|
||||
var list = KeyValueList.init();
|
||||
try list.ensureTotalCapacity(arena, it.count);
|
||||
@@ -58,9 +59,10 @@ pub fn fromJsObject(arena: Allocator, js_obj: js.Object) !KeyValueList {
|
||||
while (try it.next()) |name| {
|
||||
const js_value = try js_obj.get(name);
|
||||
const value = try js_value.toString(arena);
|
||||
const normalized = if (comptime normalizer) |n| n(name, page) else name;
|
||||
|
||||
try list._entries.append(arena, .{
|
||||
.name = try String.init(arena, name, .{}),
|
||||
list._entries.appendAssumeCapacity(.{
|
||||
.name = try String.init(arena, normalized, .{}),
|
||||
.value = try String.init(arena, value, .{}),
|
||||
});
|
||||
}
|
||||
@@ -68,6 +70,21 @@ pub fn fromJsObject(arena: Allocator, js_obj: js.Object) !KeyValueList {
|
||||
return list;
|
||||
}
|
||||
|
||||
pub fn fromArray(arena: Allocator, kvs: []const [2][]const u8, comptime normalizer: ?Normalizer, page: *Page) !KeyValueList {
|
||||
var list = KeyValueList.init();
|
||||
try list.ensureTotalCapacity(arena, kvs.len);
|
||||
|
||||
for (kvs) |pair| {
|
||||
const normalized = if (comptime normalizer) |n| n(pair[0], page) else pair[0];
|
||||
|
||||
list._entries.appendAssumeCapacity(.{
|
||||
.name = try String.init(arena, normalized, .{}),
|
||||
.value = try String.init(arena, pair[1], .{}),
|
||||
});
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
pub const Entry = struct {
|
||||
name: String,
|
||||
value: String,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const std = @import("std");
|
||||
const js = @import("../../js/js.zig");
|
||||
const log = @import("../../../log.zig");
|
||||
const String = @import("../../../string.zig").String;
|
||||
|
||||
const Page = @import("../../Page.zig");
|
||||
const KeyValueList = @import("../KeyValueList.zig");
|
||||
@@ -13,13 +14,15 @@ _list: KeyValueList,
|
||||
|
||||
pub const InitOpts = union(enum) {
|
||||
obj: *Headers,
|
||||
strings: []const [2][]const u8,
|
||||
js_obj: js.Object,
|
||||
};
|
||||
|
||||
pub fn init(opts_: ?InitOpts, page: *Page) !*Headers {
|
||||
const list = if (opts_) |opts| switch (opts) {
|
||||
.obj => |obj| try KeyValueList.copy(page.arena, obj._list),
|
||||
.js_obj => |js_obj| try KeyValueList.fromJsObject(page.arena, js_obj),
|
||||
.js_obj => |js_obj| try KeyValueList.fromJsObject(page.arena, js_obj, normalizeHeaderName, page),
|
||||
.strings => |kvs| try KeyValueList.fromArray(page.arena, kvs, normalizeHeaderName, page),
|
||||
} else KeyValueList.init();
|
||||
|
||||
return page._factory.create(Headers{
|
||||
@@ -27,12 +30,6 @@ pub fn init(opts_: ?InitOpts, page: *Page) !*Headers {
|
||||
});
|
||||
}
|
||||
|
||||
// pub fn fromJsObject(js_obj: js.Object, page: *Page) !*Headers {
|
||||
// return page._factory.create(Headers{
|
||||
// ._list = try KeyValueList.fromJsObject(page.arena, js_obj),
|
||||
// });
|
||||
// }
|
||||
|
||||
pub fn append(self: *Headers, name: []const u8, value: []const u8, page: *Page) !void {
|
||||
const normalized_name = normalizeHeaderName(name, page);
|
||||
try self._list.append(page.arena, normalized_name, value);
|
||||
@@ -43,9 +40,17 @@ pub fn delete(self: *Headers, name: []const u8, page: *Page) void {
|
||||
self._list.delete(normalized_name, null);
|
||||
}
|
||||
|
||||
pub fn get(self: *const Headers, name: []const u8, page: *Page) ?[]const u8 {
|
||||
pub fn get(self: *const Headers, name: []const u8, page: *Page) !?[]const u8 {
|
||||
const normalized_name = normalizeHeaderName(name, page);
|
||||
return self._list.get(normalized_name);
|
||||
const all_values = try self._list.getAll(normalized_name, page);
|
||||
|
||||
if (all_values.len == 0) {
|
||||
return null;
|
||||
}
|
||||
if (all_values.len == 1) {
|
||||
return all_values[0];
|
||||
}
|
||||
return try std.mem.join(page.call_arena, ", ", all_values);
|
||||
}
|
||||
|
||||
pub fn has(self: *const Headers, name: []const u8, page: *Page) bool {
|
||||
@@ -97,6 +102,7 @@ fn normalizeHeaderName(name: []const u8, page: *Page) []const u8 {
|
||||
return std.ascii.lowerString(&page.buf, name);
|
||||
}
|
||||
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(Headers);
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ pub fn init(opts_: ?InitOpts, page: *Page) !*URLSearchParams {
|
||||
.query_string => |qs| break :blk try paramsFromString(arena, qs, &page.buf),
|
||||
.value => |js_val| {
|
||||
if (js_val.isObject()) {
|
||||
break :blk try KeyValueList.fromJsObject(arena, js_val.toObject());
|
||||
break :blk try KeyValueList.fromJsObject(arena, js_val.toObject(), null, page);
|
||||
}
|
||||
if (js_val.isString()) {
|
||||
break :blk try paramsFromString(arena, try js_val.toString(arena), &page.buf);
|
||||
|
||||
Reference in New Issue
Block a user