mirror of
				https://github.com/lightpanda-io/browser.git
				synced 2025-10-29 15:13:28 +00:00 
			
		
		
		
	css: move match_test into selector
This commit is contained in:
		| @@ -1,591 +0,0 @@ | ||||
| // Copyright (C) 2023-2024  Lightpanda (Selecy SAS) | ||||
| // | ||||
| // Francis Bouvier <francis@lightpanda.io> | ||||
| // Pierre Tachoire <pierre@lightpanda.io> | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU Affero General Public License as | ||||
| // published by the Free Software Foundation, either version 3 of the | ||||
| // License, or (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU Affero General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU Affero General Public License | ||||
| // along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||
|  | ||||
| const std = @import("std"); | ||||
| const css = @import("css.zig"); | ||||
|  | ||||
| // Node mock implementation for test only. | ||||
| pub const Node = struct { | ||||
|     child: ?*const Node = null, | ||||
|     last: ?*const Node = null, | ||||
|     sibling: ?*const Node = null, | ||||
|     prev: ?*const Node = null, | ||||
|     par: ?*const Node = null, | ||||
|  | ||||
|     name: []const u8 = "", | ||||
|     att: ?[]const u8 = null, | ||||
|  | ||||
|     pub fn firstChild(n: *const Node) !?*const Node { | ||||
|         return n.child; | ||||
|     } | ||||
|  | ||||
|     pub fn lastChild(n: *const Node) !?*const Node { | ||||
|         return n.last; | ||||
|     } | ||||
|  | ||||
|     pub fn nextSibling(n: *const Node) !?*const Node { | ||||
|         return n.sibling; | ||||
|     } | ||||
|  | ||||
|     pub fn prevSibling(n: *const Node) !?*const Node { | ||||
|         return n.prev; | ||||
|     } | ||||
|  | ||||
|     pub fn parent(n: *const Node) !?*const Node { | ||||
|         return n.par; | ||||
|     } | ||||
|  | ||||
|     pub fn isElement(_: *const Node) bool { | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     pub fn isDocument(_: *const Node) bool { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     pub fn isComment(_: *const Node) bool { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     pub fn isText(_: *const Node) bool { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     pub fn isEmptyText(_: *const Node) !bool { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     pub fn tag(n: *const Node) ![]const u8 { | ||||
|         return n.name; | ||||
|     } | ||||
|  | ||||
|     pub fn attr(n: *const Node, _: []const u8) !?[]const u8 { | ||||
|         return n.att; | ||||
|     } | ||||
|  | ||||
|     pub fn eql(a: *const Node, b: *const Node) bool { | ||||
|         return a == b; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| const Matcher = struct { | ||||
|     const Nodes = std.ArrayListUnmanaged(*const Node); | ||||
|  | ||||
|     nodes: Nodes, | ||||
|     allocator: Allocator, | ||||
|  | ||||
|     fn init(allocator: Allocator) Matcher { | ||||
|         return .{ | ||||
|             .nodes = .empty, | ||||
|             .allocator = allocator, | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     fn deinit(m: *Matcher) void { | ||||
|         m.nodes.deinit(self.allocator); | ||||
|     } | ||||
|  | ||||
|     fn reset(m: *Matcher) void { | ||||
|         m.nodes.clearRetainingCapacity(); | ||||
|     } | ||||
|  | ||||
|     pub fn match(m: *Matcher, n: *const Node) !void { | ||||
|         try m.nodes.append(self.allocator, n); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| test "matchFirst" { | ||||
|     const alloc = std.testing.allocator; | ||||
|  | ||||
|     var matcher = Matcher.init(alloc); | ||||
|     defer matcher.deinit(); | ||||
|  | ||||
|     const testcases = [_]struct { | ||||
|         q: []const u8, | ||||
|         n: Node, | ||||
|         exp: usize, | ||||
|     }{ | ||||
|         .{ | ||||
|             .q = "address", | ||||
|             .n = .{ .child = &.{ .name = "body", .child = &.{ .name = "address" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "#foo", | ||||
|             .n = .{ .child = &.{ .name = "p", .att = "foo", .child = &.{ .name = "p" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = ".t1", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "t1" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = ".t1", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "foo t1" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p" } } }, | ||||
|             .exp = 0, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo=bar]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo=baz]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar" } } }, | ||||
|             .exp = 0, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo!=bar]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo!=baz]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo~=bar]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "baz bar" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo~=bar]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "barbaz" } } }, | ||||
|             .exp = 0, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo^=bar]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "barbaz" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo$=baz]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "barbaz" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo*=rb]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "barbaz" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo|=bar]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo|=bar]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar-baz" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo|=bar]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "ba" } } }, | ||||
|             .exp = 0, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "strong, a", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "p a", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a", .par = &.{ .name = "p" } }, .sibling = &.{ .name = "a" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "p a", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "span", .child = &.{ | ||||
|                 .name = "a", | ||||
|                 .par = &.{ .name = "span", .par = &.{ .name = "p" } }, | ||||
|             } } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = ":not(p)", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "p:has(a)", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "p:has(strong)", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, | ||||
|             .exp = 0, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "p:haschild(a)", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "p:haschild(strong)", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, | ||||
|             .exp = 0, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "p:lang(en)", | ||||
|             .n = .{ .child = &.{ .name = "p", .att = "en-US", .child = &.{ .name = "a" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "a:lang(en)", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a", .par = &.{ .att = "en-US" } } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|     }; | ||||
|  | ||||
|     for (testcases) |tc| { | ||||
|         matcher.reset(); | ||||
|  | ||||
|         const s = try css.parse(alloc, tc.q, .{}); | ||||
|         defer s.deinit(alloc); | ||||
|  | ||||
|         _ = css.matchFirst(s, &tc.n, &matcher) catch |e| { | ||||
|             std.debug.print("query: {s}, parsed selector: {any}\n", .{ tc.q, s }); | ||||
|             return e; | ||||
|         }; | ||||
|  | ||||
|         std.testing.expectEqual(tc.exp, matcher.nodes.items.len) catch |e| { | ||||
|             std.debug.print("query: {s}, parsed selector: {any}\n", .{ tc.q, s }); | ||||
|             return e; | ||||
|         }; | ||||
|     } | ||||
| } | ||||
|  | ||||
| test "matchAll" { | ||||
|     const alloc = std.testing.allocator; | ||||
|  | ||||
|     var matcher = Matcher.init(alloc); | ||||
|     defer matcher.deinit(); | ||||
|  | ||||
|     const testcases = [_]struct { | ||||
|         q: []const u8, | ||||
|         n: Node, | ||||
|         exp: usize, | ||||
|     }{ | ||||
|         .{ | ||||
|             .q = "address", | ||||
|             .n = .{ .child = &.{ .name = "body", .child = &.{ .name = "address" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "#foo", | ||||
|             .n = .{ .child = &.{ .name = "p", .att = "foo", .child = &.{ .name = "p" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = ".t1", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "t1" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = ".t1", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "foo t1" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p" } } }, | ||||
|             .exp = 0, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo=bar]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo=baz]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar" } } }, | ||||
|             .exp = 0, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo!=bar]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo!=baz]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar" } } }, | ||||
|             .exp = 2, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo~=bar]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "baz bar" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo~=bar]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "barbaz" } } }, | ||||
|             .exp = 0, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo^=bar]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "barbaz" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo$=baz]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "barbaz" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo*=rb]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "barbaz" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo|=bar]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo|=bar]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar-baz" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo|=bar]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "ba" } } }, | ||||
|             .exp = 0, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "strong, a", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, | ||||
|             .exp = 2, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "p a", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a", .par = &.{ .name = "p" } }, .sibling = &.{ .name = "a" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "p a", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "span", .child = &.{ | ||||
|                 .name = "a", | ||||
|                 .par = &.{ .name = "span", .par = &.{ .name = "p" } }, | ||||
|             } } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = ":not(p)", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, | ||||
|             .exp = 2, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "p:has(a)", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "p:has(strong)", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, | ||||
|             .exp = 0, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "p:haschild(a)", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "p:haschild(strong)", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, | ||||
|             .exp = 0, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "p:lang(en)", | ||||
|             .n = .{ .child = &.{ .name = "p", .att = "en-US", .child = &.{ .name = "a" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "a:lang(en)", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a", .par = &.{ .att = "en-US" } } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|     }; | ||||
|  | ||||
|     for (testcases) |tc| { | ||||
|         matcher.reset(); | ||||
|  | ||||
|         const s = try css.parse(alloc, tc.q, .{}); | ||||
|         defer s.deinit(alloc); | ||||
|  | ||||
|         css.matchAll(s, &tc.n, &matcher) catch |e| { | ||||
|             std.debug.print("query: {s}, parsed selector: {any}\n", .{ tc.q, s }); | ||||
|             return e; | ||||
|         }; | ||||
|  | ||||
|         std.testing.expectEqual(tc.exp, matcher.nodes.items.len) catch |e| { | ||||
|             std.debug.print("query: {s}, parsed selector: {any}\n", .{ tc.q, s }); | ||||
|             return e; | ||||
|         }; | ||||
|     } | ||||
| } | ||||
|  | ||||
| test "pseudo class" { | ||||
|     const alloc = std.testing.allocator; | ||||
|  | ||||
|     var matcher = Matcher.init(alloc); | ||||
|     defer matcher.deinit(); | ||||
|  | ||||
|     var p1: Node = .{ .name = "p" }; | ||||
|     var p2: Node = .{ .name = "p" }; | ||||
|     var a1: Node = .{ .name = "a" }; | ||||
|  | ||||
|     p1.sibling = &p2; | ||||
|     p2.prev = &p1; | ||||
|  | ||||
|     p2.sibling = &a1; | ||||
|     a1.prev = &p2; | ||||
|  | ||||
|     var root: Node = .{ .child = &p1, .last = &a1 }; | ||||
|     p1.par = &root; | ||||
|     p2.par = &root; | ||||
|     a1.par = &root; | ||||
|  | ||||
|     const testcases = [_]struct { | ||||
|         q: []const u8, | ||||
|         n: Node, | ||||
|         exp: ?*const Node, | ||||
|     }{ | ||||
|         .{ .q = "p:only-child", .n = root, .exp = null }, | ||||
|         .{ .q = "a:only-of-type", .n = root, .exp = &a1 }, | ||||
|     }; | ||||
|  | ||||
|     for (testcases) |tc| { | ||||
|         matcher.reset(); | ||||
|  | ||||
|         const s = try css.parse(alloc, tc.q, .{}); | ||||
|         defer s.deinit(alloc); | ||||
|  | ||||
|         css.matchAll(s, &tc.n, &matcher) catch |e| { | ||||
|             std.debug.print("query: {s}, parsed selector: {any}\n", .{ tc.q, s }); | ||||
|             return e; | ||||
|         }; | ||||
|  | ||||
|         if (tc.exp) |exp_n| { | ||||
|             const exp: usize = 1; | ||||
|             std.testing.expectEqual(exp, matcher.nodes.items.len) catch |e| { | ||||
|                 std.debug.print("query: {s}, parsed selector: {any}\n", .{ tc.q, s }); | ||||
|                 return e; | ||||
|             }; | ||||
|  | ||||
|             std.testing.expectEqual(exp_n, matcher.nodes.items[0]) catch |e| { | ||||
|                 std.debug.print("query: {s}, parsed selector: {any}\n", .{ tc.q, s }); | ||||
|                 return e; | ||||
|             }; | ||||
|  | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         const exp: usize = 0; | ||||
|         std.testing.expectEqual(exp, matcher.nodes.items.len) catch |e| { | ||||
|             std.debug.print("query: {s}, parsed selector: {any}\n", .{ tc.q, s }); | ||||
|             return e; | ||||
|         }; | ||||
|     } | ||||
| } | ||||
|  | ||||
| test "nth pseudo class" { | ||||
|     const alloc = std.testing.allocator; | ||||
|  | ||||
|     var matcher = Matcher.init(alloc); | ||||
|     defer matcher.deinit(); | ||||
|  | ||||
|     var p1: Node = .{ .name = "p" }; | ||||
|     var p2: Node = .{ .name = "p" }; | ||||
|  | ||||
|     p1.sibling = &p2; | ||||
|     p2.prev = &p1; | ||||
|  | ||||
|     var root: Node = .{ .child = &p1, .last = &p2 }; | ||||
|     p1.par = &root; | ||||
|     p2.par = &root; | ||||
|  | ||||
|     const testcases = [_]struct { | ||||
|         q: []const u8, | ||||
|         n: Node, | ||||
|         exp: ?*const Node, | ||||
|     }{ | ||||
|         .{ .q = "a:nth-of-type(1)", .n = root, .exp = null }, | ||||
|         .{ .q = "p:nth-of-type(1)", .n = root, .exp = &p1 }, | ||||
|         .{ .q = "p:nth-of-type(2)", .n = root, .exp = &p2 }, | ||||
|         .{ .q = "p:nth-of-type(0)", .n = root, .exp = null }, | ||||
|         .{ .q = "p:nth-of-type(2n)", .n = root, .exp = &p2 }, | ||||
|         .{ .q = "p:nth-last-child(1)", .n = root, .exp = &p2 }, | ||||
|         .{ .q = "p:nth-last-child(2)", .n = root, .exp = &p1 }, | ||||
|         .{ .q = "p:nth-child(1)", .n = root, .exp = &p1 }, | ||||
|         .{ .q = "p:nth-child(2)", .n = root, .exp = &p2 }, | ||||
|         .{ .q = "p:nth-child(odd)", .n = root, .exp = &p1 }, | ||||
|         .{ .q = "p:nth-child(even)", .n = root, .exp = &p2 }, | ||||
|         .{ .q = "p:nth-child(n+2)", .n = root, .exp = &p2 }, | ||||
|     }; | ||||
|  | ||||
|     for (testcases) |tc| { | ||||
|         matcher.reset(); | ||||
|  | ||||
|         const s = try css.parse(alloc, tc.q, .{}); | ||||
|         defer s.deinit(alloc); | ||||
|  | ||||
|         css.matchAll(s, &tc.n, &matcher) catch |e| { | ||||
|             std.debug.print("query: {s}, parsed selector: {any}\n", .{ tc.q, s }); | ||||
|             return e; | ||||
|         }; | ||||
|  | ||||
|         if (tc.exp) |exp_n| { | ||||
|             const exp: usize = 1; | ||||
|             std.testing.expectEqual(exp, matcher.nodes.items.len) catch |e| { | ||||
|                 std.debug.print("query: {s}, parsed selector: {any}\n", .{ tc.q, s }); | ||||
|                 return e; | ||||
|             }; | ||||
|  | ||||
|             std.testing.expectEqual(exp_n, matcher.nodes.items[0]) catch |e| { | ||||
|                 std.debug.print("query: {s}, parsed selector: {any}\n", .{ tc.q, s }); | ||||
|                 return e; | ||||
|             }; | ||||
|  | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         const exp: usize = 0; | ||||
|         std.testing.expectEqual(exp, matcher.nodes.items.len) catch |e| { | ||||
|             std.debug.print("query: {s}, parsed selector: {any}\n", .{ tc.q, s }); | ||||
|             return e; | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| @@ -17,6 +17,8 @@ | ||||
| // along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||
|  | ||||
| const std = @import("std"); | ||||
| const Allocator = std.mem.Allocator; | ||||
| const css = @import("css.zig"); | ||||
|  | ||||
| pub const AttributeOP = enum { | ||||
|     eql, // = | ||||
| @@ -827,3 +829,578 @@ pub const Selector = union(enum) { | ||||
|         } | ||||
|     } | ||||
| }; | ||||
|  | ||||
| // NodeTest mock implementation for test only. | ||||
| pub const NodeTest = struct { | ||||
|     child: ?*const NodeTest = null, | ||||
|     last: ?*const NodeTest = null, | ||||
|     sibling: ?*const NodeTest = null, | ||||
|     prev: ?*const NodeTest = null, | ||||
|     par: ?*const NodeTest = null, | ||||
|  | ||||
|     name: []const u8 = "", | ||||
|     att: ?[]const u8 = null, | ||||
|  | ||||
|     pub fn firstChild(n: *const NodeTest) !?*const NodeTest { | ||||
|         return n.child; | ||||
|     } | ||||
|  | ||||
|     pub fn lastChild(n: *const NodeTest) !?*const NodeTest { | ||||
|         return n.last; | ||||
|     } | ||||
|  | ||||
|     pub fn nextSibling(n: *const NodeTest) !?*const NodeTest { | ||||
|         return n.sibling; | ||||
|     } | ||||
|  | ||||
|     pub fn prevSibling(n: *const NodeTest) !?*const NodeTest { | ||||
|         return n.prev; | ||||
|     } | ||||
|  | ||||
|     pub fn parent(n: *const NodeTest) !?*const NodeTest { | ||||
|         return n.par; | ||||
|     } | ||||
|  | ||||
|     pub fn isElement(_: *const NodeTest) bool { | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     pub fn isDocument(_: *const NodeTest) bool { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     pub fn isComment(_: *const NodeTest) bool { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     pub fn text(_: *const NodeTest) !?[]const u8 { | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     pub fn isText(_: *const NodeTest) bool { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     pub fn isEmptyText(_: *const NodeTest) !bool { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     pub fn tag(n: *const NodeTest) ![]const u8 { | ||||
|         return n.name; | ||||
|     } | ||||
|  | ||||
|     pub fn attr(n: *const NodeTest, _: []const u8) !?[]const u8 { | ||||
|         return n.att; | ||||
|     } | ||||
|  | ||||
|     pub fn eql(a: *const NodeTest, b: *const NodeTest) bool { | ||||
|         return a == b; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| const MatcherTest = struct { | ||||
|     const NodeTests = std.ArrayListUnmanaged(*const NodeTest); | ||||
|  | ||||
|     nodes: NodeTests, | ||||
|     allocator: Allocator, | ||||
|  | ||||
|     fn init(allocator: Allocator) MatcherTest { | ||||
|         return .{ | ||||
|             .nodes = .empty, | ||||
|             .allocator = allocator, | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     fn deinit(m: *MatcherTest) void { | ||||
|         m.nodes.deinit(m.allocator); | ||||
|     } | ||||
|  | ||||
|     fn reset(m: *MatcherTest) void { | ||||
|         m.nodes.clearRetainingCapacity(); | ||||
|     } | ||||
|  | ||||
|     pub fn match(m: *MatcherTest, n: *const NodeTest) !void { | ||||
|         try m.nodes.append(m.allocator, n); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| test "Browser.CSS.Selector: matchFirst" { | ||||
|     const alloc = std.testing.allocator; | ||||
|  | ||||
|     var matcher = MatcherTest.init(alloc); | ||||
|     defer matcher.deinit(); | ||||
|  | ||||
|     const testcases = [_]struct { | ||||
|         q: []const u8, | ||||
|         n: NodeTest, | ||||
|         exp: usize, | ||||
|     }{ | ||||
|         .{ | ||||
|             .q = "address", | ||||
|             .n = .{ .child = &.{ .name = "body", .child = &.{ .name = "address" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "#foo", | ||||
|             .n = .{ .child = &.{ .name = "p", .att = "foo", .child = &.{ .name = "p" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = ".t1", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "t1" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = ".t1", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "foo t1" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p" } } }, | ||||
|             .exp = 0, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo=bar]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo=baz]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar" } } }, | ||||
|             .exp = 0, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo!=bar]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo!=baz]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo~=bar]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "baz bar" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo~=bar]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "barbaz" } } }, | ||||
|             .exp = 0, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo^=bar]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "barbaz" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo$=baz]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "barbaz" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo*=rb]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "barbaz" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo|=bar]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo|=bar]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar-baz" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo|=bar]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "ba" } } }, | ||||
|             .exp = 0, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "strong, a", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "p a", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a", .par = &.{ .name = "p" } }, .sibling = &.{ .name = "a" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "p a", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "span", .child = &.{ | ||||
|                 .name = "a", | ||||
|                 .par = &.{ .name = "span", .par = &.{ .name = "p" } }, | ||||
|             } } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = ":not(p)", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "p:has(a)", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "p:has(strong)", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, | ||||
|             .exp = 0, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "p:haschild(a)", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "p:haschild(strong)", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, | ||||
|             .exp = 0, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "p:lang(en)", | ||||
|             .n = .{ .child = &.{ .name = "p", .att = "en-US", .child = &.{ .name = "a" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "a:lang(en)", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a", .par = &.{ .att = "en-US" } } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|     }; | ||||
|  | ||||
|     for (testcases) |tc| { | ||||
|         matcher.reset(); | ||||
|  | ||||
|         const s = try css.parse(alloc, tc.q, .{}); | ||||
|         defer s.deinit(alloc); | ||||
|  | ||||
|         _ = css.matchFirst(&s, &tc.n, &matcher) catch |e| { | ||||
|             std.debug.print("query: {s}, parsed selector: {any}\n", .{ tc.q, s }); | ||||
|             return e; | ||||
|         }; | ||||
|  | ||||
|         std.testing.expectEqual(tc.exp, matcher.nodes.items.len) catch |e| { | ||||
|             std.debug.print("query: {s}, parsed selector: {any}\n", .{ tc.q, s }); | ||||
|             return e; | ||||
|         }; | ||||
|     } | ||||
| } | ||||
|  | ||||
| test "Browser.CSS.Selector: matchAll" { | ||||
|     const alloc = std.testing.allocator; | ||||
|  | ||||
|     var matcher = MatcherTest.init(alloc); | ||||
|     defer matcher.deinit(); | ||||
|  | ||||
|     const testcases = [_]struct { | ||||
|         q: []const u8, | ||||
|         n: NodeTest, | ||||
|         exp: usize, | ||||
|     }{ | ||||
|         .{ | ||||
|             .q = "address", | ||||
|             .n = .{ .child = &.{ .name = "body", .child = &.{ .name = "address" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "#foo", | ||||
|             .n = .{ .child = &.{ .name = "p", .att = "foo", .child = &.{ .name = "p" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = ".t1", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "t1" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = ".t1", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "foo t1" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p" } } }, | ||||
|             .exp = 0, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo=bar]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo=baz]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar" } } }, | ||||
|             .exp = 0, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo!=bar]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo!=baz]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar" } } }, | ||||
|             .exp = 2, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo~=bar]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "baz bar" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo~=bar]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "barbaz" } } }, | ||||
|             .exp = 0, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo^=bar]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "barbaz" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo$=baz]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "barbaz" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo*=rb]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "barbaz" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo|=bar]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo|=bar]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar-baz" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "[foo|=bar]", | ||||
|             .n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "ba" } } }, | ||||
|             .exp = 0, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "strong, a", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, | ||||
|             .exp = 2, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "p a", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a", .par = &.{ .name = "p" } }, .sibling = &.{ .name = "a" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "p a", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "span", .child = &.{ | ||||
|                 .name = "a", | ||||
|                 .par = &.{ .name = "span", .par = &.{ .name = "p" } }, | ||||
|             } } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = ":not(p)", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, | ||||
|             .exp = 2, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "p:has(a)", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "p:has(strong)", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, | ||||
|             .exp = 0, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "p:haschild(a)", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "p:haschild(strong)", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, | ||||
|             .exp = 0, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "p:lang(en)", | ||||
|             .n = .{ .child = &.{ .name = "p", .att = "en-US", .child = &.{ .name = "a" } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|         .{ | ||||
|             .q = "a:lang(en)", | ||||
|             .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a", .par = &.{ .att = "en-US" } } } }, | ||||
|             .exp = 1, | ||||
|         }, | ||||
|     }; | ||||
|  | ||||
|     for (testcases) |tc| { | ||||
|         matcher.reset(); | ||||
|  | ||||
|         const s = try css.parse(alloc, tc.q, .{}); | ||||
|         defer s.deinit(alloc); | ||||
|  | ||||
|         css.matchAll(&s, &tc.n, &matcher) catch |e| { | ||||
|             std.debug.print("query: {s}, parsed selector: {any}\n", .{ tc.q, s }); | ||||
|             return e; | ||||
|         }; | ||||
|  | ||||
|         std.testing.expectEqual(tc.exp, matcher.nodes.items.len) catch |e| { | ||||
|             std.debug.print("query: {s}, parsed selector: {any}\n", .{ tc.q, s }); | ||||
|             return e; | ||||
|         }; | ||||
|     } | ||||
| } | ||||
|  | ||||
| test "Browser.CSS.Selector: pseudo class" { | ||||
|     const alloc = std.testing.allocator; | ||||
|  | ||||
|     var matcher = MatcherTest.init(alloc); | ||||
|     defer matcher.deinit(); | ||||
|  | ||||
|     var p1: NodeTest = .{ .name = "p" }; | ||||
|     var p2: NodeTest = .{ .name = "p" }; | ||||
|     var a1: NodeTest = .{ .name = "a" }; | ||||
|  | ||||
|     p1.sibling = &p2; | ||||
|     p2.prev = &p1; | ||||
|  | ||||
|     p2.sibling = &a1; | ||||
|     a1.prev = &p2; | ||||
|  | ||||
|     var root: NodeTest = .{ .child = &p1, .last = &a1 }; | ||||
|     p1.par = &root; | ||||
|     p2.par = &root; | ||||
|     a1.par = &root; | ||||
|  | ||||
|     const testcases = [_]struct { | ||||
|         q: []const u8, | ||||
|         n: NodeTest, | ||||
|         exp: ?*const NodeTest, | ||||
|     }{ | ||||
|         .{ .q = "p:only-child", .n = root, .exp = null }, | ||||
|         .{ .q = "a:only-of-type", .n = root, .exp = &a1 }, | ||||
|     }; | ||||
|  | ||||
|     for (testcases) |tc| { | ||||
|         matcher.reset(); | ||||
|  | ||||
|         const s = try css.parse(alloc, tc.q, .{}); | ||||
|         defer s.deinit(alloc); | ||||
|  | ||||
|         css.matchAll(&s, &tc.n, &matcher) catch |e| { | ||||
|             std.debug.print("query: {s}, parsed selector: {any}\n", .{ tc.q, s }); | ||||
|             return e; | ||||
|         }; | ||||
|  | ||||
|         if (tc.exp) |exp_n| { | ||||
|             const exp: usize = 1; | ||||
|             std.testing.expectEqual(exp, matcher.nodes.items.len) catch |e| { | ||||
|                 std.debug.print("query: {s}, parsed selector: {any}\n", .{ tc.q, s }); | ||||
|                 return e; | ||||
|             }; | ||||
|  | ||||
|             std.testing.expectEqual(exp_n, matcher.nodes.items[0]) catch |e| { | ||||
|                 std.debug.print("query: {s}, parsed selector: {any}\n", .{ tc.q, s }); | ||||
|                 return e; | ||||
|             }; | ||||
|  | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         const exp: usize = 0; | ||||
|         std.testing.expectEqual(exp, matcher.nodes.items.len) catch |e| { | ||||
|             std.debug.print("query: {s}, parsed selector: {any}\n", .{ tc.q, s }); | ||||
|             return e; | ||||
|         }; | ||||
|     } | ||||
| } | ||||
|  | ||||
| test "Browser.CSS.Selector: nth pseudo class" { | ||||
|     const alloc = std.testing.allocator; | ||||
|  | ||||
|     var matcher = MatcherTest.init(alloc); | ||||
|     defer matcher.deinit(); | ||||
|  | ||||
|     var p1: NodeTest = .{ .name = "p" }; | ||||
|     var p2: NodeTest = .{ .name = "p" }; | ||||
|  | ||||
|     p1.sibling = &p2; | ||||
|     p2.prev = &p1; | ||||
|  | ||||
|     var root: NodeTest = .{ .child = &p1, .last = &p2 }; | ||||
|     p1.par = &root; | ||||
|     p2.par = &root; | ||||
|  | ||||
|     const testcases = [_]struct { | ||||
|         q: []const u8, | ||||
|         n: NodeTest, | ||||
|         exp: ?*const NodeTest, | ||||
|     }{ | ||||
|         .{ .q = "a:nth-of-type(1)", .n = root, .exp = null }, | ||||
|         .{ .q = "p:nth-of-type(1)", .n = root, .exp = &p1 }, | ||||
|         .{ .q = "p:nth-of-type(2)", .n = root, .exp = &p2 }, | ||||
|         .{ .q = "p:nth-of-type(0)", .n = root, .exp = null }, | ||||
|         .{ .q = "p:nth-of-type(2n)", .n = root, .exp = &p2 }, | ||||
|         .{ .q = "p:nth-last-child(1)", .n = root, .exp = &p2 }, | ||||
|         .{ .q = "p:nth-last-child(2)", .n = root, .exp = &p1 }, | ||||
|         .{ .q = "p:nth-child(1)", .n = root, .exp = &p1 }, | ||||
|         .{ .q = "p:nth-child(2)", .n = root, .exp = &p2 }, | ||||
|         .{ .q = "p:nth-child(odd)", .n = root, .exp = &p1 }, | ||||
|         .{ .q = "p:nth-child(even)", .n = root, .exp = &p2 }, | ||||
|         .{ .q = "p:nth-child(n+2)", .n = root, .exp = &p2 }, | ||||
|     }; | ||||
|  | ||||
|     for (testcases) |tc| { | ||||
|         matcher.reset(); | ||||
|  | ||||
|         const s = try css.parse(alloc, tc.q, .{}); | ||||
|         defer s.deinit(alloc); | ||||
|  | ||||
|         css.matchAll(&s, &tc.n, &matcher) catch |e| { | ||||
|             std.debug.print("query: {s}, parsed selector: {any}\n", .{ tc.q, s }); | ||||
|             return e; | ||||
|         }; | ||||
|  | ||||
|         if (tc.exp) |exp_n| { | ||||
|             const exp: usize = 1; | ||||
|             std.testing.expectEqual(exp, matcher.nodes.items.len) catch |e| { | ||||
|                 std.debug.print("query: {s}, parsed selector: {any}\n", .{ tc.q, s }); | ||||
|                 return e; | ||||
|             }; | ||||
|  | ||||
|             std.testing.expectEqual(exp_n, matcher.nodes.items[0]) catch |e| { | ||||
|                 std.debug.print("query: {s}, parsed selector: {any}\n", .{ tc.q, s }); | ||||
|                 return e; | ||||
|             }; | ||||
|  | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         const exp: usize = 0; | ||||
|         std.testing.expectEqual(exp, matcher.nodes.items.len) catch |e| { | ||||
|             std.debug.print("query: {s}, parsed selector: {any}\n", .{ tc.q, s }); | ||||
|             return e; | ||||
|         }; | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Pierre Tachoire
					Pierre Tachoire