/* * Copyright 2011 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.common.css.compiler.passes; import com.google.common.css.compiler.ast.GssParserException; import com.google.common.css.compiler.passes.testing.PassesTestBase; /** * Unit tests for the {@link ReplaceMixins} compiler pass. * * @author fbenz@google.com (Florian Benz) */ public class ReplaceMixinsTest extends PassesTestBase { public void testMixinReplacement1() { testTreeConstruction(linesToString( "@defmixin test(COLOR) {", " color: COLOR;", "}", "p {", " @mixin test(#FFF);", "}"), "[[p]{[color:[[#fff]];]}]"); } public void testMixinReplacement2() { testTreeConstruction(linesToString( "@defmixin color(COLOR) {", " color: COLOR;", "}", "@defmixin size(H, W) {", " width: W;", " height: H;", "}", "p {", " @mixin size(50%, 60%);", " position: absolute;", " @mixin color(red);", "}"), "[[p]{[width:[[60%]];height:[[50%]];position:[[absolute]];color:[[red]];]}]"); } public void testMixinReplacement3() { testTreeConstruction(linesToString( "@defmixin size(H, W) {", " width: W;", " height: H;", "}", "p {", " @mixin size(50%, 60%);", " position: absolute;", "}", "div {", " @mixin size(100px, 200px);", "}"), "[[p]{[width:[[60%]];height:[[50%]];position:[[absolute]];]}" + "[div]{[width:[[200px]];height:[[100px]];]}]"); } public void testMixinReplacement4() { testTreeConstruction(linesToString( "@defmixin size(W, H) {", " width: W;", " height: H;", "}", "@defmixin color(COLOR) {", " color: COLOR;", "}", "@defmixin position(POS) {", " position: POS;", "}", "p {", " @mixin color(#fff);", " @mixin size(50%, 60%);", " @mixin position(absolute);", "}"), "[[p]{[color:[[#fff]];width:[[50%]];height:[[60%]];position:[[absolute]];]}]"); } public void testMixinReplacement5() { testTreeConstruction(linesToString( "@defmixin empty(X, Y) {", "}", "p {", " @mixin empty(10, 20);", "}"), "[[p]{[]}]"); } public void testMixinReplacementTwice() { testTreeConstruction(linesToString( "@defmixin test(COLOR) {", " color: COLOR;", "}", "p {", " @mixin test(#FFF);", " @mixin test(#FFF);", "}"), "[[p]{[color:[[#fff]];color:[[#fff]];]}]"); } public void testMixinReplacementNested1() { testTreeConstruction(linesToString( "@defmixin width(W) {", " width: W;", "}", "@defmixin size(W, H) {", " @mixin width(W);", " height: H;", "}", "p {", " @mixin size(50%, 60%);", " position: absolute;", "}"), "[[p]{[width:[[50%]];height:[[60%]];position:[[absolute]];]}]"); } public void testMixinReplacementNested2() { testTreeConstruction(linesToString( "@defmixin width(W2) {", " width: W2;", "}", "@defmixin size(W, H) {", " @mixin width(W);", " height: H;", "}", "p {", " @mixin size(50%, 60%);", " @mixin width(10px);", " position: absolute;", "}"), "[[p]{[width:[[50%]];height:[[60%]];width:[[10px]];position:[[absolute]];]}]"); } public void testMixinReplacementNested3() { testTreeConstruction(linesToString( "@defmixin width(W2) {", " width: W2;", "}", "@defmixin size(W, H) {", " @mixin width(W);", " height: H;", " @mixin width(H);", "}", "p {", " @mixin size(50%, 60%);", " @mixin width(10px);", " position: absolute;", "}"), "[[p]{[width:[[50%]];height:[[60%]];width:[[60%]];width:[[10px]];" + "position:[[absolute]];]}]"); } public void testMixinReplacementNested4() { testTreeConstruction(linesToString( "@defmixin empty() {", "}", "@defmixin callEmpty() {", " @mixin empty();", "}", "p {", " @mixin empty();", " @mixin callEmpty();", " @mixin callEmpty();", " @mixin empty();", "}"), "[[p]{[]}]"); } public void testMixinReplacementNested5() { testTreeConstruction(linesToString( "@defmixin empty() {", "}", "@defmixin callEmpty1() {", " @mixin empty();", "}", "@defmixin callEmpty2() {", " @mixin callEmpty1();", "}", "@defmixin callEmpty3() {", " @mixin callEmpty2();", "}", "p {", " @mixin empty();", " @mixin callEmpty3();", " @mixin empty();", "}"), "[[p]{[]}]"); } public void testCompositeValues1() { testTreeConstruction(linesToString( "@defmixin font(RATIO) {", " font: RATIO Arial;", "}", "p {", " @mixin font(1em/1.3em);", "}"), // TODO(user): note Arial has been incorrectly parsed here // as a GSS variable use rather than a font-family list. // There's no comma so no way to tell the difference. It // doesn't make any difference though because the ASTs are // identical, but someday if we want to // expand shorthands we will need to disambiguate these // cases in the AST. "[[p]{[font:[[[1em]/[1.3em]][Arial]];]}]"); } public void testCompositeValues2() { testTreeConstruction(linesToString( "@defmixin margin(MARGIN) {", " margin: MARGIN;", "}", "p {", " @mixin margin(1px 2px 3px 4px);", "}"), "[[p]{[margin:[[1px][2px][3px][4px]];]}]"); } public void testMixinReplacementComponents() { testTreeConstruction(linesToString( "@defmixin col(PAR) {", " color: PAR;", " background-color: CHANGES;", "}", "@abstract_component ABY {", " @def STAYS red;", " @def CHANGES none;", " .ABSTRACT { background: CHANGES; font: STAYS; }", "}", "@component BLUE_ABY extends ABY {", " @def CHANGES blue;", " .INCOMP { @mixin col(CHANGES); }", "}", "@component YELLOW_ABY extends ABY {", " @def CHANGES yellow;", " .INCOMP { @mixin col(green);}", "}"), "[[.BLUE_ABY-ABSTRACT]{[background:[[blue]];font:[[red]];]}" + "[.BLUE_ABY-INCOMP]{[color:[[blue]];background-color:[[blue]];]}" + "[.YELLOW_ABY-ABSTRACT]{[background:[[yellow]];font:[[red]];]}" + "[.YELLOW_ABY-INCOMP]{[color:[[green]];background-color:[[yellow]];]}]"); } public void testMixinReplacementGradient1() { testTreeConstruction(linesToString( "@def GRADIENT_POS top;", "@def BASE_COLOR #cc0000;", "@def FONT_SIZE_SMALL 80%;", "@defmixin gradient(HSL1, HSL2, HSL3, COLOR) {", " background-color: COLOR;", " background-image:", " -webkit-linear-gradient(GRADIENT_POS, hsl(HSL1, HSL2, HSL3),", " COLOR);", "}", ".SOME_CLASS {", " @mixin gradient(0%, 50%, 70%, BASE_COLOR);", " font-size: FONT_SIZE_SMALL;", "}"), "[[.SOME_CLASS]{[background-color:[[#cc0000]];" + "background-image:" + "[-webkit-linear-gradient(top,hsl(0%,50%,70%),#cc0000)];" + "font-size:[[80%]];]}]"); } public void testMixinReplacementGradient2() { testTreeConstruction(linesToString( "@defmixin grad(A, B, C, D, E) {", " background-image:-webkit-linear-gradient(A, B, C, D, E);", "}", ".SOME_CLASS {", " @mixin grad(bottom left, red 20px, yellow, green, blue 90%);", "}"), "[[.SOME_CLASS]{[" + "background-image:[-webkit-linear-gradient(" + "bottom left,red 20px,yellow,green,blue 90%)];]}]"); } public void testMixinReplacementGradient3() { testTreeConstruction(linesToString( "@defmixin grad(A, B, C, D, E) {", " background-image:-webkit-linear-gradient(A, B, C, D, E);", "}", ".SOME_CLASS {", " @mixin grad( bottom left, red 20px, yellow, green , blue 90%);", "}"), "[[.SOME_CLASS]{[" + "background-image:[-webkit-linear-gradient(" + "bottom left,red 20px,yellow,green,blue 90%)];]}]"); } public void testMixinReplacementOnCompositeValueNodeWithNestedValues() { testTreeConstruction(linesToString( "@defmixin foo(A, B, C) {", " background: rgba(A, B, C, .2), rgba(A, B, C, .2);", "}", ".a { @mixin foo(255, 255, 255); }", ".b { @mixin foo(0, 0, 0); }"), "[[.a]{[background:[[rgba(255,255,255,.2),rgba(255,255,255,.2)]];]}" + "[.b]{[background:[[rgba(0,0,0,.2),rgba(0,0,0,.2)]];]}]"); } public void testMixinReplacementGradientYouTube() { testTreeConstruction(linesToString( "@def _VR_MAIN rgba(0, 0, 0, .12), rgba(0, 0, 0, .08) 1px," + " rgba(0, 0, 0, .08) 1px, rgba(0, 0, 0, 0) 30px, transparent 100%;", "@defmixin rule_main(ORIENTATION, GRADIENT_PARAMS) {", "/* @alternate */ background: -webkit-linear-gradient(ORIENTATION,", "GRADIENT_PARAMS);", "}", "", ".test {", "@mixin rule_main(left, _VR_MAIN);", "}"), "[[.test]{[[/* @alternate */]background:[-webkit-linear-gradient(left," + "[rgba(0,0,0,.12),rgba(0,0,0,.08)] [[1px],rgba(0,0,0,.08)]" + " [[1px],rgba(0,0,0,0)] [[30px],[transparent]] 100%)];]}]"); } public void testNoMatchingMixinDefinition() throws GssParserException { parseAndRun("@defmixin test() {} h1 { @mixin unkown(); }", ReplaceMixins.NO_MATCHING_MIXIN_DEFINITION_ERROR_MESSAGE); } public void testDifferentArgumentCount1() throws GssParserException { parseAndRun("@defmixin test(PAR1) {} h1 { @mixin test(10px, 20px); }", ReplaceMixins.ARGUMENT_MISMATCH_ERROR_MESSAGE); } public void testDifferentArgumentCount2() throws GssParserException { parseAndRun("@defmixin test(PAR1, PAR2) {} h1 { @mixin test(10px); }", ReplaceMixins.ARGUMENT_MISMATCH_ERROR_MESSAGE); } public void testDifferentArgumentCount3() throws GssParserException { parseAndRun("@defmixin test() {} h1 { @mixin test(10px, 20px); }", ReplaceMixins.ARGUMENT_MISMATCH_ERROR_MESSAGE); } public void testDifferentArgumentCount4() throws GssParserException { parseAndRun("@defmixin test(PAR1) {} h1 { @mixin test(); }", ReplaceMixins.ARGUMENT_MISMATCH_ERROR_MESSAGE); } public void testDifferentArgumentCount5() throws GssParserException { parseAndRun(linesToString( "@defmixin someMixin(A, C, D, E) {", " background-image:some-function(A, C, D, E);", "}", ".SOME_CLASS {", " @mixin someMixin(bottom left, red 20px, yellow, green, blue 90%);", "}"), "GSS constant not defined: A", "GSS constant not defined: C", "GSS constant not defined: D", "GSS constant not defined: E", ReplaceMixins.ARGUMENT_MISMATCH_ERROR_MESSAGE); } public void testNestedBad() throws GssParserException { parseAndRun(linesToString( "@defmixin color(COL) {", " width: COLOR;", "}", "@defmixin box(W, H, COLOR) {", " width: W;", " height: H;", " @mixin color(COLOR);", "}", "p {", " @mixin box(50%, 60%, red);", "}"), "GSS constant not defined: COLOR"); } public void testCycle() throws GssParserException { parseAndRun(linesToString( "@defmixin a(A) {", " @mixin c(A);", " height: A;", "}", "@defmixin b(B) {", " @mixin a(B);", " width: B;", "}", "@defmixin c(C) {", " @mixin b(C);", "}", "div {", " @mixin a(10px);", "}"), ReplaceMixins.CYCLE_ERROR_MESSAGE); } public void testIdSeqInFontShorthandUsingDefMixin() throws Exception { testTreeConstruction(linesToString( "@defmixin myfont(FONTSTYLE, FONTFACEA, FONTFACEB) {", " font: FONTSTYLE FONTFACEA, FONTFACEB;", "}", ".foo { @mixin myfont(bold, arial, serif); }"), "[[.foo]{[font:[[bold][[arial],[serif]]];]}]"); } @Override protected void runPass() { // This passes have to run before. new CreateMixins(tree.getMutatingVisitController(), errorManager).runPass(); new CreateConstantReferences(tree.getMutatingVisitController()).runPass(); // This passes should run before to produce the expected behavior. // They are needed for testMixinReplacementComponents. new CreateDefinitionNodes(tree.getMutatingVisitController(), errorManager).runPass(); new CreateComponentNodes(tree.getMutatingVisitController(), errorManager).runPass(); // The passes tested here. CollectMixinDefinitions collectDefinitions = new CollectMixinDefinitions( tree.getMutatingVisitController(), errorManager); collectDefinitions.runPass(); new ReplaceMixins(tree.getMutatingVisitController(), errorManager, collectDefinitions.getDefinitions()).runPass(); // This passes should run afterwards to produce the expected behavior. // They are needed for testMixinReplacementComponents. new ProcessComponents<Object>(tree.getMutatingVisitController(), errorManager).runPass(); CollectConstantDefinitions collectConstantDefinitionsPass = new CollectConstantDefinitions(tree); collectConstantDefinitionsPass.runPass(); ReplaceConstantReferences replaceConstantReferences = new ReplaceConstantReferences(tree, collectConstantDefinitionsPass.getConstantDefinitions(), true /* removeDefs */, errorManager, false /* allowUndefinedConstants */); replaceConstantReferences.runPass(); } }