54 std::cout <<
"=== approximateSelectorMatch ===" << std::endl;
57 std::vector<std::string> classes = {
"card",
"featured"};
59 check(CSSStyleSheet::approximateSelectorMatch(
"div",
"div", classes,
"hero"),
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");
78 std::cout <<
"=== approximateSelectorMatch rejects unsafe syntax ===" << std::endl;
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");
102 std::cout <<
"=== approximateSelectorMatch with ancestor chain ===" << std::endl;
106 std::vector<std::string> classes = {
"frame"};
108 check(CSSStyleSheet::approximateSelectorMatch(
".header .frame",
"div", classes,
""),
109 "with no ancestor chain given (nullptr), leading compounds are still ignored -- unchanged behavior");
111 std::vector<AncestorFrame> matchingAncestors = {
112 {
"div", {
"header"},
""},
114 check(CSSStyleSheet::approximateSelectorMatch(
".header .frame",
"div", classes,
"", &matchingAncestors),
115 "leading compound found in the supplied ancestor chain -- matches");
117 std::vector<AncestorFrame> unrelatedAncestors = {
118 {
"div", {
"hero"},
""},
120 check(!CSSStyleSheet::approximateSelectorMatch(
".header .frame",
"div", classes,
"", &unrelatedAncestors),
121 "leading compound absent from the supplied ancestor chain -- no longer matches (the actual bug)");
123 check(CSSStyleSheet::approximateSelectorMatch(
".header .frame",
"div", classes,
"",
124 static_cast<const std::vector<AncestorFrame>*
>(
nullptr)),
125 "sanity: explicit nullptr behaves the same as the default (still matches, unverified)");
127 std::vector<AncestorFrame> outOfOrderAncestors = {
131 check(!CSSStyleSheet::approximateSelectorMatch(
".a .b .frame",
"div", classes,
"", &outOfOrderAncestors),
132 "ancestor compounds must be found in left-to-right order, not just anywhere in the chain");
134 std::vector<AncestorFrame> inOrderAncestors = {
138 check(CSSStyleSheet::approximateSelectorMatch(
".a .b .frame",
"div", classes,
"", &inOrderAncestors),
139 "same two ancestors in the correct left-to-right order do match");
141 std::vector<AncestorFrame> onlyOuterAncestor = {
144 check(!CSSStyleSheet::approximateSelectorMatch(
".a .b .frame",
"div", classes,
"", &onlyOuterAncestor),
145 "a missing middle ancestor compound still fails the match");
149 std::cout <<
"=== collectApproximateMatches cascade ===" << std::endl;
153 ".box { color: red; font-size: 12px; }\n"
154 ".box { color: blue; }\n";
158 std::map<std::string,std::string> props;
159 props[
"color"] =
"inline-value";
160 std::string mediaRules;
161 std::set<std::string> seen;
162 sheet.collectApproximateMatches(
"div",
"box",
"hero", props, mediaRules, seen);
164 check(props[
"color"] ==
"inline-value",
165 "inline value beats a later plain (non-important) rule");
166 check(props[
"font-size"] ==
"12px",
"non-conflicting property still merged in");
170 std::string css =
"#hero { color: green !important; }\n";
174 std::map<std::string,std::string> props;
175 props[
"color"] =
"inline-value";
176 std::string mediaRules;
177 std::set<std::string> seen;
178 sheet.collectApproximateMatches(
"div",
"box",
"hero", props, mediaRules, seen);
180 check(props[
"color"] ==
"green",
181 "an !important rule overrides even an inline-set value (matches real cascade precedence)");
186 ".box { color: red; }\n"
187 ".box { color: blue; }\n";
191 std::map<std::string,std::string> props;
192 std::string mediaRules;
193 std::set<std::string> seen;
194 sheet.collectApproximateMatches(
"div",
"box",
"", props, mediaRules, seen);
196 check(props[
"color"] ==
"blue",
"later plain rule wins over an earlier plain rule");
201 ".box { color: red !important; }\n"
202 ".box { color: blue; }\n";
206 std::map<std::string,std::string> props;
207 std::string mediaRules;
208 std::set<std::string> seen;
209 sheet.collectApproximateMatches(
"div",
"box",
"", props, mediaRules, seen);
211 check(props[
"color"] ==
"red",
"!important beats a later plain rule");
215 std::cout <<
"=== collectApproximateMatches @media dedup ===" << std::endl;
218 std::string css =
"@media (max-width: 600px) { .box { color: red; } }";
222 std::set<std::string> seen;
223 for (
int i = 0; i < 3; ++i) {
224 std::map<std::string,std::string> props;
225 std::string mediaRules;
226 sheet.collectApproximateMatches(
"div",
"box",
"", props, mediaRules, seen);
227 check((i == 0) == !mediaRules.empty(),
228 "media block text only appended the first time it's seen");
230 check(seen.size() == 1,
"seenMediaBlocks accumulated exactly one block");
234 std::cout <<
"=== collectStyleBlocks + getCSSRules ===" << std::endl;
238 "<style>.card { color: red; } #hero { font-size: 20px; }</style>"
240 "<div id=\"hero\" class=\"card\" style=\"margin: 0\">hi</div>"
248 check(sheet.
getRuleCount() == 2,
"collected both <style> block rules");
251 check(target !=
nullptr,
"found target element by id");
254 std::set<std::string> seen;
256 check(result.properties[
"color"] ==
"red",
"getCSSRules folds in matching <style> block rule by class");
257 check(result.properties[
"font-size"] ==
"20px",
"getCSSRules folds in matching <style> block rule by id");
258 check(result.properties[
"margin"] ==
"0",
"getCSSRules includes the element's own inline style");
263 std::cout <<
"=== getCSSRules with ancestor chain ===" << std::endl;
269 ".frame { padding-top: 100px; }"
270 ".header .frame { padding-top: 0; }"
273 "<div class=\"header\"><div id=\"headerFrame\" class=\"frame\">hi</div></div>"
274 "<div class=\"hero\"><div id=\"heroFrame\" class=\"frame\">hi</div></div>"
287 check(headerFrame !=
nullptr && heroFrame !=
nullptr,
"found both target elements by id");
289 if (headerFrame && heroFrame) {
290 std::set<std::string> seen;
292 check(noAncestorsResult.properties[
"padding-top"] ==
"0",
293 "without an ancestor chain, the header-only rule still bleeds onto an unrelated "
294 "element -- this is the pre-fix behavior, unchanged for callers that pass none");
296 std::vector<AncestorFrame> headerAncestors = {{
"div", {
"header"},
""}};
297 std::set<std::string> seen2;
299 check(headerResult.properties[
"padding-top"] ==
"0",
300 "with its real ancestor chain, the header's own frame still gets the header-only rule");
302 std::vector<AncestorFrame> heroAncestors = {{
"div", {
"hero"},
""}};
303 std::set<std::string> seen3;
305 check(heroResult.properties[
"padding-top"] ==
"100px",
306 "with its real (non-header) ancestor chain, the hero's frame keeps the general "
307 "rule instead of the header-only one -- the actual bug fix");
312 std::cout <<
"=== resolveCSSVariables ===" << std::endl;
314 std::map<std::string,std::string> props = {{
"--color-bg",
"0,0,0"}, {
"--alpha-bg",
"1"}};
317 "simple substitution, multiple var() in one value");
320 std::map<std::string,std::string> props;
322 "fallback used when the referenced name is unset");
325 std::map<std::string,std::string> props;
327 "no fallback and unset name resolves to empty (invalid, per spec)");
332 std::map<std::string,std::string> props = {
333 {
"--color-bg",
"10,20,30"},
334 {
"--alpha-bg",
"0.5"},
335 {
"--gradient-bg",
"rgba(var(--color-bg),var(--alpha-bg))"},
338 "multi-hop resolution through an intermediate custom property");
342 std::map<std::string,std::string> props = {{
"--color-bg",
"0,0,0"}};
344 "var(--gradient-bg, rgba(var(--color-bg),1))", props) ==
"rgba(0,0,0,1)",
345 "nested var() inside a fallback value is resolved too");
349 std::map<std::string,std::string> props = {{
"--a",
"var(--b)"}, {
"--b",
"var(--a)"}};
351 "cyclic custom-property reference resolves to empty instead of looping forever");
355 "a value with no var( at all is returned unchanged");
359 std::cout <<
"\n=== " << passCount <<
"/" << testCount <<
" tests passed ===" << std::endl;
361 return (passCount == testCount) ? 0 : -1;