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+}