/* * Copyright 2009 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 static com.google.common.css.compiler.passes.ResolveCustomFunctionNodesForChunks.DEF_PREFIX; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.css.compiler.ast.CssDefinitionNode; import com.google.common.css.compiler.ast.CssLiteralNode; import com.google.common.css.compiler.ast.CssTree; import com.google.common.css.compiler.ast.CssValueNode; import com.google.common.css.compiler.ast.DefaultTreeVisitor; import com.google.common.css.compiler.ast.ErrorManager; import com.google.common.css.compiler.ast.GssFunction; import com.google.common.css.compiler.passes.testing.PassesTestBase; import java.util.List; /** * Unit tests for {@link ProcessComponents}. * * @author dgajda@google.com (Damian Gajda) */ public class ProcessComponentsTest extends PassesTestBase { private static final String FILE1 = "file1"; private static final String FILE2 = "file2"; private static final String FILE3 = "file3"; private static final String TEST_CHUNK = "testChunk"; private static final String CHUNK1 = "chunk1"; private static final String CHUNK2 = "chunk2"; private static final String CHUNK3 = "chunk3"; private static final ImmutableMap<String, String> FILE_TO_CHUNK = ImmutableMap.of( TEST_FILENAME, TEST_CHUNK, FILE1, CHUNK1, FILE2, CHUNK2, FILE3, CHUNK3); private static final String FAKE_FUNCTION_REF = "~fake~function~ref~"; private final String globalInput = "@def GLOBAL_COLOR white;"; private final String globalOutput = "@def GLOBAL_COLOR [[white]]"; private final ImmutableList<String> topComponentPrefixInput = ImmutableList.of( "@component CSS_TOP {"); private final ImmutableList<String> topComponentInputConstants = ImmutableList.of( " @def SOME_COLOR black;", " @def OTHER_BG_COLOR GLOBAL_COLOR;", " @def OTHER_COLOR someColorFunction(SOME_COLOR, OTHER_BG_COLOR);"); private final String topComponentOutputConstantsTemplate = "@def CSS_TOP__SOME_COLOR [[black]];" + "@def CSS_TOP__OTHER_BG_COLOR [[GLOBAL_COLOR]];" + "@def CSS_TOP__OTHER_COLOR [[" + FAKE_FUNCTION_REF + "]];"; private final ImmutableList<String> topComponentInputRules = ImmutableList.of( " .CSS_SOME_CLASS.CSS_SOME_VARIATION, .CSS_SOME_OTHER_CLASS {", " color: SOME_COLOR;", " background-color: OTHER_BG_COLOR;", " width: 100px;", " border-color: someColorFunction(SOME_COLOR, OTHER_BG_COLOR);", " }" ); private final String topComponentOutputRulesTemplate = "[.CSS_TOP-CSS_SOME_CLASS.CSS_SOME_VARIATION,.CSS_TOP-CSS_SOME_OTHER_CLASS]{" + "[color:[[CSS_TOP__SOME_COLOR]];" + "background-color:[[CSS_TOP__OTHER_BG_COLOR]];" + "width:[[100px]];" + "border-color:[[" + FAKE_FUNCTION_REF + "]];]}"; private final String abstractTopComponentInput = joinNl(Iterables.concat( ImmutableList.of("@abstract_component CSS_TOP {"), topComponentInputConstants, topComponentInputRules, ImmutableList.of("}"))); private final String abstractTopComponentOutput = replaceFunction(topComponentOutputConstantsTemplate, "someColorFunction(CSS_TOP__SOME_COLOR,CSS_TOP__OTHER_BG_COLOR)"); private final String abstractTopComponentOutputResolved = replaceFunction(topComponentOutputConstantsTemplate, "[" + DEF_PREFIX + "0]"); private final String topComponentInput = joinNl(Iterables.concat( topComponentPrefixInput, topComponentInputConstants, topComponentInputRules, ImmutableList.of("}"))); private final String topComponentOutput = abstractTopComponentOutput + replaceFunction(topComponentOutputRulesTemplate, "someColorFunction(CSS_TOP__SOME_COLOR,CSS_TOP__OTHER_BG_COLOR)"); private final String topComponentOutputResolved = globalOutput + ";" + abstractTopComponentOutputResolved + // This is the second occurrence of the function. replaceFunction(topComponentOutputRulesTemplate, "[" + DEF_PREFIX + "1]"); private final String childComponentInput = linesToString( "@component CSS_CHILD extends CSS_TOP {", " @def SOME_COLOR yellow;", " .CSS_SOME_CLASS {", " border: 1px solid SOME_COLOR", " }", "}"); private final String childComponentOutputTemplate = "@def CSS_CHILD__SOME_COLOR [[CSS_TOP__SOME_COLOR]];" + "@def CSS_CHILD__OTHER_BG_COLOR [[CSS_TOP__OTHER_BG_COLOR]];" + "@def CSS_CHILD__OTHER_COLOR [[CSS_TOP__OTHER_COLOR]];" + "[.CSS_CHILD-CSS_SOME_CLASS.CSS_SOME_VARIATION,.CSS_CHILD-CSS_SOME_OTHER_CLASS]{" + "[color:[[CSS_CHILD__SOME_COLOR]];" + "background-color:[[CSS_CHILD__OTHER_BG_COLOR]];" + "width:[[100px]];" + "border-color:[[" + FAKE_FUNCTION_REF + "]];]}" + "@def CSS_CHILD__SOME_COLOR [[yellow]];" + "[.CSS_CHILD-CSS_SOME_CLASS]{" + "[border:[[1px][solid][CSS_CHILD__SOME_COLOR]];]}"; private final String childComponentOutput = replaceFunction(childComponentOutputTemplate, "someColorFunction(CSS_CHILD__SOME_COLOR,CSS_CHILD__OTHER_BG_COLOR)"); private final String grandChildComponentInput = linesToString( "@component CSS_GRAND_CHILD extends CSS_CHILD {", " @def NEW_BORDER_COLOR green;", " .CSS_SOME_OTHER_CLASS {", " border: 1px solid NEW_BORDER_COLOR;", " }", "}"); private final String grandChildComponentOutputTemplate = "@def CSS_GRAND_CHILD__SOME_COLOR [[CSS_CHILD__SOME_COLOR]];" + "@def CSS_GRAND_CHILD__OTHER_BG_COLOR [[CSS_CHILD__OTHER_BG_COLOR]];" + "@def CSS_GRAND_CHILD__OTHER_COLOR [[CSS_CHILD__OTHER_COLOR]];" + "[.CSS_GRAND_CHILD-CSS_SOME_CLASS.CSS_SOME_VARIATION," + ".CSS_GRAND_CHILD-CSS_SOME_OTHER_CLASS]{" + "[color:[[CSS_GRAND_CHILD__SOME_COLOR]];" + "background-color:[[CSS_GRAND_CHILD__OTHER_BG_COLOR]];" + "width:[[100px]];" + "border-color:[[" + FAKE_FUNCTION_REF + "]];]}" + "@def CSS_GRAND_CHILD__SOME_COLOR [[CSS_CHILD__SOME_COLOR]];" + "[.CSS_GRAND_CHILD-CSS_SOME_CLASS]{" + "[border:[[1px][solid][CSS_GRAND_CHILD__SOME_COLOR]];]}" + "@def CSS_GRAND_CHILD__NEW_BORDER_COLOR [[green]];" + "[.CSS_GRAND_CHILD-CSS_SOME_OTHER_CLASS]{" + "[border:[[1px][solid][CSS_GRAND_CHILD__NEW_BORDER_COLOR]];]}"; private final String grandChildComponentOutput = replaceFunction(grandChildComponentOutputTemplate, "someColorFunction(CSS_GRAND_CHILD__SOME_COLOR,CSS_GRAND_CHILD__OTHER_BG_COLOR)"); private final ImmutableList<String> topComponentWithRefWorkaroundInputConstants = ImmutableList.of( " @def SOME_COLOR black;", " @def OTHER_BG_COLOR GLOBAL_COLOR;", " @def OTHER_COLOR someColorFunction(CSS_TOP__SOME_COLOR, CSS_TOP__OTHER_BG_COLOR);"); private final String childComponentWithRefWorkaroundInput = linesToString( "@component CSS_CHILD extends CSS_TOP {", " @def SOME_COLOR yellow;", " .CSS_SOME_CLASS {", " border: 1px solid CSS_CHILD__SOME_COLOR", " }", "}"); private final String ifComponentInput = linesToString( "@component CSS_IF {" + " @if (BROWSER_IE) {", " @if (RTL_LANG) {", " .CSS_BAR {", " border: 1px;", " }", " } @else {", " .CSS_BAR {", " border: 2px;", " }", " }", " }", "}"); private final String ifComponentOutput = "@if[BROWSER_IE]{" + "@if[RTL_LANG]{[.CSS_IF-CSS_BAR]{[border:[[1px]];]}}" + "@else[]{[.CSS_IF-CSS_BAR]{[border:[[2px]];]}}}"; private final ImmutableList<String> namelessComponentPrefixInput = ImmutableList.of( "@provide \"some.example.package\";", "@component {"); private final String namelessComponentInput = joinNl(Iterables.concat( namelessComponentPrefixInput, topComponentInputConstants, topComponentInputRules, ImmutableList.of("}"))); private final ImmutableList<String> stringNamedComponentPrefixInput = ImmutableList.of( "@component \"some.example.package\" {"); private final String stringNamedComponentInput = joinNl(Iterables.concat( stringNamedComponentPrefixInput, topComponentInputConstants, topComponentInputRules, ImmutableList.of("}"))); private final String camelCasedComponentOutput = "@def SOME_EXAMPLE_PACKAGE_SOME_COLOR [[black]];" + "@def SOME_EXAMPLE_PACKAGE_OTHER_BG_COLOR [[GLOBAL_COLOR]];" + "@def SOME_EXAMPLE_PACKAGE_OTHER_COLOR [someColorFunction(" + "SOME_EXAMPLE_PACKAGE_SOME_COLOR,SOME_EXAMPLE_PACKAGE_OTHER_BG_COLOR)];" + "[.someExamplePackageCSS_SOME_CLASS.CSS_SOME_VARIATION," + ".someExamplePackageCSS_SOME_OTHER_CLASS]{" + "[color:[[SOME_EXAMPLE_PACKAGE_SOME_COLOR]];" + "background-color:[[SOME_EXAMPLE_PACKAGE_OTHER_BG_COLOR]];" + "width:[[100px]];" + "border-color:[someColorFunction(" + "SOME_EXAMPLE_PACKAGE_SOME_COLOR,SOME_EXAMPLE_PACKAGE_OTHER_BG_COLOR)];]}"; // This tests a bunch of permutations. The naming convention of the class selector // (PREFIX vs NO_PREFIX) indicates whether we expect the selector to be prefixed // in the output. private final ImmutableList<String> prefixingTestInputRules = ImmutableList.of( " .PREFIX_A1.NO_PREFIX_A2,", // Complex selector " .PREFIX_A1:not(.NO_PREFIX_A2),", // :not() selector " .PREFIX_A1:not(.%PREFIX_A2),", // :not() selector " .PREFIX_A1 .NO_PREFIX_B2:not(.NO_PREFIX_B3),", // :not() selector " .PREFIX_A1 .NO_PREFIX_B2:not(.%PREFIX_B3),", // :not() selector " :not(.PREFIX_A1).NO_PREFIX_A2,", // :not() selector as first class selector " :not(.PREFIX_A1).%PREFIX_A2,", // :not() selector as first class selector " .PREFIX_B1 .NO_PREFIX_B2,", // Descendant combinator " .PREFIX_C1 > .NO_PREFIX_C2,", // Child combinator " TD.PREFIX_D1.NO_PREFIX_D2,", // Element refiner before class refiner " TD.PREFIX_E1 .NO_PREFIX_E2,", // Element refiner with combinator " TD.PREFIX_F1 TD.NO_PREFIX_F2,", // Multiple element refiners " #X.PREFIX_G1.NO_PREFIX_G2,", // ID refiner " #X.PREFIX_H1 .NO_PREFIX_H2,", // ID refiner with combinator " .^NO_PREFIX_A1.NO_PREFIX_A2,", // Explicit unscoped with complex selector " .PREFIX_I1.%PREFIX_I2,", // Explicit scoped with complex selector " .PREFIX_J1 .%PREFIX_J2,", // Explicit scoped with descendant combinator " .PREFIX_K1 > .%PREFIX_K2,", // Explicit scoped with child combinator " TD.PREFIX_L1.%PREFIX_L2,", // Explicit scoped with element refiner " TD.PREFIX_M1 .%PREFIX_M2,", // Explicit scoped with element refiner and combinator " TD.PREFIX_N1 TD.%PREFIX_N2,", // Explicit scoped with multiple element refiners " #X.PREFIX_O1.%PREFIX_O2,", // Explicit scoped with ID refiner " #X.PREFIX_P1 .%PREFIX_P2,", // Explicit scoped with ID refiner and combinator " TD .PREFIX_Q1.NO_PREFIX_Q2,", // First class refiner not in first refiner list " TD > .PREFIX_R1.NO_PREFIX_R2,", // First class refiner not in first selector " .PREFIX_S1.%PREFIX_S2>.%PREFIX_S3,", // Percent and combo " .PREFIX_T1.%PREFIX_T2.NO_PREFIX_T3.%PREFIX_T4,", // " .%PREFIX_U1.NO_PREFIX_U2", // Redundant opt-in " {", " color: SOME_COLOR;", " }" ); private final String prefixingTestComponentInput = joinNl(Iterables.concat( namelessComponentPrefixInput, prefixingTestInputRules, ImmutableList.of("}"))); // This could have been done using a regex, but I think spelling it out like this makes // errors easier to diagnose. private final String prefixingTestComponentOutput = "[.someExamplePackagePREFIX_A1.NO_PREFIX_A2," + ".someExamplePackagePREFIX_A1:not(.NO_PREFIX_A2)," + ".someExamplePackagePREFIX_A1:not(.someExamplePackagePREFIX_A2)," + ".someExamplePackagePREFIX_A1 .NO_PREFIX_B2:not(.NO_PREFIX_B3)," + ".someExamplePackagePREFIX_A1 .NO_PREFIX_B2:not(.someExamplePackagePREFIX_B3)," + ":not(.someExamplePackagePREFIX_A1).NO_PREFIX_A2," + ":not(.someExamplePackagePREFIX_A1).someExamplePackagePREFIX_A2," + ".someExamplePackagePREFIX_B1 .NO_PREFIX_B2," + ".someExamplePackagePREFIX_C1>.NO_PREFIX_C2," + "TD.someExamplePackagePREFIX_D1.NO_PREFIX_D2," + "TD.someExamplePackagePREFIX_E1 .NO_PREFIX_E2," + "TD.someExamplePackagePREFIX_F1 TD.NO_PREFIX_F2," + "#X.someExamplePackagePREFIX_G1.NO_PREFIX_G2," + "#X.someExamplePackagePREFIX_H1 .NO_PREFIX_H2," + ".NO_PREFIX_A1.NO_PREFIX_A2," + ".someExamplePackagePREFIX_I1.someExamplePackagePREFIX_I2," + ".someExamplePackagePREFIX_J1 .someExamplePackagePREFIX_J2," + ".someExamplePackagePREFIX_K1>.someExamplePackagePREFIX_K2," + "TD.someExamplePackagePREFIX_L1.someExamplePackagePREFIX_L2," + "TD.someExamplePackagePREFIX_M1 .someExamplePackagePREFIX_M2," + "TD.someExamplePackagePREFIX_N1 TD.someExamplePackagePREFIX_N2," + "#X.someExamplePackagePREFIX_O1.someExamplePackagePREFIX_O2," + "#X.someExamplePackagePREFIX_P1 .someExamplePackagePREFIX_P2," + "TD .someExamplePackagePREFIX_Q1.NO_PREFIX_Q2," + "TD>.someExamplePackagePREFIX_R1.NO_PREFIX_R2," + ".someExamplePackagePREFIX_S1.someExamplePackagePREFIX_S2>.someExamplePackagePREFIX_S3," + ".someExamplePackagePREFIX_T1.someExamplePackagePREFIX_T2" + ".NO_PREFIX_T3.someExamplePackagePREFIX_T4," + ".someExamplePackagePREFIX_U1.NO_PREFIX_U2]{" + "[color:[[SOME_COLOR]];]}"; @Override protected void runPass() { new CreateDefinitionNodes(tree.getMutatingVisitController(), errorManager).runPass(); new MapChunkAwareNodesToChunk<String>(tree, FILE_TO_CHUNK).runPass(); new CreateConstantReferences(tree.getMutatingVisitController()).runPass(); new CreateConditionalNodes(tree.getMutatingVisitController(), errorManager).runPass(); new CheckDependencyNodes(tree.getMutatingVisitController(), errorManager).runPass(); new CreateComponentNodes(tree.getMutatingVisitController(), errorManager).runPass(); ProcessComponents<String> processComponentsPass = new ProcessComponents<String>( tree.getMutatingVisitController(), errorManager, FILE_TO_CHUNK); processComponentsPass.runPass(); } protected void testTreeConstructionWithResolve( ImmutableMap<String, String> fileNameToGss, String expectedOutput) { parseAndBuildTree(fileNameToGss); runPass(); ResolveCustomFunctionNodesForChunks<String> resolveFunctions = new ResolveCustomFunctionNodesForChunks<String>( tree.getMutatingVisitController(), errorManager, ImmutableMap.of("someColorFunction", SOME_COLOR_FUNCTION), false /* allowUnknownFunctions */, ImmutableSet.<String>of() /* allowedNonStandardFunctions */, new UniqueSuffixFunction()); resolveFunctions.runPass(); checkTreeDebugString(expectedOutput); } public void testTopComponent() throws Exception { testTreeConstruction(topComponentInput, "[" + topComponentOutput + "]"); testTreeConstructionWithResolve( ImmutableMap.of( FILE1, globalInput, FILE2, topComponentInput), "[" + topComponentOutputResolved + "]"); } public void testChildComponent() throws Exception { testTreeConstruction( topComponentInput + "\n" + childComponentInput, "[" + topComponentOutput + childComponentOutput + "]"); testTreeConstructionWithResolve( ImmutableMap.of( FILE1, globalInput, FILE2, topComponentInput, FILE3, childComponentInput), "[" + topComponentOutputResolved + // Suffix is 2 since the top component is also resolved. replaceFunction(childComponentOutputTemplate, "[" + DEF_PREFIX + "2]") + "]"); } public void testChildComponentWithReferenceWorkaround() throws Exception { String topComponentWithWorkaroundInput = joinNl(Iterables.concat( topComponentPrefixInput, topComponentWithRefWorkaroundInputConstants, topComponentInputRules, ImmutableList.of("}"))); testTreeConstruction( topComponentWithWorkaroundInput + "\n" + childComponentWithRefWorkaroundInput, "[" + topComponentOutput + childComponentOutput + "]"); testTreeConstructionWithResolve( ImmutableMap.of( FILE1, globalInput, FILE2, topComponentWithWorkaroundInput, FILE3, childComponentWithRefWorkaroundInput), "[" + topComponentOutputResolved + // Suffix is 2 since the top component is also resolved. replaceFunction(childComponentOutputTemplate, "[" + DEF_PREFIX + "2]") + "]"); } public void testChildComponentWithAbstractParent() throws Exception { testTreeConstruction( abstractTopComponentInput + "\n" + childComponentInput, "[" + abstractTopComponentOutput + childComponentOutput + "]"); testTreeConstructionWithResolve( ImmutableMap.of( FILE1, globalInput, FILE2, abstractTopComponentInput, FILE3, childComponentInput), "[" + globalOutput + ";" + abstractTopComponentOutputResolved + // Suffix is 1 since the abstract top component is also resolved. replaceFunction(childComponentOutputTemplate, "[" + DEF_PREFIX + "1]") + "]"); } public void testGrandChildComponent() throws Exception { testTreeConstruction( topComponentInput + "\n" + childComponentInput + "\n" + grandChildComponentInput, "[" + topComponentOutput + childComponentOutput + grandChildComponentOutput + "]"); testTreeConstructionWithResolve( ImmutableMap.of( FILE1, globalInput, FILE2, topComponentInput, FILE3, childComponentInput, TEST_FILENAME, grandChildComponentInput), "[" + topComponentOutputResolved + // Suffixes are 2 and 3 since the top component is also resolved. replaceFunction(childComponentOutputTemplate, "[" + DEF_PREFIX + "2]") + replaceFunction(grandChildComponentOutputTemplate, "[" + DEF_PREFIX + "3]") + "]"); } public void testIfComponent() throws Exception { testTreeConstruction(ifComponentInput, "[" + ifComponentOutput + "]"); } public void testUndefinedParentComponentError() throws Exception { parseAndRun("@component CSS_X extends CSS_Y { }", "parent component is undefined in chunk " + TEST_CHUNK); assertTrue(isEmptyBody()); } public void testRedefinedComponentError() throws Exception { parseAndRun(ImmutableMap.of( FILE1, "@component CSS_X { }", FILE2, "@component CSS_X { }"), "cannot redefine component in chunk " + CHUNK2); assertTrue(isEmptyBody()); } public void testNestedComponentsError1() throws Exception { parseAndRun("@component CSS_X { @component CSS_Y {} }", "nested components are not allowed"); assertTrue(isEmptyBody()); } public void testNestedComponentsError2() throws Exception { parseAndRun("@component CSS_X { @component CSS_Y {} }\n@component CSS_Z extends CSS_X {}", "nested components are not allowed"); assertTrue(isEmptyBody()); } public void testImplicitlyNamed() throws Exception { testTreeConstruction(namelessComponentInput, "[" + camelCasedComponentOutput + "]"); } public void testStringNamed() throws Exception { testTreeConstruction(stringNamedComponentInput, "[" + camelCasedComponentOutput + "]"); } public void testImplicitlyNamedNoPackageError() throws Exception { parseAndRun("@component { }", "implicitly-named @components require a prior @provide declaration " + TEST_CHUNK); assertTrue(isEmptyBody()); } public void testImplicitlyNamedMultiplePackage() throws Exception { // Construct gss consisting of three @provides and one implicit @component sandwiched // in-between. Verify that the produced css uses the name of the @provide immediately preceeding // the @compoment, not the one before or after. testTreeConstruction( "@provide \"another.example.package\";\n" + namelessComponentInput + "\n" + "@provide \"yetanother.example.package\";\n", "[" + camelCasedComponentOutput + "]"); } public void testImplicitlyNamedMultipleComponents() throws Exception { parseAndRun(ImmutableMap.<String, String>of( "file1", "@provide \"some.example.package\";\n" + "@component { }", "file2", "@provide \"another.example.package\";\n" + "@component { }")); } public void testImplicitlyNamedMultipleComponentsPackageError() throws Exception { parseAndRun(ImmutableMap.<String, String>of( "file1", "@provide \"some.example.package\";\n" + "@component { }", "file2", "@provide \"some.example.package\";\n" + "@component { }"), "cannot redefine component in chunk chunk2"); } public void testMultiplePackageWithNoComponentError() throws Exception { testTreeConstruction( "@provide \"some.example.package\";\n" + "@provide \"another.example.package\";\n", "[]"); } public void testPrefixingRules() throws Exception { testTreeConstruction(prefixingTestComponentInput, "[" + prefixingTestComponentOutput + "]"); } public void testComponentDefsSourceCodeLocation() throws Exception { CssTree tree = parseAndRun(ImmutableMap.of( FILE1, joinNl(ImmutableList.of( "@provide \"some.example.package\";", "@component PARENT {", " @def BASE_COLOR red;", "}")), FILE2, joinNl(ImmutableList.of( "@require \"some.example.package\";", "@provide \"another.example.package\";", "@component CHILD extends PARENT {", " @def SPECIFIC_COLOR blue;", " @def DERIVED_COLOR BASE_COLOR;", " .Foo { background-color: DERIVED_COLOR; color: SPECIFIC_COLOR; }", "}")))); final ImmutableMap.Builder<String, String> foundDefs = ImmutableMap.builder(); tree.getVisitController().startVisit(new DefaultTreeVisitor() { @Override public boolean enterDefinition(CssDefinitionNode node) { String defName = node.getName().toString(); String sourceFileName = node.getSourceCodeLocation().getSourceCode().getFileName(); foundDefs.put(defName, sourceFileName); return true; } }); ImmutableMap<String, String> expectedDefs = ImmutableMap.of( "PARENT__BASE_COLOR", FILE1, "CHILD__BASE_COLOR", FILE2, "CHILD__SPECIFIC_COLOR", FILE2, "CHILD__DERIVED_COLOR", FILE2); assertEquals(expectedDefs, foundDefs.build()); } private String joinNl(Iterable<String> lines) { return Joiner.on('\n').join(lines); } /** * Replaces the fake function reference in the output. * @param componentOutputTemplate the component output with the fake function * reference * @param replacement the replacement value to use as the function reference * @return the component output with the fake function reference replaced */ private String replaceFunction(String componentOutputTemplate, String replacement) { return componentOutputTemplate.replace("[" + FAKE_FUNCTION_REF + "]", replacement); } private static class UniqueSuffixFunction implements Function<String, String> { private int count = 0; @Override public String apply(String chunk) { assertNotNull(chunk); return String.valueOf(count++); } } private static final GssFunction SOME_COLOR_FUNCTION = new GssFunction() { @Override public Integer getNumExpectedArguments() { return 2; } @Override public String getCallResultString(List<String> args) { return Joiner.on("~").join(args); } @Override public List<CssValueNode> getCallResultNodes( List<CssValueNode> args, ErrorManager errorManager) { ImmutableList<String> stringArgs = ImmutableList.of( args.get(0).getValue(), args.get(1).getValue()); String stringResult = getCallResultString(stringArgs); CssLiteralNode nodeResult = new CssLiteralNode(stringResult); return ImmutableList.<CssValueNode>of(nodeResult); } }; }