001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.util.tester; 018 019import static org.junit.jupiter.api.Assertions.fail; 020 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.Iterator; 024import java.util.List; 025 026import org.apache.wicket.Component; 027import org.apache.wicket.Page; 028import org.apache.wicket.ajax.AjaxEventBehavior; 029import org.apache.wicket.behavior.Behavior; 030import org.apache.wicket.util.io.IClusterable; 031import org.apache.wicket.util.lang.Args; 032import org.apache.wicket.util.string.Strings; 033import org.apache.wicket.util.visit.IVisit; 034import org.apache.wicket.util.visit.IVisitor; 035 036/** 037 * A <code>WicketTester</code>-specific helper class. 038 * 039 * @author Ingram Chen 040 * @since 1.2.6 041 */ 042public class WicketTesterHelper 043{ 044 /** 045 * <code>ComponentData</code> class. 046 * 047 * @author Ingram Chen 048 * @since 1.2.6 049 */ 050 public static class ComponentData implements IClusterable 051 { 052 private static final long serialVersionUID = 1L; 053 054 /** Component path. */ 055 public String path; 056 057 /** Component type. */ 058 public String type; 059 060 /** Component value. */ 061 public String value; 062 063 /** Component visibility */ 064 public boolean isVisible; 065 066 /** Whether Component is Enabled */ 067 public boolean isEnabled; 068 } 069 070 /** 071 * Gets recursively all <code>Component</code>s of a given <code>Page</code>, extracts the 072 * information relevant to us, and adds them to a <code>List</code>. 073 * 074 * @param page 075 * the <code>Page</code> to analyze 076 * @return a <code>List</code> of <code>Component</code> data objects 077 */ 078 public static List<WicketTesterHelper.ComponentData> getComponentData(final Page page) 079 { 080 final List<ComponentData> data = new ArrayList<>(); 081 082 if (page != null) 083 { 084 page.visitChildren(new IVisitor<Component, Void>() 085 { 086 @Override 087 public void component(final Component component, final IVisit<Void> visit) 088 { 089 final ComponentData object = new ComponentData(); 090 091 // anonymous class? Get the parent's class name 092 String name = component.getClass().getName(); 093 if (name.indexOf("$") > 0) 094 { 095 name = component.getClass().getSuperclass().getName(); 096 } 097 098 // remove the path component 099 name = Strings.lastPathComponent(name, Component.PATH_SEPARATOR); 100 101 object.path = component.getPageRelativePath(); 102 object.type = name; 103 object.isVisible = component.isVisible(); 104 object.isEnabled = component.isEnabled(); 105 try 106 { 107 object.value = component.getDefaultModelObjectAsString(); 108 } 109 catch (Exception e) 110 { 111 object.value = e.getMessage(); 112 } 113 114 data.add(object); 115 } 116 }); 117 } 118 return data; 119 } 120 121 /** 122 * Asserts that both <code>Collection</code>s contain the same elements. 123 * 124 * @param expects 125 * a <code>Collection</code> object 126 * @param actuals 127 * a <code>Collection</code> object 128 */ 129 public static void assertEquals(final Collection<?> expects, final Collection<?> actuals) 130 { 131 if ((actuals.size() != expects.size()) || !expects.containsAll(actuals) || 132 !actuals.containsAll(expects)) 133 { 134 failWithVerboseMessage(expects, actuals); 135 } 136 } 137 138 /** 139 * Fails with a verbose error message. 140 * 141 * @param expects 142 * a <code>Collection</code> object 143 * @param actuals 144 * a <code>Collection</code> object 145 */ 146 public static void failWithVerboseMessage(final Collection<?> expects, 147 final Collection<?> actuals) 148 { 149 fail("\nexpect (" + expects.size() + "):\n" + asLined(expects) + "\nbut was (" + 150 actuals.size() + "):\n" + asLined(actuals)); 151 } 152 153 /** 154 * A <code>toString</code> method for the given <code>Collection</code>. 155 * 156 * @param objects 157 * a <code>Collection</code> object 158 * @return a <code>String</code> representation of the <code>Collection</code> 159 */ 160 public static String asLined(final Collection<?> objects) 161 { 162 StringBuilder lined = new StringBuilder(); 163 for (Iterator<?> iter = objects.iterator(); iter.hasNext();) 164 { 165 String objectString = iter.next().toString(); 166 lined.append(" "); 167 lined.append(objectString); 168 if (iter.hasNext()) 169 { 170 lined.append('\n'); 171 } 172 } 173 return lined.toString(); 174 } 175 176 /** 177 * Finds the first AjaxEventBehavior attached to the specified component with the 178 * specified event. 179 * 180 * @param component 181 * @param event 182 * @return the first behavior for this event, or {@code null} 183 */ 184 public static AjaxEventBehavior findAjaxEventBehavior(Component component, String event) 185 { 186 List<AjaxEventBehavior> behaviors = findAjaxEventBehaviors(component, event); 187 AjaxEventBehavior behavior = null; 188 if (behaviors != null && behaviors.isEmpty() == false) 189 { 190 behavior = behaviors.get(0); 191 } 192 return behavior; 193 } 194 195 /** 196 * Finds all AjaxEventBehavior's attached to the specified component with 197 * the specified event. 198 * 199 * @param component 200 * @param event 201 * @return a list of all found AjaxEventBehavior or an empty list 202 */ 203 public static List<AjaxEventBehavior> findAjaxEventBehaviors(Component component, String event) 204 { 205 Args.notEmpty(event, "event"); 206 List<AjaxEventBehavior> behaviors = new ArrayList<>(); 207 String[] eventNames = Strings.split(event, ' '); 208 for (String eventName : eventNames) 209 { 210 for (Behavior behavior : component.getBehaviors()) 211 { 212 if (behavior instanceof AjaxEventBehavior) 213 { 214 String behaviorEvent = ((AjaxEventBehavior)behavior).getEvent(); 215 String[] behaviorEventNames = Strings.split(behaviorEvent, ' '); 216 for (String behaviorEventName : behaviorEventNames) 217 { 218 if (eventName.equalsIgnoreCase(behaviorEventName)) 219 { 220 behaviors.add((AjaxEventBehavior)behavior); 221 } 222 } 223 } 224 } 225 } 226 return behaviors; 227 } 228 229 /** 230 * @param component 231 * @param behaviorClass 232 * @return Behavior or null 233 */ 234 public static Behavior findBehavior(Component component, Class<? extends Behavior> behaviorClass) 235 { 236 for (Behavior behavior : component.getBehaviors(behaviorClass)) 237 { 238 return behavior; 239 } 240 return null; 241 } 242 243}