/* * � Copyright IBM Corp. 2011, 2014 * * 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. */ /* * Author: Maire Kehoe (mkehoe@ie.ibm.com) * Date: 20 Dec 2011 * RenderDojoPropertyTest.java */ package com.ibm.xsp.test.framework.render; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import javax.faces.component.UIComponent; import javax.faces.component.UIViewRoot; import javax.faces.context.FacesContext; import com.ibm.commons.util.StringUtil; import com.ibm.xsp.registry.FacesComponentDefinition; import com.ibm.xsp.registry.FacesProperty; import com.ibm.xsp.registry.FacesSharableRegistry; import com.ibm.xsp.registry.FacesSimpleProperty; import com.ibm.xsp.registry.RegistryUtil; import com.ibm.xsp.test.framework.AbstractXspTest; import com.ibm.xsp.test.framework.TestProject; import com.ibm.xsp.test.framework.XspRenderUtil; import com.ibm.xsp.test.framework.XspTestUtil; import com.ibm.xsp.test.framework.registry.annotate.PropertyTagsAnnotater; import com.ibm.xsp.test.framework.setup.SkipFileContent; import com.ibm.xsp.util.TypedUtil; /** * * @author Maire Kehoe (mkehoe@ie.ibm.com) */ public class RenderDojoPropertyTest extends AbstractXspTest { @Override public String getDescription() { // Note, it would be best to verify that the property default values in the // dojo dijit are the same as the property default values in the // UIComponent getter methods, and the property default values in the // Renderers, but there's no easy way to junit test that. // This is only checking that the default in the getter matches // the default in the Renderer. The rest would have to be // verified manually by the control developer. return "for controls that have a dojoType in the output by default, " + "verify that property values absent in the XPage source are absent in the HTML source."; } public void testRenderDojoPropertyTest() throws Exception { // create an empty view FacesContext context = TestProject.createFacesContext(this); ResponseBuffer.initContext(context); UIViewRoot root = TestProject.loadEmptyPage(this, context); UIComponent p = XspRenderUtil.createContainerParagraph(root); FacesSharableRegistry reg = TestProject.createRegistryWithAnnotater(this, new PropertyTagsAnnotater()); HashMap<Class<?>, Object[]> proposedValueMap = null; FacesComponentDefinition uiComponentDef = (FacesComponentDefinition) reg.findDef("javax.faces.Component"); Object[][] propertyTestGivesExceptionSkips = getPropertyTestGivesExceptionSkips(); String fails = ""; for (FacesComponentDefinition def : TestProject.getLibComponents(reg, this)) { if( !def.isTag() ){ continue; } // create a control instance {// open instance UIComponent instance; try{ instance = (UIComponent) def.getJavaClass().newInstance(); }catch(Exception e){ // note, ComponentRendererTest will fail if non-instantiable, so not log here continue; } // render the un-modified blank control instance XspRenderUtil.resetContainerChild(root, p, instance); XspRenderUtil.initControl(this, instance, context); }// close instance String page; try{ page = ResponseBuffer.encode(p, context); }catch(Exception e){ // note, RenderControlTest will fail for this, so not log here ResponseBuffer.clear(context); continue; } if( ! page.startsWith("<p>") || page.equals("<p></p>")){ // note, RenderControlTest will fail for this, so not log here continue; } String existingRenderDojoType = XspRenderUtil.findAttribute(page, "dojoType"); if( null == existingRenderDojoType ){ // this test is only checking controls with a dojoType in the HTML output continue; } String pageWithNoPropertyValueSet, pageWithFirstPropertyValueSet, pageWithSecondPropertyValueSet; pageWithNoPropertyValueSet = page; if( null == proposedValueMap ){ proposedValueMap = new HashMap<Class<?>, Object[]>(); Object[][] values = new Object[][]{ new Object[]{ String.class, "testString", ""}, // booleans are tested in RenderBooleanPropertyTest // new Object[]{ boolean.class, true, false}, new Object[]{ int.class, 10, 0}, new Object[]{ double.class, 5.5, 0.0}, new Object[]{ Object.class, "testObject", "testObject2"}, }; for (Object[] valueArr : values) { proposedValueMap.put((Class<?>)valueArr[0], new Object[]{valueArr[1], valueArr[2]}); } } Collection<FacesProperty> testedProps = new ArrayList<FacesProperty>(); for (FacesProperty prop : RegistryUtil.getProperties(def)) { if( null != uiComponentDef.getProperty(prop.getName()) || "dojoType".equals(prop.getName())){ continue; } if( boolean.class.equals(prop.getJavaClass()) ){ continue; // booleans are tested in RenderBooleanPropertyTest } Object[] valueArrToSet = proposedValueMap.get(prop.getJavaClass()); if( null == valueArrToSet ){ if( prop instanceof FacesSimpleProperty ){ if( ((FacesSimpleProperty)prop).getTypeDefinition() == null ){ System.err.println("RenderDojoPropertyTest.testRenderDojoPropertyTest() " + "Unhandled property class: " + prop.getName()+" "+prop.getJavaClass()); } // else property expects a complex-type, not print to console. }// else expects a collection or method-binding continue; } if( isPropertyTestGivesExceptionSkip(propertyTestGivesExceptionSkips, def, prop) ){ continue; } testedProps.add(prop); } // set a value for each property on the control {// open instance UIComponent instance = (UIComponent) def.getJavaClass().newInstance(); for (FacesProperty prop : testedProps) { Object[] valueArrToSet = proposedValueMap.get(prop.getJavaClass()); Object firstValue = valueArrToSet[0]; TypedUtil.getAttributes(instance).put(prop.getName(), firstValue); } XspRenderUtil.resetContainerChild(root, p, instance); XspRenderUtil.initControl(this, instance, context); }// close instance try{ page = ResponseBuffer.encode(p, context); }catch(Exception e){ e.printStackTrace(); String msg = XspTestUtil.loc(def) + " Problem rendering page with test values: "+e; fails += msg + "\n"; ResponseBuffer.clear(context); continue; } pageWithFirstPropertyValueSet = page; // set another value for each property on the control {// open instance UIComponent instance = (UIComponent) def.getJavaClass().newInstance(); for (FacesProperty prop : testedProps) { Object[] valueArrToSet = proposedValueMap.get(prop.getJavaClass()); Object secondValue = valueArrToSet[1]; TypedUtil.getAttributes(instance).put(prop.getName(), secondValue); } XspRenderUtil.resetContainerChild(root, p, instance); XspRenderUtil.initControl(this, instance, context); }// close instance page = ResponseBuffer.encode(p, context); pageWithSecondPropertyValueSet = page; // Now that you have the 3 page outputs String[] pages = new String[]{pageWithNoPropertyValueSet, pageWithFirstPropertyValueSet, pageWithSecondPropertyValueSet}; // for each property check whether the property is ever passed through to the HTML page. for (FacesProperty prop : testedProps) { boolean foundInAny = false; String[] propertyValuesInHTML = new String[3]; int k = 0; for (String outputPage : pages) { propertyValuesInHTML[k] = XspRenderUtil.findAttribute(outputPage, prop.getName()); if( null != propertyValuesInHTML[k] ){ foundInAny = true; } k++; } if( ! foundInAny ){ // property never passes through to HTML, // assume it's a server-side only property. continue; } // ok, so the property is sometimes passed through to HTML. // verify the 3 different scenarios // when property value absent in XPage source, should be absent in HTML output if( null != propertyValuesInHTML[0] ){ String msg = XspTestUtil.loc(def) + " " + prop.getName() + " Property absent in .xsp, should be absent in HTML output. Was " + reconstructHtml(prop.getName(),nonRandom(propertyValuesInHTML[0])); fails += msg + "\n"; System.err.println("RenderDojoPropertyTest.testRenderDojoPropertyTest() "+msg +"\n"+pageWithNoPropertyValueSet); } String renderedValueModificationType = isPropertyModifiesRenderedValue(def, prop); Object[] values = proposedValueMap.get(prop.getJavaClass()); int resultIndex = 1; //(start from 1, not 0) for (Object valueObj : values) { String valueSetInXsp = valueObj.toString(); String actualRenderedValue = propertyValuesInHTML[resultIndex]; if( null == renderedValueModificationType ){ String expectedRenderedValue = valueSetInXsp; if( ! StringUtil.equals(expectedRenderedValue, actualRenderedValue ) ){ String msg = XspTestUtil.loc(def) + " " + prop.getName() + " Property " +prop.getName()+ "=\"" + valueSetInXsp + "\" in .xsp" +((resultIndex == 1)?",":"") // keep fails output consistent with historical +" not as expected in HTML output. Was " + reconstructHtml(prop.getName(),nonRandom(actualRenderedValue)); fails += msg + "\n"; System.err.println("RenderDojoPropertyTest.testRenderDojoPropertyTest() "+msg +"\n"+pageWithFirstPropertyValueSet); } }else{ // null != renderedValueModificationType String expectedModifiedValue = getExpectedModifiedValue(renderedValueModificationType, valueSetInXsp); if( ! StringUtil.equals(expectedModifiedValue, actualRenderedValue ) ){ String msg = XspTestUtil.loc(def) + " " + prop.getName() + " Property " +prop.getName()+ "=\""+valueSetInXsp + "\" in .xsp, " + "not as expected in HTML output for " +renderedValueModificationType +". Was " + reconstructHtml(prop.getName(),nonRandom(actualRenderedValue)) + ", expected "+reconstructHtml(prop.getName(), expectedModifiedValue)+"."; fails += msg + "\n"; System.err.println("RenderDojoPropertyTest.testRenderDojoPropertyTest() "+msg +"\n"+pageWithFirstPropertyValueSet); } } resultIndex++; } }// end for each property }// end for each def fails += getUnusedSkipsForPropertyTestGivesException(propertyTestGivesExceptionSkips); fails = XspTestUtil.removeMultilineFailSkips(fails, SkipFileContent.concatSkips(getSkips(), this, "testRenderDojoPropertyTest")); if( fails.length() > 0 ){ fail( XspTestUtil.getMultilineFailMessage(fails) ); } } /** * Available to extend in subclass */ protected Object[][] getPropertyTestGivesExceptionSkips() { return new Object[0][]; } /** * Available to extend in subclasses. */ protected String isPropertyModifiesRenderedValue(FacesComponentDefinition def, FacesProperty prop) { if( PropertyTagsAnnotater.isTaggedRenderWithPrefixHash(prop) ){ return "render-with-prefix-hash"; } if( PropertyTagsAnnotater.isTaggedRenderWithRequestPathPrefix(prop) ){ return "render-with-request-path-prefix"; } return null; } /** * Available to extend in subclasses. */ protected String getExpectedModifiedValue(String renderedValueModificationType, String valueSetInXsp) { if( null != renderedValueModificationType){ if( "render-with-prefix-hash".equals(renderedValueModificationType) ){ if( StringUtil.isEmpty(valueSetInXsp) ){ return null; // expect absent in rendered output } return "#"+valueSetInXsp; } if( "render-with-request-path-prefix".equals(renderedValueModificationType) ){ if( StringUtil.isEmpty(valueSetInXsp) ){ return null; // expect absent in rendered output } if( renderedValueModificationType.length() > 0 && '/' == renderedValueModificationType.charAt(0) ){ renderedValueModificationType = renderedValueModificationType.substring(1); } return "/xsp/"+valueSetInXsp; } } throw new IllegalArgumentException("String renderedValueModificationType is: "+renderedValueModificationType); } private String reconstructHtml(String propertyName, String propertyValue) { if( null == propertyValue ){ return "(absent)"; } return propertyName+"="+'"'+propertyValue+'"'; } protected String[] getSkips(){ return StringUtil.EMPTY_STRING_ARRAY; } /** * Available to override in subclass to change the value * to sanitize any random values with hard-coded values, * so that subsequent runs of the junit tests don't give * different fail messages. * @param string * @return */ protected String nonRandom(String stringInHtmlPage) { return stringInHtmlPage; } private boolean isPropertyTestGivesExceptionSkip(Object[][] propertyTestGivesExceptionSkips, FacesComponentDefinition def, FacesProperty prop){ //{ [0]defClassOr prefixedTagName or prefixedRefId, [1]propName} int i = 0; for (Object[] skip : propertyTestGivesExceptionSkips) { try{ if( null == skip ){ continue; } Object skipDefMatchObj = skip[0]; String skipPropName = (String) skip[1]; boolean defMatched; if( skipDefMatchObj instanceof String ){ String skipDefStr = (String) skipDefMatchObj; int colonIndex = skipDefStr.indexOf(':'); if( -1 != colonIndex ){ defMatched = isPrefixedTagNameMatch(def, skipDefStr, colonIndex); }else{ int dashIndex = skipDefStr.indexOf('-'); if( -1 != dashIndex ){ defMatched = isPrefixedReferenceIdMatch(def, skipDefStr, dashIndex); }else{ throw new RuntimeException("Bad skip def format, expect ':' or '-' in def in skip: "+toSkipString(skip)); } } }else{ Class<?> skipDefJavaClass = (Class<?>) skipDefMatchObj; defMatched = def.getJavaClass().equals(skipDefJavaClass); } if( defMatched ){ boolean propMatch = prop.getName().equals(skipPropName); if( propMatch ){ // skip is used up so remove it from the list propertyTestGivesExceptionSkips[i] = null; return true; } } }finally{ i++; } } return false; } private boolean isPrefixedTagNameMatch(FacesComponentDefinition def, String skipPrefixedTagName, int colonIndex) { String defTagName = def.getTagName(); if( null == defTagName ){ return false; } String skipTagName = skipPrefixedTagName.substring(colonIndex+1); if(!skipTagName.equals(defTagName) ){ return false; } String skipPrefix = skipPrefixedTagName.substring(0, colonIndex); return skipPrefix.equals(def.getFile().getDefaultPrefix()); } private boolean isPrefixedReferenceIdMatch(FacesComponentDefinition def, String skipPrefixedReferenceId, int dashIndex) { String defReferenceId = def.getReferenceId(); String skipReferenceId = skipPrefixedReferenceId.substring(dashIndex+1); if(!skipReferenceId.equals(defReferenceId) ){ return false; } String skipPrefix = skipPrefixedReferenceId.substring(0, dashIndex); return skipPrefix.equals(def.getFile().getDefaultPrefix()); } private String getUnusedSkipsForPropertyTestGivesException(Object[][] propertyTestGivesExceptionSkips) { String fails = ""; for (Object[] skip : propertyTestGivesExceptionSkips) { if( null == skip ){ continue; } String line = "Unused skip in getPropertyTestGivesExceptionSkips(): " +toSkipString(skip) + "\n"; fails += line; } return fails; } private String toSkipString(Object[] skip) { String skipDef; Object skipDefObj = skip[0]; if( skipDefObj instanceof String){ skipDef = '"'+((String)skipDefObj)+'"'; }else{ skipDef = ((Class<?>)skipDefObj).getName()+".class"; } String skipProperty = (String) skip[1]; String skipAsString = "{" +skipDef+", " +'"'+skipProperty+'"'+"},"; return skipAsString; } }