Index: packages/csslib/test/mixin_test.dart |
diff --git a/packages/csslib/test/mixin_test.dart b/packages/csslib/test/mixin_test.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..c94c9c018d5f5e3b63525e7eb08581b6d69db277 |
--- /dev/null |
+++ b/packages/csslib/test/mixin_test.dart |
@@ -0,0 +1,659 @@ |
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+library mixin_test; |
+ |
+import 'package:test/test.dart'; |
+ |
+import 'testing.dart'; |
+ |
+compileAndValidate(String input, String generated) { |
+ var errors = []; |
+ var stylesheet = compileCss(input, errors: errors, opts: options); |
+ expect(stylesheet != null, true); |
+ expect(errors.isEmpty, true, reason: errors.toString()); |
+ expect(prettyPrint(stylesheet), generated); |
+} |
+ |
+compilePolyfillAndValidate(String input, String generated) { |
+ var errors = []; |
+ var stylesheet = polyFillCompileCss(input, errors: errors, opts: options); |
+ expect(stylesheet != null, true); |
+ expect(errors.isEmpty, true, reason: errors.toString()); |
+ expect(prettyPrint(stylesheet), generated); |
+} |
+ |
+void topLevelMixin() { |
+ compileAndValidate(r''' |
+@mixin silly-links { |
+ a { |
+ color: blue; |
+ background-color: red; |
+ } |
+} |
+ |
+@include silly-links; |
+''', r'''a { |
+ color: #00f; |
+ background-color: #f00; |
+}'''); |
+} |
+ |
+void topLevelMixinTwoIncludes() { |
+ compileAndValidate(r''' |
+@mixin a { |
+ a { |
+ color: blue; |
+ background-color: red; |
+ } |
+} |
+@mixin b { |
+ span { |
+ color: black; |
+ background-color: orange; |
+ } |
+} |
+@include a; |
+@include b; |
+''', r'''a { |
+ color: #00f; |
+ background-color: #f00; |
+} |
+span { |
+ color: #000; |
+ background-color: #ffa500; |
+}'''); |
+} |
+ |
+/** Tests top-level mixins that includes another mixin. */ |
+void topLevelMixinMultiRulesets() { |
+ compileAndValidate(r''' |
+@mixin a { |
+ a { |
+ color: blue; |
+ background-color: red; |
+ } |
+} |
+@mixin b { |
+ #foo-id { |
+ border-top: 1px solid red; |
+ border-bottom: 2px solid green; |
+ } |
+} |
+@mixin c { |
+ span { |
+ color: black; |
+ background-color: orange; |
+ } |
+ @include b; |
+} |
+@include a; |
+@include c; |
+''', r'''a { |
+ color: #00f; |
+ background-color: #f00; |
+} |
+span { |
+ color: #000; |
+ background-color: #ffa500; |
+} |
+#foo-id { |
+ border-top: 1px solid #f00; |
+ border-bottom: 2px solid #008000; |
+}'''); |
+} |
+ |
+void topLevelMixinDeeplyNestedRulesets() { |
+ compileAndValidate(r''' |
+@mixin a { |
+ a { |
+ color: blue; |
+ background-color: red; |
+ } |
+} |
+@mixin b { |
+ #foo-id { |
+ border-top: 1px solid red; |
+ border-bottom: 2px solid green; |
+ } |
+} |
+@mixin f { |
+ #split-bar div { |
+ border: 1px solid lightgray; |
+ } |
+} |
+@mixin e { |
+ #split-bar:visited { |
+ color: gray; |
+ } |
+ @include f; |
+} |
+@mixin d { |
+ a:hover { |
+ cursor: arrow; |
+ } |
+ @include e |
+} |
+@mixin c { |
+ @include a; |
+ span { |
+ color: black; |
+ background-color: orange; |
+ } |
+ @include b; |
+ @include d; |
+} |
+@include c; |
+''', r'''a { |
+ color: #00f; |
+ background-color: #f00; |
+} |
+span { |
+ color: #000; |
+ background-color: #ffa500; |
+} |
+#foo-id { |
+ border-top: 1px solid #f00; |
+ border-bottom: 2px solid #008000; |
+} |
+a:hover { |
+ cursor: arrow; |
+} |
+#split-bar:visited { |
+ color: #808080; |
+} |
+#split-bar div { |
+ border: 1px solid #d3d3d3; |
+}'''); |
+} |
+ |
+/** Tests selector groups and other combinators. */ |
+void topLevelMixinSelectors() { |
+ compileAndValidate(r''' |
+@mixin a { |
+ a, b { |
+ color: blue; |
+ background-color: red; |
+ } |
+ div > span { |
+ color: black; |
+ background-color: orange; |
+ } |
+} |
+ |
+@include a; |
+''', r'''a, b { |
+ color: #00f; |
+ background-color: #f00; |
+} |
+div > span { |
+ color: #000; |
+ background-color: #ffa500; |
+}'''); |
+} |
+ |
+void declSimpleMixin() { |
+ compileAndValidate(r''' |
+@mixin div-border { |
+ border: 2px dashed red; |
+} |
+div { |
+ @include div-border; |
+} |
+''', r'''div { |
+ border: 2px dashed #f00; |
+}'''); |
+} |
+ |
+void declMixinTwoIncludes() { |
+ compileAndValidate(r''' |
+@mixin div-border { |
+ border: 2px dashed red; |
+} |
+@mixin div-color { |
+ color: blue; |
+} |
+div { |
+ @include div-border; |
+ @include div-color; |
+} |
+''', r'''div { |
+ border: 2px dashed #f00; |
+ color: #00f; |
+}'''); |
+} |
+ |
+void declMixinNestedIncludes() { |
+ compileAndValidate(r''' |
+@mixin div-border { |
+ border: 2px dashed red; |
+} |
+@mixin div-padding { |
+ padding: .5em; |
+} |
+@mixin div-margin { |
+ margin: 5px; |
+} |
+@mixin div-color { |
+ @include div-padding; |
+ color: blue; |
+ @include div-margin; |
+} |
+div { |
+ @include div-border; |
+ @include div-color; |
+} |
+''', r'''div { |
+ border: 2px dashed #f00; |
+ padding: .5em; |
+ color: #00f; |
+ margin: 5px; |
+}'''); |
+} |
+ |
+void declMixinDeeperNestedIncludes() { |
+ compileAndValidate(r''' |
+@mixin div-border { |
+ border: 2px dashed red; |
+} |
+@mixin div-padding { |
+ padding: .5em; |
+} |
+@mixin div-margin { |
+ margin: 5px; |
+} |
+@mixin div-color { |
+ @include div-padding; |
+ @include div-margin; |
+} |
+div { |
+ @include div-border; |
+ @include div-color; |
+} |
+''', r'''div { |
+ border: 2px dashed #f00; |
+ padding: .5em; |
+ margin: 5px; |
+}'''); |
+} |
+ |
+void mixinArg() { |
+ compileAndValidate(r''' |
+@mixin div-border-1 { |
+ border: 2px dashed red; |
+} |
+@mixin div-border-2() { |
+ border: 22px solid blue; |
+} |
+@mixin div-left(@dist) { |
+ margin-left: @dist; |
+} |
+@mixin div-right(var-margin) { |
+ margin-right: var(margin); |
+} |
+div-1 { |
+ @include div-left(10px); |
+ @include div-right(100px); |
+ @include div-border-1; |
+} |
+div-2 { |
+ @include div-left(20em); |
+ @include div-right(5in); |
+ @include div-border-2(); |
+} |
+div-3 { |
+ @include div-border-1(); |
+} |
+div-4 { |
+ @include div-border-2; |
+} |
+''', r'''div-1 { |
+ margin-left: 10px; |
+ margin-right: 100px; |
+ border: 2px dashed #f00; |
+} |
+div-2 { |
+ margin-left: 20em; |
+ margin-right: 5in; |
+ border: 22px solid #00f; |
+} |
+div-3 { |
+ border: 2px dashed #f00; |
+} |
+div-4 { |
+ border: 22px solid #00f; |
+}'''); |
+} |
+ |
+void mixinArgs() { |
+ compileAndValidate(r''' |
+@mixin box-shadow(@shadows...) { |
+ -moz-box-shadow: @shadows; |
+ -webkit-box-shadow: @shadows; |
+ box-shadow: var(shadows); |
+} |
+ |
+.shadows { |
+ @include box-shadow(0px 4px 5px #666, 2px 6px 10px #999); |
+}''', r''' |
+.shadowed { |
+ -moz-box-shadow: 0px 4px 5px #666, 2px 6px 10px #999; |
+ -webkit-box-shadow: 0px 4px 5px #666, 2px 6px 10px #999; |
+ box-shadow: 0px 4px 5px #666, 2px 6px 10px #999; |
+} |
+'''); |
+} |
+ |
+void mixinManyArgs() { |
+ compileAndValidate(r''' |
+@mixin border(@border-values) { |
+ border: @border-values |
+} |
+ |
+.primary { |
+ @include border(3px solid green); |
+} |
+''', r''' |
+.primary { |
+ border: 3px solid #008000; |
+}'''); |
+ |
+ compileAndValidate(r''' |
+@mixin setup(@border-color, @border-style, @border-size, @color) { |
+ border: @border-size @border-style @border-color; |
+ color: @color; |
+} |
+ |
+.primary { |
+ @include setup(red, solid, 5px, blue); |
+} |
+''', r''' |
+.primary { |
+ border: 5px solid #f00; |
+ color: #00f; |
+}'''); |
+ |
+ // Test passing a declaration that is multiple parameters. |
+ compileAndValidate(r''' |
+@mixin colors(@text, @background, @border) { |
+ color: @text; |
+ background-color: @background; |
+ border-color: @border; |
+} |
+ |
+@values: #ff0000, #00ff00, #0000ff; |
+.primary { |
+ @include colors(@values); |
+} |
+''', r'''var-values: #f00, #0f0, #00f; |
+ |
+.primary { |
+ color: #f00; |
+ background-color: #0f0; |
+ border-color: #00f; |
+}'''); |
+ |
+ compilePolyfillAndValidate(r''' |
+@mixin colors(@text, @background, @border) { |
+ color: @text; |
+ background-color: @background; |
+ border-color: @border; |
+} |
+ |
+@values: #ff0000, #00ff00, #0000ff; |
+.primary { |
+ @include colors(@values); |
+} |
+''', r'''.primary { |
+ color: #f00; |
+ background-color: #0f0; |
+ border-color: #00f; |
+}'''); |
+} |
+ |
+void badDeclarationInclude() { |
+ final errors = []; |
+ final input = r''' |
+@mixin a { |
+ #foo-id { |
+ color: red; |
+ } |
+} |
+@mixin b { |
+ span { |
+ border: 2px dashed red; |
+ @include a; |
+ } |
+} |
+@include b; |
+'''; |
+ |
+ var stylesheet = compileCss(input, errors: errors, opts: options); |
+ |
+ expect(stylesheet != null, true); |
+ expect(errors.isNotEmpty, true); |
+ expect(errors.length, 1, reason: errors.toString()); |
+ var error = errors[0]; |
+ expect(error.message, 'Using top-level mixin a as a declaration'); |
+ expect(error.span.start.line, 8); |
+ expect(error.span.end.offset, 105); |
+} |
+ |
+void badTopInclude() { |
+ final errors = []; |
+ final input = r''' |
+@mixin b { |
+ color: red; |
+} |
+ |
+@mixin a { |
+ span { |
+ border: 2px dashed red; |
+ } |
+ @include b; |
+} |
+ |
+@include a; |
+ '''; |
+ |
+ var stylesheet = compileCss(input, errors: errors, opts: options); |
+ expect(stylesheet != null, true); |
+ expect(errors.length, 1, reason: errors.toString()); |
+ var error = errors[0]; |
+ expect(error.message, 'Using declaration mixin b as top-level mixin'); |
+ expect(error.span.start.line, 8); |
+ expect(error.span.end.offset, 90); |
+} |
+ |
+void emptyMixin() { |
+ final errors = []; |
+ final input = r''' |
+@mixin a { |
+} |
+@mixin b { |
+ border: 2px dashed red; |
+ @include a; |
+} |
+div { |
+ @include b; |
+} |
+ '''; |
+ |
+ var generated = r'''div { |
+ border: 2px dashed #f00; |
+}'''; |
+ |
+ var stylesheet = compileCss(input, errors: errors, opts: options); |
+ |
+ expect(stylesheet != null, true); |
+ expect(errors.isEmpty, true, reason: errors.toString()); |
+ expect(prettyPrint(stylesheet), generated); |
+} |
+ |
+void undefinedTopLevel() { |
+ final errors = []; |
+ final input = r''' |
+@mixin a { |
+ @include b; |
+} |
+@mixin b { |
+ span { |
+ border: 2px dashed red; |
+ } |
+ @include a; |
+} |
+ |
+@include b; |
+ |
+ '''; |
+ |
+ var stylesheet = compileCss(input, errors: errors, opts: options); |
+ |
+ expect(stylesheet != null, true); |
+ expect(errors.isNotEmpty, true); |
+ expect(errors.length, 1, reason: errors.toString()); |
+ var error = errors[0]; |
+ expect(error.message, 'Undefined mixin b'); |
+ expect(error.span.start.line, 1); |
+ expect(error.span.start.offset, 14); |
+} |
+ |
+void undefinedDeclaration() { |
+ final errors = []; |
+ final input = r''' |
+@mixin a { |
+ @include b; |
+} |
+@mixin b { |
+ border: 2px dashed red; |
+ @include a; |
+} |
+div { |
+ @include b; |
+} |
+ '''; |
+ |
+ var stylesheet = compileCss(input, errors: errors, opts: options); |
+ |
+ expect(stylesheet != null, true); |
+ expect(errors.isNotEmpty, true); |
+ expect(errors.length, 1, reason: errors.toString()); |
+ var error = errors[0]; |
+ expect(error.message, 'Undefined mixin b'); |
+ expect(error.span.start.line, 1); |
+ expect(error.span.start.offset, 14); |
+} |
+ |
+void includeGrammar() { |
+ compileAndValidate(r''' |
+@mixin a { |
+ foo { color: red } |
+} |
+ |
+@mixin b { |
+ @include a; |
+ @include a; |
+} |
+ |
+@include b; |
+''', r'''foo { |
+ color: #f00; |
+} |
+foo { |
+ color: #f00; |
+}'''); |
+ |
+ compileAndValidate(r''' |
+@mixin a { |
+ color: red |
+} |
+ |
+foo { |
+ @include a; |
+ @include a |
+} |
+''', r'''foo { |
+ color: #f00; |
+ color: #f00; |
+}'''); |
+ |
+ var errors = []; |
+ var input = r''' |
+@mixin a { |
+ foo { color: red } |
+} |
+ |
+@mixin b { |
+ @include a |
+ @include a |
+} |
+ |
+@include b |
+'''; |
+ |
+ var stylesheet = compileCss(input, errors: errors, opts: options); |
+ |
+ expect(stylesheet != null, true); |
+ |
+ expect(errors.isNotEmpty, true); |
+ expect(errors.length, 6, reason: errors.toString()); |
+ var error = errors[0]; |
+ expect(error.message, 'parsing error expected ;'); |
+ expect(error.span.start.line, 6); |
+ expect(error.span.end.offset, 69); |
+ error = errors[1]; |
+ expect(error.message, 'expected :, but found }'); |
+ expect(error.span.start.line, 7); |
+ expect(error.span.end.offset, 73); |
+ error = errors[2]; |
+ expect(error.message, 'parsing error expected }'); |
+ expect(error.span.start.line, 9); |
+ expect(error.span.end.offset, 83); |
+ error = errors[3]; |
+ expect(error.message, 'expected {, but found end of file()'); |
+ expect(error.span.start.line, 9); |
+ expect(error.span.end.offset, 86); |
+ error = errors[4]; |
+ expect(error.message, 'expected }, but found end of file()'); |
+ expect(error.span.start.line, 10); |
+ expect(error.span.end.offset, 86); |
+ error = errors[5]; |
+ expect(error.message, 'Using top-level mixin a as a declaration'); |
+ expect(error.span.start.line, 5); |
+ expect(error.span.end.offset, 56); |
+} |
+ |
+main() { |
+ group('Basic mixin', () { |
+ test('include grammar', includeGrammar); |
+ test('empty mixin content', emptyMixin); |
+ }); |
+ |
+ group('Top-level mixin', () { |
+ test('simple mixin', topLevelMixin); |
+ test('mixin with two @includes', topLevelMixinTwoIncludes); |
+ test('multi rulesets', topLevelMixinMultiRulesets); |
+ test('multi rulesets and nesting', topLevelMixinDeeplyNestedRulesets); |
+ test('selector groups', topLevelMixinSelectors); |
+ }); |
+ |
+ group('Declaration mixin', () { |
+ test('simple', declSimpleMixin); |
+ test('with two @includes', declMixinTwoIncludes); |
+ test('with includes', declMixinNestedIncludes); |
+ test('with deeper nesting', declMixinDeeperNestedIncludes); |
+ }); |
+ |
+ group('Mixin arguments', () { |
+ test('simple arg', mixinArg); |
+ test('multiple args and var decls as args', mixinManyArgs); |
+ }); |
+ |
+ group('Mixin warnings', () { |
+ test('undefined top-level', undefinedTopLevel); |
+ test('undefined declaration', undefinedDeclaration); |
+ test('detect bad top-level as declaration', badDeclarationInclude); |
+ test('detect bad declaration as top-level', badTopInclude); |
+ }); |
+} |