/* * � 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: 15 Jun 2011 * RenderIdTest.java */ package com.ibm.xsp.test.framework.render; import java.util.ArrayList; import java.util.List; 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.FacesSharableRegistry; 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.setup.SkipFileContent; /** * * @author Maire Kehoe (mkehoe@ie.ibm.com) */ public class RenderIdTest extends AbstractXspTest { @Override public String getDescription() { return "that the clientId is output (or not) as the HTML id attribute, for each control in the registry"; } public void testRenderId() throws Exception { String fails = ""; // set up the page to be rendered FacesContext context = TestProject.createFacesContext(this); ResponseBuffer.initContext(context); UIViewRoot root = TestProject.loadEmptyPage(this, context); UIComponent p = XspRenderUtil.createContainerParagraph(root); // for each control FacesSharableRegistry reg = TestProject.createRegistry(this); for (FacesComponentDefinition def : TestProject.getLibComponents(reg, this)) { if( !def.isTag() ){ // Do not try to render abstract controls continue; } // create a control instance UIComponent instance; try{ instance = (UIComponent) def.getJavaClass().newInstance(); }catch(Exception e){ // no need to fail here as RenderControlTest // will already be failing for the issue. continue; } XspRenderUtil.resetContainerChild(root, p, instance); XspRenderUtil.initControl(this, instance, context); // test rendering with an auto-generated ID. { String page; try{ page = ResponseBuffer.encode(p, context); }catch(Exception e){ // no need to fail here as RenderControlTest // will already be failing for the issue. ResponseBuffer.clear(context); continue; } //Verify that the page was rendered correctly if( !page.startsWith("<p>") || page.equals("<p></p>") ){ // no need to fail here as RenderControlTest // will already be failing for the issue. continue; } System.out.println("RenderIdTest.testRenderId() with " +XspTestUtil.loc(def)+" auto-gen id: \n"+page); String autoGeneratedId = instance.getId(); if( StringUtil.isEmpty(autoGeneratedId) ){ fails += XspTestUtil.loc(def)+" id not auto-generated\n"; continue; } String clientId = instance.getClientId(context); String renderedIdOnMainTag = findAttributeOnMainTag(page, "id", true); String renderedDojoType = findAttributeOnMainTag(page, "dojoType", true); boolean expectIdPresent = (null != renderedDojoType); boolean actualIdPresent = (null != renderedIdOnMainTag); if( expectIdPresent != actualIdPresent ){ if( expectIdPresent ){ fails += XspTestUtil.loc(def) + " Expected id= attribute not present in HTML. Should be present because there is a default dojoType.\n"; }else{ fails += XspTestUtil.loc(def) + " Unexpected id= attribute in HTML. Should not be present when the id property is absent in the XPage source, and there is no default dojoType.\n"; } // failed, but not continue loop, keep testing this rendered output } if(actualIdPresent && expectIdPresent){ if( ! StringUtil.equals(renderedIdOnMainTag, clientId) ){ fails += XspTestUtil.loc(def) + " Wrong id= attribute in HTML, " + "was: id=" +renderedIdOnMainTag+"" + " expected: id=" +clientId+" \n"; continue; } } String[] renderedIdsOnInnerTagsArr = findAttributesOnInnerTag(page, "id"); if( null != renderedIdsOnInnerTagsArr ){ for (String renderedIdOnInnerTag : renderedIdsOnInnerTagsArr) { boolean sameAsClientId = StringUtil.equals(renderedIdOnInnerTag, clientId); boolean isClientIdWithSuffix = !sameAsClientId && renderedIdOnInnerTag.startsWith(clientId); if( sameAsClientId ){ if( expectIdPresent ){ fails += XspTestUtil.loc(def) + " Unexpected id=clientId attribute on an inner tag (not main tag). The clientId should only be written to the control's outer tag, for partial refresh reasons.\n"; }else{ fails += XspTestUtil.loc(def) + " Unexpected id=clientId attribute on an inner tag (not main tag). The clientId should only be written to the control's outer tag, for partial refresh reasons. Also, the control should not be outputting the clientId because there is no default dojoType.\n"; } continue; } if( !isClientIdWithSuffix ){ fails += XspTestUtil.loc(def) + " The id= attribute on an inner tag (not main tag) has an unexpected value - inner id= values should be the clientID and some suffix. Was: " +renderedIdOnInnerTag+"\n"; } // note this test is allowing inner clientIdWithSuffix } } } // test rendering with an ID configured in the XPage source { String idInSource = "control1"; instance.setId(idInSource); String clientId = instance.getClientId(context); if( ! clientId.endsWith(idInSource) ){ fails += XspTestUtil.loc(def) + " clientId does not end in component id. " + "clientId: " +clientId + " componentId: " +idInSource + ".\n"; continue; } XspRenderUtil.resetContainerChild(root, p, instance); XspRenderUtil.initControl(this, instance, context); String page = ResponseBuffer.encode(p, context); System.out.println("RenderIdTest.testRenderId() with " +XspTestUtil.loc(def)+" id=control1: \n"+page); if( ! page.startsWith("<p>") ){ fails += XspTestUtil.loc(def) + " Wrote attributes to the parent <p> tag: " + page + "\n"; continue; }if( page.equals("<p></p>") ){ fails += XspTestUtil.loc(def) + " No output rendered. \n"; continue; } String renderedId = findAttributeOnMainTag(page, "id", true); boolean expectIdPresent = true; // because ID set in XPage source boolean actualIdPresent = (null != renderedId); if( expectIdPresent != actualIdPresent ){ fails += XspTestUtil.loc(def) + " Expected id= attribute not present in HTML. Should be present because the id is set in the XPage source.\n"; continue; }else if( ! StringUtil.equals(renderedId, clientId) ){ fails += XspTestUtil.loc(def) + " Wrong id= attribute in HTML, " + "was: id=\"" +renderedId+"\"" + " expected: id=\"" +clientId+"\" \n"; continue; } String[] renderedIdsOnInnerTagsArr = findAttributesOnInnerTag(page, "id"); if( null != renderedIdsOnInnerTagsArr ){ for (String renderedIdOnInnerTag : renderedIdsOnInnerTagsArr) { boolean sameAsClientId = StringUtil.equals(renderedIdOnInnerTag, clientId); boolean isClientIdWithSuffix = !sameAsClientId && renderedIdOnInnerTag.startsWith(clientId); if( sameAsClientId ){ // if( expectIdPresent ){ fails += XspTestUtil.loc(def) + " Unexpected id=clientId attribute on an inner tag (not main tag). The clientId should only be written to the control's outer tag, for partial refresh reasons.\n"; // }else{ // fails += XspTestUtil.loc(def) + " Unexpected id=clientId attribute on an inner tag (not main tag). The clientId should only be written to the control's outer tag, for partial refresh reasons. Also, the control should not be outputting the clientId because there is no default dojoType.\n"; // } continue; } if( !isClientIdWithSuffix ){ fails += XspTestUtil.loc(def) + " The id= attribute on an inner tag (not main tag) has an unexpected value - inner id= values should be the clientID and some suffix. Was: " +renderedIdOnInnerTag+"\n"; } // note this test is allowing inner clientIdWithSuffix } } } } fails = XspTestUtil.removeMultilineFailSkips(fails, SkipFileContent.concatSkips(getSkipFails(), this, "testRenderId")); if( fails.length() > 0 ){ fail(XspTestUtil.getMultilineFailMessage(fails)); } } /** * Available to override in subclasses. * @return */ protected String[] getSkipFails() { return StringUtil.EMPTY_STRING_ARRAY; } public static String findAttributeOnMainTag(String page, String attributeName, boolean skipOuterParagraph) { if( skipOuterParagraph ){ if( "<p/>".equals(page) ){ return null; } if( ! (page.startsWith("<p>") && page.endsWith("</p>"))){ throw new RuntimeException("Expected container paragraph not found"); } page = page.substring("<p>".length(), page.length()-"</p>".length()); } String attributeValue = null; String searchFor = attributeName+"="; int foundIndex = page.indexOf(searchFor); if( -1 != foundIndex ){ int endOpenTag = page.indexOf('>'); if( -1 != endOpenTag && foundIndex > endOpenTag ){ // foundIndex is not in the 1st HTML element. return null; } if( '"' != page.charAt(foundIndex+searchFor.length()) ){ throw new RuntimeException(); } foundIndex++; // skip the open quote " int startIndex = foundIndex+searchFor.length(); int endIndex = page.indexOf('"', startIndex); attributeValue = page.substring(startIndex, endIndex); } return attributeValue; } public static String[] findAttributesOnInnerTag(String page, String attributeName){ // skip the page <p> tag if( "<p/>".equals(page) ){ return null; } if( ! (page.startsWith("<p>") && page.endsWith("</p>"))){ throw new RuntimeException("Expected container paragraph not found"); } page = page.substring("<p>".length(), page.length()-"</p>".length()); page = page.trim(); if( page.length() == 0 ){ return null; } // skip the main control tag if( page.charAt(0) == '<'){ int endOpenTag = page.indexOf('>'); if( -1 == endOpenTag ){ // does not have a main control tag nor any inner tags return null; } int endTagName = page.indexOf(' '); if( -1 == endTagName || endTagName > endOpenTag ){ endTagName = endOpenTag; } String tagName = page.substring(1, endTagName); if( endOpenTag == page.length() - 1 ){ // main tag is self-closing, like <foo/>, so no inner tags return null; } endOpenTag++; // skip the > page = page.trim(); // remove any trailing whitespace if( page.endsWith("</"+tagName+">") ){ // trim down the page to skip the main tag int startCloseTag = page.lastIndexOf('<'); page = page.substring(endOpenTag, startCloseTag); }// else treat the entire page as inner tag contents } page = page.trim(); if( page.length() == 0 ){ return null; } // now find all the id attributes on the inner tags. List<String> attributeValues = null; String searchFor = attributeName+"=\""; while( page.length() > 0){ int foundIndex = page.indexOf(searchFor); if( -1 == foundIndex ){ break; } // aria-invalid="true" // should not match id=? if( foundIndex > 0 && page.charAt(foundIndex-1) != ' '){ page = page.substring(foundIndex+searchFor.length()); // go to next match of id=" continue; } page = page.substring(foundIndex); int startIndex = searchFor.length(); int endIndex = page.indexOf('"', startIndex); String attributeValue = page.substring(startIndex, endIndex); if( null == attributeValues ){ attributeValues = new ArrayList<String>(); } attributeValues.add(attributeValue); page = page.substring(endIndex+1); } return null == attributeValues? null : StringUtil.toStringArray(attributeValues); } public static String findRenderedTag(String page, String tagName, boolean skipOuterParagraph) throws BadXmlException { if( skipOuterParagraph ){ if( "<p/>".equals(page) ){ return null; } if( ! (page.startsWith("<p>") && page.endsWith("</p>"))){ throw new RuntimeException("Expected container paragraph not found"); } page = page.substring("<p>".length(), page.length()-"</p>".length()); } String searchFor = "<" + tagName; int foundIndex = page.indexOf(searchFor); if( -1 != foundIndex ){ int endOpenTag = page.indexOf('>'); if( -1 != endOpenTag && foundIndex > endOpenTag ){ // foundIndex is not in the 1st HTML element. return null; } if( page.indexOf("/>") == (endOpenTag -1) ){ // self-closing tag like <input id="foo"/> return page.substring(foundIndex, endOpenTag+1); } String closeSearchFor = "</"+tagName+">"; int startCloseTag = page.indexOf(closeSearchFor, /*fromIndex*/foundIndex); if( -1 == startCloseTag ){ throw new BadXmlException(searchFor+" not matched by "+closeSearchFor); } return page.substring(foundIndex, startCloseTag + closeSearchFor.length()); } return null; } public static class BadXmlException extends Exception{ private static final long serialVersionUID = 1L; public BadXmlException(String message) { super(message); } } public static String findRenderedInnerTag(String page, String tag) throws BadXmlException{ // skip the page <p> tag if( "<p/>".equals(page) ){ return null; } if( ! (page.startsWith("<p>") && page.endsWith("</p>"))){ throw new RuntimeException("Expected container paragraph not found"); } page = page.substring("<p>".length(), page.length()-"</p>".length()); if( page.length() == 0 ){ return null; } // skip the main control tag if( page.charAt(0) == '<'){ int endOpenTag = page.indexOf('>'); if( -1 == endOpenTag ){ // does not have a main control tag nor any inner tags return null; } int endTagName = page.indexOf(' '); if( -1 == endTagName ){ endTagName = endOpenTag; } String tagName = page.substring(1, endTagName); if( endOpenTag == page.length() - 1 ){ // main tag is self-closing, like <foo/>, so no inner tags return null; } endOpenTag++; // skip the > page = page.trim(); // remove any trailing whitespace if( page.endsWith("</"+tagName+">") ){ // trim down the page to skip the main tag int startCloseTag = page.lastIndexOf('<'); page = page.substring(endOpenTag, startCloseTag); }// else treat the entire page as inner tag contents } page = page.trim(); if( page.length() == 0 ){ return null; } // find the inner <input/> tag String searchFor = "<" + tag; int foundIndex = page.indexOf(searchFor); if( -1 != foundIndex ){ int endOpenTag = page.indexOf(">", foundIndex); if( -1 == endOpenTag ){ throw new BadXmlException(searchFor+" not matched by >"); } if( page.startsWith("/>", (endOpenTag -1)) ){ // self-closing tag like <input id="foo"/> return page.substring(foundIndex, endOpenTag+1); } String closeSearchFor = "</"+tag+">"; int startCloseTag = page.indexOf(closeSearchFor, /*fromIndex*/foundIndex); if( -1 == startCloseTag ){ throw new BadXmlException(searchFor+" not matched by "+closeSearchFor); } return page.substring(foundIndex, startCloseTag + closeSearchFor.length()); } return null; } public static String findAttributeOnATag(String page, String tag, String attributeName){ // skip the page <p> tag if( "<p/>".equals(page) ){ return null; } if( ! (page.startsWith("<p>") && page.endsWith("</p>"))){ throw new RuntimeException("Expected container paragraph not found"); } page = page.substring("<p>".length(), page.length()-"</p>".length()); if( page.length() == 0 ){ return null; } // get the attribute contents of the specified tag String attributes = null; { String searchFor = "<" + tag; int foundIndex = page.indexOf(searchFor); if( -1 != foundIndex ){ int endTag = page.indexOf('>', foundIndex); if( -1 != endTag && foundIndex > endTag ){ // foundIndex is not in the 1st HTML element. return null; } if( ' ' == page.charAt(foundIndex) ){ foundIndex++; // skip the " " character of the tag } int startIndex = foundIndex+searchFor.length(); attributes = page.substring(startIndex, endTag); } } // now find the specified attribute on the specified tag String attributeValue = null; //will return attribute value if( null != attributes ){ String searchFor = attributeName+"=\""; int foundIndex = attributes.indexOf(searchFor); if( -1 == foundIndex ){ return null; } attributes = attributes.substring(foundIndex); int startIndex = searchFor.length(); int endIndex = attributes.indexOf('"', startIndex); attributeValue = attributes.substring(startIndex, endIndex); } return null == attributeValue? null : attributeValue; } public static String findTagText(String page, String tag){ // skip the page <p> tag if( "<p/>".equals(page) ){ return null; } if( ! (page.startsWith("<p>") && page.endsWith("</p>"))){ throw new RuntimeException("Expected container paragraph not found"); } page = page.substring("<p>".length(), page.length()-"</p>".length()); if( page.length() == 0 ){ return null; } // get the text content of the specified tag String text = null; { String searchFor = "<" + tag; int foundIndex = page.indexOf(searchFor); if( -1 != foundIndex ){ foundIndex++; // skip the " " character of the tag int closeTag = page.indexOf('>', foundIndex); int endTag = page.indexOf('<', closeTag); if( -1 != endTag && foundIndex > endTag && endTag > closeTag+1 ){ // foundIndex is not in the 1st HTML element. return null; } try{ text = page.substring(closeTag, endTag); }catch(Exception e) { e.printStackTrace(); } } } return null == text? null : text; } public static String findTableCaptionText(String page, String tableTagName, String captionTagName) throws BadXmlException{ { if( "<p/>".equals(page) ){ return null; } if( ! (page.startsWith("<p>") && page.endsWith("</p>"))){ throw new RuntimeException("Expected container paragraph not found"); } page = page.substring("<p>".length(), page.length()-"</p>".length()); } // find the text in the caption tag: // <table> // <caption>Quarterly results for Q3</caption> if( !"table".equals(tableTagName)|| ! "caption".equals(captionTagName) ){ throw new IllegalArgumentException("Bad table or caption param, was "+tableTagName+" and "+captionTagName); } String tableTagContents; { String searchFor = "<" + tableTagName; int foundIndex = page.indexOf(searchFor); if( -1 == foundIndex ){ return null; } int endOpenTag = page.indexOf('>'); if( -1 != endOpenTag && foundIndex > endOpenTag ){ // foundIndex is not in the 1st HTML element. return null; } if( page.indexOf("/>") == (endOpenTag -1) ){ // self-closing tag like <table id="foo"/> return null; // no inner <caption> tag }else{ String closeSearchFor = "</"+tableTagName+">"; int startCloseTag = page.indexOf(closeSearchFor, /*fromIndex*/foundIndex); if( -1 == startCloseTag ){ throw new RenderIdTest.BadXmlException(searchFor+" not matched by "+closeSearchFor); } tableTagContents = page.substring(endOpenTag+1, startCloseTag); } } // find caption tag { String searchFor = "<" + captionTagName; int foundIndex = tableTagContents.indexOf(searchFor); if( -1 != foundIndex ){ int endOpenTag = tableTagContents.indexOf('>'); if( -1 != endOpenTag && foundIndex > endOpenTag ){ // foundIndex is not in the 1st HTML element. return null; } if( tableTagContents.indexOf("/>") == (endOpenTag -1) ){ // self-closing tag like <caption/> throw new RenderIdTest.BadXmlException("<caption/> should not be self-closing - should be <caption>text</caption>"); }else{ String closeSearchFor = "</"+captionTagName+">"; int startCloseTag = tableTagContents.indexOf(closeSearchFor, /*fromIndex*/foundIndex); if( -1 == startCloseTag ){ throw new RenderIdTest.BadXmlException(searchFor+" not matched by "+closeSearchFor); } String captionTagContents = tableTagContents.substring(endOpenTag+1, startCloseTag); return captionTagContents; } } } return null; } }