mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-12-17 00:38:59 +00:00
Pass Headers legacy tests
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
<script src="../testing.js"></script>
|
<script src="../testing.js"></script>
|
||||||
|
|
||||||
<script id=headers>
|
<script id=headers>
|
||||||
@@ -48,9 +49,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
testing.expectEqual(3, keys.length);
|
testing.expectEqual(3, keys.length);
|
||||||
testing.expectEqual(true, keys.includes("Content-Type"));
|
testing.expectEqual(true, keys.includes("content-type"));
|
||||||
testing.expectEqual(true, keys.includes("Authorization"));
|
testing.expectEqual(true, keys.includes("authorization"));
|
||||||
testing.expectEqual(true, keys.includes("X-Custom"));
|
testing.expectEqual(true, keys.includes("x-custom"));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script id=values>
|
<script id=values>
|
||||||
@@ -84,15 +85,15 @@
|
|||||||
testing.expectEqual(3, entries.length);
|
testing.expectEqual(3, entries.length);
|
||||||
|
|
||||||
const entryMap = new Map(entries);
|
const entryMap = new Map(entries);
|
||||||
testing.expectEqual("application/json", entryMap.get("Content-Type"));
|
testing.expectEqual("application/json", entryMap.get("content-type"));
|
||||||
testing.expectEqual("Bearer token123", entryMap.get("Authorization"));
|
testing.expectEqual("Bearer token123", entryMap.get("authorization"));
|
||||||
testing.expectEqual("test-value", entryMap.get("X-Custom"));
|
testing.expectEqual("test-value", entryMap.get("x-custom"));
|
||||||
|
|
||||||
const entryKeys = Array.from(entryMap.keys());
|
const entryKeys = Array.from(entryMap.keys());
|
||||||
testing.expectEqual(3, entryKeys.length);
|
testing.expectEqual(3, entryKeys.length);
|
||||||
testing.expectEqual(true, entryKeys.includes("Content-Type"));
|
testing.expectEqual(true, entryKeys.includes("content-type"));
|
||||||
testing.expectEqual(true, entryKeys.includes("Authorization"));
|
testing.expectEqual(true, entryKeys.includes("authorization"));
|
||||||
testing.expectEqual(true, entryKeys.includes("X-Custom"));
|
testing.expectEqual(true, entryKeys.includes("x-custom"));
|
||||||
|
|
||||||
const entryValues = Array.from(entryMap.values());
|
const entryValues = Array.from(entryMap.values());
|
||||||
testing.expectEqual(3, entryValues.length);
|
testing.expectEqual(3, entryValues.length);
|
||||||
|
|||||||
@@ -72,6 +72,137 @@
|
|||||||
testing.expectEqual('authorization', entries[0][0]);
|
testing.expectEqual('authorization', entries[0][0]);
|
||||||
testing.expectEqual('Bearer token2', entries[0][1]);
|
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>
|
||||||
|
|
||||||
<script id=iterators>
|
<script id=iterators>
|
||||||
@@ -159,3 +290,47 @@
|
|||||||
testing.expectEqual('text/plain', values[2]);
|
testing.expectEqual('text/plain', values[2]);
|
||||||
}
|
}
|
||||||
</script>
|
</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();
|
pub const KeyValueList = @This();
|
||||||
|
|
||||||
_entries: std.ArrayListUnmanaged(Entry) = .empty,
|
_entries: std.ArrayListUnmanaged(Entry) = .empty,
|
||||||
@@ -50,7 +51,7 @@ pub fn copy(arena: Allocator, original: KeyValueList) !KeyValueList {
|
|||||||
return list;
|
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 it = js_obj.nameIterator();
|
||||||
var list = KeyValueList.init();
|
var list = KeyValueList.init();
|
||||||
try list.ensureTotalCapacity(arena, it.count);
|
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| {
|
while (try it.next()) |name| {
|
||||||
const js_value = try js_obj.get(name);
|
const js_value = try js_obj.get(name);
|
||||||
const value = try js_value.toString(arena);
|
const value = try js_value.toString(arena);
|
||||||
|
const normalized = if (comptime normalizer) |n| n(name, page) else name;
|
||||||
|
|
||||||
try list._entries.append(arena, .{
|
list._entries.appendAssumeCapacity(.{
|
||||||
.name = try String.init(arena, name, .{}),
|
.name = try String.init(arena, normalized, .{}),
|
||||||
.value = try String.init(arena, value, .{}),
|
.value = try String.init(arena, value, .{}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -68,6 +70,21 @@ pub fn fromJsObject(arena: Allocator, js_obj: js.Object) !KeyValueList {
|
|||||||
return list;
|
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 {
|
pub const Entry = struct {
|
||||||
name: String,
|
name: String,
|
||||||
value: String,
|
value: String,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const js = @import("../../js/js.zig");
|
const js = @import("../../js/js.zig");
|
||||||
const log = @import("../../../log.zig");
|
const log = @import("../../../log.zig");
|
||||||
|
const String = @import("../../../string.zig").String;
|
||||||
|
|
||||||
const Page = @import("../../Page.zig");
|
const Page = @import("../../Page.zig");
|
||||||
const KeyValueList = @import("../KeyValueList.zig");
|
const KeyValueList = @import("../KeyValueList.zig");
|
||||||
@@ -13,13 +14,15 @@ _list: KeyValueList,
|
|||||||
|
|
||||||
pub const InitOpts = union(enum) {
|
pub const InitOpts = union(enum) {
|
||||||
obj: *Headers,
|
obj: *Headers,
|
||||||
|
strings: []const [2][]const u8,
|
||||||
js_obj: js.Object,
|
js_obj: js.Object,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn init(opts_: ?InitOpts, page: *Page) !*Headers {
|
pub fn init(opts_: ?InitOpts, page: *Page) !*Headers {
|
||||||
const list = if (opts_) |opts| switch (opts) {
|
const list = if (opts_) |opts| switch (opts) {
|
||||||
.obj => |obj| try KeyValueList.copy(page.arena, obj._list),
|
.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();
|
} else KeyValueList.init();
|
||||||
|
|
||||||
return page._factory.create(Headers{
|
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 {
|
pub fn append(self: *Headers, name: []const u8, value: []const u8, page: *Page) !void {
|
||||||
const normalized_name = normalizeHeaderName(name, page);
|
const normalized_name = normalizeHeaderName(name, page);
|
||||||
try self._list.append(page.arena, normalized_name, value);
|
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);
|
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);
|
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 {
|
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);
|
return std.ascii.lowerString(&page.buf, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub const JsApi = struct {
|
pub const JsApi = struct {
|
||||||
pub const bridge = js.Bridge(Headers);
|
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),
|
.query_string => |qs| break :blk try paramsFromString(arena, qs, &page.buf),
|
||||||
.value => |js_val| {
|
.value => |js_val| {
|
||||||
if (js_val.isObject()) {
|
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()) {
|
if (js_val.isString()) {
|
||||||
break :blk try paramsFromString(arena, try js_val.toString(arena), &page.buf);
|
break :blk try paramsFromString(arena, try js_val.toString(arena), &page.buf);
|
||||||
|
|||||||
Reference in New Issue
Block a user