libhtmlpp 1.0.0
Loading...
Searching...
No Matches
htmlcsstest.cpp
Go to the documentation of this file.
1/*******************************************************************************
2Copyright (c) 2021, Jan Koester jan.koester@gmx.net
3All rights reserved.
4
5Redistribution and use in source and binary forms, with or without
6modification, are permitted provided that the following conditions are met:
7 * Redistributions of source code must retain the above copyright
8 notice, this list of conditions and the following disclaimer.
9 * Redistributions in binary form must reproduce the above copyright
10 notice, this list of conditions and the following disclaimer in the
11 documentation and/or other materials provided with the distribution.
12 * Neither the name of the <organization> nor the
13 names of its contributors may be used to endorse or promote products
14 derived from this software without specific prior written permission.
15
16THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
20DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26*******************************************************************************/
27
28#include <iostream>
29#include <set>
30
31#include "../src/html.h"
32#include "../src/css.h"
33#include "../src/htmlcss.h"
34
35#define Red "\033[0;31m"
36#define Green "\033[0;32m"
37#define NOCOLOR "\033[0m"
38
39static int testCount = 0;
40static int passCount = 0;
41
42static void check(bool cond, const char *desc) {
43 ++testCount;
44 if (cond) {
45 ++passCount;
46 std::cout << Green << " PASS: " << desc << NOCOLOR << std::endl;
47 } else {
48 std::cout << Red << " FAIL: " << desc << NOCOLOR << std::endl;
49 }
50}
51
52int main(){
53 // --- approximateSelectorMatch: tag/class/id/compound ---
54 std::cout << "=== approximateSelectorMatch ===" << std::endl;
55 {
57 std::vector<std::string> classes = {"card", "featured"};
58
59 check(CSSStyleSheet::approximateSelectorMatch("div", "div", classes, "hero"),
60 "bare tag matches");
61 check(!CSSStyleSheet::approximateSelectorMatch("span", "div", classes, "hero"),
62 "wrong tag doesn't match");
63 check(CSSStyleSheet::approximateSelectorMatch(".card", "div", classes, "hero"),
64 "single class matches");
65 check(CSSStyleSheet::approximateSelectorMatch(".card.featured", "div", classes, "hero"),
66 "every class in compound must be present -- matches when all are");
67 check(!CSSStyleSheet::approximateSelectorMatch(".card.missing", "div", classes, "hero"),
68 "every class in compound must be present -- rejects when one is absent");
69 check(CSSStyleSheet::approximateSelectorMatch("#hero", "div", classes, "hero"),
70 "id selector matches");
71 check(CSSStyleSheet::approximateSelectorMatch("div.card.featured#hero", "div", classes, "hero"),
72 "full compound tag.class.class#id matches");
73 check(CSSStyleSheet::approximateSelectorMatch(".other, .card", "div", classes, "hero"),
74 "comma list matches if any branch matches");
75 }
76
77 // --- Unsupported syntax is rejected, never guessed ---
78 std::cout << "=== approximateSelectorMatch rejects unsafe syntax ===" << std::endl;
79 {
81 std::vector<std::string> classes = {"card"};
82 check(!CSSStyleSheet::approximateSelectorMatch("[data-x]", "div", classes, ""),
83 "attribute selector rejected");
84 check(!CSSStyleSheet::approximateSelectorMatch("div:hover", "div", classes, ""),
85 "pseudo-class rejected");
86 check(!CSSStyleSheet::approximateSelectorMatch("*", "div", classes, ""),
87 "universal selector rejected");
88 check(!CSSStyleSheet::approximateSelectorMatch("[data-atom=header]>div", "div", classes, ""),
89 "bare tag left after a combinator is rejected (would over-match every element of that tag)");
90 check(CSSStyleSheet::approximateSelectorMatch("[data-atom=header]>div.card", "div", classes, ""),
91 "a qualified compound after a combinator is still specific enough to check");
92 }
93
94 // --- collectApproximateMatches: cascade priority ---
95 std::cout << "=== collectApproximateMatches cascade ===" << std::endl;
96 {
98 std::string css =
99 ".box { color: red; font-size: 12px; }\n"
100 ".box { color: blue; }\n";
101 CSSStyleSheet sheet;
102 sheet.parse(css);
103
104 std::map<std::string,std::string> props;
105 props["color"] = "inline-value"; // simulates an inline style="" already present
106 std::string mediaRules;
107 std::set<std::string> seen;
108 sheet.collectApproximateMatches("div", "box", "hero", props, mediaRules, seen);
109
110 check(props["color"] == "inline-value",
111 "inline value beats a later plain (non-important) rule");
112 check(props["font-size"] == "12px", "non-conflicting property still merged in");
113 }
114 {
116 std::string css = "#hero { color: green !important; }\n";
117 CSSStyleSheet sheet;
118 sheet.parse(css);
119
120 std::map<std::string,std::string> props;
121 props["color"] = "inline-value";
122 std::string mediaRules;
123 std::set<std::string> seen;
124 sheet.collectApproximateMatches("div", "box", "hero", props, mediaRules, seen);
125
126 check(props["color"] == "green",
127 "an !important rule overrides even an inline-set value (matches real cascade precedence)");
128 }
129 {
131 std::string css =
132 ".box { color: red; }\n"
133 ".box { color: blue; }\n";
134 CSSStyleSheet sheet;
135 sheet.parse(css);
136
137 std::map<std::string,std::string> props; // no inline style
138 std::string mediaRules;
139 std::set<std::string> seen;
140 sheet.collectApproximateMatches("div", "box", "", props, mediaRules, seen);
141
142 check(props["color"] == "blue", "later plain rule wins over an earlier plain rule");
143 }
144 {
146 std::string css =
147 ".box { color: red !important; }\n"
148 ".box { color: blue; }\n";
149 CSSStyleSheet sheet;
150 sheet.parse(css);
151
152 std::map<std::string,std::string> props;
153 std::string mediaRules;
154 std::set<std::string> seen;
155 sheet.collectApproximateMatches("div", "box", "", props, mediaRules, seen);
156
157 check(props["color"] == "red", "!important beats a later plain rule");
158 }
159
160 // --- @media dedup ---
161 std::cout << "=== collectApproximateMatches @media dedup ===" << std::endl;
162 {
164 std::string css = "@media (max-width: 600px) { .box { color: red; } }";
165 CSSStyleSheet sheet;
166 sheet.parse(css);
167
168 std::set<std::string> seen;
169 for (int i = 0; i < 3; ++i) {
170 std::map<std::string,std::string> props;
171 std::string mediaRules;
172 sheet.collectApproximateMatches("div", "box", "", props, mediaRules, seen);
173 check((i == 0) == !mediaRules.empty(),
174 "media block text only appended the first time it's seen");
175 }
176 check(seen.size() == 1, "seenMediaBlocks accumulated exactly one block");
177 }
178
179 // --- collectStyleBlocks + getCSSRules end-to-end ---
180 std::cout << "=== collectStyleBlocks + getCSSRules ===" << std::endl;
181 {
182 std::string html =
183 "<html><head>"
184 "<style>.card { color: red; } #hero { font-size: 20px; }</style>"
185 "</head><body>"
186 "<div id=\"hero\" class=\"card\" style=\"margin: 0\">hi</div>"
187 "</body></html>";
188
189 libhtmlpp::HtmlString htmlString(html);
190 libhtmlpp::Element &root = htmlString.parse();
191
194 check(sheet.getRuleCount() == 2, "collected both <style> block rules");
195
196 libhtmlpp::HtmlElement *target = static_cast<libhtmlpp::HtmlElement*>(&root)->getElementbyID("hero");
197 check(target != nullptr, "found target element by id");
198
199 if (target) {
200 std::set<std::string> seen;
201 auto result = libhtmlpp::getCSSRules(*target, sheet, seen);
202 check(result.properties["color"] == "red", "getCSSRules folds in matching <style> block rule by class");
203 check(result.properties["font-size"] == "20px", "getCSSRules folds in matching <style> block rule by id");
204 check(result.properties["margin"] == "0", "getCSSRules includes the element's own inline style");
205 }
206 }
207
208 // --- resolveCSSVariables ---
209 std::cout << "=== resolveCSSVariables ===" << std::endl;
210 {
211 std::map<std::string,std::string> props = {{"--color-bg", "0,0,0"}, {"--alpha-bg", "1"}};
212 check(libhtmlpp::resolveCSSVariables("rgba(var(--color-bg),var(--alpha-bg))", props) ==
213 "rgba(0,0,0,1)",
214 "simple substitution, multiple var() in one value");
215 }
216 {
217 std::map<std::string,std::string> props;
218 check(libhtmlpp::resolveCSSVariables("var(--undefined, blue)", props) == "blue",
219 "fallback used when the referenced name is unset");
220 }
221 {
222 std::map<std::string,std::string> props;
223 check(libhtmlpp::resolveCSSVariables("var(--undefined)", props) == "",
224 "no fallback and unset name resolves to empty (invalid, per spec)");
225 }
226 {
227 // Mirrors the real-world pattern: --gradient-bg's OWN stored value
228 // references --color-bg/--alpha-bg (multi-hop resolution).
229 std::map<std::string,std::string> props = {
230 {"--color-bg", "10,20,30"},
231 {"--alpha-bg", "0.5"},
232 {"--gradient-bg", "rgba(var(--color-bg),var(--alpha-bg))"},
233 };
234 check(libhtmlpp::resolveCSSVariables("var(--gradient-bg)", props) == "rgba(10,20,30,0.5)",
235 "multi-hop resolution through an intermediate custom property");
236 }
237 {
238 // Fallback itself contains a nested var() -- also resolved.
239 std::map<std::string,std::string> props = {{"--color-bg", "0,0,0"}};
241 "var(--gradient-bg, rgba(var(--color-bg),1))", props) == "rgba(0,0,0,1)",
242 "nested var() inside a fallback value is resolved too");
243 }
244 {
245 // a -> var(b), b -> var(a): cyclic, must not infinite-loop.
246 std::map<std::string,std::string> props = {{"--a", "var(--b)"}, {"--b", "var(--a)"}};
247 check(libhtmlpp::resolveCSSVariables("var(--a)", props) == "",
248 "cyclic custom-property reference resolves to empty instead of looping forever");
249 }
250 {
251 check(libhtmlpp::resolveCSSVariables("10px solid red", {}) == "10px solid red",
252 "a value with no var( at all is returned unchanged");
253 }
254
255 // --- Summary ---
256 std::cout << "\n=== " << passCount << "/" << testCount << " tests passed ===" << std::endl;
257
258 return (passCount == testCount) ? 0 : -1;
259}
void parse(const std::string &input)
Definition css.cpp:484
size_t getRuleCount() const
Definition css.cpp:604
Abstract base class for all nodes in the HTML tree.
Definition html.h:76
libhtmlpp::Element & parse()
Parses the current buffer into a DOM-like tree and returns the root element.
Definition html.cpp:365
#define Green
#define NOCOLOR
int main()
#define Red
void collectStyleBlocks(CSSStyleSheet &sheet, Element &root)
Finds every <style> element anywhere in the subtree rooted at root (document order) and appends each ...
Definition htmlcss.cpp:67
std::string resolveCSSVariables(const std::string &value, const std::map< std::string, std::string > &customProperties)
Resolves every var(--name) / var(--name, fallback) reference in value using customProperties (custom-...
Definition css.cpp:757
CSSRuleResult getCSSRules(HtmlElement &target, const CSSStyleSheet &sheet, std::set< std::string > &seenMediaBlocks)
Returns every CSS rule that applies to target: its own inline style="..." attribute,...
Definition htmlcss.cpp:116