Skip to content

Commit 234e7af

Browse files
authored
Merge pull request #721 from lightpanda-io/HTMLInputElement-properties
Input element properties
2 parents d95a18b + ceb9453 commit 234e7af

File tree

4 files changed

+418
-11
lines changed

4 files changed

+418
-11
lines changed

src/browser/html/elements.zig

Lines changed: 221 additions & 2 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;
@@ -216,8 +217,7 @@ pub const HTMLAnchorElement = struct {
216217
}
217218

218219
pub fn set_href(self: *parser.Anchor, href: []const u8, page: *const Page) !void {
219-
const stitch = @import("../../url.zig").stitch;
220-
const full = try stitch(page.call_arena, href, page.url.raw, .{});
220+
const full = try urlStitch(page.call_arena, href, page.url.raw, .{});
221221
return try parser.anchorSetHref(self, full);
222222
}
223223

@@ -647,6 +647,89 @@ pub const HTMLInputElement = struct {
647647
pub const Self = parser.Input;
648648
pub const prototype = *HTMLElement;
649649
pub const subtype = .node;
650+
651+
pub fn get_defaultValue(self: *parser.Input) ![]const u8 {
652+
return try parser.inputGetDefaultValue(self);
653+
}
654+
pub fn set_defaultValue(self: *parser.Input, default_value: []const u8) !void {
655+
try parser.inputSetDefaultValue(self, default_value);
656+
}
657+
pub fn get_defaultChecked(self: *parser.Input) !bool {
658+
return try parser.inputGetDefaultChecked(self);
659+
}
660+
pub fn set_defaultChecked(self: *parser.Input, default_checked: bool) !void {
661+
try parser.inputSetDefaultChecked(self, default_checked);
662+
}
663+
pub fn get_form(self: *parser.Input) !?*parser.Form {
664+
return try parser.inputGetForm(self);
665+
}
666+
pub fn get_accept(self: *parser.Input) ![]const u8 {
667+
return try parser.inputGetAccept(self);
668+
}
669+
pub fn set_accept(self: *parser.Input, accept: []const u8) !void {
670+
try parser.inputSetAccept(self, accept);
671+
}
672+
pub fn get_alt(self: *parser.Input) ![]const u8 {
673+
return try parser.inputGetAlt(self);
674+
}
675+
pub fn set_alt(self: *parser.Input, alt: []const u8) !void {
676+
try parser.inputSetAlt(self, alt);
677+
}
678+
pub fn get_checked(self: *parser.Input) !bool {
679+
return try parser.inputGetChecked(self);
680+
}
681+
pub fn set_checked(self: *parser.Input, checked: bool) !void {
682+
try parser.inputSetChecked(self, checked);
683+
}
684+
pub fn get_disabled(self: *parser.Input) !bool {
685+
return try parser.inputGetDisabled(self);
686+
}
687+
pub fn set_disabled(self: *parser.Input, disabled: bool) !void {
688+
try parser.inputSetDisabled(self, disabled);
689+
}
690+
pub fn get_maxLength(self: *parser.Input) !i32 {
691+
return try parser.inputGetMaxLength(self);
692+
}
693+
pub fn set_maxLength(self: *parser.Input, max_length: i32) !void {
694+
try parser.inputSetMaxLength(self, max_length);
695+
}
696+
pub fn get_name(self: *parser.Input) ![]const u8 {
697+
return try parser.inputGetName(self);
698+
}
699+
pub fn set_name(self: *parser.Input, name: []const u8) !void {
700+
try parser.inputSetName(self, name);
701+
}
702+
pub fn get_readOnly(self: *parser.Input) !bool {
703+
return try parser.inputGetReadOnly(self);
704+
}
705+
pub fn set_readOnly(self: *parser.Input, read_only: bool) !void {
706+
try parser.inputSetReadOnly(self, read_only);
707+
}
708+
pub fn get_size(self: *parser.Input) !u32 {
709+
return try parser.inputGetSize(self);
710+
}
711+
pub fn set_size(self: *parser.Input, size: i32) !void {
712+
try parser.inputSetSize(self, size);
713+
}
714+
pub fn get_src(self: *parser.Input) ![]const u8 {
715+
return try parser.inputGetSrc(self);
716+
}
717+
pub fn set_src(self: *parser.Input, src: []const u8, page: *Page) !void {
718+
const new_src = try urlStitch(page.call_arena, src, page.url.raw, .{ .alloc = .if_needed });
719+
try parser.inputSetSrc(self, new_src);
720+
}
721+
pub fn get_type(self: *parser.Input) ![]const u8 {
722+
return try parser.inputGetType(self);
723+
}
724+
pub fn set_type(self: *parser.Input, type_: []const u8) !void {
725+
try parser.inputSetType(self, type_);
726+
}
727+
pub fn get_value(self: *parser.Input) ![]const u8 {
728+
return try parser.inputGetValue(self);
729+
}
730+
pub fn set_value(self: *parser.Input, value: []const u8) !void {
731+
try parser.inputSetValue(self, value);
732+
}
650733
};
651734

