1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28#include <QtTest/QtTest>
29#include <QtXml/QtXml>
30#include <QtGui/QFontInfo>
31#include <QtGui/QFontMetrics>
32
33#include "private/qcssparser_p.h"
34
35class tst_QCssParser : public QObject
36{
37 Q_OBJECT
38
39private slots:
40 void scanner_data();
41 void scanner();
42 void term_data();
43 void term();
44 void expr_data();
45 void expr();
46 void import();
47 void media();
48 void page();
49 void ruleset();
50 void selector_data();
51 void selector();
52 void prio();
53 void escapes();
54 void malformedDeclarations_data();
55 void malformedDeclarations();
56 void invalidAtKeywords();
57 void marginValue();
58 void marginValue_data();
59 void colorValue_data();
60 void colorValue();
61 void styleSelector_data();
62 void styleSelector();
63 void specificity_data();
64 void specificity();
65 void specificitySort_data();
66 void specificitySort();
67 void rulesForNode_data();
68 void rulesForNode();
69 void shorthandBackgroundProperty_data();
70 void shorthandBackgroundProperty();
71 void pseudoElement_data();
72 void pseudoElement();
73 void gradient_data();
74 void gradient();
75 void extractFontFamily_data();
76 void extractFontFamily();
77 void extractBorder_data();
78 void extractBorder();
79 void noTextDecoration();
80 void quotedAndUnquotedIdentifiers();
81 void whitespaceValues_data();
82 void whitespaceValues();
83};
84
85void tst_QCssParser::scanner_data()
86{
87 QTest::addColumn<QString>(name: "input");
88 QTest::addColumn<QString>(name: "output");
89
90#if defined(Q_OS_ANDROID) || defined(Q_OS_WINRT)
91 QDir d(":/");
92#else
93 QDir d(SRCDIR);
94#endif
95 d.cd(dirName: "testdata");
96 d.cd(dirName: "scanner");
97 foreach (QFileInfo test, d.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) {
98 QString dir = test.absoluteFilePath() + QDir::separator();
99 QTest::newRow(qPrintable(test.baseName()))
100 << dir + "input"
101 << dir + "output"
102 ;
103 }
104}
105
106
107static const char *tokenName(QCss::TokenType t)
108{
109 switch (t) {
110 case QCss::NONE: return "NONE";
111 case QCss::S: return "S";
112 case QCss::CDO: return "CDO";
113 case QCss::CDC: return "CDC";
114 case QCss::INCLUDES: return "INCLUDES";
115 case QCss::DASHMATCH: return "DASHMATCH";
116 case QCss::BEGINSWITH: return "BEGINSWITH";
117 case QCss::ENDSWITH: return "ENDSWITH";
118 case QCss::CONTAINS: return "CONTAINS";
119 case QCss::LBRACE: return "LBRACE";
120 case QCss::PLUS: return "PLUS";
121 case QCss::GREATER: return "GREATER";
122 case QCss::COMMA: return "COMMA";
123 case QCss::TILDE: return "TILDE";
124 case QCss::STRING: return "STRING";
125 case QCss::INVALID: return "INVALID";
126 case QCss::IDENT: return "IDENT";
127 case QCss::HASH: return "HASH";
128 case QCss::ATKEYWORD_SYM: return "ATKEYWORD_SYM";
129 case QCss::EXCLAMATION_SYM: return "EXCLAMATION_SYM";
130 case QCss::LENGTH: return "LENGTH";
131 case QCss::PERCENTAGE: return "PERCENTAGE";
132 case QCss::NUMBER: return "NUMBER";
133 case QCss::FUNCTION: return "FUNCTION";
134 case QCss::COLON: return "COLON";
135 case QCss::SEMICOLON: return "SEMICOLON";
136 case QCss::RBRACE: return "RBRACE";
137 case QCss::SLASH: return "SLASH";
138 case QCss::MINUS: return "MINUS";
139 case QCss::DOT: return "DOT";
140 case QCss::STAR: return "STAR";
141 case QCss::LBRACKET: return "LBRACKET";
142 case QCss::RBRACKET: return "RBRACKET";
143 case QCss::EQUAL: return "EQUAL";
144 case QCss::LPAREN: return "LPAREN";
145 case QCss::RPAREN: return "RPAREN";
146 case QCss::OR: return "OR";
147 }
148 return "";
149}
150
151static void debug(const QVector<QCss::Symbol> &symbols, int index = -1)
152{
153 qDebug() << "all symbols:";
154 for (int i = 0; i < symbols.count(); ++i)
155 qDebug() << '(' << i << "); Token:" << tokenName(t: symbols.at(i).token) << "; Lexem:" << symbols.at(i).lexem();
156 if (index != -1)
157 qDebug() << "failure at index" << index;
158}
159
160//static void debug(const QCss::Parser &p) { debug(p.symbols); }
161
162void tst_QCssParser::scanner()
163{
164 QFETCH(QString, input);
165 QFETCH(QString, output);
166
167 QFile inputFile(input);
168 QVERIFY(inputFile.open(QIODevice::ReadOnly|QIODevice::Text));
169 QVector<QCss::Symbol> symbols;
170 QCss::Scanner::scan(preprocessedInput: QCss::Scanner::preprocess(input: QString::fromUtf8(str: inputFile.readAll())), symbols: &symbols);
171
172 QVERIFY(symbols.count() > 1);
173 QCOMPARE(symbols.last().token, QCss::S);
174 QCOMPARE(symbols.last().lexem(), QLatin1String("\n"));
175 symbols.remove(i: symbols.count() - 1, n: 1);
176
177 QFile outputFile(output);
178 QVERIFY(outputFile.open(QIODevice::ReadOnly|QIODevice::Text));
179 QStringList lines;
180 while (!outputFile.atEnd()) {
181 QString line = QString::fromUtf8(str: outputFile.readLine());
182 if (line.endsWith(c: QLatin1Char('\n')))
183 line.chop(n: 1);
184 lines.append(t: line);
185 }
186
187 if (lines.count() != symbols.count()) {
188 debug(symbols);
189 QCOMPARE(lines.count(), symbols.count());
190 }
191
192 for (int i = 0; i < lines.count(); ++i) {
193 QStringList l = lines.at(i).split(sep: QChar::fromLatin1(c: '|'));
194 QCOMPARE(l.count(), 2);
195 const QString expectedToken = l.at(i: 0);
196 const QString expectedLexem = l.at(i: 1);
197 QString actualToken = QString::fromLatin1(str: tokenName(t: symbols.at(i).token));
198 if (actualToken != expectedToken) {
199 debug(symbols, index: i);
200 QCOMPARE(actualToken, expectedToken);
201 }
202 if (symbols.at(i).lexem() != expectedLexem) {
203 debug(symbols, index: i);
204 QCOMPARE(symbols.at(i).lexem(), expectedLexem);
205 }
206 }
207}
208
209Q_DECLARE_METATYPE(QCss::Value)
210
211void tst_QCssParser::term_data()
212{
213 QTest::addColumn<bool>(name: "parseSuccess");
214 QTest::addColumn<QString>(name: "css");
215 QTest::addColumn<QCss::Value>(name: "expectedValue");
216
217 QCss::Value val;
218
219 val.type = QCss::Value::Percentage;
220 val.variant = QVariant(double(200));
221 QTest::newRow(dataTag: "percentage") << true << "200%" << val;
222
223 val.type = QCss::Value::Length;
224 val.variant = QString("10px");
225 QTest::newRow(dataTag: "px") << true << "10px" << val;
226
227 val.type = QCss::Value::Length;
228 val.variant = QString("10cm");
229 QTest::newRow(dataTag: "cm") << true << "10cm" << val;
230
231 val.type = QCss::Value::Length;
232 val.variant = QString("10mm");
233 QTest::newRow(dataTag: "mm") << true << "10mm" << val;
234
235 val.type = QCss::Value::Length;
236 val.variant = QString("10pt");
237 QTest::newRow(dataTag: "pt") << true << "10pt" << val;
238
239 val.type = QCss::Value::Length;
240 val.variant = QString("10pc");
241 QTest::newRow(dataTag: "pc") << true << "10pc" << val;
242
243 val.type = QCss::Value::Length;
244 val.variant = QString("42in");
245 QTest::newRow(dataTag: "inch") << true << "42in" << val;
246
247 val.type = QCss::Value::Length;
248 val.variant = QString("10deg");
249 QTest::newRow(dataTag: "deg") << true << "10deg" << val;
250
251 val.type = QCss::Value::Length;
252 val.variant = QString("10rad");
253 QTest::newRow(dataTag: "rad") << true << "10rad" << val;
254
255 val.type = QCss::Value::Length;
256 val.variant = QString("10grad");
257 QTest::newRow(dataTag: "grad") << true << "10grad" << val;
258
259 val.type = QCss::Value::Length;
260 val.variant = QString("10ms");
261 QTest::newRow(dataTag: "time") << true << "10ms" << val;
262
263 val.type = QCss::Value::Length;
264 val.variant = QString("10s");
265 QTest::newRow(dataTag: "times") << true << "10s" << val;
266
267 val.type = QCss::Value::Length;
268 val.variant = QString("10hz");
269 QTest::newRow(dataTag: "hz") << true << "10hz" << val;
270
271 val.type = QCss::Value::Length;
272 val.variant = QString("10khz");
273 QTest::newRow(dataTag: "khz") << true << "10khz" << val;
274
275 val.type = QCss::Value::Length;
276 val.variant = QString("10myunit");
277 QTest::newRow(dataTag: "dimension") << true << "10myunit" << val;
278
279 val.type = QCss::Value::Percentage;
280
281 val.type = QCss::Value::Percentage;
282 val.variant = QVariant(double(-200));
283 QTest::newRow(dataTag: "minuspercentage") << true << "-200%" << val;
284
285 val.type = QCss::Value::Length;
286 val.variant = QString("10em");
287 QTest::newRow(dataTag: "ems") << true << "10em" << val;
288
289 val.type = QCss::Value::String;
290 val.variant = QVariant(QString("foo"));
291 QTest::newRow(dataTag: "string") << true << "\"foo\"" << val;
292
293 val.type = QCss::Value::Function;
294 val.variant = QVariant(QStringList() << "myFunc" << "23, (nested text)");
295 QTest::newRow(dataTag: "function") << true << "myFunc(23, (nested text))" << val;
296
297 QTest::newRow(dataTag: "function_failure") << false << "myFunction((blah)" << val;
298 QTest::newRow(dataTag: "function_failure2") << false << "+myFunc(23, (nested text))" << val;
299
300 val.type = QCss::Value::Color;
301 val.variant = QVariant(QColor("#12ff34"));
302 QTest::newRow(dataTag: "hexcolor") << true << "#12ff34" << val;
303
304 val.type = QCss::Value::Color;
305 val.variant = QVariant(QColor("#ffbb00"));
306 QTest::newRow(dataTag: "hexcolor2") << true << "#fb0" << val;
307
308 val.type = QCss::Value::Uri;
309 val.variant = QString("www.kde.org");
310 QTest::newRow(dataTag: "uri1") << true << "url(\"www.kde.org\")" << val;
311
312 QTest::newRow(dataTag: "uri2") << true << "url(www.kde.org)" << val;
313
314 val.type = QCss::Value::KnownIdentifier;
315 val.variant = int(QCss::Value_Italic);
316 QTest::newRow(dataTag: "italic") << true << "italic" << val;
317
318 val.type = QCss::Value::KnownIdentifier;
319 val.variant = int(QCss::Value_Italic);
320 QTest::newRow(dataTag: "ItaLIc") << true << "ItaLIc" << val;
321}
322
323void tst_QCssParser::term()
324{
325 QFETCH(bool, parseSuccess);
326 QFETCH(QString, css);
327 QFETCH(QCss::Value, expectedValue);
328
329 QCss::Parser parser(css);
330 QCss::Value val;
331 QVERIFY(parser.testTerm());
332 QCOMPARE(parser.parseTerm(&val), parseSuccess);
333 if (parseSuccess) {
334 QCOMPARE(int(val.type), int(expectedValue.type));
335 if (val.variant != expectedValue.variant) {
336 qDebug() << "val.variant:" << val.variant << "expectedValue.variant:" << expectedValue.variant;
337 QCOMPARE(val.variant, expectedValue.variant);
338 }
339 }
340}
341
342void tst_QCssParser::expr_data()
343{
344 QTest::addColumn<bool>(name: "parseSuccess");
345 QTest::addColumn<QString>(name: "css");
346 QTest::addColumn<QVector<QCss::Value> >(name: "expectedValues");
347
348 QVector<QCss::Value> values;
349 QCss::Value val;
350
351 QCss::Value comma;
352 comma.type = QCss::Value::TermOperatorComma;
353
354 val.type = QCss::Value::Identifier;
355 val.variant = QLatin1String("foo");
356 values << val;
357 values << comma;
358 val.variant = QLatin1String("bar");
359 values << val;
360 values << comma;
361 val.variant = QLatin1String("baz");
362 values << val;
363 QTest::newRow(dataTag: "list") << true << "foo, bar, baz" << values;
364 values.clear();
365}
366
367void tst_QCssParser::expr()
368{
369 QFETCH(bool, parseSuccess);
370 QFETCH(QString, css);
371 QFETCH(QVector<QCss::Value>, expectedValues);
372
373 QCss::Parser parser(css);
374 QVector<QCss::Value> values;
375 QVERIFY(parser.testExpr());
376 QCOMPARE(parser.parseExpr(&values), parseSuccess);
377 if (parseSuccess) {
378 QCOMPARE(values.count(), expectedValues.count());
379
380 for (int i = 0; i < values.count(); ++i) {
381 QCOMPARE(int(values.at(i).type), int(expectedValues.at(i).type));
382 QCOMPARE(values.at(i).variant, expectedValues.at(i).variant);
383 }
384 }
385}
386
387void tst_QCssParser::import()
388{
389 QCss::Parser parser("@import \"plainstring\";");
390 QVERIFY(parser.testImport());
391 QCss::ImportRule rule;
392 QVERIFY(parser.parseImport(&rule));
393 QCOMPARE(rule.href, QString("plainstring"));
394
395 parser = QCss::Parser("@import url(\"www.kde.org\") print/*comment*/,screen;");
396 QVERIFY(parser.testImport());
397 QVERIFY(parser.parseImport(&rule));
398 QCOMPARE(rule.href, QString("www.kde.org"));
399 QCOMPARE(rule.media.count(), 2);
400 QCOMPARE(rule.media.at(0), QString("print"));
401 QCOMPARE(rule.media.at(1), QString("screen"));
402}
403
404void tst_QCssParser::media()
405{
406 QCss::Parser parser("@media print/*comment*/,screen /*comment to ignore*/{ }");
407 QVERIFY(parser.testMedia());
408 QCss::MediaRule rule;
409 QVERIFY(parser.parseMedia(&rule));
410 QCOMPARE(rule.media.count(), 2);
411 QCOMPARE(rule.media.at(0), QString("print"));
412 QCOMPARE(rule.media.at(1), QString("screen"));
413 QVERIFY(rule.styleRules.isEmpty());
414}
415
416void tst_QCssParser::page()
417{
418 QCss::Parser parser("@page :first/*comment to ignore*/{ }");
419 QVERIFY(parser.testPage());
420 QCss::PageRule rule;
421 QVERIFY(parser.parsePage(&rule));
422 QCOMPARE(rule.selector, QString("first"));
423 QVERIFY(rule.declarations.isEmpty());
424}
425
426void tst_QCssParser::ruleset()
427{
428 {
429 QCss::Parser parser("p/*foo*/{ }");
430 QVERIFY(parser.testRuleset());
431 QCss::StyleRule rule;
432 QVERIFY(parser.parseRuleset(&rule));
433 QCOMPARE(rule.selectors.count(), 1);
434 QCOMPARE(rule.selectors.at(0).basicSelectors.count(), 1);
435 QCOMPARE(rule.selectors.at(0).basicSelectors.at(0).elementName, QString("p"));
436 QVERIFY(rule.declarations.isEmpty());
437 }
438
439 {
440 QCss::Parser parser("p/*comment*/,div{ }");
441 QVERIFY(parser.testRuleset());
442 QCss::StyleRule rule;
443 QVERIFY(parser.parseRuleset(&rule));
444 QCOMPARE(rule.selectors.count(), 2);
445 QCOMPARE(rule.selectors.at(0).basicSelectors.count(), 1);
446 QCOMPARE(rule.selectors.at(0).basicSelectors.at(0).elementName, QString("p"));
447 QCOMPARE(rule.selectors.at(1).basicSelectors.count(), 1);
448 QCOMPARE(rule.selectors.at(1).basicSelectors.at(0).elementName, QString("div"));
449 QVERIFY(rule.declarations.isEmpty());
450 }
451
452 {
453 QCss::Parser parser(":before, :after { }");
454 QVERIFY(parser.testRuleset());
455 QCss::StyleRule rule;
456 QVERIFY(parser.parseRuleset(&rule));
457 QCOMPARE(rule.selectors.count(), 2);
458
459 QCOMPARE(rule.selectors.at(0).basicSelectors.count(), 1);
460 QCOMPARE(rule.selectors.at(0).basicSelectors.at(0).pseudos.count(), 1);
461 QCOMPARE(rule.selectors.at(0).basicSelectors.at(0).pseudos.at(0).name, QString("before"));
462
463 QCOMPARE(rule.selectors.at(1).basicSelectors.count(), 1);
464 QCOMPARE(rule.selectors.at(1).basicSelectors.at(0).pseudos.count(), 1);
465 QCOMPARE(rule.selectors.at(1).basicSelectors.at(0).pseudos.at(0).name, QString("after"));
466
467 QVERIFY(rule.declarations.isEmpty());
468 }
469
470}
471
472Q_DECLARE_METATYPE(QCss::Selector)
473
474void tst_QCssParser::selector_data()
475{
476 QTest::addColumn<QString>(name: "css");
477 QTest::addColumn<QCss::Selector>(name: "expectedSelector");
478
479 {
480 QCss::Selector sel;
481 QCss::BasicSelector basic;
482
483 basic.elementName = "p";
484 basic.relationToNext = QCss::BasicSelector::MatchNextSelectorIfDirectAdjecent;
485 sel.basicSelectors << basic;
486
487 basic = QCss::BasicSelector();
488 basic.elementName = "div";
489 sel.basicSelectors << basic;
490
491 QTest::newRow(dataTag: "comment") << QString("p/* */+ div") << sel;
492 }
493
494 {
495 QCss::Selector sel;
496 QCss::BasicSelector basic;
497
498 basic.elementName = QString();
499 sel.basicSelectors << basic;
500
501 QTest::newRow(dataTag: "any") << QString("*") << sel;
502 }
503
504 {
505 QCss::Selector sel;
506 QCss::BasicSelector basic;
507
508 basic.elementName = "e";
509 sel.basicSelectors << basic;
510
511 QTest::newRow(dataTag: "element") << QString("e") << sel;
512 }
513
514 {
515 QCss::Selector sel;
516 QCss::BasicSelector basic;
517
518 basic.elementName = "e";
519 basic.relationToNext = QCss::BasicSelector::MatchNextSelectorIfAncestor;
520 sel.basicSelectors << basic;
521
522 basic.elementName = "f";
523 basic.relationToNext = QCss::BasicSelector::NoRelation;
524 sel.basicSelectors << basic;
525
526 QTest::newRow(dataTag: "descendant") << QString("e f") << sel;
527 }
528
529 {
530 QCss::Selector sel;
531 QCss::BasicSelector basic;
532
533 basic.elementName = "e";
534 basic.relationToNext = QCss::BasicSelector::MatchNextSelectorIfParent;
535 sel.basicSelectors << basic;
536
537 basic.elementName = "f";
538 basic.relationToNext = QCss::BasicSelector::NoRelation;
539 sel.basicSelectors << basic;
540
541 QTest::newRow(dataTag: "parent") << QString("e > f") << sel;
542 }
543
544 {
545 QCss::Selector sel;
546 QCss::BasicSelector basic;
547
548 basic.elementName = "e";
549 QCss::Pseudo pseudo;
550 pseudo.name = "first-child";
551 basic.pseudos.append(t: pseudo);
552 sel.basicSelectors << basic;
553
554 QTest::newRow(dataTag: "first-child") << QString("e:first-child") << sel;
555 }
556
557 {
558 QCss::Selector sel;
559 QCss::BasicSelector basic;
560
561 basic.elementName = "e";
562 QCss::Pseudo pseudo;
563 pseudo.name = "c";
564 pseudo.function = "lang";
565 basic.pseudos.append(t: pseudo);
566 sel.basicSelectors << basic;
567
568 QTest::newRow(dataTag: "lang") << QString("e:lang(c)") << sel;
569 }
570
571 {
572 QCss::Selector sel;
573 QCss::BasicSelector basic;
574
575 basic.elementName = "e";
576 basic.relationToNext = QCss::BasicSelector::MatchNextSelectorIfDirectAdjecent;
577 sel.basicSelectors << basic;
578
579 basic.elementName = "f";
580 basic.relationToNext = QCss::BasicSelector::NoRelation;
581 sel.basicSelectors << basic;
582
583 QTest::newRow(dataTag: "lastsibling") << QString("e + f") << sel;
584 }
585
586 {
587 QCss::Selector sel;
588 QCss::BasicSelector basic;
589
590 basic.elementName = "e";
591 basic.relationToNext = QCss::BasicSelector::MatchNextSelectorIfIndirectAdjecent;
592 sel.basicSelectors << basic;
593
594 basic.elementName = "f";
595 basic.relationToNext = QCss::BasicSelector::NoRelation;
596 sel.basicSelectors << basic;
597
598 QTest::newRow(dataTag: "previoussibling") << QString("e ~ f") << sel;
599 }
600
601 {
602 QCss::Selector sel;
603 QCss::BasicSelector basic;
604
605 basic.elementName = "e";
606 QCss::AttributeSelector attrSel;
607 attrSel.name = "foo";
608 basic.attributeSelectors << attrSel;
609 sel.basicSelectors << basic;
610
611 QTest::newRow(dataTag: "attr") << QString("e[foo]") << sel;
612 }
613
614 {
615 QCss::Selector sel;
616 QCss::BasicSelector basic;
617
618 basic.elementName = "e";
619 QCss::AttributeSelector attrSel;
620 attrSel.name = "foo";
621 attrSel.value = "warning";
622 attrSel.valueMatchCriterium = QCss::AttributeSelector::MatchEqual;
623 basic.attributeSelectors << attrSel;
624 sel.basicSelectors << basic;
625
626 QTest::newRow(dataTag: "attr-equal") << QString("e[foo=\"warning\"]") << sel;
627 }
628
629 {
630 QCss::Selector sel;
631 QCss::BasicSelector basic;
632
633 basic.elementName = "e";
634 QCss::AttributeSelector attrSel;
635 attrSel.name = "foo";
636 attrSel.value = "warning";
637 attrSel.valueMatchCriterium = QCss::AttributeSelector::MatchIncludes;
638 basic.attributeSelectors << attrSel;
639 sel.basicSelectors << basic;
640
641 QTest::newRow(dataTag: "attr-includes") << QString("e[foo~=\"warning\"]") << sel;
642 }
643
644 {
645 QCss::Selector sel;
646 QCss::BasicSelector basic;
647
648 basic.elementName = "e";
649 QCss::AttributeSelector attrSel;
650 attrSel.name = "lang";
651 attrSel.value = "en";
652 attrSel.valueMatchCriterium = QCss::AttributeSelector::MatchDashMatch;
653 basic.attributeSelectors << attrSel;
654 sel.basicSelectors << basic;
655
656 QTest::newRow(dataTag: "attr-dash") << QString("e[lang|=\"en\"]") << sel;
657 }
658
659 {
660 QCss::Selector sel;
661 QCss::BasicSelector basic;
662
663 basic.elementName = "e";
664 QCss::AttributeSelector attrSel;
665 attrSel.name = "foo";
666 attrSel.value = "warning";
667 attrSel.valueMatchCriterium = QCss::AttributeSelector::MatchContains;
668 basic.attributeSelectors << attrSel;
669 sel.basicSelectors << basic;
670
671 QTest::newRow(dataTag: "attr-contains") << QString("e[foo*=\"warning\"]") << sel;
672 }
673
674 {
675 QCss::Selector sel;
676 QCss::BasicSelector basic;
677
678 basic.elementName = "div";
679
680 QCss::AttributeSelector attrSel;
681 attrSel.name = "class";
682 attrSel.valueMatchCriterium = QCss::AttributeSelector::MatchIncludes;
683 attrSel.value = "warning";
684 basic.attributeSelectors.append(t: attrSel);
685
686 attrSel.value = "foo";
687 basic.attributeSelectors.append(t: attrSel);
688
689 sel.basicSelectors << basic;
690
691 QTest::newRow(dataTag: "class") << QString("div.warning.foo") << sel;
692 }
693
694 {
695 QCss::Selector sel;
696 QCss::BasicSelector basic;
697
698 basic.elementName = "e";
699 basic.ids << "myid";
700 sel.basicSelectors << basic;
701
702 QTest::newRow(dataTag: "id") << QString("e#myid") << sel;
703 }
704}
705
706void tst_QCssParser::selector()
707{
708 QFETCH(QString, css);
709 QFETCH(QCss::Selector, expectedSelector);
710
711 QCss::Parser parser(css);
712 QVERIFY(parser.testSelector());
713 QCss::Selector selector;
714 QVERIFY(parser.parseSelector(&selector));
715
716 QCOMPARE(selector.basicSelectors.count(), expectedSelector.basicSelectors.count());
717 for (int i = 0; i < selector.basicSelectors.count(); ++i) {
718 const QCss::BasicSelector sel = selector.basicSelectors.at(i);
719 const QCss::BasicSelector expectedSel = expectedSelector.basicSelectors.at(i);
720 QCOMPARE(sel.elementName, expectedSel.elementName);
721 QCOMPARE(int(sel.relationToNext), int(expectedSel.relationToNext));
722
723 QCOMPARE(sel.pseudos.count(), expectedSel.pseudos.count());
724 for (int i = 0; i < sel.pseudos.count(); ++i) {
725 QCOMPARE(sel.pseudos.at(i).name, expectedSel.pseudos.at(i).name);
726 QCOMPARE(sel.pseudos.at(i).function, expectedSel.pseudos.at(i).function);
727 }
728
729 QCOMPARE(sel.attributeSelectors.count(), expectedSel.attributeSelectors.count());
730 for (int i = 0; i < sel.attributeSelectors.count(); ++i) {
731 QCOMPARE(sel.attributeSelectors.at(i).name, expectedSel.attributeSelectors.at(i).name);
732 QCOMPARE(sel.attributeSelectors.at(i).value, expectedSel.attributeSelectors.at(i).value);
733 QCOMPARE(int(sel.attributeSelectors.at(i).valueMatchCriterium), int(expectedSel.attributeSelectors.at(i).valueMatchCriterium));
734 }
735 }
736}
737
738void tst_QCssParser::prio()
739{
740 {
741 QCss::Parser parser("!important");
742 QVERIFY(parser.testPrio());
743 }
744 {
745 QCss::Parser parser("!impOrTAnt");
746 QVERIFY(parser.testPrio());
747 }
748 {
749 QCss::Parser parser("!\"important\"");
750 QVERIFY(!parser.testPrio());
751 QCOMPARE(parser.index, 0);
752 }
753 {
754 QCss::Parser parser("!importbleh");
755 QVERIFY(!parser.testPrio());
756 QCOMPARE(parser.index, 0);
757 }
758}
759
760void tst_QCssParser::escapes()
761{
762 QCss::Parser parser("\\hello");
763 parser.test(t: QCss::IDENT);
764 QCOMPARE(parser.lexem(), QString("hello"));
765}
766
767void tst_QCssParser::malformedDeclarations_data()
768{
769 QTest::addColumn<QString>(name: "css");
770
771 QTest::newRow(dataTag: "1") << QString("p { color:green }");
772 QTest::newRow(dataTag: "2") << QString("p { color:green; color } /* malformed declaration missing ':', value */");
773 QTest::newRow(dataTag: "3") << QString("p { color:red; color; color:green } /* same with expected recovery */");
774 QTest::newRow(dataTag: "4") << QString("p { color:green; color: } /* malformed declaration missing value */");
775 QTest::newRow(dataTag: "5") << QString("p { color:red; color:; color:green } /* same with expected recovery */");
776 QTest::newRow(dataTag: "6") << QString("p { color:green; color{;color:maroon} } /* unexpected tokens { } */");
777 QTest::newRow(dataTag: "7") << QString("p { color:red; color{;color:maroon}; color:green } /* same with recovery */");
778}
779
780void tst_QCssParser::malformedDeclarations()
781{
782 QFETCH(QString, css);
783 QCss::Parser parser(css);
784 QVERIFY(parser.testRuleset());
785 QCss::StyleRule rule;
786 QVERIFY(parser.parseRuleset(&rule));
787
788 QCOMPARE(rule.selectors.count(), 1);
789 QCOMPARE(rule.selectors.at(0).basicSelectors.count(), 1);
790 QCOMPARE(rule.selectors.at(0).basicSelectors.at(0).elementName, QString("p"));
791
792 QVERIFY(rule.declarations.count() >= 1);
793 QCOMPARE(int(rule.declarations.last().d->propertyId), int(QCss::Color));
794 QCOMPARE(rule.declarations.last().d->values.count(), 1);
795 QCOMPARE(int(rule.declarations.last().d->values.at(0).type), int(QCss::Value::Identifier));
796 QCOMPARE(rule.declarations.last().d->values.at(0).variant.toString(), QString("green"));
797}
798
799void tst_QCssParser::invalidAtKeywords()
800{
801 QCss::Parser parser(""
802 "@three-dee {"
803 " @background-lighting {"
804 " azimuth: 30deg;"
805 " elevation: 190deg;"
806 " }"
807 " h1 { color: red }"
808 "}"
809 "h1 { color: blue }");
810
811 QCss::StyleSheet sheet;
812 QVERIFY(parser.parse(&sheet));
813
814 QCOMPARE(sheet.styleRules.count() + sheet.nameIndex.count(), 1);
815 QCss::StyleRule rule = (!sheet.styleRules.isEmpty()) ?
816 sheet.styleRules.at(i: 0) : *sheet.nameIndex.begin();
817
818 QCOMPARE(rule.selectors.count(), 1);
819 QCOMPARE(rule.selectors.at(0).basicSelectors.count(), 1);
820 QCOMPARE(rule.selectors.at(0).basicSelectors.at(0).elementName, QString("h1"));
821
822 QCOMPARE(rule.declarations.count(), 1);
823 QCOMPARE(int(rule.declarations.at(0).d->propertyId), int(QCss::Color));
824 QCOMPARE(rule.declarations.at(0).d->values.count(), 1);
825 QCOMPARE(int(rule.declarations.at(0).d->values.at(0).type), int(QCss::Value::Identifier));
826 QCOMPARE(rule.declarations.at(0).d->values.at(0).variant.toString(), QString("blue"));
827}
828
829
830void tst_QCssParser::colorValue_data()
831{
832 QTest::addColumn<QString>(name: "css");
833 QTest::addColumn<QColor>(name: "expectedColor");
834
835 QTest::newRow(dataTag: "identifier") << "color: black" << QColor("black");
836 QTest::newRow(dataTag: "string") << "color: \"green\"" << QColor("green");
837 QTest::newRow(dataTag: "hexcolor") << "color: #12af0e" << QColor(0x12, 0xaf, 0x0e);
838 QTest::newRow(dataTag: "functional1") << "color: rgb(21, 45, 73)" << QColor(21, 45, 73);
839 QTest::newRow(dataTag: "functional2") << "color: rgb(100%, 0%, 100%)" << QColor(0xff, 0, 0xff);
840 QTest::newRow(dataTag: "rgb") << "color: rgb(10, 20, 30)" << QColor(10, 20, 30);
841 QTest::newRow(dataTag: "rgba") << "color: rgba(10, 20, 30, 40)" << QColor(10, 20, 30, 40);
842 QTest::newRow(dataTag: "rgbaf") << "color: rgba(10, 20, 30, 0.5)" << QColor(10, 20, 30, 127);
843 QTest::newRow(dataTag: "hsv") << "color: hsv(10, 20, 30)" << QColor::fromHsv(h: 10, s: 20, v: 30);
844 QTest::newRow(dataTag: "hsva") << "color: hsva(10, 20, 30, 40)" << QColor::fromHsv(h: 10, s: 20, v: 30, a: 40);
845 // the percent and float values are well chosen to not get in trouble due to rounding errors
846 QTest::newRow(dataTag: "hsva-percent") << "color: hsva(100%, 20%, 40%, 60%)" << QColor::fromHsv(h: 359, s: 51, v: 102, a: 153);
847 QTest::newRow(dataTag: "hsva-float") << "color: hsva(180, 20%, 40%, 0.6)" << QColor::fromHsvF(h: 0.5, s: 0.2, v: 0.4, a: 0.6);
848 QTest::newRow(dataTag: "hsl") << "color: hsl(60, 100%, 50%)" << QColor::fromHsl(h: 60., s: 255, l: 127);
849 QTest::newRow(dataTag: "hsla") << "color: hsla(240, 255, 127, 192)" << QColor::fromHsl(h: 240, s: 255, l: 127, a: 192);
850 QTest::newRow(dataTag: "hsla-percent") << "color: hsla(100%, 80%, 40%, 0%)" << QColor::fromHsl(h: 359, s: 204, l: 102, a: 0);
851 QTest::newRow(dataTag: "hsla-float") << "color: hsla(252, 40%, 60%, 0.2)" << QColor::fromHslF(h: 0.7, s: 0.4, l: 0.6, a: 0.2);
852 QTest::newRow(dataTag: "invalid1") << "color: rgb(why, does, it, always, rain, on, me)" << QColor();
853 QTest::newRow(dataTag: "invalid2") << "color: rgba(i, meant, norway)" << QColor();
854 QTest::newRow(dataTag: "invalid3") << "color: rgb(21)" << QColor();
855 QTest::newRow(dataTag: "invalid4") << "color: rgbx(1, 2, 3)" << QColor();
856 QTest::newRow(dataTag: "invalid5") << "color: rgbax(1, 2, 3, 4)" << QColor();
857 QTest::newRow(dataTag: "invalid6") << "color: hsv(360, 0, 0)" << QColor();
858 QTest::newRow(dataTag: "invalid7") << "color: hsla(1, a, 1, 21)" << QColor();
859 QTest::newRow(dataTag: "role") << "color: palette(base)" << qApp->palette().color(cr: QPalette::Base);
860 QTest::newRow(dataTag: "role2") << "color: palette( window-text ) " << qApp->palette().color(cr: QPalette::WindowText);
861 QTest::newRow(dataTag: "transparent") << "color: transparent" << QColor(Qt::transparent);
862
863 // ### Qt6: no longer valid
864 QTest::newRow(dataTag: "rgb-invalid") << "color: rgb(10, 20, 30, 40)" << QColor(10, 20, 30, 40);
865 QTest::newRow(dataTag: "rgba-invalid") << "color: rgba(10, 20, 30)" << QColor(10, 20, 30, 255);
866}
867
868void tst_QCssParser::colorValue()
869{
870 QFETCH(QString, css);
871 QFETCH(QColor, expectedColor);
872
873 QCss::Parser parser(css);
874 QCss::Declaration decl;
875 QVERIFY(parser.parseNextDeclaration(&decl));
876 const QColor col = decl.colorValue();
877 QCOMPARE(expectedColor.isValid(), col.isValid());
878 QCOMPARE(col, expectedColor);
879}
880
881class DomStyleSelector : public QCss::StyleSelector
882{
883public:
884 inline DomStyleSelector(const QDomDocument &doc, const QCss::StyleSheet &sheet)
885 : doc(doc)
886 {
887 styleSheets.append(t: sheet);
888 }
889
890 virtual QStringList nodeNames(NodePtr node) const { return QStringList(reinterpret_cast<QDomElement *>(node.ptr)->tagName()); }
891 virtual QString attribute(NodePtr node, const QString &name) const { return reinterpret_cast<QDomElement *>(node.ptr)->attribute(name); }
892 virtual bool hasAttribute(NodePtr node, const QString &name) const { return reinterpret_cast<QDomElement *>(node.ptr)->hasAttribute(name); }
893 virtual bool hasAttributes(NodePtr node) const { return reinterpret_cast<QDomElement *>(node.ptr)->hasAttributes(); }
894
895 virtual bool isNullNode(NodePtr node) const {
896 return reinterpret_cast<QDomElement *>(node.ptr)->isNull();
897 }
898 virtual NodePtr parentNode(NodePtr node) const {
899 NodePtr parent;
900 parent.ptr = new QDomElement(reinterpret_cast<QDomElement *>(node.ptr)->parentNode().toElement());
901 return parent;
902 }
903 virtual NodePtr duplicateNode(NodePtr node) const {
904 NodePtr n;
905 n.ptr = new QDomElement(*reinterpret_cast<QDomElement *>(node.ptr));
906 return n;
907 }
908 virtual NodePtr previousSiblingNode(NodePtr node) const {
909 NodePtr sibling;
910 sibling.ptr = new QDomElement(reinterpret_cast<QDomElement *>(node.ptr)->previousSiblingElement());
911 return sibling;
912 }
913 virtual void freeNode(NodePtr node) const {
914 delete reinterpret_cast<QDomElement *>(node.ptr);
915 }
916
917private:
918 QDomDocument doc;
919};
920
921Q_DECLARE_METATYPE(QDomDocument)
922
923void tst_QCssParser::marginValue_data()
924{
925 QTest::addColumn<QString>(name: "css");
926 QTest::addColumn<QString>(name: "expectedMargin");
927
928 QFont f;
929 int ex = QFontMetrics(f).xHeight();
930 int em = QFontMetrics(f).height();
931
932 const QString ex1234 = QString::number(ex) + QLatin1Char(' ') + QString::number(2 * ex)
933 + QLatin1Char(' ') + QString::number(3 * ex) + QLatin1Char(' ')
934 + QString::number(4 * ex);
935 const QString em2ex4 = QLatin1String("1 ") + QString::number(2*em) + QLatin1String(" 3 ")
936 + QString::number(4 * ex);
937
938 QTest::newRow(dataTag: "one value") << "margin: 1px" << "1 1 1 1";
939 QTest::newRow(dataTag: "two values") << "margin: 1px 2px" << "1 2 1 2";
940 QTest::newRow(dataTag: "three value") << "margin: 1px 2px 3px" << "1 2 3 2";
941 QTest::newRow(dataTag: "four values") << "margin: 1px 2px 3px 4px" << "1 2 3 4";
942 QTest::newRow(dataTag: "default px") << "margin: 1 2 3 4" << "1 2 3 4";
943 QTest::newRow(dataTag: "no unit") << "margin: 1 2 3 4" << "1 2 3 4";
944 QTest::newRow(dataTag: "em") << "margin: 1ex 2ex 3ex 4ex"
945 << (QString::number(ex) + QLatin1Char(' ') + QString::number(2 * ex)
946 + QLatin1Char(' ') + QString::number(3 * ex) + QLatin1Char(' ')
947 + QString::number(4 * ex));
948 QTest::newRow(dataTag: "ex") << "margin: 1 2em 3px 4ex"
949 << (QLatin1String("1 ") + QString::number(2 * em) + QLatin1String(" 3 ")
950 + QString::number(4 * ex));
951
952 f.setPointSize(20);
953 f.setBold(true);
954 ex = QFontMetrics(f).xHeight();
955 em = QFontMetrics(f).height();
956 QTest::newRow(dataTag: "em2") << "font: bold 20pt; margin: 1ex 2ex 3ex 4ex"
957 << (QString::number(ex) + QLatin1Char(' ') + QString::number(2 * ex)
958 + QLatin1Char(' ') + QString::number(3 * ex) + QLatin1Char(' ')
959 + QString::number(4 * ex));
960 QTest::newRow(dataTag: "ex2") << "margin: 1 2em 3px 4ex; font-size: 20pt; font-weight: bold;"
961 << (QLatin1String("1 ") + QString::number(2 * em) + QLatin1String(" 3 ")
962 + QString::number(4 * ex));
963
964 QTest::newRow(dataTag: "crap") << "margin: crap" << "0 0 0 0";
965}
966
967void tst_QCssParser::marginValue()
968{
969 QFETCH(QString, css);
970 QFETCH(QString, expectedMargin);
971
972 QDomDocument doc;
973 QVERIFY(doc.setContent(QLatin1String("<!DOCTYPE test><test> <dummy/> </test>")));
974
975 css.prepend(s: "dummy {");
976 css.append(s: "}");
977
978 QCss::Parser parser(css);
979 QCss::StyleSheet sheet;
980 QVERIFY(parser.parse(&sheet));
981
982 DomStyleSelector testSelector(doc, sheet);
983 QDomElement e = doc.documentElement().firstChildElement();
984 QCss::StyleSelector::NodePtr n;
985 n.ptr = &e;
986 QVector<QCss::StyleRule> rules = testSelector.styleRulesForNode(node: n);
987 QVector<QCss::Declaration> decls = rules.at(i: 0).declarations;
988 QCss::ValueExtractor v(decls);
989
990 {
991 int m[4];
992 int p[4];
993 int spacing;
994 v.extractBox(margins: m, paddings: p, spacing: &spacing);
995 QString str = QString::number(m[0]) + QLatin1Char(' ') + QString::number(m[1])
996 + QLatin1Char(' ') + QString::number(m[2]) + QLatin1Char(' ') + QString::number(m[3]);
997 QCOMPARE(str, expectedMargin);
998 }
999}
1000
1001void tst_QCssParser::styleSelector_data()
1002{
1003 QTest::addColumn<bool>(name: "match");
1004 QTest::addColumn<QString>(name: "selector");
1005 QTest::addColumn<QString>(name: "xml");
1006 QTest::addColumn<QString>(name: "elementToCheck");
1007
1008 QTest::newRow(dataTag: "plain") << true << QString("p") << QString("<p />") << QString();
1009 QTest::newRow(dataTag: "noplain") << false << QString("bar") << QString("<p />") << QString();
1010
1011 QTest::newRow(dataTag: "class") << true << QString(".foo") << QString("<p class=\"foo\" />") << QString();
1012 QTest::newRow(dataTag: "noclass") << false << QString(".bar") << QString("<p class=\"foo\" />") << QString();
1013
1014 QTest::newRow(dataTag: "attrset") << true << QString("[justset]") << QString("<p justset=\"bar\" />") << QString();
1015 QTest::newRow(dataTag: "notattrset") << false << QString("[justset]") << QString("<p otherattribute=\"blub\" />") << QString();
1016
1017 QTest::newRow(dataTag: "attrmatch") << true << QString("[foo=bar]") << QString("<p foo=\"bar\" />") << QString();
1018 QTest::newRow(dataTag: "noattrmatch") << false << QString("[foo=bar]") << QString("<p foo=\"xyz\" />") << QString();
1019
1020 QTest::newRow(dataTag: "includes") << true << QString("[foo~=bar]") << QString("<p foo=\"baz bleh bar\" />") << QString();
1021 QTest::newRow(dataTag: "notincludes") << false << QString("[foo~=bar]") << QString("<p foo=\"bazblehbar\" />") << QString();
1022
1023 QTest::newRow(dataTag: "dashmatch") << true << QString("[foo|=bar]") << QString("<p foo=\"bar-bleh\" />") << QString();
1024 QTest::newRow(dataTag: "nodashmatch") << false << QString("[foo|=bar]") << QString("<p foo=\"barbleh\" />") << QString();
1025
1026 QTest::newRow(dataTag: "beginswith") << true << QString("[foo^=bar]") << QString("<p foo=\"barbleh\" />") << QString();
1027 QTest::newRow(dataTag: "nobeginswith") << false << QString("[foo^=bar]") << QString("<p foo=\"blehbleh\" />") << QString();
1028
1029 QTest::newRow(dataTag: "endswith") << true << QString("[foo$=bar]") << QString("<p foo=\"barbar\" />") << QString();
1030 QTest::newRow(dataTag: "noendswith") << false << QString("[foo$=bar]") << QString("<p foo=\"blehbleh\" />") << QString();
1031
1032 QTest::newRow(dataTag: "contains") << true << QString("[foo*=bar]") << QString("<p foo=\"blehbarbleh\" />") << QString();
1033 QTest::newRow(dataTag: "nocontains") << false << QString("[foo*=bar]") << QString("<p foo=\"blehbleh\" />") << QString();
1034
1035 QTest::newRow(dataTag: "attr2") << true << QString("[bar=foo]") << QString("<p bleh=\"bar\" bar=\"foo\" />") << QString();
1036
1037 QTest::newRow(dataTag: "universal1") << true << QString("*") << QString("<p />") << QString();
1038
1039 QTest::newRow(dataTag: "universal3") << false << QString("*[foo=bar]") << QString("<p foo=\"bleh\" />") << QString();
1040 QTest::newRow(dataTag: "universal4") << true << QString("*[foo=bar]") << QString("<p foo=\"bar\" />") << QString();
1041
1042 QTest::newRow(dataTag: "universal5") << false << QString("[foo=bar]") << QString("<p foo=\"bleh\" />") << QString();
1043 QTest::newRow(dataTag: "universal6") << true << QString("[foo=bar]") << QString("<p foo=\"bar\" />") << QString();
1044
1045 QTest::newRow(dataTag: "universal7") << true << QString(".charfmt1") << QString("<p class=\"charfmt1\" />") << QString();
1046
1047 QTest::newRow(dataTag: "id") << true << QString("#blub") << QString("<p id=\"blub\" />") << QString();
1048 QTest::newRow(dataTag: "noid") << false << QString("#blub") << QString("<p id=\"other\" />") << QString();
1049
1050 QTest::newRow(dataTag: "childselector") << true << QString("parent > child")
1051 << QString("<parent><child /></parent>")
1052 << QString("parent/child");
1053
1054 QTest::newRow(dataTag: "nochildselector2") << false << QString("parent > child")
1055 << QString("<child><parent /></child>")
1056 << QString("child/parent");
1057
1058 QTest::newRow(dataTag: "nochildselector3") << false << QString("parent > child")
1059 << QString("<parent><intermediate><child /></intermediate></parent>")
1060 << QString("parent/intermediate/child");
1061
1062 QTest::newRow(dataTag: "childselector2") << true << QString("parent[foo=bar] > child")
1063 << QString("<parent foo=\"bar\"><child /></parent>")
1064 << QString("parent/child");
1065
1066 QTest::newRow(dataTag: "nochildselector4") << false << QString("parent[foo=bar] > child")
1067 << QString("<parent><child /></parent>")
1068 << QString("parent/child");
1069
1070 QTest::newRow(dataTag: "nochildselector5") << false << QString("parent[foo=bar] > child")
1071 << QString("<parent foo=\"bar\"><parent><child /></parent></parent>")
1072 << QString("parent/parent/child");
1073
1074 QTest::newRow(dataTag: "childselectors") << true << QString("grandparent > parent > child")
1075 << QString("<grandparent><parent><child /></parent></grandparent>")
1076 << QString("grandparent/parent/child");
1077
1078 QTest::newRow(dataTag: "descendant") << true << QString("grandparent child")
1079 << QString("<grandparent><parent><child /></parent></grandparent>")
1080 << QString("grandparent/parent/child");
1081
1082 QTest::newRow(dataTag: "nodescendant") << false << QString("grandparent child")
1083 << QString("<other><parent><child /></parent></other>")
1084 << QString("other/parent/child");
1085
1086 QTest::newRow(dataTag: "descendant2") << true << QString("grandgrandparent grandparent child")
1087 << QString("<grandgrandparent><inbetween><grandparent><parent><child /></parent></grandparent></inbetween></grandgrandparent>")
1088 << QString("grandgrandparent/inbetween/grandparent/parent/child");
1089
1090 QTest::newRow(dataTag: "combined") << true << QString("grandparent parent > child")
1091 << QString("<grandparent><inbetween><parent><child /></parent></inbetween></grandparent>")
1092 << QString("grandparent/inbetween/parent/child");
1093
1094 QTest::newRow(dataTag: "combined2") << true << QString("grandparent > parent child")
1095 << QString("<grandparent><parent><inbetween><child /></inbetween></parent></grandparent>")
1096 << QString("grandparent/parent/inbetween/child");
1097
1098 QTest::newRow(dataTag: "combined3") << true << QString("grandparent > parent child")
1099 << QString("<grandparent><parent><inbetween><child /></inbetween></parent></grandparent>")
1100 << QString("grandparent/parent/inbetween/child");
1101
1102 QTest::newRow(dataTag: "nocombined") << false << QString("grandparent parent > child")
1103 << QString("<inbetween><parent><child /></parent></inbetween>")
1104 << QString("inbetween/parent/child");
1105
1106 QTest::newRow(dataTag: "nocombined2") << false << QString("grandparent parent > child")
1107 << QString("<parent><child /></parent>")
1108 << QString("parent/child");
1109
1110 QTest::newRow(dataTag: "previoussibling") << true << QString("p1 + p2")
1111 << QString("<p1 /><p2 />")
1112 << QString("p2");
1113
1114 QTest::newRow(dataTag: "notprevioussibling") << false << QString("p2 + p1")
1115 << QString("<p1 /><p2 />")
1116 << QString("p2");
1117
1118 QTest::newRow(dataTag: "anyprevioussibling") << true << QString("p1 ~ p3")
1119 << QString("<p1 /><p2 /><p3 />")
1120 << QString("p3");
1121
1122 QTest::newRow(dataTag: "noprevioussibling") << false << QString("p3 ~ p2")
1123 << QString("<p1 /><p2 /><p3 />")
1124 << QString("p3");
1125
1126
1127 QTest::newRow(dataTag: "ancestry_firstmismatch") << false << QString("parent child[foo=bar]")
1128 << QString("<parent><child /></parent>")
1129 << QString("parent/child");
1130
1131 QTest::newRow(dataTag: "unknown-pseudo") << false << QString("p:enabled:foobar") << QString("<p/>") << QString();
1132}
1133
1134void tst_QCssParser::styleSelector()
1135{
1136 QFETCH(bool, match);
1137 QFETCH(QString, selector);
1138 QFETCH(QString, xml);
1139 QFETCH(QString, elementToCheck);
1140
1141 const QString css = selector + QLatin1String(" { background-color: green }");
1142 QCss::Parser parser(css);
1143 QCss::StyleSheet sheet;
1144 QVERIFY(parser.parse(&sheet));
1145
1146 QDomDocument doc;
1147 xml.prepend(s: "<!DOCTYPE test><test>");
1148 xml.append(s: "</test>");
1149 QVERIFY(doc.setContent(xml));
1150
1151 DomStyleSelector testSelector(doc, sheet);
1152
1153 QDomElement e = doc.documentElement();
1154 if (elementToCheck.isEmpty()) {
1155 e = e.firstChildElement();
1156 } else {
1157 QStringList path = elementToCheck.split(sep: QLatin1Char('/'));
1158 do {
1159 e = e.namedItem(name: path.takeFirst()).toElement();
1160 } while (!path.isEmpty());
1161 }
1162 QVERIFY(!e.isNull());
1163 QCss::StyleSelector::NodePtr n;
1164 n.ptr = &e;
1165 QVector<QCss::Declaration> decls = testSelector.declarationsForNode(node: n);
1166
1167 if (match) {
1168 QCOMPARE(decls.count(), 1);
1169 QCOMPARE(int(decls.at(0).d->propertyId), int(QCss::BackgroundColor));
1170 QCOMPARE(decls.at(0).d->values.count(), 1);
1171 QCOMPARE(int(decls.at(0).d->values.at(0).type), int(QCss::Value::Identifier));
1172 QCOMPARE(decls.at(0).d->values.at(0).variant.toString(), QString("green"));
1173 } else {
1174 QVERIFY(decls.isEmpty());
1175 }
1176}
1177
1178void tst_QCssParser::specificity_data()
1179{
1180 QTest::addColumn<QString>(name: "selector");
1181 QTest::addColumn<int>(name: "specificity");
1182
1183 QTest::newRow(dataTag: "universal") << QString("*") << 0;
1184
1185 QTest::newRow(dataTag: "elements+pseudos1") << QString("foo") << 1;
1186 QTest::newRow(dataTag: "elements+pseudos2") << QString("foo *[blah]") << 1 + (1 * 0x10);
1187
1188 // should strictly speaking be '2', but we don't support pseudo-elements yet,
1189 // only pseudo-classes
1190 QTest::newRow(dataTag: "elements+pseudos3") << QString("li:first-line") << 1 + (1 * 0x10);
1191
1192 QTest::newRow(dataTag: "elements+pseudos4") << QString("ul li") << 2;
1193 QTest::newRow(dataTag: "elements+pseudos5") << QString("ul ol+li") << 3;
1194 QTest::newRow(dataTag: "elements+pseudos6") << QString("h1 + *[rel=up]") << 1 + (1 * 0x10);
1195
1196 QTest::newRow(dataTag: "elements+pseudos7") << QString("ul ol li.red") << 3 + (1 * 0x10);
1197 QTest::newRow(dataTag: "elements+pseudos8") << QString("li.red.level") << 1 + (2 * 0x10);
1198 QTest::newRow(dataTag: "id") << QString("#x34y") << 1 * 0x100;
1199}
1200
1201void tst_QCssParser::specificity()
1202{
1203 QFETCH(QString, selector);
1204
1205 QString css = selector + QLatin1String(" { }");
1206 QCss::Parser parser(css);
1207 QCss::StyleSheet sheet;
1208 QVERIFY(parser.parse(&sheet));
1209
1210 QCOMPARE(sheet.styleRules.count() + sheet.nameIndex.count() + sheet.idIndex.count() , 1);
1211 QCss::StyleRule rule = (!sheet.styleRules.isEmpty()) ? sheet.styleRules.at(i: 0)
1212 : (!sheet.nameIndex.isEmpty()) ? *sheet.nameIndex.begin()
1213 : *sheet.idIndex.begin();
1214 QCOMPARE(rule.selectors.count(), 1);
1215 QTEST(rule.selectors.at(0).specificity(), "specificity");
1216}
1217
1218void tst_QCssParser::specificitySort_data()
1219{
1220 QTest::addColumn<QString>(name: "firstSelector");
1221 QTest::addColumn<QString>(name: "secondSelector");
1222 QTest::addColumn<QString>(name: "xml");
1223
1224 QTest::newRow(dataTag: "universal1") << QString("*") << QString("p") << QString("<p />");
1225 QTest::newRow(dataTag: "attr") << QString("p") << QString("p[foo=bar]") << QString("<p foo=\"bar\" />");
1226 QTest::newRow(dataTag: "id") << QString("p") << QString("#hey") << QString("<p id=\"hey\" />");
1227 QTest::newRow(dataTag: "id2") << QString("[id=hey]") << QString("#hey") << QString("<p id=\"hey\" />");
1228 QTest::newRow(dataTag: "class") << QString("p") << QString(".hey") << QString("<p class=\"hey\" />");
1229}
1230
1231void tst_QCssParser::specificitySort()
1232{
1233 QFETCH(QString, firstSelector);
1234 QFETCH(QString, secondSelector);
1235 QFETCH(QString, xml);
1236
1237 firstSelector.append(s: " { color: green; }");
1238 secondSelector.append(s: " { color: red; }");
1239
1240 QDomDocument doc;
1241 xml.prepend(s: "<!DOCTYPE test><test>");
1242 xml.append(s: "</test>");
1243 QVERIFY(doc.setContent(xml));
1244
1245 for (int i = 0; i < 2; ++i) {
1246 QString css;
1247 if (i == 0)
1248 css = firstSelector + secondSelector;
1249 else
1250 css = secondSelector + firstSelector;
1251
1252 QCss::Parser parser(css);
1253 QCss::StyleSheet sheet;
1254 QVERIFY(parser.parse(&sheet));
1255
1256 DomStyleSelector testSelector(doc, sheet);
1257
1258 QDomElement e = doc.documentElement().firstChildElement();
1259 QCss::StyleSelector::NodePtr n;
1260 n.ptr = &e;
1261 QVector<QCss::Declaration> decls = testSelector.declarationsForNode(node: n);
1262
1263 QCOMPARE(decls.count(), 2);
1264
1265 QCOMPARE(int(decls.at(0).d->propertyId), int(QCss::Color));
1266 QCOMPARE(decls.at(0).d->values.count(), 1);
1267 QCOMPARE(int(decls.at(0).d->values.at(0).type), int(QCss::Value::Identifier));
1268 QCOMPARE(decls.at(0).d->values.at(0).variant.toString(), QString("green"));
1269
1270 QCOMPARE(int(decls.at(1).d->propertyId), int(QCss::Color));
1271 QCOMPARE(decls.at(1).d->values.count(), 1);
1272 QCOMPARE(int(decls.at(1).d->values.at(0).type), int(QCss::Value::Identifier));
1273 QCOMPARE(decls.at(1).d->values.at(0).variant.toString(), QString("red"));
1274 }
1275}
1276
1277void tst_QCssParser::rulesForNode_data()
1278{
1279 QTest::addColumn<QString>(name: "xml");
1280 QTest::addColumn<QString>(name: "css");
1281 QTest::addColumn<quint64>(name: "pseudoClass");
1282 QTest::addColumn<int>(name: "declCount");
1283 QTest::addColumn<QString>(name: "value0");
1284 QTest::addColumn<QString>(name: "value1");
1285
1286 QTest::newRow(dataTag: "universal1") << QString("<p/>") << QString("* { color: red }")
1287 << (quint64)QCss::PseudoClass_Unspecified << 1 << "red" << "";
1288
1289 QTest::newRow(dataTag: "basic") << QString("<p/>") << QString("p:enabled { color: red; bg:blue; }")
1290 << (quint64)QCss::PseudoClass_Enabled << 2 << "red" << "blue";
1291
1292 QTest::newRow(dataTag: "single") << QString("<p/>")
1293 << QString("p:enabled { color: red; } *:hover { color: white }")
1294 << (quint64)QCss::PseudoClass_Hover << 1 << "white" << "";
1295
1296 QTest::newRow(dataTag: "multisel") << QString("<p/>")
1297 << QString("p:enabled { color: red; } p:hover { color: gray } *:hover { color: white } ")
1298 << (quint64)QCss::PseudoClass_Hover << 2 << "white" << "gray";
1299
1300 QTest::newRow(dataTag: "multisel2") << QString("<p/>")
1301 << QString("p:enabled { color: red; } p:hover:focus { color: gray } *:hover { color: white } ")
1302 << quint64(QCss::PseudoClass_Hover|QCss::PseudoClass_Focus) << 2 << "white" << "gray";
1303
1304 QTest::newRow(dataTag: "multisel3-diffspec") << QString("<p/>")
1305 << QString("p:enabled { color: red; } p:hover:focus { color: gray } *:hover { color: white } ")
1306 << quint64(QCss::PseudoClass_Hover) << 1 << "white" << "";
1307
1308 QTest::newRow(dataTag: "!-1") << QString("<p/>")
1309 << QString("p:checked:!hover { color: red; } p:checked:hover { color: gray } p:checked { color: white }")
1310 << quint64(QCss::PseudoClass_Hover|QCss::PseudoClass_Checked) << 2 << "white" << "gray";
1311
1312 QTest::newRow(dataTag: "!-2") << QString("<p/>")
1313 << QString("p:checked:!hover:!pressed { color: red; } p:!checked:hover { color: gray } p:!focus { color: blue }")
1314 << quint64(QCss::PseudoClass_Focus) << 0 << "" << "";
1315
1316 QTest::newRow(dataTag: "!-3") << QString("<p/>")
1317 << QString("p:checked:!hover:!pressed { color: red; } p:!checked:hover { color: gray } p:!focus { color: blue; }")
1318 << quint64(QCss::PseudoClass_Pressed) << 1 << "blue" << "";
1319}
1320
1321void tst_QCssParser::rulesForNode()
1322{
1323 QFETCH(QString, xml);
1324 QFETCH(QString, css);
1325 QFETCH(quint64, pseudoClass);
1326 QFETCH(int, declCount);
1327 QFETCH(QString, value0);
1328 QFETCH(QString, value1);
1329
1330 QDomDocument doc;
1331 xml.prepend(s: "<!DOCTYPE test><test>");
1332 xml.append(s: "</test>");
1333 QVERIFY(doc.setContent(xml));
1334
1335 QCss::Parser parser(css);
1336 QCss::StyleSheet sheet;
1337 QVERIFY(parser.parse(&sheet));
1338
1339 DomStyleSelector testSelector(doc, sheet);
1340 QDomElement e = doc.documentElement().firstChildElement();
1341 QCss::StyleSelector::NodePtr n;
1342 n.ptr = &e;
1343 QVector<QCss::StyleRule> rules = testSelector.styleRulesForNode(node: n);
1344
1345 QVector<QCss::Declaration> decls;
1346 for (int i = 0; i < rules.count(); i++) {
1347 const QCss::Selector &selector = rules.at(i).selectors.at(i: 0);
1348 quint64 negated = 0;
1349 quint64 cssClass = selector.pseudoClass(negated: &negated);
1350 if ((cssClass == QCss::PseudoClass_Unspecified)
1351 || ((((cssClass & pseudoClass) == cssClass)) && ((negated & pseudoClass) == 0)))
1352 decls += rules.at(i).declarations;
1353 }
1354
1355 QCOMPARE(decls.count(), declCount);
1356
1357 if (declCount > 0)
1358 QCOMPARE(decls.at(0).d->values.at(0).variant.toString(), value0);
1359 if (declCount > 1)
1360 QCOMPARE(decls.at(1).d->values.at(0).variant.toString(), value1);
1361}
1362
1363void tst_QCssParser::shorthandBackgroundProperty_data()
1364{
1365 QTest::addColumn<QString>(name: "css");
1366 QTest::addColumn<QBrush>(name: "expectedBrush");
1367 QTest::addColumn<QString>(name: "expectedImage");
1368 QTest::addColumn<int>(name: "expectedRepeatValue");
1369 QTest::addColumn<int>(name: "expectedAlignment");
1370
1371 QTest::newRow(dataTag: "simple color") << "background: red" << QBrush(QColor("red")) << QString() << int(QCss::Repeat_XY) << int(Qt::AlignLeft | Qt::AlignTop);
1372 QTest::newRow(dataTag: "plain color") << "background-color: red" << QBrush(QColor("red")) << QString() << int(QCss::Repeat_XY) << int(Qt::AlignLeft | Qt::AlignTop);
1373 QTest::newRow(dataTag: "palette color") << "background-color: palette(mid)" << qApp->palette().mid() << QString() << int(QCss::Repeat_XY) << int(Qt::AlignLeft | Qt::AlignTop);
1374 QTest::newRow(dataTag: "multiple") << "background: url(chess.png) blue repeat-y" << QBrush(QColor("blue")) << QString("chess.png") << int(QCss::Repeat_Y) << int(Qt::AlignLeft | Qt::AlignTop);
1375 QTest::newRow(dataTag: "plain alignment") << "background-position: center" << QBrush() << QString() << int(QCss::Repeat_XY) << int(Qt::AlignCenter);
1376 QTest::newRow(dataTag: "plain alignment2") << "background-position: left top" << QBrush() << QString() << int(QCss::Repeat_XY) << int(Qt::AlignLeft | Qt::AlignTop);
1377 QTest::newRow(dataTag: "plain alignment3") << "background-position: left" << QBrush() << QString() << int(QCss::Repeat_XY) << int(Qt::AlignLeft | Qt::AlignVCenter);
1378 QTest::newRow(dataTag: "multi") << "background: left url(blah.png) repeat-x" << QBrush() << QString("blah.png") << int(QCss::Repeat_X) << int(Qt::AlignLeft | Qt::AlignVCenter);
1379 QTest::newRow(dataTag: "multi2") << "background: url(blah.png) repeat-x top" << QBrush() << QString("blah.png") << int(QCss::Repeat_X) << int(Qt::AlignTop | Qt::AlignHCenter);
1380 QTest::newRow(dataTag: "multi3") << "background: url(blah.png) top right" << QBrush() << QString("blah.png") << int(QCss::Repeat_XY) << int(Qt::AlignTop | Qt::AlignRight);
1381}
1382
1383void tst_QCssParser::shorthandBackgroundProperty()
1384{
1385 QFETCH(QString, css);
1386
1387 QDomDocument doc;
1388 QVERIFY(doc.setContent(QLatin1String("<!DOCTYPE test><test> <dummy/> </test>")));
1389
1390 css.prepend(s: "dummy {");
1391 css.append(c: QLatin1Char('}'));
1392
1393 QCss::Parser parser(css);
1394 QCss::StyleSheet sheet;
1395 QVERIFY(parser.parse(&sheet));
1396
1397 DomStyleSelector testSelector(doc, sheet);
1398 QDomElement e = doc.documentElement().firstChildElement();
1399 QCss::StyleSelector::NodePtr n;
1400 n.ptr = &e;
1401 QVector<QCss::StyleRule> rules = testSelector.styleRulesForNode(node: n);
1402 QVector<QCss::Declaration> decls = rules.at(i: 0).declarations;
1403 QCss::ValueExtractor v(decls);
1404
1405 QBrush brush;
1406 QString image;
1407 QCss::Repeat repeat = QCss::Repeat_XY;
1408 Qt::Alignment alignment = Qt::AlignTop | Qt::AlignLeft;
1409 QCss::Origin origin = QCss::Origin_Padding;
1410 QCss::Attachment attachment;
1411 QCss::Origin ignoredOrigin;
1412 v.extractBackground(&brush, &image, &repeat, &alignment, &origin, &attachment, &ignoredOrigin);
1413
1414 QFETCH(QBrush, expectedBrush);
1415 QCOMPARE(expectedBrush.color(), brush.color());
1416
1417 QTEST(image, "expectedImage");
1418 QTEST(int(repeat), "expectedRepeatValue");
1419 QTEST(int(alignment), "expectedAlignment");
1420
1421 //QTBUG-9674 : a second evaluation should give the same results
1422 QVERIFY(v.extractBackground(&brush, &image, &repeat, &alignment, &origin, &attachment, &ignoredOrigin));
1423 QCOMPARE(expectedBrush.color(), brush.color());
1424 QTEST(image, "expectedImage");
1425 QTEST(int(repeat), "expectedRepeatValue");
1426 QTEST(int(alignment), "expectedAlignment");
1427}
1428
1429void tst_QCssParser::pseudoElement_data()
1430{
1431 QTest::addColumn<QString>(name: "css");
1432 QTest::addColumn<QString>(name: "pseudoElement");
1433 QTest::addColumn<int>(name: "declCount");
1434
1435 // QComboBox::dropDown { border-image: blah; }
1436 QTest::newRow(dataTag: "no pseudo-elements") << QString("dummy:hover { color: red }") << "" << 1;
1437 QTest::newRow(dataTag: "no pseudo-elements") << QString("dummy:hover { color: red }") << "pe" << 0;
1438
1439 QTest::newRow(dataTag: "1 pseudo-element (1)") << QString("dummy::pe:hover { color: red }") << "pe" << 1;
1440 QTest::newRow(dataTag: "1 pseudo-element (2)") << QString("dummy::pe:hover { color: red }") << "x" << 0;
1441 QTest::newRow(dataTag: "1 pseudo-element (2)") << QString("whatever::pe:hover { color: red }") << "pe" << 0;
1442
1443 QTest::newRow(dataTag: "1 pseudo-element (3)")
1444 << QString("dummy { color: white; } dummy::pe:hover { color: red }") << "x" << 0;
1445 QTest::newRow(dataTag: "1 pseudo-element (4)")
1446 << QString("dummy { color: white; } dummy::pe:hover { color: red } dummy { x:y }") << "" << 2;
1447 QTest::newRow(dataTag: "1 pseudo-element (5)")
1448 << QString("dummy { color: white; } dummy::pe:hover { color: red }") << "pe" << 1;
1449 QTest::newRow(dataTag: "1 pseudo-element (6)")
1450 << QString("dummy { color: white; } dummy::pe:hover { color: red } dummy::pe:checked { x: y} ") << "pe" << 2;
1451
1452 QTest::newRow(dataTag: "2 pseudo-elements (1)")
1453 << QString("dummy { color: white; } dummy::pe1:hover { color: red } dummy::pe2:checked { x: y} ")
1454 << "" << 1;
1455 QTest::newRow(dataTag: "2 pseudo-elements (1)")
1456 << QString("dummy { color: white; } dummy::pe1:hover { color: red } dummy::pe2:checked { x: y} ")
1457 << "pe1" << 1;
1458 QTest::newRow(dataTag: "2 pseudo-elements (2)")
1459 << QString("dummy { color: white; } dummy::pe1:hover { color: red } dummy::pe2:checked { x: y} ")
1460 << "pe2" << 1;
1461}
1462
1463void tst_QCssParser::pseudoElement()
1464{
1465 QFETCH(QString, css);
1466 QFETCH(QString, pseudoElement);
1467 QFETCH(int, declCount);
1468
1469 QDomDocument doc;
1470 QVERIFY(doc.setContent(QLatin1String("<!DOCTYPE test><test> <dummy/> </test>")));
1471
1472 QCss::Parser parser(css);
1473 QCss::StyleSheet sheet;
1474 QVERIFY(parser.parse(&sheet));
1475
1476 DomStyleSelector testSelector(doc, sheet);
1477 QDomElement e = doc.documentElement().firstChildElement();
1478 QCss::StyleSelector::NodePtr n;
1479 n.ptr = &e;
1480 QVector<QCss::StyleRule> rules = testSelector.styleRulesForNode(node: n);
1481 QVector<QCss::Declaration> decls;
1482 for (int i = 0; i < rules.count(); i++) {
1483 const QCss::Selector& selector = rules.at(i).selectors.at(i: 0);
1484 if (pseudoElement.compare(s: selector.pseudoElement(), cs: Qt::CaseInsensitive) != 0)
1485 continue;
1486 decls += rules.at(i).declarations;
1487
1488 }
1489 QCOMPARE(decls.count(), declCount);
1490}
1491
1492void tst_QCssParser::gradient_data()
1493{
1494 QTest::addColumn<QString>(name: "css");
1495 QTest::addColumn<QString>(name: "type");
1496 QTest::addColumn<QPointF>(name: "start");
1497 QTest::addColumn<QPointF>(name: "finalStop");
1498 QTest::addColumn<int>(name: "spread");
1499 QTest::addColumn<qreal>(name: "stop0");
1500 QTest::addColumn<QColor>(name: "color0");
1501 QTest::addColumn<qreal>(name: "stop1");
1502 QTest::addColumn<QColor>(name: "color1");
1503
1504 QTest::newRow(dataTag: "color-string") <<
1505 "selection-background-color: qlineargradient(x1:1, y1:2, x2:3, y2:4, "
1506 "stop:0.2 red, stop:0.5 green)" << "linear" << QPointF(1, 2) << QPointF(3, 4)
1507 << 0 << qreal(0.2) << QColor("red") << qreal(0.5) << QColor("green");
1508
1509 QTest::newRow(dataTag: "color-#") <<
1510 "selection-background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, "
1511 "spread: reflect, stop:0.2 #123, stop:0.5 #456)" << "linear" << QPointF(0, 0) << QPointF(0, 1)
1512 << 1 << qreal(0.2) << QColor("#123") << qreal(0.5) << QColor("#456");
1513
1514 QTest::newRow(dataTag: "color-rgb") <<
1515 "selection-background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, "
1516 "spread: reflect, stop:0.2 rgb(1, 2, 3), stop:0.5 rgba(1, 2, 3, 4))" << "linear" << QPointF(0, 0) << QPointF(0, 1)
1517 << 1 << qreal(0.2) << QColor(1, 2, 3) << qreal(0.5) << QColor(1, 2, 3, 4);
1518
1519 QTest::newRow(dataTag: "color-spaces") <<
1520 "selection-background-color: qlineargradient(x1: 0, y1 :0,x2:0, y2 : 1 , "
1521 "spread: reflect, stop:0.2 rgb(1, 2, 3), stop: 0.5 rgba(1, 2, 3, 4))" << "linear" << QPointF(0, 0) << QPointF(0, 1)
1522 << 1 << qreal(0.2) << QColor(1, 2, 3) << qreal(0.5) << QColor(1, 2, 3, 4);
1523
1524 QTest::newRow(dataTag: "conical gradient") <<
1525 "selection-background-color: qconicalgradient(cx: 4, cy : 2, angle: 23, "
1526 "spread: repeat, stop:0.2 rgb(1, 2, 3), stop:0.5 rgba(1, 2, 3, 4))" << "conical" << QPointF(4, 2) << QPointF()
1527 << 2 << qreal(0.2) << QColor(1, 2, 3) << qreal(0.5) << QColor(1, 2, 3, 4);
1528
1529 // spaces before first function parameter lead to parser errors
1530 QTest::newRow(dataTag: "QTBUG-61795") <<
1531 "selection-background-color: qconicalgradient( cx: 4, cy : 2, angle: 23, "
1532 "spread: repeat, stop:0.2 rgb( 1, 2, 3), stop:0.5 rgba( 1, 2, 3, 4))" << "conical" << QPointF(4, 2) << QPointF()
1533 << 2 << qreal(0.2) << QColor(1, 2, 3) << qreal(0.5) << QColor(1, 2, 3, 4);
1534
1535 /* won't pass: stop values are expected to be sorted
1536 QTest::newRow("unsorted-stop") <<
1537 "selection-background: lineargradient(x1:0, y1:0, x2:0, y2:1, "
1538 "stop:0.5 green, stop:0.2 red)" << QPointF(0, 0) << QPointF(0, 1)
1539 0 << 0.2 << QColor("red") << 0.5 << QColor("green");
1540 */
1541}
1542
1543void tst_QCssParser::gradient()
1544{
1545 QFETCH(QString, css);
1546 QFETCH(QString, type);
1547 QFETCH(QPointF, finalStop);
1548 QFETCH(QPointF, start);
1549 QFETCH(int, spread);
1550 QFETCH(qreal, stop0); QFETCH(QColor, color0);
1551 QFETCH(qreal, stop1); QFETCH(QColor, color1);
1552
1553 QDomDocument doc;
1554 QVERIFY(doc.setContent(QLatin1String("<!DOCTYPE test><test> <dummy/> </test>")));
1555
1556 css.prepend(s: "dummy {");
1557 css.append(c: QLatin1Char('}'));
1558
1559 QCss::Parser parser(css);
1560 QCss::StyleSheet sheet;
1561 QVERIFY(parser.parse(&sheet));
1562
1563 DomStyleSelector testSelector(doc, sheet);
1564 QDomElement e = doc.documentElement().firstChildElement();
1565 QCss::StyleSelector::NodePtr n;
1566 n.ptr = &e;
1567 QVector<QCss::StyleRule> rules = testSelector.styleRulesForNode(node: n);
1568 QVector<QCss::Declaration> decls = rules.at(i: 0).declarations;
1569 QCss::ValueExtractor ve(decls);
1570 QBrush fg, sfg;
1571 QBrush sbg, abg;
1572 QVERIFY(ve.extractPalette(&fg, &sfg, &sbg, &abg));
1573 if (type == "linear") {
1574 QCOMPARE(sbg.style(), Qt::LinearGradientPattern);
1575 const QLinearGradient *lg = static_cast<const QLinearGradient *>(sbg.gradient());
1576 QCOMPARE(lg->start(), start);
1577 QCOMPARE(lg->finalStop(), finalStop);
1578 } else if (type == "conical") {
1579 QCOMPARE(sbg.style(), Qt::ConicalGradientPattern);
1580 const QConicalGradient *cg = static_cast<const QConicalGradient *>(sbg.gradient());
1581 QCOMPARE(cg->center(), start);
1582 }
1583 const QGradient *g = sbg.gradient();
1584 QCOMPARE(g->spread(), QGradient::Spread(spread));
1585 QCOMPARE(g->stops().at(0).first, stop0);
1586 QCOMPARE(g->stops().at(0).second, color0);
1587 QCOMPARE(g->stops().at(1).first, stop1);
1588 QCOMPARE(g->stops().at(1).second, color1);
1589}
1590
1591void tst_QCssParser::extractFontFamily_data()
1592{
1593 if (QFontInfo(QFont("Times New Roman")).family() != "Times New Roman")
1594 QSKIP("'Times New Roman' font not found");
1595
1596 QTest::addColumn<QString>(name: "css");
1597 QTest::addColumn<QString>(name: "expectedFamily");
1598
1599 QTest::newRow(dataTag: "quoted-family-name") << "font-family: 'Times New Roman'" << QString("Times New Roman");
1600 QTest::newRow(dataTag: "unquoted-family-name") << "font-family: Times New Roman" << QString("Times New Roman");
1601 QTest::newRow(dataTag: "unquoted-family-name2") << "font-family: Times New Roman" << QString("Times New Roman");
1602 QTest::newRow(dataTag: "multiple") << "font-family: Times New Roman , foobar, 'baz'" << QString("Times New Roman");
1603 QTest::newRow(dataTag: "multiple2") << "font-family: invalid, Times New Roman " << QString("Times New Roman");
1604 QTest::newRow(dataTag: "invalid") << "font-family: invalid" << QFontInfo(QFont("invalid font")).family();
1605 QTest::newRow(dataTag: "shorthand") << "font: 12pt Times New Roman" << QString("Times New Roman");
1606 QTest::newRow(dataTag: "shorthand multiple quote") << "font: 12pt invalid, \"Times New Roman\" " << QString("Times New Roman");
1607 QTest::newRow(dataTag: "shorthand multiple") << "font: 12pt invalid, Times New Roman " << QString("Times New Roman");
1608 QTest::newRow(dataTag: "invalid spaces") << "font-family: invalid spaces, Times New Roman " << QString("Times New Roman");
1609 QTest::newRow(dataTag: "invalid spaces quotes") << "font-family: 'invalid spaces', 'Times New Roman' " << QString("Times New Roman");
1610}
1611
1612
1613void tst_QCssParser::extractFontFamily()
1614{
1615 QFETCH(QString, css);
1616 css.prepend(s: "dummy {");
1617 css.append(c: QLatin1Char('}'));
1618
1619 QCss::Parser parser(css);
1620 QCss::StyleSheet sheet;
1621 QVERIFY(parser.parse(&sheet));
1622
1623 QCOMPARE(sheet.styleRules.count() + sheet.nameIndex.count(), 1);
1624 QCss::StyleRule rule = (!sheet.styleRules.isEmpty()) ?
1625 sheet.styleRules.at(i: 0) : *sheet.nameIndex.begin();
1626
1627 const QVector<QCss::Declaration> decls = rule.declarations;
1628 QVERIFY(!decls.isEmpty());
1629 QCss::ValueExtractor extractor(decls);
1630
1631 int adjustment = 0;
1632 QFont fnt;
1633 extractor.extractFont(font: &fnt, fontSizeAdjustment: &adjustment);
1634 QFontInfo info(fnt);
1635
1636 QTEST(info.family(), "expectedFamily");
1637}
1638
1639void tst_QCssParser::extractBorder_data()
1640{
1641 QTest::addColumn<QString>(name: "css");
1642 QTest::addColumn<int>(name: "expectedTopWidth");
1643 QTest::addColumn<int>(name: "expectedTopStyle");
1644 QTest::addColumn<QColor>(name: "expectedTopColor");
1645
1646 QTest::newRow(dataTag: "all values") << "border: 2px solid green" << 2 << (int)QCss::BorderStyle_Solid << QColor("green");
1647 QTest::newRow(dataTag: "palette") << "border: 2px solid palette(highlight)" << 2 << (int)QCss::BorderStyle_Solid << qApp->palette().color(cr: QPalette::Highlight);
1648 QTest::newRow(dataTag: "just width") << "border: 2px" << 2 << (int)QCss::BorderStyle_None << QColor();
1649 QTest::newRow(dataTag: "just style") << "border: solid" << 0 << (int)QCss::BorderStyle_Solid << QColor();
1650 QTest::newRow(dataTag: "just color") << "border: green" << 0 << (int)QCss::BorderStyle_None << QColor("green");
1651 QTest::newRow(dataTag: "width+style") << "border: 2px solid" << 2 << (int)QCss::BorderStyle_Solid << QColor();
1652 QTest::newRow(dataTag: "style+color") << "border: solid green" << 0 << (int)QCss::BorderStyle_Solid << QColor("green");
1653 QTest::newRow(dataTag: "width+color") << "border: 3px green" << 3 << (int)QCss::BorderStyle_None << QColor("green");
1654 QTest::newRow(dataTag: "groove style") << "border: groove" << 0 << (int)QCss::BorderStyle_Groove << QColor();
1655 QTest::newRow(dataTag: "ridge style") << "border: ridge" << 0 << (int)QCss::BorderStyle_Ridge << QColor();
1656 QTest::newRow(dataTag: "double style") << "border: double" << 0 << (int)QCss::BorderStyle_Double << QColor();
1657 QTest::newRow(dataTag: "inset style") << "border: inset" << 0 << (int)QCss::BorderStyle_Inset << QColor();
1658 QTest::newRow(dataTag: "outset style") << "border: outset" << 0 << (int)QCss::BorderStyle_Outset << QColor();
1659 QTest::newRow(dataTag: "dashed style") << "border: dashed" << 0 << (int)QCss::BorderStyle_Dashed << QColor();
1660 QTest::newRow(dataTag: "dotted style") << "border: dotted" << 0 << (int)QCss::BorderStyle_Dotted << QColor();
1661 QTest::newRow(dataTag: "dot-dash style") << "border: dot-dash" << 0 << (int)QCss::BorderStyle_DotDash << QColor();
1662 QTest::newRow(dataTag: "dot-dot-dash style") << "border: dot-dot-dash" << 0 << (int)QCss::BorderStyle_DotDotDash << QColor();
1663
1664 QTest::newRow(dataTag: "top-width+color") << "border-top: 3px green" << 3 << (int)QCss::BorderStyle_None << QColor("green");
1665}
1666
1667void tst_QCssParser::extractBorder()
1668{
1669 QFETCH(QString, css);
1670 QFETCH(int, expectedTopWidth);
1671 QFETCH(int, expectedTopStyle);
1672 QFETCH(QColor, expectedTopColor);
1673
1674 css.prepend(s: "dummy {");
1675 css.append(c: QLatin1Char('}'));
1676
1677 QCss::Parser parser(css);
1678 QCss::StyleSheet sheet;
1679 QVERIFY(parser.parse(&sheet));
1680
1681 QCOMPARE(sheet.styleRules.count() + sheet.nameIndex.count(), 1);
1682 QCss::StyleRule rule = (!sheet.styleRules.isEmpty()) ?
1683 sheet.styleRules.at(i: 0) : *sheet.nameIndex.begin();
1684 const QVector<QCss::Declaration> decls = rule.declarations;
1685 QVERIFY(!decls.isEmpty());
1686 QCss::ValueExtractor extractor(decls);
1687
1688 int widths[4];
1689 QBrush colors[4];
1690 QCss::BorderStyle styles[4];
1691 QSize radii[4];
1692
1693 extractor.extractBorder(borders: widths, colors, Styles: styles, radii);
1694 QCOMPARE(widths[QCss::TopEdge], expectedTopWidth);
1695 QCOMPARE(int(styles[QCss::TopEdge]), expectedTopStyle);
1696 QCOMPARE(colors[QCss::TopEdge].color(), expectedTopColor);
1697
1698 //QTBUG-9674 : a second evaluation should give the same results
1699 QVERIFY(extractor.extractBorder(widths, colors, styles, radii));
1700 QCOMPARE(widths[QCss::TopEdge], expectedTopWidth);
1701 QCOMPARE(int(styles[QCss::TopEdge]), expectedTopStyle);
1702 QCOMPARE(colors[QCss::TopEdge].color(), expectedTopColor);
1703}
1704
1705void tst_QCssParser::noTextDecoration()
1706{
1707 QCss::Parser parser("dummy { text-decoration: none; }");
1708 QCss::StyleSheet sheet;
1709 QVERIFY(parser.parse(&sheet));
1710
1711 QCOMPARE(sheet.styleRules.count() + sheet.nameIndex.count(), 1);
1712 QCss::StyleRule rule = (!sheet.styleRules.isEmpty()) ?
1713 sheet.styleRules.at(i: 0) : *sheet.nameIndex.begin();
1714 const QVector<QCss::Declaration> decls = rule.declarations;
1715 QVERIFY(!decls.isEmpty());
1716 QCss::ValueExtractor extractor(decls);
1717
1718 int adjustment = 0;
1719 QFont f;
1720 f.setUnderline(true);
1721 f.setOverline(true);
1722 f.setStrikeOut(true);
1723 QVERIFY(extractor.extractFont(&f, &adjustment));
1724
1725 QVERIFY(!f.underline());
1726 QVERIFY(!f.overline());
1727 QVERIFY(!f.strikeOut());
1728}
1729
1730void tst_QCssParser::quotedAndUnquotedIdentifiers()
1731{
1732 QCss::Parser parser("foo { font-style: \"italic\"; font-weight: bold }");
1733 QCss::StyleSheet sheet;
1734 QVERIFY(parser.parse(&sheet));
1735
1736 QCOMPARE(sheet.styleRules.count() + sheet.nameIndex.count(), 1);
1737 QCss::StyleRule rule = (!sheet.styleRules.isEmpty()) ?
1738 sheet.styleRules.at(i: 0) : *sheet.nameIndex.begin();
1739 const QVector<QCss::Declaration> decls = rule.declarations;
1740 QCOMPARE(decls.size(), 2);
1741
1742 QCOMPARE(decls.at(0).d->values.first().type, QCss::Value::String);
1743 QCOMPARE(decls.at(0).d->property, QLatin1String("font-style"));
1744 QCOMPARE(decls.at(0).d->values.first().toString(), QLatin1String("italic"));
1745
1746 QCOMPARE(decls.at(1).d->values.first().type, QCss::Value::KnownIdentifier);
1747 QCOMPARE(decls.at(1).d->property, QLatin1String("font-weight"));
1748 QCOMPARE(decls.at(1).d->values.first().toString(), QLatin1String("bold"));
1749}
1750
1751void tst_QCssParser::whitespaceValues_data()
1752{
1753 QTest::addColumn<QString>(name: "value");
1754
1755 QTest::newRow(dataTag: "normal") << "normal";
1756 QTest::newRow(dataTag: "inherit") << "inherit";
1757 QTest::newRow(dataTag: "nowrap") << "nowrap";
1758 QTest::newRow(dataTag: "pre") << "pre";
1759 QTest::newRow(dataTag: "pre-wrap") << "pre-wrap";
1760 QTest::newRow(dataTag: "pre-line") << "pre-line";
1761}
1762
1763void tst_QCssParser::whitespaceValues()
1764{
1765 QFETCH(QString, value);
1766 QCss::Parser parser(QString("foo { white-space: %1 }").arg(a: value));
1767 QCss::StyleSheet sheet;
1768 QVERIFY(parser.parse(&sheet));
1769
1770 QCss::StyleRule rule = (!sheet.styleRules.isEmpty()) ?
1771 sheet.styleRules.at(i: 0) : *sheet.nameIndex.begin();
1772 QCOMPARE(rule.declarations.size(), 1);
1773
1774 QCOMPARE(rule.declarations.at(0).d->property, QLatin1String("white-space"));
1775 QCOMPARE(rule.declarations.at(0).d->values.first().toString(), value);
1776}
1777
1778QTEST_MAIN(tst_QCssParser)
1779#include "tst_qcssparser.moc"
1780
1781

source code of qtbase/tests/auto/gui/text/qcssparser/tst_qcssparser.cpp