// 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:term_glyph/term_glyph.dart' as glyph; 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 simpleVar() { final input = ''' :root { var-color-background: red; var-color-foreground: blue; var-c: #00ff00; var-b: var(c); var-a: var(b); var-level-1-normal: 1px; } .testIt { color: var(color-foreground); background: var(color-background); border-radius: var(level-1-normal); } '''; final generated = ''' :root { var-color-background: #f00; var-color-foreground: #00f; var-c: #0f0; var-b: var(c); var-a: var(b); var-level-1-normal: 1px; } .testIt { color: var(color-foreground); background: var(color-background); border-radius: var(level-1-normal); }'''; final generatedPolyfill = ''' :root { } .testIt { color: #00f; background: #f00; border-radius: 1px; }'''; compileAndValidate(input, generated); compilePolyfillAndValidate(input, generatedPolyfill); } void expressionsVar() { final input = ''' :root { var-color-background: red; var-color-foreground: blue; var-c: #00ff00; var-b: var(c); var-a: var(b); var-image: url(test.png); var-b-width: 20cm; var-m-width: 33%; var-b-height: 30EM; var-width: .6in; var-length: 1.2in; var-web-stuff: -10Px; var-rgba: rgba(10,20,255); var-transition: color 0.4s; var-transform: rotate(20deg); var-content: "✔"; var-text-shadow: 0 -1px 0 #bfbfbf; var-font-family: Gentium; var-src: url("http://example.com/fonts/Gentium.ttf"); var-src-1: local(Gentium Bold), local(Gentium-Bold), url("GentiumBold.ttf"); var-unicode-range: U+000-49F, U+2000-27FF, U+2900-2BFF, U+1D400-1D7FF; var-unicode-range-1: U+0A-FF, U+980-9FF, U+????, U+3???; var-grid-columns: 10px ("content" 1fr 10px) [4]; } .testIt { color: var(color-foreground); background: var(c); background-image: var(image); border-width: var(b-width); margin-width: var(m-width); border-height: var(b-height); width: var(width); length: var(length); -web-stuff: var(web-stuff); background-color: var(rgba); transition: var(transition); transform: var(transform); content: var(content); text-shadow: var(text-shadow); } @font-face { font-family: var(font-family); src: var(src); unicode-range: var(unicode-range); } @font-face { font-family: var(font-family); src: var(src-1); unicode-range: var(unicode-range-1); } .foobar { grid-columns: var(grid-columns); } '''; final generated = ''' :root { var-color-background: #f00; var-color-foreground: #00f; var-c: #0f0; var-b: var(c); var-a: var(b); var-image: url("test.png"); var-b-width: 20cm; var-m-width: 33%; var-b-height: 30em; var-width: .6in; var-length: 1.2in; var-web-stuff: -10px; var-rgba: rgba(10, 20, 255); var-transition: color 0.4s; var-transform: rotate(20deg); var-content: "✔"; var-text-shadow: 0 -1px 0 #bfbfbf; var-font-family: Gentium; var-src: url("http://example.com/fonts/Gentium.ttf"); var-src-1: local(Gentium Bold), local(Gentium-Bold), url("GentiumBold.ttf"); var-unicode-range: U+000-49F, U+2000-27FF, U+2900-2BFF, U+1D400-1D7FF; var-unicode-range-1: U+0A-FF, U+980-9FF, U+????, U+3???; var-grid-columns: 10px ("content" 1fr 10px) [4]; } .testIt { color: var(color-foreground); background: var(c); background-image: var(image); border-width: var(b-width); margin-width: var(m-width); border-height: var(b-height); width: var(width); length: var(length); -web-stuff: var(web-stuff); background-color: var(rgba); transition: var(transition); transform: var(transform); content: var(content); text-shadow: var(text-shadow); } @font-face { font-family: var(font-family); src: var(src); unicode-range: var(unicode-range); } @font-face { font-family: var(font-family); src: var(src-1); unicode-range: var(unicode-range-1); } .foobar { grid-columns: var(grid-columns); }'''; compileAndValidate(input, generated); var generatedPolyfill = r''' :root { } .testIt { color: #00f; background: #0f0; background-image: url("test.png"); border-width: 20cm; margin-width: 33%; border-height: 30em; width: .6in; length: 1.2in; -web-stuff: -10px; background-color: rgba(10, 20, 255); transition: color 0.4s; transform: rotate(20deg); content: "✔"; text-shadow: 0 -1px 0 #bfbfbf; } @font-face { font-family: Gentium; src: url("http://example.com/fonts/Gentium.ttf"); unicode-range: U+000-49F, U+2000-27FF, U+2900-2BFF, U+1D400-1D7FF; } @font-face { font-family: Gentium; src: local(Gentium Bold), local(Gentium-Bold), url("GentiumBold.ttf"); unicode-range: U+0A-FF, U+980-9FF, U+????, U+3???; } .foobar { grid-columns: 10px ("content" 1fr 10px) [4]; }'''; compilePolyfillAndValidate(input, generatedPolyfill); } void defaultVar() { final input = ''' :root { var-color-background: red; var-color-foreground: blue; var-a: var(b, #0a0); var-b: var(c, #0b0); var-c: #00ff00; var-image: url(test.png); var-b-width: 20cm; var-m-width: 33%; var-b-height: 30EM; } .test { background-color: var(test, orange); } body { background: var(a) var(image) no-repeat right top; } div { background: var(color-background) url('img_tree.png') no-repeat right top; } .test-2 { background: var(color-background) var(image-2, url('img_1.png')) no-repeat right top; } .test-3 { background: var(color-background) var(image) no-repeat right top; } .test-4 { background: #ffff00 var(image) no-repeat right top; } .test-5 { background: var(test-color, var(a)) var(image) no-repeat right top; } .test-6 { border: red var(a-1, solid 20px); } '''; final generated = ''' :root { var-color-background: #f00; var-color-foreground: #00f; var-a: var(b, #0a0); var-b: var(c, #0b0); var-c: #0f0; var-image: url("test.png"); var-b-width: 20cm; var-m-width: 33%; var-b-height: 30em; } .test { background-color: var(test, #ffa500); } body { background: var(a) var(image) no-repeat right top; } div { background: var(color-background) url("img_tree.png") no-repeat right top; } .test-2 { background: var(color-background) var(image-2, url("img_1.png")) no-repeat right top; } .test-3 { background: var(color-background) var(image) no-repeat right top; } .test-4 { background: #ff0 var(image) no-repeat right top; } .test-5 { background: var(test-color, var(a)) var(image) no-repeat right top; } .test-6 { border: #f00 var(a-1, solid 20px); }'''; compileAndValidate(input, generated); var generatedPolyfill = r''' :root { } .test { background-color: #ffa500; } body { background: #0a0 url("test.png") no-repeat right top; } div { background: #f00 url("img_tree.png") no-repeat right top; } .test-2 { background: #f00 url("img_1.png") no-repeat right top; } .test-3 { background: #f00 url("test.png") no-repeat right top; } .test-4 { background: #ff0 url("test.png") no-repeat right top; } .test-5 { background: #0a0 url("test.png") no-repeat right top; } .test-6 { border: #f00 solid 20px; }'''; compilePolyfillAndValidate(input, generatedPolyfill); } void undefinedVars() { final errors = []; final input = ''' :root { var-color-background: red; var-color-foreground: blue; var-a: var(b); var-b: var(c); var-c: #00ff00; var-one: var(two); var-two: var(one); var-four: var(five); var-five: var(six); var-six: var(four); var-def-1: var(def-2); var-def-2: var(def-3); var-def-3: var(def-2); } .testIt { color: var(color-foreground); background: var(color-background); } .test-1 { color: var(c); } .test-2 { color: var(one); background: var(six); } '''; final generatedPolyfill = ''' :root { } .testIt { color: #00f; background: #f00; } .test-1 { color: #0f0; } .test-2 { color: ; background: ; }'''; var errorStrings = [ 'error on line 5, column 14: Variable is not defined.\n' ' ,\n' '5 | var-a: var(b);\n' ' | ^^\n' ' \'', 'error on line 6, column 14: Variable is not defined.\n' ' ,\n' '6 | var-b: var(c);\n' ' | ^^\n' ' \'', 'error on line 9, column 16: Variable is not defined.\n' ' ,\n' '9 | var-one: var(two);\n' ' | ^^^^\n' ' \'', 'error on line 12, column 17: Variable is not defined.\n' ' ,\n' '12 | var-four: var(five);\n' ' | ^^^^^\n' ' \'', 'error on line 13, column 17: Variable is not defined.\n' ' ,\n' '13 | var-five: var(six);\n' ' | ^^^^\n' ' \'', 'error on line 16, column 18: Variable is not defined.\n' ' ,\n' '16 | var-def-1: var(def-2);\n' ' | ^^^^^^\n' ' \'', 'error on line 17, column 18: Variable is not defined.\n' ' ,\n' '17 | var-def-2: var(def-3);\n' ' | ^^^^^^\n' ' \'', ]; var generated = r''' :root { var-color-background: #f00; var-color-foreground: #00f; var-a: var(b); var-b: var(c); var-c: #0f0; var-one: var(two); var-two: var(one); var-four: var(five); var-five: var(six); var-six: var(four); var-def-1: var(def-2); var-def-2: var(def-3); var-def-3: var(def-2); } .testIt { color: var(color-foreground); background: var(color-background); } .test-1 { color: var(c); } .test-2 { color: var(one); background: var(six); }'''; var testBitMap = 0; compileAndValidate(input, generated); var stylesheet = polyFillCompileCss(input, errors: errors..clear(), opts: options); expect(errors.length, errorStrings.length, reason: errors.toString()); testBitMap = 0; outer: for (var error in errors) { var errorString = error.toString(); for (var i = 0; i < errorStrings.length; i++) { if (errorString == errorStrings[i]) { testBitMap |= 1 << i; continue outer; } } fail('Unexpected error string: $errorString'); } expect(testBitMap, equals((1 << errorStrings.length) - 1)); expect(prettyPrint(stylesheet), generatedPolyfill); } void parserVar() { final input = ''' :root { var-color-background: red; var-color-foreground: blue; var-c: #00ff00; var-b: var(c); var-a: var(b); var-image: url(test.png); var-b-width: 20cm; var-m-width: 33%; var-b-height: 30EM; var-width: .6in; var-length: 1.2in; var-web-stuff: -10Px; var-rgba: rgba(10,20,255); var-transition: color 0.4s; var-transform: rotate(20deg); var-content: "✔"; var-text-shadow: 0 -1px 0 #bfbfbf; var-font-family: Gentium; var-src: url("http://example.com/fonts/Gentium.ttf"); var-src-1: local(Gentium Bold), local(Gentium-Bold), url("GentiumBold.ttf"); var-unicode-range: U+000-49F, U+2000-27FF, U+2900-2BFF, U+1D400-1D7FF; var-unicode-range-1: U+0A-FF, U+980-9FF, U+????, U+3???; var-grid-columns: 10px ("content" 1fr 10px) [4]; } .testIt { color: var(color-foreground); background: var(c); background-image: var(image); border-width: var(b-width); margin-width: var(m-width); border-height: var(b-height); width: var(width); length: var(length); -web-stuff: var(web-stuff); background-color: var(rgba); transition: var(transition); transform: var(transform); content: var(content); text-shadow: var(text-shadow); } @font-face { font-family: var(font-family); src: var(src); unicode-range: var(unicode-range); } @font-face { font-family: var(font-family); src: var(src-1); unicode-range: var(unicode-range-1); } .foobar { grid-columns: var(grid-columns); } '''; final generated = ''' :root { var-color-background: #f00; var-color-foreground: #00f; var-c: #0f0; var-b: var(c); var-a: var(b); var-image: url("test.png"); var-b-width: 20cm; var-m-width: 33%; var-b-height: 30em; var-width: .6in; var-length: 1.2in; var-web-stuff: -10px; var-rgba: rgba(10, 20, 255); var-transition: color 0.4s; var-transform: rotate(20deg); var-content: "✔"; var-text-shadow: 0 -1px 0 #bfbfbf; var-font-family: Gentium; var-src: url("http://example.com/fonts/Gentium.ttf"); var-src-1: local(Gentium Bold), local(Gentium-Bold), url("GentiumBold.ttf"); var-unicode-range: U+000-49F, U+2000-27FF, U+2900-2BFF, U+1D400-1D7FF; var-unicode-range-1: U+0A-FF, U+980-9FF, U+????, U+3???; var-grid-columns: 10px ("content" 1fr 10px) [4]; } .testIt { color: var(color-foreground); background: var(c); background-image: var(image); border-width: var(b-width); margin-width: var(m-width); border-height: var(b-height); width: var(width); length: var(length); -web-stuff: var(web-stuff); background-color: var(rgba); transition: var(transition); transform: var(transform); content: var(content); text-shadow: var(text-shadow); } @font-face { font-family: var(font-family); src: var(src); unicode-range: var(unicode-range); } @font-face { font-family: var(font-family); src: var(src-1); unicode-range: var(unicode-range-1); } .foobar { grid-columns: var(grid-columns); }'''; compileAndValidate(input, generated); var generatedPolyfill = r''' :root { } .testIt { color: #00f; background: #0f0; background-image: url("test.png"); border-width: 20cm; margin-width: 33%; border-height: 30em; width: .6in; length: 1.2in; -web-stuff: -10px; background-color: rgba(10, 20, 255); transition: color 0.4s; transform: rotate(20deg); content: "✔"; text-shadow: 0 -1px 0 #bfbfbf; } @font-face { font-family: Gentium; src: url("http://example.com/fonts/Gentium.ttf"); unicode-range: U+000-49F, U+2000-27FF, U+2900-2BFF, U+1D400-1D7FF; } @font-face { font-family: Gentium; src: local(Gentium Bold), local(Gentium-Bold), url("GentiumBold.ttf"); unicode-range: U+0A-FF, U+980-9FF, U+????, U+3???; } .foobar { grid-columns: 10px ("content" 1fr 10px) [4]; }'''; compilePolyfillAndValidate(input, generatedPolyfill); } void testVar() { final errors = []; final input = ''' @color-background: red; @color-foreground: blue; .test { background-color: var(color-background); color: var(color-foreground); } '''; final generated = ''' var-color-background: #f00; var-color-foreground: #00f; .test { background-color: var(color-background); color: var(color-foreground); }'''; var stylesheet = parseCss(input, errors: errors, opts: simpleOptions); expect(errors.isEmpty, true, reason: errors.toString()); expect(prettyPrint(stylesheet), generated); compileAndValidate(input, generated); final input2 = ''' @color-background: red; @color-foreground: blue; .test { background-color: @color-background; color: @color-foreground; } '''; final generated2 = ''' var-color-background: #f00; var-color-foreground: #00f; .test { background-color: var(color-background); color: var(color-foreground); }'''; stylesheet = parseCss(input, errors: errors..clear(), opts: simpleOptions); expect(errors.isEmpty, true, reason: errors.toString()); expect(prettyPrint(stylesheet), generated2); compileAndValidate(input2, generated2); } void testLess() { final errors = []; final input = ''' @color-background: red; @color-foreground: blue; .test { background-color: var(color-background); color: var(color-foreground); } '''; final generated = ''' var-color-background: #f00; var-color-foreground: #00f; .test { background-color: var(color-background); color: var(color-foreground); }'''; var stylesheet = parseCss(input, errors: errors, opts: simpleOptions); expect(errors.isEmpty, true, reason: errors.toString()); expect(prettyPrint(stylesheet), generated); compileAndValidate(input, generated); final input2 = ''' @color-background: red; @color-foreground: blue; .test { background-color: @color-background; color: @color-foreground; } '''; final generated2 = ''' var-color-background: #f00; var-color-foreground: #00f; .test { background-color: var(color-background); color: var(color-foreground); }'''; stylesheet = parseCss(input, errors: errors..clear(), opts: simpleOptions); expect(errors.isEmpty, true, reason: errors.toString()); expect(prettyPrint(stylesheet), generated2); compileAndValidate(input2, generated2); } void polyfill() { compilePolyfillAndValidate(r''' @color-background: red; @color-foreground: blue; .test { background-color: @color-background; color: @color-foreground; }''', r''' .test { background-color: #f00; color: #00f; }'''); } void testIndirects() { compilePolyfillAndValidate(''' :root { var-redef: #0f0; var-a1: #fff; var-a2: var(a1); var-a3: var(a2); var-redef: #000; } .test { background-color: @a1; color: @a2; border-color: @a3; } .test-1 { color: @redef; }''', r''' :root { } .test { background-color: #fff; color: #fff; border-color: #fff; } .test-1 { color: #000; }'''); } void includes() { var errors = []; var file1Input = r''' :root { var-redef: #0f0; var-a1: #fff; var-a2: var(a1); var-a3: var(a2); var-redef: #000; } .test-1 { background-color: @a1; color: @a2; border-color: @a3; } .test-1a { color: @redef; } '''; var file2Input = r''' :root { var-redef: #0b0; var-b3: var(a3); } .test-2 { color: var(b3); background-color: var(redef); border-color: var(a3); } '''; var input = r''' :root { var-redef: #0c0; } .test-main { color: var(b3); background-color: var(redef); border-color: var(a3); } '''; var generated1 = r''' :root { var-redef: #0f0; var-a1: #fff; var-a2: var(a1); var-a3: var(a2); var-redef: #000; } .test-1 { background-color: var(a1); color: var(a2); border-color: var(a3); } .test-1a { color: var(redef); }'''; var stylesheet1 = compileCss(file1Input, errors: errors, opts: options); expect(errors.isEmpty, true, reason: errors.toString()); expect(prettyPrint(stylesheet1), generated1); var generated2 = r''' :root { var-redef: #0b0; var-b3: var(a3); } .test-2 { color: var(b3); background-color: var(redef); border-color: var(a3); }'''; var stylesheet2 = compileCss(file2Input, includes: [stylesheet1], errors: errors..clear(), opts: options); expect(errors.isEmpty, true, reason: errors.toString()); expect(prettyPrint(stylesheet2), generated2); var generatedPolyfill1 = r''' :root { } .test-1 { background-color: #fff; color: #fff; border-color: #fff; } .test-1a { color: #000; }'''; var styleSheet1Polyfill = compileCss(file1Input, errors: errors..clear(), polyfill: true, opts: options); expect(errors.isEmpty, true, reason: errors.toString()); expect(prettyPrint(styleSheet1Polyfill), generatedPolyfill1); var generatedPolyfill2 = r''' :root { } .test-2 { color: #fff; background-color: #0b0; border-color: #fff; }'''; var styleSheet2Polyfill = compileCss(file2Input, includes: [stylesheet1], errors: errors..clear(), polyfill: true, opts: options); expect(errors.isEmpty, true, reason: errors.toString()); expect(prettyPrint(styleSheet2Polyfill), generatedPolyfill2); // Make sure includes didn't change. expect(prettyPrint(stylesheet1), generated1); var generatedPolyfill = r''' :root { } .test-main { color: #fff; background-color: #0c0; border-color: #fff; }'''; var stylesheetPolyfill = compileCss(input, includes: [stylesheet1, stylesheet2], errors: errors..clear(), polyfill: true, opts: options); expect(errors.isEmpty, true, reason: errors.toString()); expect(prettyPrint(stylesheetPolyfill), generatedPolyfill); // Make sure includes didn't change. expect(prettyPrint(stylesheet1), generated1); expect(prettyPrint(stylesheet2), generated2); } void main() { glyph.ascii = true; test('Simple var', simpleVar); test('Expressions var', expressionsVar); test('Default value in var()', defaultVar); test('CSS Parser only var', parserVar); test('Var syntax', testVar); test('Indirects', testIndirects); test('Forward Refs', undefinedVars); test('Less syntax', testLess); test('Polyfill', polyfill); test('Multi-file', includes); }