652735
pub const HTMLLIElement = struct {
@@ -1210,3 +1293,139 @@ test "Browser.HTML.Element" {
12101293
.{ "document.activeElement === focused", "true" },
12111294
}, .{});
12121295
}
1296+
test "Browser.HTML.HtmlInputElement.propeties" {
1297+
var runner = try testing.jsRunner(testing.tracking_allocator, .{ .url = "https://lightpanda.io/noslashattheend" });
1298+
defer runner.deinit();
1299+
var alloc = std.heap.ArenaAllocator.init(runner.app.allocator);
1300+
defer alloc.deinit();
1301+
const arena = alloc.allocator();
1302+
1303+
try runner.testCases(&.{.{ "let elem_input = document.createElement('input')", null }}, .{});
1304+
1305+
try runner.testCases(&.{.{ "elem_input.form", "null" }}, .{}); // Initial value
1306+
// Valid input.form is tested separately :Browser.HTML.HtmlInputElement.propeties.form
1307+
try testProperty(arena, &runner, "elem_input.form", "null", &.{.{ .input = "'foo'" }}); // Invalid
1308+
1309+
try runner.testCases(&.{.{ "elem_input.accept", "" }}, .{}); // Initial value
1310+
try testProperty(arena, &runner, "elem_input.accept", null, &str_valids); // Valid
1311+
1312+
try runner.testCases(&.{.{ "elem_input.alt", "" }}, .{}); // Initial value
1313+
try testProperty(arena, &runner, "elem_input.alt", null, &str_valids); // Valid
1314+
1315+
try runner.testCases(&.{.{ "elem_input.disabled", "false" }}, .{}); // Initial value
1316+
try testProperty(arena, &runner, "elem_input.disabled", null, &bool_valids); // Valid
1317+
1318+
try runner.testCases(&.{.{ "elem_input.maxLength", "-1" }}, .{}); // Initial value
1319+
try testProperty(arena, &runner, "elem_input.maxLength", null, &.{.{ .input = "5" }}); // Valid
1320+
try testProperty(arena, &runner, "elem_input.maxLength", "0", &.{.{ .input = "'banana'" }}); // Invalid
1321+
try testing.expectError(error.ExecutionError, runner.testCases(&.{.{ "elem_input.maxLength = -45", null }}, .{})); // Error
1322+
1323+
try runner.testCases(&.{.{ "elem_input.name", "" }}, .{}); // Initial value
1324+
try testProperty(arena, &runner, "elem_input.name", null, &str_valids); // Valid
1325+
1326+
try runner.testCases(&.{.{ "elem_input.readOnly", "false" }}, .{}); // Initial value
1327+
try testProperty(arena, &runner, "elem_input.readOnly", null, &bool_valids); // Valid
1328+
1329+
try runner.testCases(&.{.{ "elem_input.size", "20" }}, .{}); // Initial value
1330+
try testProperty(arena, &runner, "elem_input.size", null, &.{.{ .input = "5" }}); // Valid
1331+
try testProperty(arena, &runner, "elem_input.size", "20", &.{.{ .input = "-26" }}); // Invalid
1332+
try testing.expectError(error.ExecutionError, runner.testCases(&.{.{ "elem_input.size = 0", null }}, .{})); // Error
1333+
try testing.expectError(error.ExecutionError, runner.testCases(&.{.{ "elem_input.size = 'banana'", null }}, .{})); // Error
1334+
1335+
try runner.testCases(&.{.{ "elem_input.src", "" }}, .{}); // Initial value
1336+
try testProperty(arena, &runner, "elem_input.src", null, &.{
1337+
.{ .input = "'foo'", .expected = "https://lightpanda.io/foo" }, // TODO stitch should work with spaces -> %20
1338+
.{ .input = "-3", .expected = "https://lightpanda.io/-3" },
1339+
.{ .input = "''", .expected = "https://lightpanda.io/noslashattheend" },
1340+
});
1341+
1342+
try runner.testCases(&.{.{ "elem_input.type", "text" }}, .{}); // Initial value
1343+
try testProperty(arena, &runner, "elem_input.type", null, &.{.{ .input = "'checkbox'", .expected = "checkbox" }}); // Valid
1344+
try testProperty(arena, &runner, "elem_input.type", "text", &.{.{ .input = "'5'" }}); // Invalid
1345+
1346+
// Properties that are related
1347+
try runner.testCases(&.{
1348+
.{ "let input_checked = document.createElement('input')", null },
1349+
.{ "input_checked.defaultChecked", "false" },
1350+
.{ "input_checked.checked", "false" },
1351+
1352+
.{ "input_checked.defaultChecked = true", "true" },
1353+
.{ "input_checked.defaultChecked", "true" },
1354+
.{ "input_checked.checked", "true" }, // Also perceived as true
1355+
1356+
.{ "input_checked.checked = false", "false" },
1357+
.{ "input_checked.defaultChecked", "true" },
1358+
.{ "input_checked.checked", "false" },
1359+
1360+
.{ "input_checked.defaultChecked = true", "true" },
1361+
.{ "input_checked.checked", "false" }, // Still false
1362+
}, .{});
1363+
try runner.testCases(&.{
1364+
.{ "let input_value = document.createElement('input')", null },
1365+
.{ "input_value.defaultValue", "" },
1366+
.{ "input_value.value", "" },
1367+
1368+
.{ "input_value.defaultValue = 3.1", "3.1" },
1369+
.{ "input_value.defaultValue", "3.1" },
1370+
.{ "input_value.value", "3.1" }, // Also perceived as 3.1
1371+
1372+
.{ "input_value.value = 'mango'", "mango" },
1373+
.{ "input_value.defaultValue", "3.1" },
1374+
.{ "input_value.value", "mango" },
1375+
1376+
.{ "input_value.defaultValue = true", "true" },
1377+
.{ "input_value.value", "mango" }, // Still mango
1378+
}, .{});
1379+
}
1380+
test "Browser.HTML.HtmlInputElement.propeties.form" {
1381+
var runner = try testing.jsRunner(testing.tracking_allocator, .{ .html =
1382+
\\ <form action="test.php" target="_blank">
1383+
\\ <p>
1384+
\\ <label>First name: <input type="text" name="first-name" /></label>
1385+
\\ </p>
1386+
\\ </form>
1387+
});
1388+
defer runner.deinit();
1389+
1390+
try runner.testCases(&.{
1391+
.{ "let elem_input = document.querySelector('input')", null },
1392+
}, .{});
1393+
try runner.testCases(&.{.{ "elem_input.form", "[object HTMLFormElement]" }}, .{}); // Initial value
1394+
try runner.testCases(&.{
1395+
.{ "elem_input.form = 'foo'", null },
1396+
.{ "elem_input.form", "[object HTMLFormElement]" }, // Invalid
1397+
}, .{});
1398+
}
1399+
1400+
const Check = struct {
1401+
input: []const u8,
1402+
expected: ?[]const u8 = null, // Needed when input != expected
1403+
};
1404+
const bool_valids = [_]Check{
1405+
.{ .input = "true" },
1406+
.{ .input = "''", .expected = "false" },
1407+
.{ .input = "13.5", .expected = "true" },
1408+
};
1409+
const str_valids = [_]Check{
1410+
.{ .input = "'foo'", .expected = "foo" },
1411+
.{ .input = "5", .expected = "5" },
1412+
.{ .input = "''", .expected = "" },
1413+
.{ .input = "document", .expected = "[object HTMLDocument]" },
1414+
};
1415+
1416+
// .{ "elem.type = '5'", "5" },
1417+
// .{ "elem.type", "text" },
1418+
fn testProperty(
1419+
arena: std.mem.Allocator,
1420+
runner: *testing.JsRunner,
1421+
elem_dot_prop: []const u8,
1422+
always: ?[]const u8, // Ignores checks' expected if set
1423+
checks: []const Check,
1424+
) !void {
1425+
for (checks) |check| {
1426+
try runner.testCases(&.{
1427+
.{ try std.mem.concat(arena, u8, &.{ elem_dot_prop, " = ", check.input }), null },
1428+
.{ elem_dot_prop, always orelse check.expected orelse check.input },
1429+
}, .{});
1430+
}
1431+
}

0 commit comments

Comments
 (0)