8992076c3c
- Added parameter support for mixins. - Support var definition with multiple parameters as an @include parameter. - Support @extend and @extend with nested selectors. - Removed hoisting var and cycle checks - can only reference known vars. - Added bunch of tests. BUG= Review URL: https://codereview.chromium.org//23819036 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@28555 260f80e4-7a28-3924-810f-c04153c831b5
666 lines
12 KiB
Dart
666 lines
12 KiB
Dart
// 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:unittest/unittest.dart';
|
|
import 'testing.dart';
|
|
|
|
final options = ['--warnings_as_errors', '--no-colors', 'memory'];
|
|
|
|
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 generated = r'''span {
|
|
border: 2px dashed #f00;
|
|
}''';
|
|
|
|
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(\n)');
|
|
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);
|
|
});
|
|
}
|