home readme diff tree note docs

0commit 0bff1901284184c86da44aa48971c856d6d4fae3
1Author: Mason Wright <mason@lakefox.net>
2Date:   Mon Jul 7 10:53:45 2025 -0600
3
4    Added 2 cases for has
5
6diff --git a/src/grim.cc b/src/grim.cc
7index 438e45d..33fd5b7 100755
8--- a/src/grim.cc
9+++ b/src/grim.cc
10@@ -5,6 +5,7 @@
11 #include <stdexcept>
12 #include <cctype>
13 #include <string_view>
14+#include <span>
15 
16 // Constructor
17 
18@@ -632,7 +633,9 @@ bool testSelector(Node* node, std::vector<std::vector<std::string>> selector) {
19 	}
20 
21 	// Find the index of the child relative to it's parent
22-	
23+
24+	// the children variable is the PARENTS CHILDREN the the current node's children
25+	// if you want those, node->children
26 	const std::vector<std::unique_ptr<Node>>& children = (node->parent != nullptr) ? node->parent->children : EMPTY_NODE_CHILDREN_VECTOR;
27 	size_t childIndex = 0;
28 	if (children.size() > 1) {
29@@ -707,7 +710,7 @@ bool testSelector(Node* node, std::vector<std::vector<std::string>> selector) {
30 				// we need to find what the selector is targeting
31 				std::string value = p.substr(5,p.length()-6);
32 				auto innerParts = parseSelectorParts(value);
33-				bool subMatch = false;
34+				bool found = false;
35 
36 				for (auto ip : innerParts) {
37 					// if the selector was :has(+ p, div, > video)
38@@ -715,7 +718,42 @@ bool testSelector(Node* node, std::vector<std::vector<std::string>> selector) {
39 					// these differ from how we parse the parts above
40 					// if the first item in a selector is not a +~> then
41 					// it is a decendent otherwise they are the same 
42+					
43+					// We need to have a 2d with the contents of ip without the first
44+					// selector if it is't a decendent combinator so we do that here
45+					std::vector<std::string> sliceOfParts(ip.begin()+1, ip.end());
46+					std::vector<std::vector<std::string>> slicedParts;
47+					slicedParts.push_back(sliceOfParts);
48+
49+					if (ip[0] == ">") {
50+						// The child combinator only checks direct children so we 
51+						// can loop through and if we find one that matches ip[1:]
52+						// (remove the ">") we can stop and return
53+						for (const auto& c : node->children) {
54+							
55+							found = testSelector(c.get(), slicedParts);
56+							if (found) {
57+								break;
58+							}
59+						}
60+					} else if (ip[0] == "+") {
61+						if (children.size() > 1) {
62+							// If there isn't a prevous sibling then it cannot match
63+							if (childIndex < children.size()-1) {
64+								// In this selector we just need to get the direct sibling and test it
65+								found = testSelector(children[childIndex+1].get(), slicedParts);
66+							} else {
67+								// if the element is the last there is no way it could have one
68+								found = false;
69+							}
70+						}
71+					}
72+
73+					if (found) {
74+						break;
75+					}
76 				}
77+				matched = found;
78 			}
79 			// If there are any pseudo elements we will just skip them
80 			// with the transformers being replaced with pseudo elements
81@@ -745,7 +783,7 @@ bool testSelector(Node* node, std::vector<std::vector<std::string>> selector) {
82 	}
83 	
84 	if (symbol == ">") {
85-		// child cominator
86+		// child combinator
87 		// We are just testing the parent node here
88 		matched = testSelector(node->parent, {trimmed});
89 	} else if (symbol == "+") {
90diff --git a/tests/css_selector.cc b/tests/css_selector.cc
91index 90725cc..039e893 100644
92--- a/tests/css_selector.cc
93+++ b/tests/css_selector.cc
94@@ -222,10 +222,20 @@ TEST_CASE("testSelector", "[CSS]") {
95 			return testSelector(input, parseSelectorParts(":not(:is(h1, html, div))"));
96 		};
97 	}
98-	SECTION("testSelector can match a :has() selector") {
99+	SECTION("testSelector can match a :has() selector with a child combinator") {
100+		auto body = document->children[0]->children[1].get();
101+		REQUIRE(testSelector(body, parseSelectorParts("body:has(> h1)")));
102+		REQUIRE_FALSE(testSelector(body, parseSelectorParts("body:has(> h1:required)")));
103+	}
104+	SECTION("testSelector can match a :has() selector with a next-sibling combinator") {
105 		auto h1 = document->children[0]->children[1]->children[0].get();
106 		REQUIRE(testSelector(h1, parseSelectorParts("h1:has(+ p)")));
107 		REQUIRE_FALSE(testSelector(h1, parseSelectorParts("h1:has(+ p:required)")));
108+
109+
110+		BENCHMARK("has with a next sibling combinator") {
111+			return testSelector(h1, parseSelectorParts("h1:has(+ p)"));
112+		};
113 	}
114 	// make for:
115 	// pseudo elements