Index: packages/csslib/test/declaration_test.dart |
diff --git a/packages/csslib/test/declaration_test.dart b/packages/csslib/test/declaration_test.dart |
index de589d7c9a8bc668073a416b52e11365fc1f0919..af6fdefcf86199247837fd1b74c7ec78a23f883c 100644 |
--- a/packages/csslib/test/declaration_test.dart |
+++ b/packages/csslib/test/declaration_test.dart |
@@ -4,16 +4,27 @@ |
library declaration_test; |
+import 'package:csslib/src/messages.dart'; |
+import 'package:csslib/visitor.dart'; |
import 'package:test/test.dart'; |
import 'testing.dart'; |
+void expectCss(String css, String expected) { |
+ var errors = <Message>[]; |
+ var styleSheet = parseCss(css, errors: errors, opts: simpleOptions); |
+ expect(styleSheet, isNotNull); |
+ expect(errors, isEmpty); |
+ expect(prettyPrint(styleSheet), expected); |
+} |
+ |
void testSimpleTerms() { |
- var errors = []; |
+ var errors = <Message>[]; |
final String input = r''' |
@ import url("test.css"); |
.foo { |
background-color: #191919; |
+ content: "u+0041"; |
width: 10PX; |
height: 22mM !important; |
border-width: 20cm; |
@@ -27,6 +38,7 @@ void testSimpleTerms() { |
@import "test.css"; |
.foo { |
background-color: #191919; |
+ content: "u+0041"; |
width: 10px; |
height: 22mm !important; |
border-width: 20cm; |
@@ -57,6 +69,17 @@ void testSimpleTerms() { |
expect(stylesheet != null, true); |
expect(errors.isEmpty, true, reason: errors.toString()); |
expect(prettyPrint(stylesheet), generated2); |
+ |
+ // Regression test to ensure invalid percentages don't throw an exception and |
+ // instead print a useful error message when not in checked mode. |
+ var css = ''' |
+.foo { |
+ width: Infinity%; |
+}'''; |
+ stylesheet = parseCss(css, errors: errors..clear(), opts: simpleOptions); |
+ expect(errors, isNotEmpty); |
+ expect(errors.first.message, 'expected }, but found %'); |
+ expect(errors.first.span.text, '%'); |
} |
/** |
@@ -64,7 +87,7 @@ void testSimpleTerms() { |
* no quotes. Hex values with # and letters, and functions (rgba, url, etc.) |
*/ |
void testDeclarations() { |
- var errors = []; |
+ var errors = <Message>[]; |
final String input = r''' |
.more { |
color: white; |
@@ -102,7 +125,7 @@ void testDeclarations() { |
} |
void testIdentifiers() { |
- var errors = []; |
+ var errors = <Message>[]; |
final String input = r''' |
#da { |
height: 100px; |
@@ -129,7 +152,7 @@ void testIdentifiers() { |
} |
void testComposites() { |
- var errors = []; |
+ var errors = <Message>[]; |
final String input = r''' |
.xyzzy { |
border: 10px 80px 90px 100px; |
@@ -158,7 +181,7 @@ void testComposites() { |
} |
void testUnits() { |
- var errors = []; |
+ var errors = <Message>[]; |
final String input = r''' |
#id-1 { |
transition: color 0.4s; |
@@ -168,8 +191,8 @@ void testUnits() { |
right: 300px; |
bottom: 400cm; |
border-width: 2.5mm; |
- margin-top: .5in; |
- margin-left: 5pc; |
+ margin-top: -.5in; |
+ margin-left: +5pc; |
margin-right: 5ex; |
margin-bottom: 5ch; |
font-size: 10pt; |
@@ -206,8 +229,8 @@ void testUnits() { |
right: 300px; |
bottom: 400cm; |
border-width: 2.5mm; |
- margin-top: .5in; |
- margin-left: 5pc; |
+ margin-top: -.5in; |
+ margin-left: +5pc; |
margin-right: 5ex; |
margin-bottom: 5ch; |
font-size: 10pt; |
@@ -242,7 +265,7 @@ void testUnits() { |
} |
void testUnicode() { |
- var errors = []; |
+ var errors = <Message>[]; |
final String input = r''' |
.toggle:after { |
content: '✔'; |
@@ -270,7 +293,7 @@ void testUnicode() { |
} |
void testNewerCss() { |
- var errors = []; |
+ var errors = <Message>[]; |
final String input = r''' |
@media screen,print { |
.foobar_screen { |
@@ -285,6 +308,7 @@ void testNewerCss() { |
width: 10px; |
} |
@page bar : left { @top-left { margin: 8px; } } |
+@page { @top-left { margin: 8px; } width: 10px; } |
@charset "ISO-8859-1"; |
@charset 'ASCII';'''; |
@@ -306,6 +330,12 @@ void testNewerCss() { |
margin: 8px; |
} |
} |
+@page { |
+@top-left { |
+ margin: 8px; |
+} |
+ width: 10px; |
+} |
@charset "ISO-8859-1"; |
@charset "ASCII";'''; |
@@ -317,7 +347,7 @@ void testNewerCss() { |
} |
void testMediaQueries() { |
- var errors = []; |
+ var errors = <Message>[]; |
String input = ''' |
@media screen and (-webkit-min-device-pixel-ratio:0) { |
.todo-item .toggle { |
@@ -369,11 +399,13 @@ void testMediaQueries() { |
.myclass { |
height: 20px; |
} |
-} @media print AND (min-resolution:300dpi) { |
+} |
+@media print AND (min-resolution:300dpi) { |
#anotherId { |
color: #fff; |
} |
-} @media print AND (min-resolution:280dpcm) { |
+} |
+@media print AND (min-resolution:280dpcm) { |
#finalId { |
color: #aaa; |
} |
@@ -390,13 +422,13 @@ void testMediaQueries() { |
input = ''' |
@media only screen and (min-device-width: 4000px) and |
- (min-device-height: 2000px), screen (another: 100px) { |
+ (min-device-height: 2000px), screen AND (another: 100px) { |
html { |
font-size: 10em; |
} |
}'''; |
generated = '@media ONLY screen AND (min-device-width:4000px) ' |
- 'AND (min-device-height:2000px), screen (another:100px) {\n' |
+ 'AND (min-device-height:2000px), screen AND (another:100px) {\n' |
'html {\n font-size: 10em;\n}\n}'; |
stylesheet = parseCss(input, errors: errors..clear(), opts: simpleOptions); |
@@ -406,14 +438,14 @@ void testMediaQueries() { |
expect(prettyPrint(stylesheet), generated); |
input = ''' |
-@media screen,print (min-device-width: 4000px) and |
- (min-device-height: 2000px), screen (another: 100px) { |
+@media screen,print AND (min-device-width: 4000px) and |
+ (min-device-height: 2000px), screen AND (another: 100px) { |
html { |
font-size: 10em; |
} |
}'''; |
- generated = '@media screen, print (min-device-width:4000px) AND ' |
- '(min-device-height:2000px), screen (another:100px) {\n' |
+ generated = '@media screen, print AND (min-device-width:4000px) AND ' |
+ '(min-device-height:2000px), screen AND (another:100px) {\n' |
'html {\n font-size: 10em;\n}\n}'; |
stylesheet = parseCss(input, errors: errors..clear(), opts: simpleOptions); |
@@ -423,19 +455,211 @@ void testMediaQueries() { |
expect(prettyPrint(stylesheet), generated); |
input = ''' |
-@import "test.css" ONLY screen, NOT print (min-device-width: 4000px);'''; |
- generated = |
- '@import "test.css" ONLY screen, NOT print (min-device-width:4000px);'; |
+@import "test.css" ONLY screen, NOT print AND (min-device-width: 4000px);'''; |
+ generated = '@import "test.css" ONLY screen, ' |
+ 'NOT print AND (min-device-width:4000px);'; |
stylesheet = parseCss(input, errors: errors..clear(), opts: simpleOptions); |
expect(stylesheet != null, true); |
expect(errors.isEmpty, true, reason: errors.toString()); |
expect(prettyPrint(stylesheet), generated); |
+ |
+ var css = '@media (min-device-width:400px) {\n}'; |
+ expectCss(css, css); |
+ |
+ css = '@media all AND (tranform-3d), (-webkit-transform-3d) {\n}'; |
+ expectCss(css, css); |
+ |
+ // Test that AND operator is required between media type and expressions. |
+ css = '@media screen (min-device-width:400px'; |
+ stylesheet = parseCss(css, errors: errors..clear(), opts: simpleOptions); |
+ expect(errors, isNotEmpty); |
+ expect( |
+ errors.first.message, contains('expected { after media before ruleset')); |
+ expect(errors.first.span.text, '('); |
+} |
+ |
+void testMozDocument() { |
+ var errors = <Message>[]; |
+ // Test empty url-prefix, commonly used for browser detection. |
+ var css = ''' |
+@-moz-document url-prefix() { |
+ div { |
+ color: #000; |
+ } |
+}'''; |
+ var expected = '''@-moz-document url-prefix() { |
+div { |
+ color: #000; |
+} |
+}'''; |
+ var styleSheet = parseCss(css, errors: errors); |
+ expect(styleSheet, isNotNull); |
+ expect(errors, isEmpty); |
+ expect(prettyPrint(styleSheet), expected); |
+ |
+ // Test url-prefix with unquoted parameter |
+ css = ''' |
+@-moz-document url-prefix(http://www.w3.org/Style/) { |
+ div { |
+ color: #000; |
+ } |
+}'''; |
+ expected = '''@-moz-document url-prefix("http://www.w3.org/Style/") { |
+div { |
+ color: #000; |
+} |
+}'''; |
+ styleSheet = parseCss(css, errors: errors); |
+ expect(styleSheet, isNotNull); |
+ expect(errors, isEmpty); |
+ expect(prettyPrint(styleSheet), expected); |
+ |
+ // Test domain with unquoted parameter |
+ css = ''' |
+@-moz-document domain(google.com) { |
+ div { |
+ color: #000; |
+ } |
+}'''; |
+ expected = '''@-moz-document domain("google.com") { |
+div { |
+ color: #000; |
+} |
+}'''; |
+ styleSheet = parseCss(css, errors: errors); |
+ expect(styleSheet, isNotNull); |
+ expect(errors, isEmpty); |
+ expect(prettyPrint(styleSheet), expected); |
+ |
+ // Test all document functions combined. |
+ css = '@-moz-document ' + |
+ 'url(http://www.w3.org/), ' + |
+ "url-prefix('http://www.w3.org/Style/'), " + |
+ 'domain("google.com"), ' + |
+ 'regexp("https:.*") { div { color: #000; } }'; |
+ expected = '@-moz-document ' + |
+ 'url("http://www.w3.org/"), ' + |
+ 'url-prefix("http://www.w3.org/Style/"), ' + |
+ 'domain("google.com"), ' + |
+ 'regexp("https:.*") {\ndiv {\n color: #000;\n}\n}'; |
+ styleSheet = parseCss(css, errors: errors); |
+ expect(styleSheet, isNotNull); |
+ expect(errors, isEmpty); |
+ expect(prettyPrint(styleSheet), expected); |
+} |
+ |
+void testSupports() { |
+ // Test single declaration condition. |
+ var css = ''' |
+@supports (-webkit-appearance: none) { |
+ div { |
+ -webkit-appearance: none; |
+ } |
+}'''; |
+ var expected = '''@supports (-webkit-appearance: none) { |
+div { |
+ -webkit-appearance: none; |
+} |
+}'''; |
+ expectCss(css, expected); |
+ |
+ // Test negation. |
+ css = ''' |
+@supports not ( display: flex ) { |
+ body { width: 100%; } |
+}'''; |
+ expected = '''@supports not (display: flex) { |
+body { |
+ width: 100%; |
+} |
+}'''; |
+ expectCss(css, expected); |
+ |
+ // Test clause with multiple conditions. |
+ css = ''' |
+@supports (box-shadow: 0 0 2px black inset) or |
+ (-moz-box-shadow: 0 0 2px black inset) or |
+ (-webkit-box-shadow: 0 0 2px black inset) or |
+ (-o-box-shadow: 0 0 2px black inset) { |
+ .box { |
+ box-shadow: 0 0 2px black inset; |
+ } |
+}'''; |
+ expected = '@supports (box-shadow: 0 0 2px #000 inset) or ' + |
+ '(-moz-box-shadow: 0 0 2px #000 inset) or ' + |
+ '(-webkit-box-shadow: 0 0 2px #000 inset) or ' + |
+ '(-o-box-shadow: 0 0 2px #000 inset) {\n' + |
+ '.box {\n' + |
+ ' box-shadow: 0 0 2px #000 inset;\n' + |
+ '}\n' + |
+ '}'; |
+ expectCss(css, expected); |
+ |
+ // Test conjunction and disjunction together. |
+ css = ''' |
+@supports ((transition-property: color) or (animation-name: foo)) and |
+ (transform: rotate(10deg)) { |
+ div { |
+ transition-property: color; |
+ transform: rotate(10deg); |
+ } |
+}'''; |
+ |
+ expected = '@supports ' + |
+ '((transition-property: color) or (animation-name: foo)) and ' + |
+ '(transform: rotate(10deg)) {\n' + |
+ 'div {\n' + |
+ ' transition-property: color;\n' + |
+ ' transform: rotate(10deg);\n' + |
+ '}\n' + |
+ '}'; |
+ expectCss(css, expected); |
+ |
+ // Test that operators can't be mixed without parentheses. |
+ css = '@supports (a: 1) and (b: 2) or (c: 3) {}'; |
+ var errors = <Message>[]; |
+ var styleSheet = parseCss(css, errors: errors, opts: simpleOptions); |
+ expect(styleSheet, isNotNull); |
+ expect(errors, isNotEmpty); |
+ expect(errors.first.message, |
+ "Operators can't be mixed without a layer of parentheses"); |
+ expect(errors.first.span.text, 'or'); |
+} |
+ |
+void testViewport() { |
+ // No declarations. |
+ var css = '@viewport {\n}'; |
+ expectCss(css, css); |
+ |
+ // All declarations. |
+ css = ''' |
+@viewport { |
+ min-width: auto; |
+ max-width: 800px; |
+ width: 400px; |
+ min-height: 50%; |
+ max-height: 200px; |
+ height: 100px 200px; |
+ zoom: auto; |
+ min-zoom: 0.75; |
+ max-zoom: 40%; |
+ user-zoom: fixed; |
+ orientation: landscape; |
+}'''; |
+ expectCss(css, css); |
+ |
+ // Vendor specific. |
+ css = ''' |
+@-ms-viewport { |
+ width: device-width; |
+}'''; |
+ expectCss(css, css); |
} |
void testFontFace() { |
- var errors = []; |
+ var errors = <Message>[]; |
final String input = ''' |
@font-face { |
@@ -527,7 +751,7 @@ src: url(ideal-sans-serif.woff) format("woff"), |
} |
void testCssFile() { |
- var errors = []; |
+ var errors = <Message>[]; |
final String input = r''' |
@import 'simple.css' |
@import "test.css" print |
@@ -557,6 +781,10 @@ div[href^='test'] { |
.test-background { |
background: url(http://www.foo.com/bar.png); |
} |
+ |
+.test-background-with-multiple-properties { |
+ background: #000 url(http://www.foo.com/bar.png); |
+} |
'''; |
final String generated = '@import "simple.css"; ' |
@@ -582,6 +810,9 @@ div[href^='test'] { |
'}\n' |
'.test-background {\n' |
' background: url("http://www.foo.com/bar.png");\n' |
+ '}\n' |
+ '.test-background-with-multiple-properties {\n' |
+ ' background: #000 url("http://www.foo.com/bar.png");\n' |
'}'; |
var stylesheet = parseCss(input, errors: errors); |
@@ -591,7 +822,7 @@ div[href^='test'] { |
} |
void testCompactEmitter() { |
- var errors = []; |
+ var errors = <Message>[]; |
// Check !import compactly emitted. |
final String input = r''' |
@@ -599,7 +830,7 @@ div { |
color: green !important; |
} |
'''; |
- final String generated = "div { color: green!important; }"; |
+ final String generated = "div { color:green!important; }"; |
var stylesheet = parseCss(input, errors: errors); |
@@ -619,7 +850,7 @@ div { |
} |
void testNotSelectors() { |
- var errors = []; |
+ var errors = <Message>[]; |
final String input = r''' |
.details:not(.open-details) x-element, |
@@ -709,7 +940,7 @@ html|*:not(:link):not(:visited) { |
} |
void testIE() { |
- var errors = []; |
+ var errors = <Message>[]; |
final String input = ".test {\n" |
" filter: progid:DXImageTransform.Microsoft.gradient" |
"(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670');\n" |
@@ -772,6 +1003,17 @@ div { |
expect(stylesheet != null, true); |
expect(errors.isEmpty, true, reason: errors.toString()); |
expect(prettyPrint(stylesheet), generated3); |
+ |
+ final input4 = ''' |
+div { |
+ filter: FlipH; |
+}'''; |
+ |
+ stylesheet = parseCss(input4, errors: errors..clear(), opts: simpleOptions); |
+ |
+ expect(stylesheet != null, true); |
+ expect(errors.isEmpty, true, reason: errors.toString()); |
+ expect(prettyPrint(stylesheet), input4); |
} |
/** |
@@ -785,7 +1027,7 @@ div { |
* background: red\9; |
*/ |
void testIEDeclaration() { |
- var errors = []; |
+ var errors = <Message>[]; |
final input = ''' |
.testIE-6 { |
@@ -951,7 +1193,7 @@ input.search-query { |
} |
void testHangs() { |
- var errors = []; |
+ var errors = <Message>[]; |
// Bad hexvalue had caused a hang in processTerm. |
final input = r'''#a { color: #ebebeburl(0/IE8+9+); }'''; |
@@ -1008,36 +1250,48 @@ void testHangs() { |
void testExpressionSpans() { |
final input = r'''.foo { width: 50px; }'''; |
var stylesheet = parseCss(input); |
- var decl = stylesheet.topLevels.single.declarationGroup.declarations.single; |
+ var decl = (stylesheet.topLevels.single as RuleSet) |
+ .declarationGroup |
+ .declarations |
+ .single; |
// This passes |
expect(decl.span.text, 'width: 50px'); |
// This currently fails |
- expect(decl.expression.span.text, '50px'); |
+ expect((decl as Declaration).expression.span.text, '50px'); |
} |
void simpleCalc() { |
final input = r'''.foo { height: calc(100% - 55px); }'''; |
var stylesheet = parseCss(input); |
- var decl = stylesheet.topLevels.single.declarationGroup.declarations.single; |
+ var decl = (stylesheet.topLevels.single as RuleSet) |
+ .declarationGroup |
+ .declarations |
+ .single; |
expect(decl.span.text, 'height: calc(100% - 55px)'); |
} |
void complexCalc() { |
final input = r'''.foo { left: calc((100%/3 - 2) * 1em - 2 * 1px); }'''; |
var stylesheet = parseCss(input); |
- var decl = stylesheet.topLevels.single.declarationGroup.declarations.single; |
+ var decl = (stylesheet.topLevels.single as RuleSet) |
+ .declarationGroup |
+ .declarations |
+ .single; |
expect(decl.span.text, 'left: calc((100%/3 - 2) * 1em - 2 * 1px)'); |
} |
void twoCalcs() { |
final input = r'''.foo { margin: calc(1rem - 2px) calc(1rem - 1px); }'''; |
var stylesheet = parseCss(input); |
- var decl = stylesheet.topLevels.single.declarationGroup.declarations.single; |
+ var decl = (stylesheet.topLevels.single as RuleSet) |
+ .declarationGroup |
+ .declarations |
+ .single; |
expect(decl.span.text, 'margin: calc(1rem - 2px) calc(1rem - 1px)'); |
} |
void selectorWithCalcs() { |
- var errors = []; |
+ var errors = <Message>[]; |
final String input = r''' |
.foo { |
width: calc(1em + 5 * 2em); |
@@ -1061,6 +1315,20 @@ void selectorWithCalcs() { |
expect(prettyPrint(stylesheet), generated); |
} |
+void vendorPrefixedCalc() { |
+ var css = ''' |
+.foo { |
+ width: -webkit-calc((100% - 15px*1) / 1); |
+}'''; |
+ expectCss(css, css); |
+ |
+ css = ''' |
+.foo { |
+ width: -moz-calc((100% - 15px*1) / 1); |
+}'''; |
+ expectCss(css, css); |
+} |
+ |
main() { |
test('Simple Terms', testSimpleTerms); |
test('Declarations', testDeclarations); |
@@ -1070,6 +1338,9 @@ main() { |
test('Unicode', testUnicode); |
test('Newer CSS', testNewerCss); |
test('Media Queries', testMediaQueries); |
+ test('Document', testMozDocument); |
+ test('Supports', testSupports); |
+ test('Viewport', testViewport); |
test('Font-Face', testFontFace); |
test('CSS file', testCssFile); |
test('Compact Emitter', testCompactEmitter); |
@@ -1079,12 +1350,12 @@ main() { |
test('Hanging bugs', testHangs); |
test('Expression spans', testExpressionSpans, |
skip: 'expression spans are broken' |
- ' (https://github.com/dart-lang/csslib/issues/15)'); |
+ ' (https://github.com/dart-lang/csslib/issues/15)'); |
group('calc function', () { |
test('simple calc', simpleCalc); |
test('single complex', complexCalc); |
test('two calc terms for same declaration', twoCalcs); |
test('selector with many calc declarations', selectorWithCalcs); |
+ test('vendor prefixed calc', vendorPrefixedCalc); |
}); |
} |
- |