// 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 = []; 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 = []; 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 = []; 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 = []; 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 = []; 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 = []; 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 = []; 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 = []; 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); }); }