| // 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. | |
| import 'package:csslib/src/messages.dart'; | |
| import 'package:test/test.dart'; | |
| import 'testing.dart'; | |
| void compileAndValidate(String input, String generated) { | |
| var errors = <Message>[]; | |
| var stylesheet = compileCss(input, errors: errors, opts: options); | |
| expect(errors.isEmpty, true, reason: errors.toString()); | |
| expect(prettyPrint(stylesheet), generated); | |
| } | |
| void compilePolyfillAndValidate(String input, String generated) { | |
| var errors = <Message>[]; | |
| var stylesheet = polyFillCompileCss(input, errors: errors, opts: options); | |
| 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 = <Message>[]; | |
| final input = r''' | |
| @mixin a { | |
| #foo-id { | |
| color: red; | |
| } | |
| } | |
| @mixin b { | |
| span { | |
| border: 2px dashed red; | |
| @include a; | |
| } | |
| } | |
| @include b; | |
| '''; | |
| compileCss(input, errors: errors, opts: options); | |
| 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 = <Message>[]; | |
| final input = r''' | |
| @mixin b { | |
| color: red; | |
| } | |
| @mixin a { | |
| span { | |
| border: 2px dashed red; | |
| } | |
| @include b; | |
| } | |
| @include a; | |
| '''; | |
| compileCss(input, errors: errors, opts: options); | |
| 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 = <Message>[]; | |
| 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(errors.isEmpty, true, reason: errors.toString()); | |
| expect(prettyPrint(stylesheet), generated); | |
| } | |
| void undefinedTopLevel() { | |
| final errors = <Message>[]; | |
| final input = r''' | |
| @mixin a { | |
| @include b; | |
| } | |
| @mixin b { | |
| span { | |
| border: 2px dashed red; | |
| } | |
| @include a; | |
| } | |
| @include b; | |
| '''; | |
| compileCss(input, errors: errors, opts: options); | |
| 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 = <Message>[]; | |
| final input = r''' | |
| @mixin a { | |
| @include b; | |
| } | |
| @mixin b { | |
| border: 2px dashed red; | |
| @include a; | |
| } | |
| div { | |
| @include b; | |
| } | |
| '''; | |
| compileCss(input, errors: errors, opts: options); | |
| 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 = <Message>[]; | |
| var input = r''' | |
| @mixin a { | |
| foo { color: red } | |
| } | |
| @mixin b { | |
| @include a | |
| @include a | |
| } | |
| @include b | |
| '''; | |
| compileCss(input, errors: errors, opts: options); | |
| expect(errors, hasLength(4)); | |
| expect(errors[0].describe, '7:4:parsing error expected ;'); | |
| expect(errors[1].describe, '8:1:expected :, but found }'); | |
| expect(errors[2].describe, '10:11:expected }, but found end of file'); | |
| expect(errors[3].describe, '6:4:Using top-level mixin a as a declaration'); | |
| } | |
| void 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('many args', mixinArgs, skip: 'hangs at runtime'); | |
| 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); | |
| }); | |
| } |