mirror of
				https://github.com/lightpanda-io/browser.git
				synced 2025-10-28 14:43:28 +00:00 
			
		
		
		
	input prop testing
This commit is contained in:
		| @@ -22,6 +22,7 @@ const generate = @import("../../runtime/generate.zig"); | ||||
| const Env = @import("../env.zig").Env; | ||||
| const Page = @import("../page.zig").Page; | ||||
|  | ||||
| const urlStitch = @import("../../url.zig").URL.stitch; | ||||
| const URL = @import("../url/url.zig").URL; | ||||
| const Node = @import("../dom/node.zig").Node; | ||||
| const Element = @import("../dom/element.zig").Element; | ||||
| @@ -639,7 +640,7 @@ pub const HTMLInputElement = struct { | ||||
|     pub fn set_defaultChecked(self: *parser.Input, default_checked: bool) !void { | ||||
|         try parser.inputSetDefaultChecked(self, default_checked); | ||||
|     } | ||||
|     pub fn get_from(self: *parser.Input) !?*parser.Form { | ||||
|     pub fn get_form(self: *parser.Input) !?*parser.Form { | ||||
|         return try parser.inputGetForm(self); | ||||
|     } | ||||
|     pub fn get_accept(self: *parser.Input) ![]const u8 { | ||||
| @@ -669,7 +670,7 @@ pub const HTMLInputElement = struct { | ||||
|     pub fn get_maxLength(self: *parser.Input) !i32 { | ||||
|         return try parser.inputGetMaxLength(self); | ||||
|     } | ||||
|     pub fn set_maxLength(self: *parser.Input, max_length: u32) !void { | ||||
|     pub fn set_maxLength(self: *parser.Input, max_length: i32) !void { | ||||
|         try parser.inputSetMaxLength(self, max_length); | ||||
|     } | ||||
|     pub fn get_name(self: *parser.Input) ![]const u8 { | ||||
| @@ -687,18 +688,22 @@ pub const HTMLInputElement = struct { | ||||
|     pub fn get_size(self: *parser.Input) !u32 { | ||||
|         return try parser.inputGetSize(self); | ||||
|     } | ||||
|     pub fn set_size(self: *parser.Input, size: u32) !void { | ||||
|     pub fn set_size(self: *parser.Input, size: i32) !void { | ||||
|         try parser.inputSetSize(self, size); | ||||
|     } | ||||
|     pub fn get_src(self: *parser.Input) ![]const u8 { | ||||
|         return try parser.inputGetSrc(self); | ||||
|     } | ||||
|     pub fn set_src(self: *parser.Input, src: []const u8) !void { | ||||
|         try parser.inputSetSrc(self, src); | ||||
|     pub fn set_src(self: *parser.Input, src: []const u8, page: *Page) !void { | ||||
|         const new_src = try urlStitch(page.call_arena, src, page.url.raw); | ||||
|         try parser.inputSetSrc(self, new_src); | ||||
|     } | ||||
|     pub fn get_type(self: *parser.Input) ![]const u8 { | ||||
|         return try parser.inputGetType(self); | ||||
|     } | ||||
|     pub fn set_type(self: *parser.Input, type_: []const u8) !void { | ||||
|         try parser.inputSetType(self, type_); | ||||
|     } | ||||
|     pub fn get_value(self: *parser.Input) ![]const u8 { | ||||
|         return try parser.inputGetValue(self); | ||||
|     } | ||||
| @@ -1261,3 +1266,144 @@ test "Browser.HTML.Element" { | ||||
|         .{ "a.href", "https://lightpanda.io/opensource-browser/about" }, | ||||
|     }, .{}); | ||||
| } | ||||
| test "Browser.HTML.Element.propeties" { | ||||
|     var runner = try testing.jsRunner(testing.tracking_allocator, .{ .url = "https://lightpanda.io/noslashattheend" }); | ||||
|     defer runner.deinit(); | ||||
|     const bool_valids = [_]Valid{ | ||||
|         .{ .input = "true", .is_str = false }, | ||||
|         .{ .input = "", .is_str = true, .expected = "false" }, | ||||
|         .{ .input = "13.5", .is_str = true, .expected = "true" }, | ||||
|     }; | ||||
|     const str_valids = [_]Valid{ | ||||
|         .{ .input = "foo", .is_str = true }, | ||||
|         .{ .input = "5", .is_str = false, .expected = "5" }, | ||||
|         .{ .input = "", .is_str = true }, | ||||
|         .{ .input = "document", .is_str = false, .expected = "[object HTMLDocument]" }, | ||||
|     }; | ||||
|     // TODO these tests are mostly just data should we store them in Sqlite or so? | ||||
|     try testCreateElement(&runner, "input"); | ||||
|     // Valid input.form is tested separately :Browser.HTML.Element.propeties.input.form | ||||
|     try testProperty(&runner, "input", "form", "null", "null", &.{}, &.{.{ .input = "foo", .is_str = true }}); | ||||
|     try testProperty(&runner, "input", "accept", "", "", &str_valids, &.{}); | ||||
|     try testProperty(&runner, "input", "alt", "", "", &str_valids, &.{}); | ||||
|     try testProperty(&runner, "input", "disabled", "false", "false", &bool_valids, &.{}); | ||||
|     try testProperty(&runner, "input", "maxLength", "-1", "0", &.{.{ .input = "5", .is_str = false }}, &.{.{ .input = "banana", .is_str = true }}); | ||||
|     try testing.expectError(error.ExecutionError, runner.testCases(&.{.{ "elem_input.maxLength = -45", null }}, .{})); | ||||
|     try testProperty(&runner, "input", "name", "", "", &str_valids, &.{}); | ||||
|     try testProperty(&runner, "input", "readOnly", "false", "false", &bool_valids, &.{}); | ||||
|     try testProperty(&runner, "input", "size", "20", "20", &.{.{ .input = "5", .is_str = false }}, &.{.{ .input = "-26", .is_str = false }}); | ||||
|     try testing.expectError(error.ExecutionError, runner.testCases(&.{.{ "elem_input.size = 0", null }}, .{})); | ||||
|     try testing.expectError(error.ExecutionError, runner.testCases(&.{.{ "elem_input.size = 'banana'", null }}, .{})); | ||||
|     try testProperty(&runner, "input", "src", "", "", &.{ | ||||
|         .{ .input = "foo", .is_str = true, .expected = "https://lightpanda.io/foo" }, // TODO stitch should work with spaces -> %20 | ||||
|         .{ .input = "-3", .is_str = false, .expected = "https://lightpanda.io/-3" }, | ||||
|         .{ .input = "", .is_str = true, .expected = "https://lightpanda.io/noslashattheend" }, | ||||
|     }, &.{}); | ||||
|     try testProperty(&runner, "input", "type", "text", "text", &.{.{ .input = "checkbox", .is_str = true }}, &.{.{ .input = "5", .is_str = true }}); | ||||
|  | ||||
|     // Properties that are related | ||||
|     try runner.testCases(&.{ | ||||
|         .{ "let input_checked = document.createElement('input')", null }, | ||||
|         .{ "input_checked.defaultChecked", "false" }, | ||||
|         .{ "input_checked.checked", "false" }, | ||||
|  | ||||
|         .{ "input_checked.defaultChecked = true", "true" }, | ||||
|         .{ "input_checked.defaultChecked", "true" }, | ||||
|         .{ "input_checked.checked", "true" }, // Also perceived as true | ||||
|  | ||||
|         .{ "input_checked.checked = false", "false" }, | ||||
|         .{ "input_checked.defaultChecked", "true" }, | ||||
|         .{ "input_checked.checked", "false" }, | ||||
|  | ||||
|         .{ "input_checked.defaultChecked = true", "true" }, | ||||
|         .{ "input_checked.checked", "false" }, // Still false | ||||
|     }, .{}); | ||||
|     try runner.testCases(&.{ | ||||
|         .{ "let input_value = document.createElement('input')", null }, | ||||
|         .{ "input_value.defaultValue", "" }, | ||||
|         .{ "input_value.value", "" }, | ||||
|  | ||||
|         .{ "input_value.defaultValue = 3.1", "3.1" }, | ||||
|         .{ "input_value.defaultValue", "3.1" }, | ||||
|         .{ "input_value.value", "3.1" }, // Also perceived as 3.1 | ||||
|  | ||||
|         .{ "input_value.value = 'mango'", "mango" }, | ||||
|         .{ "input_value.defaultValue", "3.1" }, | ||||
|         .{ "input_value.value", "mango" }, | ||||
|  | ||||
|         .{ "input_value.defaultValue = true", "true" }, | ||||
|         .{ "input_value.value", "mango" }, // Still mango | ||||
|     }, .{}); | ||||
| } | ||||
| test "Browser.HTML.Element.propeties.input.form" { | ||||
|     var runner = try testing.jsRunner(testing.tracking_allocator, .{ .html =  | ||||
|         \\ <form action="test.php" target="_blank"> | ||||
|         \\   <p> | ||||
|         \\     <label>First name: <input type="text" name="first-name" /></label> | ||||
|         \\   </p> | ||||
|         \\ </form> | ||||
|     }); | ||||
|     defer runner.deinit(); | ||||
|  | ||||
|     try runner.testCases(&.{ | ||||
|         .{ "let elem_input = document.querySelector('input')", null }, | ||||
|     }, .{}); | ||||
|     try testProperty(&runner, "input", "form", "[object HTMLFormElement]", "[object HTMLFormElement]", &.{}, &.{.{ .input = "5", .is_str = false }}); | ||||
| } | ||||
|  | ||||
| const Valid = struct { | ||||
|     input: []const u8, | ||||
|     is_str: bool, | ||||
|     expected: ?[]const u8 = null, // Needed when input != expected | ||||
| }; | ||||
| const Invalid = struct { | ||||
|     input: []const u8, | ||||
|     is_str: bool, | ||||
| }; | ||||
|  | ||||
| fn testCreateElement(runner: *testing.JsRunner, comptime name: []const u8) !void { | ||||
|     try runner.testCases(&.{ | ||||
|         .{ "let elem_" ++ name ++ " = document.createElement('" ++ name ++ "')", null }, | ||||
|     }, .{}); | ||||
| } | ||||
| // TODO reduce comptime | ||||
| // Default is the expected value after creation and after setting an invalid value | ||||
| // Valid input is expected to return itself or the expected value | ||||
| // Invalid input is expected to return the default value | ||||
| // .{ "elem.type", "text" },                  // default | ||||
| // .{ "elem.type = 'checkbox'", "checkbox" }, // valid | ||||
| // .{ "elem.type", "checkbox" }, | ||||
| // .{ "elem.type = '5'", "5" },               // invalid | ||||
| // .{ "elem.type", "text" }, | ||||
| fn testProperty( | ||||
|     runner: *testing.JsRunner, | ||||
|     comptime name: []const u8, | ||||
|     comptime property: []const u8, | ||||
|     comptime initial: []const u8, | ||||
|     comptime default: []const u8, | ||||
|     comptime valids: []const Valid, | ||||
|     comptime invalids: []const Invalid, | ||||
| ) !void { | ||||
|     const elem_dot_prop = "elem_" ++ name ++ "." ++ property; | ||||
|  | ||||
|     try runner.testCases(&.{ | ||||
|         .{ elem_dot_prop, initial }, | ||||
|     }, .{}); | ||||
|  | ||||
|     inline for (valids) |valid| { | ||||
|         const set_input = if (valid.is_str) "'" ++ valid.input ++ "'" else valid.input; | ||||
|         const expected = valid.expected orelse valid.input; | ||||
|         try runner.testCases(&.{ | ||||
|             .{ elem_dot_prop ++ " = " ++ set_input, null }, | ||||
|             .{ elem_dot_prop, expected }, | ||||
|         }, .{}); | ||||
|     } | ||||
|  | ||||
|     inline for (invalids) |invalid| { | ||||
|         const set_input = if (invalid.is_str) "'" ++ invalid.input ++ "'" else invalid.input; | ||||
|         try runner.testCases(&.{ | ||||
|             .{ elem_dot_prop ++ " = " ++ set_input, null }, | ||||
|             .{ elem_dot_prop, default }, | ||||
|         }, .{}); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -2687,8 +2687,9 @@ pub fn inputGetMaxLength(input: *Input) !i32 { | ||||
|     try DOMErr(err); | ||||
|     return max_length; | ||||
| } | ||||
| pub fn inputSetMaxLength(input: *Input, max_length: u32) !void { | ||||
|     const err = c.dom_html_input_element_set_max_length(input, max_length); | ||||
| pub fn inputSetMaxLength(input: *Input, max_length: i32) !void { | ||||
|     if (max_length < 0) return error.NegativeValueNotAllowed; | ||||
|     const err = c.dom_html_input_element_set_max_length(input, @intCast(max_length)); | ||||
|     try DOMErr(err); | ||||
| } | ||||
|  | ||||
| @@ -2720,8 +2721,10 @@ pub fn inputGetSize(input: *Input) !u32 { | ||||
|     if (size == ulongNegativeOne) return 20; // 20 | ||||
|     return size; | ||||
| } | ||||
| pub fn inputSetSize(input: *Input, size: u32) !void { | ||||
|     const err = c.dom_html_input_element_set_size(input, size); | ||||
| pub fn inputSetSize(input: *Input, size: i32) !void { | ||||
|     if (size == 0) return error.ZeroNotAllowed; | ||||
|     const new_size = if (size < 0) 20 else size; | ||||
|     const err = c.dom_html_input_element_set_size(input, @intCast(new_size)); | ||||
|     try DOMErr(err); | ||||
| } | ||||
|  | ||||
| @@ -2732,6 +2735,7 @@ pub fn inputGetSrc(input: *Input) ![]const u8 { | ||||
|     const s = s_ orelse return ""; | ||||
|     return strToData(s); | ||||
| } | ||||
| // url should already be stitched! | ||||
| pub fn inputSetSrc(input: *Input, src: []const u8) !void { | ||||
|     const err = c.dom_html_input_element_set_src(input, try strFromData(src)); | ||||
|     try DOMErr(err); | ||||
| @@ -2741,9 +2745,22 @@ pub fn inputGetType(input: *Input) ![]const u8 { | ||||
|     var s_: ?*String = null; | ||||
|     const err = c.dom_html_input_element_get_type(input, &s_); | ||||
|     try DOMErr(err); | ||||
|     const s = s_ orelse return ""; | ||||
|     const s = s_ orelse return "text"; | ||||
|     return strToData(s); | ||||
| } | ||||
| pub fn inputSetType(input: *Input, type_: []const u8) !void { | ||||
|     // @speed sort values by usage frequency/length | ||||
|     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" }; | ||||
|     var found = false; | ||||
|     for (possible_values) |item| { | ||||
|         if (std.mem.eql(u8, type_, item)) { | ||||
|             found = true; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     const new_type = if (found) type_ else "text"; | ||||
|     try elementSetAttribute(@ptrCast(input), "type", new_type); | ||||
| } | ||||
|  | ||||
| pub fn inputGetValue(input: *Input) ![]const u8 { | ||||
|     var s_: ?*String = null; | ||||
|   | ||||
							
								
								
									
										15
									
								
								src/url.zig
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								src/url.zig
									
									
									
									
									
								
							| @@ -110,6 +110,11 @@ pub const URL = struct { | ||||
|             } | ||||
|             return src; | ||||
|         } | ||||
|         if (src.len == 0) { | ||||
|             if (opts.alloc == .always) { | ||||
|                 return allocator.dupe(u8, base); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         const protocol_end: usize = blk: { | ||||
|             if (std.mem.indexOf(u8, base, "://")) |protocol_index| { | ||||
| @@ -256,6 +261,16 @@ test "URL: Stiching src as full path" { | ||||
|     try testing.expectString("https://lightpanda.io/something.js", result); | ||||
| } | ||||
|  | ||||
| test "URL: Stitching Base & Src URLs (empty src)" { | ||||
|     const allocator = testing.allocator; | ||||
|  | ||||
|     const base = "https://www.google.com/xyz/abc/123"; | ||||
|     const src = ""; | ||||
|     const result = try URL.stitch(allocator, src, base); | ||||
|     defer allocator.free(result); | ||||
|     try testing.expectString("https://www.google.com/xyz/abc/123", result); | ||||
| } | ||||
|  | ||||
| test "URL: concatQueryString" { | ||||
|     defer testing.reset(); | ||||
|     const arena = testing.arena_allocator; | ||||
|   | ||||
							
								
								
									
										2
									
								
								vendor/netsurf/libdom
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
							
						
						
									
										2
									
								
								vendor/netsurf/libdom
									
									
									
									
										vendored
									
									
								
							 Submodule vendor/netsurf/libdom updated: f8fc21702b...9a1d1e41d1
									
								
							
		Reference in New Issue
	
	Block a user
	 sjorsdonkers
					sjorsdonkers