001: /*
002: * $Id: AbstractUITagTest.java 474560 2006-11-13 23:09:31Z hermanns $
003: *
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021: package org.apache.struts2.views.jsp;
022:
023: import java.beans.IntrospectionException;
024: import java.beans.Introspector;
025: import java.beans.PropertyDescriptor;
026: import java.io.InputStream;
027: import java.lang.reflect.InvocationTargetException;
028: import java.net.URL;
029: import java.util.Arrays;
030: import java.util.Collections;
031: import java.util.HashMap;
032: import java.util.Iterator;
033: import java.util.List;
034: import java.util.Map;
035: import java.util.StringTokenizer;
036:
037: import org.apache.commons.logging.Log;
038: import org.apache.commons.logging.LogFactory;
039: import org.apache.struts2.ServletActionContext;
040: import org.apache.struts2.views.jsp.ui.AbstractUITag;
041:
042: import com.opensymphony.xwork2.ActionContext;
043:
044: /**
045: */
046: public abstract class AbstractUITagTest extends AbstractTagTest {
047:
048: private static final Log LOG = LogFactory
049: .getLog(AbstractUITagTest.class);
050:
051: static final String FREEMARKER_ERROR_EXPECTATION = "Java backtrace for programmers:";
052:
053: /**
054: * Simple helper class for generic tag property testing mechanism. Basically it holds a property name, a property
055: * value and an output to be expected in tag output when property was accordingly set.
056: *
057: * @author <a href="mailto:gielen@it-neering.net">Rene Gielen</a>
058: */
059: public class PropertyHolder {
060: String name, value, expectation;
061:
062: public String getName() {
063: return name;
064: }
065:
066: public String getValue() {
067: return value;
068: }
069:
070: public String getExpectation() {
071: return expectation;
072: }
073:
074: /**
075: * Construct simple holder with default expectation.
076: *
077: * @param name The property name to use.
078: * @param value The property value to set.
079: * @see #PropertyHolder(String, String, String)
080: */
081: public PropertyHolder(String name, String value) {
082: this (name, value, null);
083: }
084:
085: /**
086: * Construct property holder.
087: *
088: * @param name The property name to use.
089: * @param value The property value to set.
090: * @param expectation The expected String to occur in tag output caused by setting given tag property. If
091: * <tt>null</tt>, will be set to <pre>name + "=\"" + value + "\"</pre>.
092: */
093: public PropertyHolder(String name, String value,
094: String expectation) {
095: this .name = name;
096: this .value = value;
097: if (expectation != null) {
098: this .expectation = expectation;
099: } else {
100: this .expectation = name + "=\"" + value + "\"";
101: }
102: }
103:
104: /**
105: * Convenience method for easily adding anonymous constructed instance to a given map, with {@link #getName()}
106: * as key.
107: *
108: * @param map The map to place this instance in.
109: */
110: public void addToMap(Map map) {
111: if (map != null) {
112: map.put(this .name, this );
113: }
114: }
115: }
116:
117: /**
118: * Simple Helper for setting bean properties. Although BeanUtils from oscore should provide bean property setting
119: * functionality, it does not work (at least with my JDK 1.5.0_05), failing in jdk's PropertyDescriptor constructor.
120: * This implementation works safely in any case, and does not add dependency on commons-beanutils for building.
121: * TODO: Check how we can remove this crap again.
122: *
123: * @author <a href="mailto:gielen@it-neering.net">Rene Gielen</a>
124: */
125: public class BeanHelper {
126: Map propDescriptors;
127: Object bean;
128:
129: public BeanHelper(Object bean) {
130: this .bean = bean;
131:
132: try {
133: PropertyDescriptor[] pds;
134: pds = Introspector.getBeanInfo(bean.getClass())
135: .getPropertyDescriptors();
136: propDescriptors = new HashMap(pds.length + 1, 1f);
137: for (int i = 0; i < pds.length; i++) {
138: propDescriptors.put(pds[i].getName(), pds[i]);
139: }
140: } catch (IntrospectionException e) {
141: e.printStackTrace();
142: }
143: }
144:
145: public void set(String name, Object value)
146: throws IllegalAccessException,
147: InvocationTargetException {
148: PropertyDescriptor pd = (PropertyDescriptor) propDescriptors
149: .get(name);
150:
151: if (pd != null) {
152: pd.getWriteMethod()
153: .invoke(bean, new Object[] { value });
154: }
155: }
156:
157: }
158:
159: /**
160: * Initialize a map of {@link PropertyHolder} for generic tag property testing. Will be used when calling {@link
161: * #verifyGenericProperties(org.apache.struts2.views.jsp.ui.AbstractUITag, String, String[])} as properties to
162: * verify.<p/> This implementation defines testdata for all common AbstractUITag properties and may be overridden in
163: * subclasses.
164: *
165: * @return A Map of PropertyHolders values bound to {@link org.apache.struts2.views.jsp.AbstractUITagTest.PropertyHolder#getName()}
166: * as key.
167: */
168: protected Map initializedGenericTagTestProperties() {
169: Map result = new HashMap();
170: new PropertyHolder("name", "someName").addToMap(result);
171: new PropertyHolder("id", "someId").addToMap(result);
172: new PropertyHolder("cssClass", "cssClass1",
173: "class=\"cssClass1\"").addToMap(result);
174: new PropertyHolder("cssStyle", "cssStyle1",
175: "style=\"cssStyle1\"").addToMap(result);
176: new PropertyHolder("title", "someTitle").addToMap(result);
177: new PropertyHolder("disabled", "true", "disabled=\"disabled\"")
178: .addToMap(result);
179: //new PropertyHolder("label", "label", "label=\"label\"").addToMap(result);
180: //new PropertyHolder("required", "someTitle").addToMap(result);
181: new PropertyHolder("tabindex", "99").addToMap(result);
182: new PropertyHolder("value", "someValue").addToMap(result);
183: new PropertyHolder("onclick", "onclick1").addToMap(result);
184: new PropertyHolder("ondblclick", "ondblclick1")
185: .addToMap(result);
186: new PropertyHolder("onmousedown", "onmousedown1")
187: .addToMap(result);
188: new PropertyHolder("onmouseup", "onmouseup1").addToMap(result);
189: new PropertyHolder("onmouseover", "onmouseover1")
190: .addToMap(result);
191: new PropertyHolder("onmousemove", "onmousemove1")
192: .addToMap(result);
193: new PropertyHolder("onmouseout", "onmouseout1")
194: .addToMap(result);
195: new PropertyHolder("onfocus", "onfocus1").addToMap(result);
196: new PropertyHolder("onblur", "onblur1").addToMap(result);
197: new PropertyHolder("onkeypress", "onkeypress1")
198: .addToMap(result);
199: new PropertyHolder("onkeydown", "onkeydown1").addToMap(result);
200: new PropertyHolder("onkeyup", "onkeyup1").addToMap(result);
201: new PropertyHolder("onclick", "onclick1").addToMap(result);
202: new PropertyHolder("onselect", "onchange").addToMap(result);
203: return result;
204: }
205:
206: /**
207: * Do a generic verification that setting certain properties on a tag causes expected output regarding this
208: * property. In most cases you would not call this directly, instead use {@link
209: * #verifyGenericProperties(org.apache.struts2.views.jsp.ui.AbstractUITag, String, String[])}.
210: *
211: * @param tag The fresh created tag instance to test.
212: * @param theme The theme to use. If <tt>null</tt>, use configured default theme.
213: * @param propertiesToTest Map of {@link PropertyHolder}s, defining properties to test.
214: * @param exclude Names of properties to exclude from particular test.
215: * @throws Exception
216: */
217: public void verifyGenericProperties(AbstractUITag tag,
218: String theme, Map propertiesToTest, String[] exclude)
219: throws Exception {
220: if (tag != null && propertiesToTest != null) {
221: List excludeList;
222: if (exclude != null) {
223: excludeList = Arrays.asList(exclude);
224: } else {
225: excludeList = Collections.EMPTY_LIST;
226: }
227:
228: tag.setPageContext(pageContext);
229: if (theme != null) {
230: tag.setTheme(theme);
231: }
232:
233: BeanHelper beanHelper = new BeanHelper(tag);
234: Iterator it = propertiesToTest.values().iterator();
235: while (it.hasNext()) {
236: PropertyHolder propertyHolder = (PropertyHolder) it
237: .next();
238: if (!excludeList.contains(propertyHolder.getName())) {
239: beanHelper.set(propertyHolder.getName(),
240: propertyHolder.getValue());
241: }
242: }
243: tag.doStartTag();
244: tag.doEndTag();
245: String writerString = normalize(writer.toString(), true);
246: if (LOG.isInfoEnabled()) {
247: LOG
248: .info("AbstractUITagTest - [verifyGenericProperties]: Tag output is "
249: + writerString);
250: }
251:
252: assertTrue("Freemarker error detected in tag output: "
253: + writerString, writerString
254: .indexOf(FREEMARKER_ERROR_EXPECTATION) == -1);
255:
256: it = propertiesToTest.values().iterator();
257: while (it.hasNext()) {
258: PropertyHolder propertyHolder = (PropertyHolder) it
259: .next();
260: if (!excludeList.contains(propertyHolder.getName())) {
261: assertTrue("Expected to find: "
262: + propertyHolder.getExpectation()
263: + " in resulting String: " + writerString,
264: writerString.indexOf(propertyHolder
265: .getExpectation()) > -1);
266: }
267: }
268: }
269: }
270:
271: /**
272: * Do a generic verification that setting certain properties on a tag causes expected output regarding this
273: * property. Which properties to test with which expectations will be determined by the Map retrieved by {@link #initializedGenericTagTestProperties()}.
274: *
275: * @param tag The fresh created tag instance to test.
276: * @param theme The theme to use. If <tt>null</tt>, use configured default theme.
277: * @param exclude Names of properties to exclude from particular test.
278: * @throws Exception
279: */
280: public void verifyGenericProperties(AbstractUITag tag,
281: String theme, String[] exclude) throws Exception {
282: verifyGenericProperties(tag, theme,
283: initializedGenericTagTestProperties(), exclude);
284: }
285:
286: /**
287: * Attempt to verify the contents of this.writer against the contents of the URL specified. verify() performs a
288: * trim on both ends
289: *
290: * @param url the HTML snippet that we want to validate against
291: * @throws Exception if the validation failed
292: */
293: public void verify(URL url) throws Exception {
294: if (url == null) {
295: fail("unable to verify a null URL");
296: } else if (this .writer == null) {
297: fail("AbstractJspWriter.writer not initialized. Unable to verify");
298: }
299:
300: StringBuffer buffer = new StringBuffer(128);
301: InputStream in = url.openStream();
302: byte[] buf = new byte[4096];
303: int nbytes;
304:
305: while ((nbytes = in.read(buf)) > 0) {
306: buffer.append(new String(buf, 0, nbytes));
307: }
308:
309: in.close();
310:
311: /**
312: * compare the trimmed values of each buffer and make sure they're equivalent. however, let's make sure to
313: * normalize the strings first to account for line termination differences between platforms.
314: */
315: String writerString = normalize(writer.toString(), true);
316: String bufferString = normalize(buffer.toString(), true);
317:
318: assertEquals(bufferString, writerString);
319: }
320:
321: /**
322: * Attempt to verify the contents of this.writer against the contents of the URL specified. verify() performs a
323: * trim on both ends
324: *
325: * @param url the HTML snippet that we want to validate against
326: * @throws Exception if the validation failed
327: */
328: public void verify(URL url, String[] excluded) throws Exception {
329: if (url == null) {
330: fail("unable to verify a null URL");
331: } else if (this .writer == null) {
332: fail("AbstractJspWriter.writer not initialized. Unable to verify");
333: }
334:
335: StringBuffer buffer = new StringBuffer(128);
336: InputStream in = url.openStream();
337: byte[] buf = new byte[4096];
338: int nbytes;
339:
340: while ((nbytes = in.read(buf)) > 0) {
341: buffer.append(new String(buf, 0, nbytes));
342: }
343:
344: in.close();
345:
346: /**
347: * compare the trimmed values of each buffer and make sure they're equivalent. however, let's make sure to
348: * normalize the strings first to account for line termination differences between platforms.
349: */
350: String writerString = normalize(writer.toString(), true);
351: String bufferString = normalize(buffer.toString(), true);
352:
353: assertEquals(bufferString, writerString);
354: }
355:
356: protected void setUp() throws Exception {
357: super .setUp();
358:
359: ServletActionContext.setServletContext(pageContext
360: .getServletContext());
361: }
362:
363: protected void tearDown() throws Exception {
364: super .tearDown();
365: ActionContext.setContext(null);
366: }
367:
368: /**
369: * normalizes a string so that strings generated on different platforms can be compared. any group of one or more
370: * space, tab, \r, and \n characters are converted to a single space character
371: *
372: * @param obj the object to be normalized. normalize will perform its operation on obj.toString().trim() ;
373: * @param appendSpace
374: * @return the normalized string
375: */
376: public static String normalize(Object obj, boolean appendSpace) {
377: StringTokenizer st = new StringTokenizer(obj.toString().trim(),
378: " \t\r\n");
379: StringBuffer buffer = new StringBuffer(128);
380:
381: while (st.hasMoreTokens()) {
382: buffer.append(st.nextToken());
383:
384: /*
385: if (appendSpace && st.hasMoreTokens()) {
386: buffer.append("");
387: }
388: */
389: }
390:
391: return buffer.toString();
392: }
393: }
|