Skip to content

Commit 19d4084

Browse files
committed
input prop testing
1 parent 275b979 commit 19d4084

File tree

4 files changed

+189
-11
lines changed

4 files changed

+189
-11
lines changed

src/browser/html/elements.zig

Lines changed: 151 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const generate = @import("../../runtime/generate.zig");
2222
const Env = @import("../env.zig").Env;
2323
const Page = @import("../page.zig").Page;
2424

25+
const urlStitch = @import("../../url.zig").URL.stitch;
2526
const URL = @import("../url/url.zig").URL;
2627
const Node = @import("../dom/node.zig").Node;
2728
const Element = @import("../dom/element.zig").Element;
@@ -639,7 +640,7 @@ pub const HTMLInputElement = struct {
639640
pub fn set_defaultChecked(self: *parser.Input, default_checked: bool) !void {
640641
try parser.inputSetDefaultChecked(self, default_checked);
641642
}
642-
pub fn get_from(self: *parser.Input) !?*parser.Form {
643+
pub fn get_form(self: *parser.Input) !?*parser.Form {
643644
return try parser.inputGetForm(self);
644645
}
645646
pub fn get_accept(self: *parser.Input) ![]const u8 {
@@ -669,7 +670,7 @@ pub const HTMLInputElement = struct {
669670
pub fn get_maxLength(self: *parser.Input) !i32 {
670671
return try parser.inputGetMaxLength(self);
671672
}
672-
pub fn set_maxLength(self: *parser.Input, max_length: u32) !void {
673+
pub fn set_maxLength(self: *parser.Input, max_length: i32) !void {
673674
try parser.inputSetMaxLength(self, max_length);
674675
}
675676
pub fn get_name(self: *parser.Input) ![]const u8 {
@@ -687,18 +688,22 @@ pub const HTMLInputElement = struct {
687688
pub fn get_size(self: *parser.Input) !u32 {
688689
return try parser.inputGetSize(self);
689690
}
690-
pub fn set_size(self: *parser.Input, size: u32) !void {
691+
pub fn set_size(self: *parser.Input, size: i32) !void {
691692
try parser.inputSetSize(self, size);
692693
}
693694
pub fn get_src(self: *parser.Input) ![]const u8 {
694695
return try parser.inputGetSrc(self);
695696
}
696-
pub fn set_src(self: *parser.Input, src: []const u8) !void {
697-
try parser.inputSetSrc(self, src);
697+
pub fn set_src(self: *parser.Input, src: []const u8, page: *Page) !void {
698+
const new_src = try urlStitch(page.call_arena, src, page.url.raw);
699+
try parser.inputSetSrc(self, new_src);
698700
}
699701
pub fn get_type(self: *parser.Input) ![]const u8 {
700702
return try parser.inputGetType(self);
701703
}
704+
pub fn set_type(self: *parser.Input, type_: []const u8) !void {
705+
try parser.inputSetType(self, type_);
706+
}
702707
pub fn get_value(self: *parser.Input) ![]const u8 {
703708
return try parser.inputGetValue(self);
704709
}
@@ -1261,3 +1266,144 @@ test "Browser.HTML.Element" {
12611266
.{ "a.href", "https://lightpanda.io/opensource-browser/about" },
12621267
}, .{});
12631268
}
1269+
test "Browser.HTML.Element.propeties" {
1270+
var runner = try testing.jsRunner(testing.tracking_allocator, .{ .url = "https://lightpanda.io/noslashattheend" });
1271+
defer runner.deinit();
1272+
const bool_valids = [_]Valid{
1273+
.{ .input = "true", .is_str = false },
1274+
.{ .input = "", .is_str = true, .expected = "false" },
1275+
.{ .input = "13.5", .is_str = true, .expected = "true" },
1276+
};
1277+
const str_valids = [_]Valid{
1278+
.{ .input = "foo", .is_str = true },
1279+
.{ .input = "5", .is_str = false, .expected = "5" },
1280+
.{ .input = "", .is_str = true },
1281+
.{ .input = "document", .is_str = false, .expected = "[object HTMLDocument]" },
1282+
};
1283+
// TODO these tests are mostly just data should we store them in Sqlite or so?
1284+
try testCreateElement(&runner, "input");
1285+
// Valid input.form is tested separately :Browser.HTML.Element.propeties.input.form
1286+
try testProperty(&runner, "input", "form", "null", "null", &.{}, &.{.{ .input = "foo", .is_str = true }});
1287+
try testProperty(&runner, "input", "accept", "", "", &str_valids, &.{});
1288+
try testProperty(&runner, "input", "alt", "", "", &str_valids, &.{});
1289+
try testProperty(&runner, "input", "disabled", "false", "false", &bool_valids, &.{});
1290+
try testProperty(&runner, "input", "maxLength", "-1", "0", &.{.{ .input = "5", .is_str = false }}, &.{.{ .input = "banana", .is_str = true }});
1291+
try testing.expectError(error.ExecutionError, runner.testCases(&.{.{ "elem_input.maxLength = -45", null }}, .{}));
1292+
try testProperty(&runner, "input", "name", "", "", &str_valids, &.{});
1293+
try testProperty(&runner, "input", "readOnly", "false", "false", &bool_valids, &.{});
1294+
try testProperty(&runner, "input", "size", "20", "20", &.{.{ .input = "5", .is_str = false }}, &.{.{ .input = "-26", .is_str = false }});
1295+
try testing.expectError(error.ExecutionError, runner.testCases(&.{.{ "elem_input.size = 0", null }}, .{}));
1296+
try testing.expectError(error.ExecutionError, runner.testCases(&.{.{ "elem_input.size = 'banana'", null }}, .{}));
1297+
try testProperty(&runner, "input", "src", "", "", &.{
1298+
.{ .input = "foo", .is_str = true, .expected = "https://lightpanda.io/foo" }, // TODO stitch should work with spaces -> %20
1299+
.{ .input = "-3", .is_str = false, .expected = "https://lightpanda.io/-3" },
1300+
.{ .input = "", .is_str = true, .expected = "https://lightpanda.io/noslashattheend" },
1301+
}, &.{});
1302+
try testProperty(&runner, "input", "type", "text", "text", &.{.{ .input = "checkbox", .is_str = true }}, &.{.{ .input = "5", .is_str = true }});
1303+
1304+
// Properties that are related
1305+
try runner.testCases(&.{
1306+
.{ "let input_checked = document.createElement('input')", null },
1307+
.{ "input_checked.defaultChecked", "false" },
1308+
.{ "input_checked.checked", "false" },
1309+
1310+
.{ "input_checked.defaultChecked = true", "true" },
1311+
.{ "input_checked.defaultChecked", "true" },
1312+
.{ "input_checked.checked", "true" }, // Also perceived as true
1313+
1314+
.{ "input_checked.checked = false", "false" },
1315+
.{ "input_checked.defaultChecked", "true" },
1316+
.{ "input_checked.checked", "false" },
1317+
1318+
.{ "input_checked.defaultChecked = true", "true" },
1319+
.{ "input_checked.checked", "false" }, // Still false
1320+
}, .{});
1321+
try runner.testCases(&.{
1322+
.{ "let input_value = document.createElement('input')", null },
1323+
.{ "input_value.defaultValue", "" },
1324+
.{ "input_value.value", "" },
1325+
1326+
.{ "input_value.defaultValue = 3.1", "3.1" },
1327+
.{ "input_value.defaultValue", "3.1" },
1328+
.{ "input_value.value", "3.1" }, // Also perceived as 3.1
1329+
1330+
.{ "input_value.value = 'mango'", "mango" },
1331+
.{ "input_value.defaultValue", "3.1" },
1332+
.{ "input_value.value", "mango" },
1333+
1334+
.{ "input_value.defaultValue = true", "true" },
1335+
.{ "input_value.value", "mango" }, // Still mango
1336+
}, .{});
1337+
}
1338+
test "Browser.HTML.Element.propeties.input.form" {
1339+
var runner = try testing.jsRunner(testing.tracking_allocator, .{ .html =
1340+
\\ <form action="test.php" target="_blank">
1341+
\\ <p>
1342+
\\ <label>First name: <input type="text" name="first-name" /></label>
1343+
\\ </p>
1344+
\\ </form>
1345+
});
1346+
defer runner.deinit();
1347+
1348+
try runner.testCases(&.{
1349+
.{ "let elem_input = document.querySelector('input')", null },
1350+
}, .{});
1351+
try testProperty(&runner, "input", "form", "[object HTMLFormElement]", "[object HTMLFormElement]", &.{}, &.{.{ .input = "5", .is_str = false }});
1352+
}
1353+
1354+
const Valid = struct {
1355+
input: []const u8,
1356+
is_str: bool,
1357+
expected: ?[]const u8 = null, // Needed when input != expected
1358+
};
1359+
const Invalid = struct {
1360+
input: []const u8,
1361+
is_str: bool,
1362+
};
1363+
1364+
fn testCreateElement(runner: *testing.JsRunner, comptime name: []const u8) !void {
1365+
try runner.testCases(&.{
1366+
.{ "let elem_" ++ name ++ " = document.createElement('" ++ name ++ "')", null },
1367+
}, .{});
1368+
}
1369+
// TODO reduce comptime
1370+
// Default is the expected value after creation and after setting an invalid value
1371+
// Valid input is expected to return itself or the expected value
1372+
// Invalid input is expected to return the default value
1373+
// .{ "elem.type", "text" }, // default
1374+
// .{ "elem.type = 'checkbox'", "checkbox" }, // valid
1375+
// .{ "elem.type", "checkbox" },
1376+
// .{ "elem.type = '5'", "5" }, // invalid
1377+
// .{ "elem.type", "text" },
1378+
fn testProperty(
1379+
runner: *testing.JsRunner,
1380+
comptime name: []const u8,
1381+
comptime property: []const u8,
1382+
comptime initial: []const u8,
1383+
comptime default: []const u8,
1384+
comptime valids: []const Valid,
1385+
comptime invalids: []const Invalid,
1386+
) !void {
1387+
const elem_dot_prop = "elem_" ++ name ++ "." ++ property;
1388+
1389+
try runner.testCases(&.{
1390+
.{ elem_dot_prop, initial },
1391+
}, .{});
1392+
1393+
inline for (valids) |valid| {
1394+
const set_input = if (valid.is_str) "'" ++ valid.input ++ "'" else valid.input;
1395+
const expected = valid.expected orelse valid.input;
1396+
try runner.testCases(&.{
1397+
.{ elem_dot_prop ++ " = " ++ set_input, null },
1398+
.{ elem_dot_prop, expected },
1399+
}, .{});
1400+
}
1401+
1402+
inline for (invalids) |invalid| {
1403+
const set_input = if (invalid.is_str) "'" ++ invalid.input ++ "'" else invalid.input;
1404+
try runner.testCases(&.{
1405+
.{ elem_dot_prop ++ " = " ++ set_input, null },
1406+
.{ elem_dot_prop, default },
1407+
}, .{});
1408+
}
1409+
}

src/browser/netsurf.zig

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2687,8 +2687,9 @@ pub fn inputGetMaxLength(input: *Input) !i32 {
26872687
try DOMErr(err);
26882688
return max_length;
26892689
}
2690-
pub fn inputSetMaxLength(input: *Input, max_length: u32) !void {
2691-
const err = c.dom_html_input_element_set_max_length(input, max_length);
2690+
pub fn inputSetMaxLength(input: *Input, max_length: i32) !void {
2691+
if (max_length < 0) return error.NegativeValueNotAllowed;
2692+
const err = c.dom_html_input_element_set_max_length(input, @intCast(max_length));
26922693
try DOMErr(err);
26932694
}
26942695

@@ -2720,8 +2721,10 @@ pub fn inputGetSize(input: *Input) !u32 {
27202721
if (size == ulongNegativeOne) return 20; // 20
27212722
return size;
27222723
}
2723-
pub fn inputSetSize(input: *Input, size: u32) !void {
2724-
const err = c.dom_html_input_element_set_size(input, size);
2724+
pub fn inputSetSize(input: *Input, size: i32) !void {
2725+
if (size == 0) return error.ZeroNotAllowed;
2726+
const new_size = if (size < 0) 20 else size;
2727+
const err = c.dom_html_input_element_set_size(input, @intCast(new_size));
27252728
try DOMErr(err);
27262729
}
27272730

@@ -2732,6 +2735,7 @@ pub fn inputGetSrc(input: *Input) ![]const u8 {
27322735
const s = s_ orelse return "";
27332736
return strToData(s);
27342737
}
2738+
// url should already be stitched!
27352739
pub fn inputSetSrc(input: *Input, src: []const u8) !void {
27362740
const err = c.dom_html_input_element_set_src(input, try strFromData(src));
27372741
try DOMErr(err);
@@ -2741,9 +2745,22 @@ pub fn inputGetType(input: *Input) ![]const u8 {
27412745
var s_: ?*String = null;
27422746
const err = c.dom_html_input_element_get_type(input, &s_);
27432747
try DOMErr(err);
2744-
const s = s_ orelse return "";
2748+
const s = s_ orelse return "text";
27452749
return strToData(s);
27462750
}
2751+
pub fn inputSetType(input: *Input, type_: []const u8) !void {
2752+
// @speed sort values by usage frequency/length
2753+
const possible_values = [_][]const u8{ "text", "search", "tel", "url", "email", "password", "date", "month", "week", "time", "datetime-local", "number", "range", "color", "checkbox", "radio", "file", "hidden", "image", "button", "submit", "reset" };
2754+
var found = false;
2755+
for (possible_values) |item| {
2756+
if (std.mem.eql(u8, type_, item)) {
2757+
found = true;
2758+
break;
2759+
}
2760+
}
2761+
const new_type = if (found) type_ else "text";
2762+
try elementSetAttribute(@ptrCast(input), "type", new_type);
2763+
}
27472764

27482765
pub fn inputGetValue(input: *Input) ![]const u8 {
27492766
var s_: ?*String = null;

src/url.zig

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@ pub const URL = struct {
110110
}
111111
return src;
112112
}
113+
if (src.len == 0) {
114+
if (opts.alloc == .always) {
115+
return allocator.dupe(u8, base);
116+
}
117+
}
113118

114119
const protocol_end: usize = blk: {
115120
if (std.mem.indexOf(u8, base, "://")) |protocol_index| {
@@ -256,6 +261,16 @@ test "URL: Stiching src as full path" {
256261
try testing.expectString("https://lightpanda.io/something.js", result);
257262
}
258263

264+
test "URL: Stitching Base & Src URLs (empty src)" {
265+
const allocator = testing.allocator;
266+
267+
const base = "https://www.google.com/xyz/abc/123";
268+
const src = "";
269+
const result = try URL.stitch(allocator, src, base);
270+
defer allocator.free(result);
271+
try testing.expectString("https://www.google.com/xyz/abc/123", result);
272+
}
273+
259274
test "URL: concatQueryString" {
260275
defer testing.reset();
261276
const arena = testing.arena_allocator;

vendor/netsurf/libdom

0 commit comments

Comments
 (0)