Skip to content

Commit 0e0f577

Browse files
committed
input prop testing
1 parent ea9ede1 commit 0e0f577

File tree

4 files changed

+187
-11
lines changed

4 files changed

+187
-11
lines changed

src/browser/html/elements.zig

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

24+
const urlStitch = @import("../../url.zig").URL.stitch;
2425
const URL = @import("../url/url.zig").URL;
2526
const Node = @import("../dom/node.zig").Node;
2627
const Element = @import("../dom/element.zig").Element;
@@ -630,7 +631,7 @@ pub const HTMLInputElement = struct {
630631
pub fn set_defaultChecked(self: *parser.Input, default_checked: bool) !void {
631632
try parser.inputSetDefaultChecked(self, default_checked);
632633
}
633-
pub fn get_from(self: *parser.Input) !?*parser.Form {
634+
pub fn get_form(self: *parser.Input) !?*parser.Form {
634635
return try parser.inputGetForm(self);
635636
}
636637
pub fn get_accept(self: *parser.Input) ![]const u8 {
@@ -660,7 +661,7 @@ pub const HTMLInputElement = struct {
660661
pub fn get_maxLength(self: *parser.Input) !i32 {
661662
return try parser.inputGetMaxLength(self);
662663
}
663-
pub fn set_maxLength(self: *parser.Input, max_length: u32) !void {
664+
pub fn set_maxLength(self: *parser.Input, max_length: i32) !void {
664665
try parser.inputSetMaxLength(self, max_length);
665666
}
666667
pub fn get_name(self: *parser.Input) ![]const u8 {
@@ -678,18 +679,22 @@ pub const HTMLInputElement = struct {
678679
pub fn get_size(self: *parser.Input) !u32 {
679680
return try parser.inputGetSize(self);
680681
}
681-
pub fn set_size(self: *parser.Input, size: u32) !void {
682+
pub fn set_size(self: *parser.Input, size: i32) !void {
682683
try parser.inputSetSize(self, size);
683684
}
684685
pub fn get_src(self: *parser.Input) ![]const u8 {
685686
return try parser.inputGetSrc(self);
686687
}
687-
pub fn set_src(self: *parser.Input, src: []const u8) !void {
688-
try parser.inputSetSrc(self, src);
688+
pub fn set_src(self: *parser.Input, src: []const u8, page: *Page) !void {
689+
const new_src = try urlStitch(page.call_arena, src, page.url.raw);
690+
try parser.inputSetSrc(self, new_src);
689691
}
690692
pub fn get_type(self: *parser.Input) ![]const u8 {
691693
return try parser.inputGetType(self);
692694
}
695+
pub fn set_type(self: *parser.Input, type_: []const u8) !void {
696+
try parser.inputSetType(self, type_);
697+
}
693698
pub fn get_value(self: *parser.Input) ![]const u8 {
694699
return try parser.inputGetValue(self);
695700
}
@@ -1223,3 +1228,144 @@ test "Browser.HTML.Element" {
12231228
.{ "lyric.src", "15" },
12241229
}, .{});
12251230
}
1231+
test "Browser.HTML.Element.propeties" {
1232+
var runner = try testing.jsRunner(testing.tracking_allocator, .{ .url = "https://lightpanda.io/noslashattheend" });
1233+
defer runner.deinit();
1234+
const bool_valids = [_]Valid{
1235+
.{ .input = "true", .is_str = false },
1236+
.{ .input = "", .is_str = true, .expected = "false" },
1237+
.{ .input = "13.5", .is_str = true, .expected = "true" },
1238+
};
1239+
const str_valids = [_]Valid{
1240+
.{ .input = "foo", .is_str = true },
1241+
.{ .input = "5", .is_str = false, .expected = "5" },
1242+
.{ .input = "", .is_str = true },
1243+
.{ .input = "document", .is_str = false, .expected = "[object HTMLDocument]" },
1244+
};
1245+
// TODO these tests are mostly just data should we store them in Sqlite or so?
1246+
try testCreateElement(&runner, "input");
1247+
// Valid input.form is tested separately :Browser.HTML.Element.propeties.input.form
1248+
try testProperty(&runner, "input", "form", "null", "null", &.{}, &.{.{ .input = "foo", .is_str = true }});
1249+
try testProperty(&runner, "input", "accept", "", "", &str_valids, &.{});
1250+
try testProperty(&runner, "input", "alt", "", "", &str_valids, &.{});
1251+
try testProperty(&runner, "input", "disabled", "false", "false", &bool_valids, &.{});
1252+
try testProperty(&runner, "input", "maxLength", "-1", "0", &.{.{ .input = "5", .is_str = false }}, &.{.{ .input = "banana", .is_str = true }});
1253+
try testing.expectError(error.ExecutionError, runner.testCases(&.{.{ "elem_input.maxLength = -45", null }}, .{}));
1254+
try testProperty(&runner, "input", "name", "", "", &str_valids, &.{});
1255+
try testProperty(&runner, "input", "readOnly", "false", "false", &bool_valids, &.{});
1256+
try testProperty(&runner, "input", "size", "20", "20", &.{.{ .input = "5", .is_str = false }}, &.{.{ .input = "-26", .is_str = false }});
1257+
try testing.expectError(error.ExecutionError, runner.testCases(&.{.{ "elem_input.size = 0", null }}, .{}));
1258+
try testing.expectError(error.ExecutionError, runner.testCases(&.{.{ "elem_input.size = 'banana'", null }}, .{}));
1259+
try testProperty(&runner, "input", "src", "", "", &.{
1260+
.{ .input = "foo", .is_str = true, .expected = "https://lightpanda.io/foo" }, // TODO stitch should work with spaces -> %20
1261+
.{ .input = "-3", .is_str = false, .expected = "https://lightpanda.io/-3" },
1262+
.{ .input = "", .is_str = true, .expected = "https://lightpanda.io/noslashattheend" },
1263+
}, &.{});
1264+
try testProperty(&runner, "input", "type", "text", "text", &.{.{ .input = "checkbox", .is_str = true }}, &.{.{ .input = "5", .is_str = true }});
1265+
1266+
// Properties that are related
1267+
try runner.testCases(&.{
1268+
.{ "let input_checked = document.createElement('input')", null },
1269+
.{ "input_checked.defaultChecked", "false" },
1270+
.{ "input_checked.checked", "false" },
1271+
1272+
.{ "input_checked.defaultChecked = true", "true" },
1273+
.{ "input_checked.defaultChecked", "true" },
1274+
.{ "input_checked.checked", "true" }, // Also perceived as true
1275+
1276+
.{ "input_checked.checked = false", "false" },
1277+
.{ "input_checked.defaultChecked", "true" },
1278+
.{ "input_checked.checked", "false" },
1279+
1280+
.{ "input_checked.defaultChecked = true", "true" },
1281+
.{ "input_checked.checked", "false" }, // Still false
1282+
}, .{});
1283+
try runner.testCases(&.{
1284+
.{ "let input_value = document.createElement('input')", null },
1285+
.{ "input_value.defaultValue", "" },
1286+
.{ "input_value.value", "" },
1287+
1288+
.{ "input_value.defaultValue = 3.1", "3.1" },
1289+
.{ "input_value.defaultValue", "3.1" },
1290+
.{ "input_value.value", "3.1" }, // Also perceived as 3.1
1291+
1292+
.{ "input_value.value = 'mango'", "mango" },
1293+
.{ "input_value.defaultValue", "3.1" },
1294+
.{ "input_value.value", "mango" },
1295+
1296+
.{ "input_value.defaultValue = true", "true" },
1297+
.{ "input_value.value", "mango" }, // Still mango
1298+
}, .{});
1299+
}
1300+
test "Browser.HTML.Element.propeties.input.form" {
1301+
var runner = try testing.jsRunner(testing.tracking_allocator, .{ .html =
1302+
\\ <form action="test.php" target="_blank">
1303+
\\ <p>
1304+
\\ <label>First name: <input type="text" name="first-name" /></label>
1305+
\\ </p>
1306+
\\ </form>
1307+
});
1308+
defer runner.deinit();
1309+
1310+
try runner.testCases(&.{
1311+
.{ "let elem_input = document.querySelector('input')", null },
1312+
}, .{});
1313+
try testProperty(&runner, "input", "form", "[object HTMLFormElement]", "[object HTMLFormElement]", &.{}, &.{.{ .input = "5", .is_str = false }});
1314+
}
1315+
1316+
const Valid = struct {
1317+
input: []const u8,
1318+
is_str: bool,
1319+
expected: ?[]const u8 = null, // Needed when input != expected
1320+
};
1321+
const Invalid = struct {
1322+
input: []const u8,
1323+
is_str: bool,
1324+
};
1325+
1326+
fn testCreateElement(runner: *testing.JsRunner, comptime name: []const u8) !void {
1327+
try runner.testCases(&.{
1328+
.{ "let elem_" ++ name ++ " = document.createElement('" ++ name ++ "')", null },
1329+
}, .{});
1330+
}
1331+
// TODO reduce comptime
1332+
// Default is the expected value after creation and after setting an invalid value
1333+
// Valid input is expected to return itself or the expected value
1334+
// Invalid input is expected to return the default value
1335+
// .{ "elem.type", "text" }, // default
1336+
// .{ "elem.type = 'checkbox'", "checkbox" }, // valid
1337+
// .{ "elem.type", "checkbox" },
1338+
// .{ "elem.type = '5'", "5" }, // invalid
1339+
// .{ "elem.type", "text" },
1340+
fn testProperty(
1341+
runner: *testing.JsRunner,
1342+
comptime name: []const u8,
1343+
comptime property: []const u8,
1344+
comptime initial: []const u8,
1345+
comptime default: []const u8,
1346+
comptime valids: []const Valid,
1347+
comptime invalids: []const Invalid,
1348+
) !void {
1349+
const elem_dot_prop = "elem_" ++ name ++ "." ++ property;
1350+
1351+
try runner.testCases(&.{
1352+
.{ elem_dot_prop, initial },
1353+
}, .{});
1354+
1355+
inline for (valids) |valid| {
1356+
const set_input = if (valid.is_str) "'" ++ valid.input ++ "'" else valid.input;
1357+
const expected = valid.expected orelse valid.input;
1358+
try runner.testCases(&.{
1359+
.{ elem_dot_prop ++ " = " ++ set_input, null },
1360+
.{ elem_dot_prop, expected },
1361+
}, .{});
1362+
}
1363+
1364+
inline for (invalids) |invalid| {
1365+
const set_input = if (invalid.is_str) "'" ++ invalid.input ++ "'" else invalid.input;
1366+
try runner.testCases(&.{
1367+
.{ elem_dot_prop ++ " = " ++ set_input, null },
1368+
.{ elem_dot_prop, default },
1369+
}, .{});
1370+
}
1371+
}

src/browser/netsurf.zig

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2679,8 +2679,9 @@ pub fn inputGetMaxLength(input: *Input) !i32 {
26792679
try DOMErr(err);
26802680
return max_length;
26812681
}
2682-
pub fn inputSetMaxLength(input: *Input, max_length: u32) !void {
2683-
const err = c.dom_html_input_element_set_max_length(input, max_length);
2682+
pub fn inputSetMaxLength(input: *Input, max_length: i32) !void {
2683+
if (max_length < 0) return error.NegativeValueNotAllowed;
2684+
const err = c.dom_html_input_element_set_max_length(input, @intCast(max_length));
26842685
try DOMErr(err);
26852686
}
26862687

@@ -2712,8 +2713,10 @@ pub fn inputGetSize(input: *Input) !u32 {
27122713
if (size == ulongNegativeOne) return 20; // 20
27132714
return size;
27142715
}
2715-
pub fn inputSetSize(input: *Input, size: u32) !void {
2716-
const err = c.dom_html_input_element_set_size(input, size);
2716+
pub fn inputSetSize(input: *Input, size: i32) !void {
2717+
if (size == 0) return error.ZeroNotAllowed;
2718+
const new_size = if (size < 0) 20 else size;
2719+
const err = c.dom_html_input_element_set_size(input, @intCast(new_size));
27172720
try DOMErr(err);
27182721
}
27192722

@@ -2724,6 +2727,7 @@ pub fn inputGetSrc(input: *Input) ![]const u8 {
27242727
const s = s_ orelse return "";
27252728
return strToData(s);
27262729
}
2730+
// url should already be stitched!
27272731
pub fn inputSetSrc(input: *Input, src: []const u8) !void {
27282732
const err = c.dom_html_input_element_set_src(input, try strFromData(src));
27292733
try DOMErr(err);
@@ -2733,9 +2737,22 @@ pub fn inputGetType(input: *Input) ![]const u8 {
27332737
var s_: ?*String = null;
27342738
const err = c.dom_html_input_element_get_type(input, &s_);
27352739
try DOMErr(err);
2736-
const s = s_ orelse return "";
2740+
const s = s_ orelse return "text";
27372741
return strToData(s);
27382742
}
2743+
pub fn inputSetType(input: *Input, type_: []const u8) !void {
2744+
// @speed sort values by usage frequency/length
2745+
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" };
2746+
var found = false;
2747+
for (possible_values) |item| {
2748+
if (std.mem.eql(u8, type_, item)) {
2749+
found = true;
2750+
break;
2751+
}
2752+
}
2753+
const new_type = if (found) type_ else "text";
2754+
try elementSetAttribute(@ptrCast(input), "type", new_type);
2755+
}
27392756

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

src/url.zig

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ pub const URL = struct {
9191
if (base.len == 0) {
9292
return src;
9393
}
94+
if (src.len == 0) {
95+
return base;
96+
}
9497

9598
const protocol_end: usize = blk: {
9699
if (std.mem.indexOf(u8, base, "://")) |protocol_index| {
@@ -170,3 +173,13 @@ test "URL: Stiching Base & Src URLs (Both Local)" {
170173
defer allocator.free(result);
171174
try testing.expectString("./abcdef/something.js", result);
172175
}
176+
177+
test "URL: Stitching Base & Src URLs (empty src)" {
178+
const allocator = testing.allocator;
179+
180+
const base = "https://www.google.com/xyz/abc/123";
181+
const src = "";
182+
const result = try URL.stitch(allocator, src, base);
183+
// defer allocator.free(result);
184+
try testing.expectString("https://www.google.com/xyz/abc/123", result);
185+
}

0 commit comments

Comments
 (0)