home readme diff tree note docs

0commit fc556c26a73a47fe5a718a1cad4eead8c1873174
1Author: Mason Wright <mason@lakefox.net>
2Date:   Wed Jul 9 16:29:59 2025 -0600
3
4    Docs and Tests
5
6diff --git a/docs/content/_layout.html b/docs/content/_layout.html
7new file mode 100644
8index 0000000..58a1838
9--- /dev/null
10+++ b/docs/content/_layout.html
11@@ -0,0 +1,33 @@
12+<!DOCTYPE html>
13+<html lang="en">
14+  <head>
15+    <meta charset="UTF-8" />
16+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
17+    <title>{{ .Global.siteName }}</title>
18+    <style>
19+      table {
20+        width: 100%;
21+        border-collapse: collapse;
22+      }
23+      th,
24+      td {
25+        text-align: center;
26+        border: 1px solid #ddd;
27+        padding: 8px;
28+      }
29+      th {
30+        background-color: #f2f2f2;
31+      }
32+    </style>
33+  </head>
34+  <body>
35+    <main style="margin: 0 4rem 0 4rem">
36+      <div style="display: flex; gap: 1rem">
37+        {{ template "navbar.html" . }}
38+        <div style="width: 100%">
39+          <section>{{ .Content }}</section>
40+        </div>
41+      </div>
42+    </main>
43+  </body>
44+</html>
45diff --git a/docs/content/features/supported-css-selectors.md b/docs/content/features/supported-css-selectors.md
46new file mode 100644
47index 0000000..709935e
48--- /dev/null
49+++ b/docs/content/features/supported-css-selectors.md
50@@ -0,0 +1,103 @@
51+# Supported Selectors
52+
53+## Selectors 
54+| Name | Support | Test Case | Section | Test File |
55+|-|-|-|-| - |
56+| Type | X | testSelector | testSelector can match a element given it's tagName | tests/css_selector.cc |
57+| Attribute | X | testSelector | testSelector can match a element by it's attributes only | tests/css_selector.cc |
58+| ID | X | testSelector | testSelector can match a element by it's id only | tests/css_selector.cc |
59+| Class | X | testSelector | testSelector can match a element by it's class and not matach on a non class | tests/css_selector.cc |
60+| Universal | testSelector can match a universal selector | testSelector | |  |
61+| Nesting |  | testSelector | |  |
62+| & Nesting |  | testSelector | |  |
63+
64+## Combinators
65+| Name | Support | Test Case | Section | Test File |
66+|-|-|-|-| - |
67+| Child Combinator | X | testSelector | testSelector can match a child combinator | tests/css_selector.cc |
68+| Next-Sibling Combinator | X | testSelector | testSelector can match a next sibling combinator | tests/css_selector.cc |
69+| Descendant Combinator | X | testSelector | testSelector can match a descendant combinator | tests/css_selector.cc |
70+| Subsequent-Sibling Combinator | X | testSelector | testSelector can match a subsequent-sibling combinator | tests/css_selector.cc |
71+
72+## Pseudo Classes
73+| Name | Support | Test Case | Section | Test File | Note |
74+|-|-|-|-| - | - |
75+| :active | | | | | Will be added when links are|
76+| :any-link | | | | | Will be added when links are|
77+| :autofill | | | | | Will be added when inputs are added |
78+| :buffering | | | | | Videos are not currently supported |
79+| :checked | X | testSelector | testSelector can match a element with a pseudo class | tests/css_selector.cc | |
80+| :default | | | | | Will be added when inputs are added |
81+| :defined | | |  |  | |
82+| :dir() | | |  |  | |
83+| :disabled | X | testSelector | testSelector can match a element with a pseudo class | tests/css_selector.cc | |
84+| :empty | | |  |  | Will be added when inputs are added |
85+| :enabled | X | testSelector | testSelector can match a element with a pseudo class | tests/css_selector.cc | |
86+| :first | / | |  |  | Will not be supported, currently there are no plans to support printing |
87+| :first-child | X | testSelector | testSelector can match a element with first-child | tests/css_selector.cc | |
88+| :first-of-type | X | testSelector | testSelector can match a element with first-of-type | tests/css_selector.cc | |
89+| :focus | | |  |  | |
90+| :focus-visable | | |  |  | |
91+| :focus-within | | |  |  | |
92+| :fullscreen | | |  |  | Videos are not currently supported |
93+| :future | | |  |  | |
94+| :has-slotted | | |  |  | |
95+| :has() | X | testSelector | testSelector can match a :has() selector with a child combinator, testSelector can match a :has() selector with a next-sibling combinator, testSelector can match a :has() selector with a subsequent-sibling combinator, testSelector can match a :has() selector with a decendent combinator | tests/css_selector.cc | |
96+| :host | | | | | |
97+| :host-context() | | | | | |
98+| :host() | | | | | |
99+| :hover | | | | | |
100+| :in-range | | | | | Will be added when inputs are added |
101+| :indeterminate | | | | | Will be added when inputs are added |
102+| :invalid | | | | | Will be added when inputs are added |
103+| :is() | X | testSelector | testSelector can match :is() selectors | tests/css_selector.cc | |
104+| :lang() | | | | | |
105+| :last-child | X | testSelector | testSelector can match a element with last-child | tests/css_selector.cc | |
106+| :last-of-type | X | testSelector | testSelector can match a element with last-of-type | tests/css_selector.cc | |
107+| :left | / | | | | Will not be supported, currently there are no plans to support printing |
108+| :local-link | / | | | | Doesn't make sense to add because all links will be local |
109+| :modal | | | | | |
110+| :muted | | | | | Videos are not currently supported |
111+| :not() | X | testSelector | testSelector can match :not() selectors | tests/css_selector.cc | |
112+| :nth-child() | X | testSelector | testSelector can match a :nth-child(even\|odd), testSelector can match a :nth-child(A), testSelector can match a :nth-child(An), testSelector can match a :nth-child(An+B), testSelector can match a :nth-child(An+B of type), testSelector can match a :nth-child(n+B), testSelector can match a :nth-child(A of type), testSelector can match a :nth-child(An of type), testSelector can match a :nth-child(n+B of type) | tests/css_selector.cc | when A is 0 it is changed to a 1, the only case this will cause breaking behavour is if you are trying :nth-child(0n+B) which is the same as calling :nth-child(B). This behavour applys to all nth-*|
113+| :nth-last-child() | X | testSelector | testSelector can match a :nth-last-child(even\|odd), testSelector can match a :nth-last-child(A), testSelector can match a :nth-last-child(An), testSelector can match a :nth-last-child(An+B), testSelector can match a :nth-last-child(n+B), testSelector can match a :nth-last-child(An+B of type), testSelector can match a :nth-last-child(A of type), testSelector can match a :nth-last-child(An of type), testSelector can match a :nth-last-child(n+B of type) | tests/css_selector.cc | |
114+| :nth-last-of-type() | X | testSelector | testSelector can match a :nth-last-of-type(even\|odd), testSelector can match a :nth-last-of-type(A), testSelector can match a :nth-last-of-type(An), testSelector can match a :nth-last-of-type(An+B), testSelector can match a :nth-last-of-type(n+B),  | tests/css_selector.cc | |
115+| :nth-of-type() | X | testSelector | testSelector can match a :nth-of-type(even\|odd), testSelector can match a :nth-of-type(A), testSelector can match a :nth-of-type(An), testSelector can match a :nth-of-type(An+B), testSelector can match a :nth-of-type(n+B),  | tests/css_selector.cc | |
116+| :only-child | X | testSelector | testSelector can match :only-child | tests/css_selector.cc | |
117+| :only-of-type | X | testSelector | testSelector can match :only-of-type | tests/css_selector.cc | |
118+| :open | | | | | |
119+| :optional | | | | | |
120+| :out-of-range | | | | | Will be added when inputs are added |
121+| :past | | | | | |
122+| :paused | | | | | Videos are not currently supported |
123+| :picture-in-picture | | | | | Videos are not currently supported |
124+| :placeholder-shown | | | | | Will be added when inputs are added |
125+| :playing | | | | | Videos are not currently supported |
126+| :popover-open | | | | |  |
127+| :read-only | X | testSelector | testSelector can match :read-only normal element, testSelector can match :read-only on a radio input, testSelector can match :read-only on a readonly text input, testSelector can match :read-only on a disabled text input, testSelector can match :read-only on a readonly textarea, testSelector can match :read-only on a disabled textarea | tests/css_selector.cc | |
128+| :read-write | X | testSelector | testSelector can match :read-write on a text input, testSelector can match :read-write on a textarea, testSelector can match :read-write on a contenteditable div | tests/css_selector.cc | |
129+| :required | X | testSelector | testSelector can match a element with a pseudo class | tests/css_selector.cc | |
130+| :right | / | | | | Will not be supported, currently there are no plans to support printing |
131+| :root | X | testSelector | testSelector can match :root | tests/css_selector.cc | |
132+| :scope | | | | | |
133+| :seeking | | | | | Videos are not currently supported |
134+| :stalled | | | | | Video and Audio are not currently supported |
135+| :state() | | | | | Shadow DOM |
136+| :target | | | | | |
137+| :target-current | | | | | |
138+| :target-within | | | | | |
139+| :user-invalid | | | | | |
140+| :user-valid | | | | | |
141+| :valid | | | | | |
142+| :visited | | | | | |
143+| :volume-locked | | | | | Video and Audio are not currently supported |
144+| :where() | X | testSelector |testSelector can match :where() selectors | tests/css_selector.cc | |
145+
146+
147+
148+
149+
150+## Pseudo Elements
151+## At-rules
152+## Functions
153+## Types
154diff --git a/docs/content/index.md b/docs/content/index.md
155new file mode 100644
156index 0000000..bad5562
157--- /dev/null
158+++ b/docs/content/index.md
159@@ -0,0 +1 @@
160+# Getting Started
161diff --git a/docs/evoke.yaml b/docs/evoke.yaml
162new file mode 100644
163index 0000000..1f23f5d
164--- /dev/null
165+++ b/docs/evoke.yaml
166@@ -0,0 +1 @@
167+siteName: Grim
168diff --git a/docs/partials/navbar.html b/docs/partials/navbar.html
169new file mode 100644
170index 0000000..6d0fa47
171--- /dev/null
172+++ b/docs/partials/navbar.html
173@@ -0,0 +1,23 @@
174+<aside style="min-width: 200px">
175+	<img src="/logo.webp"/ width="150px">
176+	<ul>
177+		<li>
178+			<strong><a href="/">Home</a></strong>
179+		</li>
180+	</ul>
181+	<details open>
182+		<summary>Links</summary>
183+		<ul>
184+			<li><a href="https://git.grimui.com">Git</a></li>
185+			<li><a href="https://tracker.grimui.com">Trackers</a></li>
186+		</ul>
187+	</details>
188+	<details open>
189+		<summary>Feature Support</summary>
190+		<ul>
191+			<li><a href="/features/supported-css-selectors.html">CSS Selectors</a></li>
192+			<li><a href="/features/supported-html-tags.html">HTML Tags</a></li>
193+			<li><a href="/features/supported-dom-apis.html">DOM APIs</a></li>
194+		</ul>
195+	</details>
196+</aside>
197diff --git a/src/grim.cc b/src/grim.cc
198index c7909f0..09efbb0 100755
199--- a/src/grim.cc
200+++ b/src/grim.cc
201@@ -577,6 +577,7 @@ bool testSelector(Node* node, std::vector<std::vector<std::string>> selector) {
202 		return false;
203 	}
204 
205+	std::string tagName = node->getTagName();
206 
207 	std::vector<std::string> trimmed;
208 	std::vector<std::string> parts;
209@@ -656,7 +657,7 @@ bool testSelector(Node* node, std::vector<std::vector<std::string>> selector) {
210 				childIndex = i;	
211 				childTypeIndex = childTypeCount;
212 			}
213-			if (child_ptr->getTagName() == node->getTagName()) {
214+			if (child_ptr->getTagName() == tagName) {
215 				childTypeCount++;
216 			}
217 		}
218@@ -675,6 +676,47 @@ bool testSelector(Node* node, std::vector<std::vector<std::string>> selector) {
219 	if (childTypeIndex == childTypeCount-1) {
220 		attrs.push_back("last-of-type");
221 	}
222+	if (childIndex == 0 && children.size() == 1) {
223+		attrs.push_back("only-child");
224+	}
225+	if (childTypeIndex == 0 && childTypeCount == 1) {
226+		attrs.push_back("only-of-type");
227+	}
228+	if (tagName == "html") {
229+		attrs.push_back("root");
230+	}
231+	
232+	if (tagName == "input") {
233+		std::string type = node->getAttribute("type");
234+		bool readonly = attributes.find("readonly") != attributes.end();
235+		bool disabled = attributes.find("disabled") != attributes.end();
236+		if (type == "text" || type == "search" || type == "tel" || type == "url" || type == "email" 
237+				|| type == "password" || type == "date" || type == "month" || type == "week" 
238+				|| type == "time" || type == "datetime-local" || type == "number") {
239+			if (readonly || disabled) {
240+				attrs.push_back("read-only");
241+			} else {
242+				attrs.push_back("read-write");
243+			}
244+		} else {
245+			attrs.push_back("read-only");
246+		}
247+	} else if (tagName == "textarea") {
248+		bool readonly = attributes.find("readonly") != attributes.end();
249+		bool disabled = attributes.find("disabled") != attributes.end();
250+		if (readonly || disabled) {
251+			attrs.push_back("read-only");
252+		} else {
253+			attrs.push_back("read-write");
254+		}
255+	} else {
256+		std::string contentEditable = node->getAttribute("contenteditable");
257+		if (contentEditable == "true") {
258+			attrs.push_back("read-write");
259+		} else {
260+			attrs.push_back("read-only");
261+		}
262+	}
263 
264 
265 
266@@ -684,7 +726,7 @@ bool testSelector(Node* node, std::vector<std::vector<std::string>> selector) {
267 			matched = true;
268 		} else 
269 		if (i == 0 && p[0] != '#' && p[0] != '.' &&  p[0] != '[' && p[0] != ':') {
270-			matched = node->getTagName() == p;
271+			matched = tagName == p;
272 		} else if (p[0] == '#') {
273 			matched = "#"+node->getId() == p;
274 		} else if (p[0] == '.') {
275@@ -946,50 +988,53 @@ bool testSelector(Node* node, std::vector<std::vector<std::string>> selector) {
276 					// If the nth child selector has -of-type we need to add of+tagname
277 					// the of is added because thats how the parser parses the rest of them
278 					if (ofType) {
279-						ofSelector = "of"+node->getTagName();
280+						ofSelector = "of"+tagName;
281 					}
282+
283 					if (ofSelector == "") {
284 						if (last) {
285 							// If we are checking from the back we need to invert the
286 							// childIndex
287-							index = children.size() - childIndex;
288+							index = children.size() - 1 - childIndex;
289 						} else {
290 							index = childIndex;
291 						}
292 					} else {
293 						// Parse the of selector
294 						auto pOS = parseSelectorParts(ofSelector.substr(2));
295-						if (last) {
296-							// Reverse through the siblings
297-							for (size_t i = children.size() - 1; i >= 0; i--) {
298-								Node* child_ptr = children[i].get();
299-								if (child_ptr != node) {
300-									if (testSelector(child_ptr, pOS)) {
301-										// if the selector matches the ofSelector add one
302-										index++;
303+						if (testSelector(node, pOS)) {
304+							if (last) {
305+								// Reverse through the siblings
306+								for (size_t i = children.size() - 1; i >= 0; i--) {
307+									Node* child_ptr = children[i].get();
308+									if (child_ptr != node) {
309+										if (testSelector(child_ptr, pOS)) {
310+											// if the selector matches the ofSelector add one
311+											index++;
312+										}
313+									} else {
314+										// We are at the pointer address of the target node
315+										break;
316 									}
317-								} else {
318-									// We are at the pointer address of the target node
319-									// add 1 to index and break
320-									index++;
321-									break;
322 								}
323-							}
324-						} else {
325-							for (size_t i = 0; i < children.size(); i++) {
326-								Node* child_ptr = children[i].get();
327-								if (child_ptr != node) {
328-									if (testSelector(child_ptr, pOS)) {
329-										// if the selector matches the ofSelector add one
330-										index++;
331+							} else {
332+								for (size_t i = 0; i < children.size(); i++) {
333+									Node* child_ptr = children[i].get();
334+									if (child_ptr != node) {
335+										if (testSelector(child_ptr, pOS)) {
336+											// if the selector matches the ofSelector add one
337+											index++;
338+										}
339+									} else {
340+										// We are at the pointer address of the target node
341+										break;
342 									}
343-								} else {
344-									// We are at the pointer address of the target node
345-									// add 1 to index and break
346-									index++;
347-									break;
348 								}
349 							}
350+						} else {
351+							// the target node doesn't match the of selector then it can not
352+							// be a match so return false no need to set matched to false and continue
353+							return false;
354 						}
355 					}
356 
357@@ -1002,9 +1047,14 @@ bool testSelector(Node* node, std::vector<std::vector<std::string>> selector) {
358 	// 	n cannot be a float
359 	// so we solve for n with (index-B)/A then we check those two conditions
360 	// if nfound is false and A != 0 then we can just compare the two numbers
361-					if (!nfound && A != 0) {
362+	
363+					// Given n+B A is implicitly 1, this will fail if A is set to 0
364+					if (A == 0) {
365+						A = 1;
366+					}
367+					if (!nfound) {
368 						matched = A == index;
369-					} else if (A != 0) {
370+					} else {
371 						matched = (index-B)/A >= 0 && (index-B)%A == 0;
372 					}
373 
374diff --git a/tests/css_selector.cc b/tests/css_selector.cc
375index b4e7ca5..eb0fcfe 100644
376--- a/tests/css_selector.cc
377+++ b/tests/css_selector.cc
378@@ -102,6 +102,9 @@ TEST_CASE( "CSS Selection", "[css]" ) {
379 	SECTION("parseSelectorParts can parse a relative selector without another reference") {
380 		checkSelectorParts("+ p", {{"+", "p"}});
381 	}
382+	SECTION("parseSelectorParts can parse a tagName with a attribute selector and a pseudo class") {
383+		checkSelectorParts("input[type=\"text\"]:disabled", {{"input", "[type=\"text\"]", "[disabled]"}});
384+	}
385 }
386 
387 TEST_CASE("testSelector", "[CSS]") {
388@@ -114,14 +117,22 @@ TEST_CASE("testSelector", "[CSS]") {
389 				"<body>"
390 					"<h1 id=\"h1Tag\">Header</h1>"
391 					"<p id=\"paragraph\" class=\"class1 class2\">This is some text for a simple test</p>"
392-					"<div></div>"
393-					"<input type=\"radio\" checked required/>"
394+					"<div contenteditable=\"true\">"
395+						"<p>this is a only child</p>"
396+					"</div>"
397+					"<input type=\"text\" checked required/>"
398 					"<ul>"
399 						"<li>one</li>"
400 						"<li>two</li>"
401 						"<li>three</li>"
402 						"<li>four</li>"
403 					"</ul>"
404+					"<input type=\"radio\" checked required/>"
405+					"<input type=\"text\" readonly/>"
406+					"<input type=\"text\" disabled/>"
407+					"<textarea></textarea>"
408+					"<textarea readonly></textarea>"
409+					"<textarea disabled></textarea>"
410 				"</body>"
411 			"</html>";
412 
413@@ -196,8 +207,8 @@ TEST_CASE("testSelector", "[CSS]") {
414 		REQUIRE(testSelector(li, parseSelectorParts(":first-of-type")));
415 	}
416 	SECTION("testSelector can match a element with last-child") {
417-		auto ul = document->children[0]->children[1]->children[4].get();
418-		REQUIRE(testSelector(ul, parseSelectorParts(":last-child")));
419+		auto textarea = document->children[0]->children[1]->children[10].get();
420+		REQUIRE(testSelector(textarea, parseSelectorParts(":last-child")));
421 	}
422 	SECTION("testSelector can match a element with last-of-type") {
423 		auto h1 = document->children[0]->children[1]->children[0].get();
424@@ -210,19 +221,19 @@ TEST_CASE("testSelector", "[CSS]") {
425 		li = document->children[0]->children[1]->children[4]->children[3].get();
426 		REQUIRE(testSelector(li, parseSelectorParts(":last-of-type")));
427 	}
428-	SECTION("testSelector can match multiple selectors") {
429-		auto ul = document->children[0]->children[1]->children[4].get();
430-		// Should only match on :last-child
431-		REQUIRE(testSelector(ul, parseSelectorParts("h1, h1 ~p, :last-child")));
432-		REQUIRE_FALSE(testSelector(ul, parseSelectorParts("h1, h1 ~p, :first-child")));
433+	SECTION("testselector can match multiple selectors") {
434+		auto textarea = document->children[0]->children[1]->children[10].get();
435+		// should only match on :last-child
436+		REQUIRE(testSelector(textarea, parseSelectorParts("h1, h1 ~p, :last-child")));
437+		REQUIRE_FALSE(testSelector(textarea, parseSelectorParts("h1, h1 ~p, :first-child")));
438 
439-		BENCHMARK("Multiselector and parse") {
440-			return testSelector(ul, parseSelectorParts("h1, h1 ~p, :last-child"));
441+		BENCHMARK("multiselector and parse") {
442+			return testSelector(textarea, parseSelectorParts("h1, h1 ~p, :last-child"));
443 		};
444 
445 		auto parts = parseSelectorParts("h1, h1 ~p, :last-child");
446-		BENCHMARK("Multiselector pre-parse") {
447-			return testSelector(ul, parts);
448+		BENCHMARK("multiselector pre-parse") {
449+			return testSelector(textarea, parts);
450 		};
451 	}
452 	SECTION("testSelector can match :is() selectors") {
453@@ -295,15 +306,19 @@ TEST_CASE("testSelector", "[CSS]") {
454 			return testSelector(html, parseSelectorParts(":is(html, div, p):has(:where(html, input:checked, input))"));
455 		};
456 	}
457-
458-	SECTION("testSelector can match a :nth-child(even)") {
459+// +------------------------------------------------------+
460+// |(d678a) Nth child selection                           |
461+// +------------------------------------------------------+
462+	SECTION("testSelector can match a :nth-child(even|odd)") {
463 		auto li = document->children[0]->children[1]->children[4]->children[1].get();
464+		REQUIRE(testSelector(li, parseSelectorParts(":nth-child(even)")));
465 		REQUIRE(testSelector(li, parseSelectorParts("li:nth-child(even)")));
466 		REQUIRE_FALSE(testSelector(li, parseSelectorParts("li:nth-child(odd)")));
467 	}
468 
469-	SECTION("testSelector can match a :nth-child(2)") {
470+	SECTION("testSelector can match a :nth-child(A)") {
471 		auto li = document->children[0]->children[1]->children[4]->children[1].get();
472+		REQUIRE(testSelector(li, parseSelectorParts(":nth-child(2)")));
473 		REQUIRE(testSelector(li, parseSelectorParts("li:nth-child(2)")));
474 		REQUIRE_FALSE(testSelector(li, parseSelectorParts("li:nth-child(1)")));
475 
476@@ -312,8 +327,9 @@ TEST_CASE("testSelector", "[CSS]") {
477 		};
478 	}
479 
480-	SECTION("testSelector can match a :nth-child(2n)") {
481+	SECTION("testSelector can match a :nth-child(An)") {
482 		auto li = document->children[0]->children[1]->children[4]->children[1].get();
483+		REQUIRE(testSelector(li, parseSelectorParts(":nth-child(2n)")));
484 		REQUIRE(testSelector(li, parseSelectorParts("li:nth-child(2n)")));
485 		REQUIRE_FALSE(testSelector(li, parseSelectorParts("li:nth-child(3n)")));
486 
487@@ -322,8 +338,9 @@ TEST_CASE("testSelector", "[CSS]") {
488 		};
489 	}
490 
491-	SECTION("testSelector can match a :nth-child(2n+1)") {
492+	SECTION("testSelector can match a :nth-child(An+B)") {
493 		auto li = document->children[0]->children[1]->children[4]->children[2].get();
494+		REQUIRE(testSelector(li, parseSelectorParts(":nth-child(2n+1)")));
495 		REQUIRE(testSelector(li, parseSelectorParts("li:nth-child(2n+1)")));
496 		REQUIRE_FALSE(testSelector(li, parseSelectorParts("li:nth-child(3n+1)")));
497 
498@@ -332,12 +349,348 @@ TEST_CASE("testSelector", "[CSS]") {
499 		};
500 	}
501 
502-	
503+	SECTION("testSelector can match a :nth-child(n+B)") {
504+		auto li = document->children[0]->children[1]->children[4]->children[0].get();
505+		REQUIRE(testSelector(li, parseSelectorParts(":nth-child(n+1)")));
506+		REQUIRE(testSelector(li, parseSelectorParts("li:nth-child(n-3)")));
507+		REQUIRE_FALSE(testSelector(li, parseSelectorParts("li:nth-child(n+2)")));
508+
509+		BENCHMARK(":nth-child(n+1)") {
510+			return testSelector(li, parseSelectorParts("li:nth-child(n+1)"));
511+		};
512+	}
513+
514+	SECTION("testSelector can match a :nth-child(An+B of type)") {
515+		auto li = document->children[0]->children[1]->children[4]->children[2].get();
516+		REQUIRE(testSelector(li, parseSelectorParts(":nth-child(2n+1 of li)")));
517+		REQUIRE_FALSE(testSelector(li, parseSelectorParts(":nth-child(2n+1 of ul)")));
518+		REQUIRE_FALSE(testSelector(li, parseSelectorParts(":nth-child(3n+1 of li)")));
519+	}
520+
521+	SECTION("testSelector can match a :nth-child(A of type)") {
522+		auto li = document->children[0]->children[1]->children[4]->children[1].get();
523+		REQUIRE(testSelector(li, parseSelectorParts(":nth-child(2 of li)")));
524+		REQUIRE(testSelector(li, parseSelectorParts("li:nth-child(2 of li)")));
525+		REQUIRE_FALSE(testSelector(li, parseSelectorParts("li:nth-child(2 of ul)")));
526+
527+		BENCHMARK(":nth-child(2 of li)") {
528+			return testSelector(li, parseSelectorParts("li:nth-child(2 of li)"));
529+		};
530+	}
531+
532+	SECTION("testSelector can match a :nth-child(An of type)") {
533+		auto li = document->children[0]->children[1]->children[4]->children[1].get();
534+		REQUIRE(testSelector(li, parseSelectorParts(":nth-child(2n of li)")));
535+		REQUIRE(testSelector(li, parseSelectorParts("li:nth-child(2n of li)")));
536+		REQUIRE_FALSE(testSelector(li, parseSelectorParts("li:nth-child(2n of ul)")));
537+
538+		BENCHMARK(":nth-child(2n of li)") {
539+			return testSelector(li, parseSelectorParts("li:nth-child(2n of li)"));
540+		};
541+	}
542+
543+	SECTION("testSelector can match a :nth-child(n+B of type)") {
544+		auto li = document->children[0]->children[1]->children[4]->children[0].get();
545+		REQUIRE(testSelector(li, parseSelectorParts(":nth-child(n+1 of li)")));
546+		REQUIRE(testSelector(li, parseSelectorParts("li:nth-child(n-3 of li)")));
547+		REQUIRE_FALSE(testSelector(li, parseSelectorParts("li:nth-child(n+1 of ul)")));
548+
549+		BENCHMARK(":nth-child(n+1 of li)") {
550+			return testSelector(li, parseSelectorParts("li:nth-child(n+1 of li)"));
551+		};
552+	}
553+	// +------------------------------------------------------+
554+	// |Nth last child                                        |
555+	// +------------------------------------------------------+
556+	SECTION("testSelector can match a :nth-last-child(even|odd)") {
557+		auto li = document->children[0]->children[1]->children[4]->children[3].get();
558+		REQUIRE(testSelector(li, parseSelectorParts(":nth-last-child(even)")));
559+		REQUIRE(testSelector(li, parseSelectorParts("li:nth-last-child(even)")));
560+		REQUIRE_FALSE(testSelector(li, parseSelectorParts("li:nth-last-child(odd)")));
561+	}
562+
563+	SECTION("testSelector can match a :nth-last-child(A)") {
564+		auto li = document->children[0]->children[1]->children[4]->children[2].get();
565+		REQUIRE(testSelector(li, parseSelectorParts(":nth-last-child(2)")));
566+		REQUIRE(testSelector(li, parseSelectorParts("li:nth-last-child(2)")));
567+		REQUIRE_FALSE(testSelector(li, parseSelectorParts("li:nth-last-child(1)")));
568+
569+		BENCHMARK(":nth-last-child(2)") {
570+			return testSelector(li, parseSelectorParts("li:nth-last-child(2)"));
571+		};
572+	}
573+
574+	SECTION("testSelector can match a :nth-last-child(An)") {
575+		auto li = document->children[0]->children[1]->children[4]->children[2].get();
576+		REQUIRE(testSelector(li, parseSelectorParts(":nth-last-child(2n)")));
577+		REQUIRE(testSelector(li, parseSelectorParts("li:nth-last-child(2n)")));
578+		REQUIRE_FALSE(testSelector(li, parseSelectorParts("li:nth-last-child(3n)")));
579+
580+		BENCHMARK(":nth-last-child(2n)") {
581+			return testSelector(li, parseSelectorParts("li:nth-last-child(2n)"));
582+		};
583+	}
584+
585+	SECTION("testSelector can match a :nth-last-child(An+B)") {
586+		auto li = document->children[0]->children[1]->children[4]->children[1].get();
587+		REQUIRE(testSelector(li, parseSelectorParts(":nth-last-child(2n+1)")));
588+		REQUIRE(testSelector(li, parseSelectorParts("li:nth-last-child(2n+1)")));
589+		REQUIRE_FALSE(testSelector(li, parseSelectorParts("li:nth-last-child(3n+1)")));
590+
591+		BENCHMARK(":nth-last-child(2n+1)") {
592+			return testSelector(li, parseSelectorParts("li:nth-last-child(2n+1)"));
593+		};
594+	}
595+
596+	SECTION("testSelector can match a :nth-last-child(n+B)") {
597+		auto li = document->children[0]->children[1]->children[4]->children[3].get();
598+		REQUIRE(testSelector(li, parseSelectorParts(":nth-last-child(n+1)")));
599+		REQUIRE(testSelector(li, parseSelectorParts("li:nth-last-child(n-3)")));
600+		REQUIRE_FALSE(testSelector(li, parseSelectorParts("li:nth-last-child(n+2)")));
601+
602+		BENCHMARK(":nth-last-child(n+1)") {
603+			return testSelector(li, parseSelectorParts("li:nth-last-child(n+1)"));
604+		};
605+	}
606+
607+	SECTION("testSelector can match a :nth-last-child(An+B of type)") {
608+		auto li = document->children[0]->children[1]->children[4]->children[1].get();
609+		REQUIRE(testSelector(li, parseSelectorParts(":nth-last-child(2n+1 of li)")));
610+		REQUIRE_FALSE(testSelector(li, parseSelectorParts(":nth-last-child(2n+1 of ul)")));
611+		REQUIRE_FALSE(testSelector(li, parseSelectorParts(":nth-last-child(3n+1 of li)")));
612+	}
613+
614+	SECTION("testSelector can match a :nth-last-child(A of type)") {
615+		auto li = document->children[0]->children[1]->children[4]->children[2].get();
616+		REQUIRE(testSelector(li, parseSelectorParts(":nth-last-child(2 of li)")));
617+		REQUIRE(testSelector(li, parseSelectorParts("li:nth-last-child(2 of li)")));
618+		REQUIRE_FALSE(testSelector(li, parseSelectorParts("li:nth-last-child(2 of ul)")));
619+
620+		BENCHMARK(":nth-last-child(2 of li)") {
621+			return testSelector(li, parseSelectorParts("li:nth-last-child(2 of li)"));
622+		};
623+	}
624+
625+	SECTION("testSelector can match a :nth-last-child(An of type)") {
626+		auto li = document->children[0]->children[1]->children[4]->children[2].get();
627+		REQUIRE(testSelector(li, parseSelectorParts(":nth-last-child(2n of li)")));
628+		REQUIRE(testSelector(li, parseSelectorParts("li:nth-last-child(2n of li)")));
629+		REQUIRE_FALSE(testSelector(li, parseSelectorParts("li:nth-last-child(2n of ul)")));
630+
631+		BENCHMARK(":nth-last-child(2n of li)") {
632+			return testSelector(li, parseSelectorParts("li:nth-last-child(2n of li)"));
633+		};
634+	}
635 
636-	// need to make for all 4
637+	SECTION("testSelector can match a :nth-last-child(n+B of type)") {
638+		auto li = document->children[0]->children[1]->children[4]->children[3].get();
639+		REQUIRE(testSelector(li, parseSelectorParts(":nth-last-child(n+1 of li)")));
640+		REQUIRE(testSelector(li, parseSelectorParts("li:nth-last-child(n-3 of li)")));
641+		REQUIRE_FALSE(testSelector(li, parseSelectorParts("li:nth-last-child(n+1 of ul)")));
642+
643+		BENCHMARK(":nth-last-child(n+1 of li)") {
644+			return testSelector(li, parseSelectorParts("li:nth-last-child(n+1 of li)"));
645+		};
646+	}
647+	// +------------------------------------------------------+
648+	// |Nth last of type                                      |
649+	// +------------------------------------------------------+
650+	SECTION("testSelector can match a :nth-last-of-type(even|odd)") {
651+		auto li = document->children[0]->children[1]->children[4]->children[3].get();
652+		REQUIRE(testSelector(li, parseSelectorParts(":nth-last-of-type(even)")));
653+		REQUIRE(testSelector(li, parseSelectorParts("li:nth-last-of-type(even)")));
654+		REQUIRE_FALSE(testSelector(li, parseSelectorParts("li:nth-last-of-type(odd)")));
655+	}
656+
657+	SECTION("testSelector can match a :nth-last-of-type(A)") {
658+		auto li = document->children[0]->children[1]->children[4]->children[2].get();
659+		REQUIRE(testSelector(li, parseSelectorParts(":nth-last-of-type(2)")));
660+		REQUIRE(testSelector(li, parseSelectorParts("li:nth-last-of-type(2)")));
661+		REQUIRE_FALSE(testSelector(li, parseSelectorParts("li:nth-last-of-type(1)")));
662+
663+		BENCHMARK(":nth-last-of-type(2)") {
664+			return testSelector(li, parseSelectorParts("li:nth-last-of-type(2)"));
665+		};
666+	}
667+
668+	SECTION("testSelector can match a :nth-last-of-type(An)") {
669+		auto li = document->children[0]->children[1]->children[4]->children[2].get();
670+		REQUIRE(testSelector(li, parseSelectorParts(":nth-last-of-type(2n)")));
671+		REQUIRE(testSelector(li, parseSelectorParts("li:nth-last-of-type(2n)")));
672+		REQUIRE_FALSE(testSelector(li, parseSelectorParts("li:nth-last-of-type(3n)")));
673+
674+		BENCHMARK(":nth-last-of-type(2n)") {
675+			return testSelector(li, parseSelectorParts("li:nth-last-of-type(2n)"));
676+		};
677+	}
678+
679+	SECTION("testSelector can match a :nth-last-of-type(An+B)") {
680+		auto li = document->children[0]->children[1]->children[4]->children[1].get();
681+		REQUIRE(testSelector(li, parseSelectorParts(":nth-last-of-type(2n+1)")));
682+		REQUIRE(testSelector(li, parseSelectorParts("li:nth-last-of-type(2n+1)")));
683+		REQUIRE_FALSE(testSelector(li, parseSelectorParts("li:nth-last-of-type(3n+1)")));
684+
685+		BENCHMARK(":nth-last-of-type(2n+1)") {
686+			return testSelector(li, parseSelectorParts("li:nth-last-of-type(2n+1)"));
687+		};
688+	}
689+
690+	SECTION("testSelector can match a :nth-last-of-type(n+B)") {
691+		auto li = document->children[0]->children[1]->children[4]->children[3].get();
692+		REQUIRE(testSelector(li, parseSelectorParts(":nth-last-of-type(n+1)")));
693+		REQUIRE(testSelector(li, parseSelectorParts("li:nth-last-of-type(n-3)")));
694+		REQUIRE_FALSE(testSelector(li, parseSelectorParts("li:nth-last-of-type(n+2)")));
695+
696+		BENCHMARK(":nth-last-of-type(n+1)") {
697+			return testSelector(li, parseSelectorParts("li:nth-last-of-type(n+1)"));
698+		};
699+	}
700+
701+	// +------------------------------------------------------+
702+	// |Nth of type                                           |
703+	// +------------------------------------------------------+
704+
705+	SECTION("testSelector can match a :nth-of-type(even|odd)") {
706+		auto li = document->children[0]->children[1]->children[4]->children[1].get();
707+		REQUIRE(testSelector(li, parseSelectorParts(":nth-of-type(even)")));
708+		REQUIRE(testSelector(li, parseSelectorParts("li:nth-of-type(even)")));
709+		REQUIRE_FALSE(testSelector(li, parseSelectorParts("li:nth-of-type(odd)")));
710+	}
711+
712+	SECTION("testSelector can match a :nth-of-type(A)") {
713+		auto li = document->children[0]->children[1]->children[4]->children[1].get();
714+		REQUIRE(testSelector(li, parseSelectorParts(":nth-of-type(2)")));
715+		REQUIRE(testSelector(li, parseSelectorParts("li:nth-of-type(2)")));
716+		REQUIRE_FALSE(testSelector(li, parseSelectorParts("li:nth-of-type(1)")));
717+
718+		BENCHMARK(":nth-of-type(2)") {
719+			return testSelector(li, parseSelectorParts("li:nth-of-type(2)"));
720+		};
721+	}
722+
723+	SECTION("testSelector can match a :nth-of-type(An)") {
724+		auto li = document->children[0]->children[1]->children[4]->children[1].get();
725+		REQUIRE(testSelector(li, parseSelectorParts(":nth-of-type(2n)")));
726+		REQUIRE(testSelector(li, parseSelectorParts("li:nth-of-type(2n)")));
727+		REQUIRE_FALSE(testSelector(li, parseSelectorParts("li:nth-of-type(3n)")));
728+
729+		BENCHMARK(":nth-of-type(2n)") {
730+			return testSelector(li, parseSelectorParts("li:nth-of-type(2n)"));
731+		};
732+	}
733+
734+	SECTION("testSelector can match a :nth-of-type(An+B)") {
735+		auto li = document->children[0]->children[1]->children[4]->children[2].get();
736+		REQUIRE(testSelector(li, parseSelectorParts(":nth-of-type(2n+1)")));
737+		REQUIRE(testSelector(li, parseSelectorParts("li:nth-of-type(2n+1)")));
738+		REQUIRE_FALSE(testSelector(li, parseSelectorParts("li:nth-of-type(3n+1)")));
739+
740+		BENCHMARK(":nth-of-type(2n+1)") {
741+			return testSelector(li, parseSelectorParts("li:nth-of-type(2n+1)"));
742+		};
743+	}
744+
745+	SECTION("testSelector can match a :nth-of-type(n+B)") {
746+		auto li = document->children[0]->children[1]->children[4]->children[0].get();
747+		REQUIRE(testSelector(li, parseSelectorParts(":nth-of-type(n+1)")));
748+		REQUIRE(testSelector(li, parseSelectorParts("li:nth-of-type(n-3)")));
749+		REQUIRE_FALSE(testSelector(li, parseSelectorParts("li:nth-of-type(n+2)")));
750+
751+		BENCHMARK(":nth-of-type(n+1)") {
752+			return testSelector(li, parseSelectorParts("li:nth-of-type(n+1)"));
753+		};
754+	}
755+// +------------------------------------------------------+
756+// | Only child                                           |
757+// +------------------------------------------------------+
758+	SECTION("testselector can match :only-child") {
759+		auto p = document->children[0]->children[1]->children[2]->children[0].get();
760+
761+		REQUIRE(testSelector(p, parseSelectorParts(":only-child")));
762+	}
763+
764+	SECTION("testselector can match :only-of-type") {
765+		auto h1 = document->children[0]->children[1]->children[0].get();
766+
767+		REQUIRE(testSelector(h1, parseSelectorParts(":only-of-type")));
768+	}
769+
770+	SECTION("testSelector can match :root") {
771+		auto html = document->children[0].get();
772+		REQUIRE(testSelector(html, parseSelectorParts(":root")));
773+	}
774+
775+// +------------------------------------------------------+
776+// | Input checkes                                        |
777+// +------------------------------------------------------+
778+/*	"<body>"
779+		"<h1 id=\"h1Tag\">Header</h1>"
780+		"<p id=\"paragraph\" class=\"class1 class2\">This is some text for a simple test</p>"
781+		"<div contenteditable=\"true\">"
782+			"<p>this is a only child</p>"
783+		"</div>"
784+		"<input type=\"text\" checked required/>"
785+		"<ul>"
786+			"<li>one</li>"
787+			"<li>two</li>"
788+			"<li>three</li>"
789+			"<li>four</li>"
790+		"</ul>"
791+		"<input type=\"radio\" checked required/>"
792+		"<input type=\"text\" readonly/>"
793+		"<input type=\"text\" disabled/>"
794+		"<textarea></textarea>"
795+		"<textarea readonly></textarea>"
796+		"<textarea disabled></textarea>"
797+	"</body>"*/
798+
799+	SECTION("testSelector can match :read-only normal element") {
800+		auto p = document->children[0]->children[1]->children[1].get();
801+		REQUIRE(testSelector(p, parseSelectorParts("p:read-only")));
802+	}
803+	SECTION("testSelector can match :read-write on a text input") {
804+		auto input = document->children[0]->children[1]->children[3].get();
805+		REQUIRE(testSelector(input, parseSelectorParts("input[type=\"text\"]:read-write")));
806+	}
807+	SECTION("testSelector can match :read-only on a radio input") {
808+		auto input = document->children[0]->children[1]->children[5].get();
809+		REQUIRE(testSelector(input, parseSelectorParts("input[type=\"radio\"]:read-only")));
810+		REQUIRE_FALSE(testSelector(input, parseSelectorParts("input[type=\"radio\"]:read-write")));
811+	}
812+	SECTION("testSelector can match :read-only on a readonly text input") {
813+		auto input = document->children[0]->children[1]->children[6].get();
814+		REQUIRE(testSelector(input, parseSelectorParts("input[type=\"text\"]:read-only")));
815+	}
816+	SECTION("testSelector can match :read-only on a disabled text input") {
817+		auto input = document->children[0]->children[1]->children[7].get();
818+		REQUIRE(testSelector(input, parseSelectorParts("input[type=\"text\"]:read-only")));
819+		REQUIRE(testSelector(input, parseSelectorParts("input[type=\"text\"]:disabled")));
820+	}
821+	SECTION("testSelector can match :read-write on a textarea") {
822+		auto textarea = document->children[0]->children[1]->children[8].get();
823+		REQUIRE(testSelector(textarea, parseSelectorParts(":read-write")));
824+	}
825+	SECTION("testSelector can match :read-only on a readonly textarea") {
826+		auto textarea = document->children[0]->children[1]->children[9].get();
827+		REQUIRE(testSelector(textarea, parseSelectorParts(":read-only")));
828+	}
829+	SECTION("testSelector can match :read-only on a disabled textarea") {
830+		auto textarea = document->children[0]->children[1]->children[10].get();
831+		REQUIRE(testSelector(textarea, parseSelectorParts(":read-only")));
832+	}
833+	SECTION("testSelector can match :read-write on a contenteditable div") {
834+		auto div = document->children[0]->children[1]->children[2].get();
835+		REQUIRE(testSelector(div, parseSelectorParts(":read-write")));
836+	}
837+// +------------------------------------------------------+
838+// | Universal selector                                   |
839+// +------------------------------------------------------+
840+	SECTION("testSelector can match a universal selector") {
841+		auto div = document->children[0]->children[1]->children[2].get();
842+		REQUIRE(testSelector(div, parseSelectorParts("*")));
843+		REQUIRE(testSelector(div, parseSelectorParts("h1, *")));
844+	}
845 	// make for:
846 	// pseudo elements
847 	// *
848-	// nth-child
849-	// nth-*
850 }
851diff --git a/tests/index.html b/tests/index.html
852new file mode 100644
853index 0000000..289b7f3
854--- /dev/null
855+++ b/tests/index.html
856@@ -0,0 +1,25 @@
857+<!DOCTYPE html>
858+<html>
859+	<head>
860+<style>
861+	h1 {
862+		color: red;
863+		content: "<h1>hi</h1>";
864+	}
865+</style>
866+	</head>
867+	<body>
868+	<h1 id="<">
869+		Hello world
870+	</h1>
871+	<br />
872+	<input type="text"/>
873+	<p id="test">
874+		this is a test
875+	</p>
876+	<pre>
877+	<script> console.log("</pre>");</script>
878+	</pre>
879+	<textarea><script>console.log(\"</pre>\");</script></textarea>
880+	</body>
881+</html>
882diff --git a/tests/style.css b/tests/style.css
883new file mode 100644
884index 0000000..2e59320
885--- /dev/null
886+++ b/tests/style.css
887@@ -0,0 +1,7 @@
888+h1 {
889+	color: red;
890+}
891+
892+h1 > #id {
893+	background: purple;
894+}