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}