1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.eclipse.org/org/documents/epl-v10.php
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.ide.common.layout;
18 
19 import static com.android.SdkConstants.ANDROID_URI;
20 import static com.android.SdkConstants.ATTR_ID;
21 
22 import com.android.annotations.NonNull;
23 import com.android.annotations.Nullable;
24 import com.android.ide.common.api.DropFeedback;
25 import com.android.ide.common.api.IClientRulesEngine;
26 import com.android.ide.common.api.IDragElement;
27 import com.android.ide.common.api.INode;
28 import com.android.ide.common.api.IValidator;
29 import com.android.ide.common.api.IViewMetadata;
30 import com.android.ide.common.api.IViewRule;
31 import com.android.ide.common.api.Margins;
32 import com.android.ide.common.api.Point;
33 import com.android.ide.common.api.Rect;
34 import com.android.ide.eclipse.adt.internal.editors.layout.gre.ViewMetadataRepository;
35 
36 import java.util.ArrayList;
37 import java.util.Collection;
38 import java.util.Collections;
39 import java.util.List;
40 import java.util.Map;
41 
42 import junit.framework.TestCase;
43 
44 /**
45  * Common layout helpers from LayoutRule tests
46  */
47 @SuppressWarnings("javadoc")
48 public class LayoutTestBase extends TestCase {
49     /**
50      * Helper function used by tests to drag a button into a canvas containing
51      * the given children.
52      *
53      * @param rule The rule to test on
54      * @param targetNode The target layout node to drag into
55      * @param dragBounds The (original) bounds of the dragged item
56      * @param dropPoint The drag point we should drag to and drop
57      * @param secondDropPoint An optional second drag point to drag to before
58      *            drawing graphics and dropping (or null if not applicable)
59      * @param insertIndex The expected insert position we end up with after
60      *            dropping at the dropPoint
61      * @param currentIndex If the dragged widget is already in the canvas this
62      *            should be its child index; if not, pass in -1
63      * @param graphicsFragments This is a varargs array of String fragments
64      *            we expect to see in the graphics output on the drag over
65      *            event.
66      * @return The inserted node
67      */
dragInto(IViewRule rule, INode targetNode, Rect dragBounds, Point dropPoint, Point secondDropPoint, int insertIndex, int currentIndex, String... graphicsFragments)68     protected INode dragInto(IViewRule rule, INode targetNode, Rect dragBounds, Point dropPoint,
69             Point secondDropPoint, int insertIndex, int currentIndex,
70             String... graphicsFragments) {
71 
72         String draggedButtonId = (currentIndex == -1) ? "@+id/DraggedButton" : targetNode
73                 .getChildren()[currentIndex].getStringAttr(ANDROID_URI, ATTR_ID);
74 
75         IDragElement[] elements = TestDragElement.create(TestDragElement.create(
76                 "android.widget.Button", dragBounds).id(draggedButtonId));
77 
78         // Enter target
79         DropFeedback feedback = rule.onDropEnter(targetNode, null/*targetView*/, elements);
80         assertNotNull(feedback);
81         assertFalse(feedback.invalidTarget);
82         assertNotNull(feedback.painter);
83 
84         if (currentIndex != -1) {
85             feedback.sameCanvas = true;
86         }
87 
88         // Move near top left corner of the target
89         feedback = rule.onDropMove(targetNode, elements, feedback, dropPoint);
90         assertNotNull(feedback);
91 
92         if (secondDropPoint != null) {
93             feedback = rule.onDropMove(targetNode, elements, feedback, secondDropPoint);
94             assertNotNull(feedback);
95         }
96 
97         if (insertIndex == -1) {
98             assertTrue(feedback.invalidTarget);
99         } else {
100             assertFalse(feedback.invalidTarget);
101         }
102 
103         // Paint feedback and make sure it's what we expect
104         TestGraphics graphics = new TestGraphics();
105         assertNotNull(feedback.painter);
106         feedback.painter.paint(graphics, targetNode, feedback);
107         String drawn = graphics.getDrawn().toString();
108 
109         // Check that each graphics fragment is drawn
110         for (String fragment : graphicsFragments) {
111             if (!drawn.contains(fragment)) {
112                 // Get drawn-output since unit test truncates message in below
113                 // contains-assertion
114                 System.out.println("Could not find: " + fragment);
115                 System.out.println("Full graphics output: " + drawn);
116             }
117             assertTrue(fragment + " not found; full=" + drawn, drawn.contains(fragment));
118         }
119 
120         // Attempt a drop?
121         if (insertIndex == -1) {
122             // No, not expected to succeed (for example, when drop point is over an
123             // invalid region in RelativeLayout) - just return.
124             return null;
125         }
126         int childrenCountBefore = targetNode.getChildren().length;
127         rule.onDropped(targetNode, elements, feedback, dropPoint);
128 
129         if (currentIndex == -1) {
130             // Inserting new from outside
131             assertEquals(childrenCountBefore+1, targetNode.getChildren().length);
132         } else {
133             // Moving from existing; must remove in old position first
134             ((TestNode) targetNode).removeChild(currentIndex);
135 
136             assertEquals(childrenCountBefore, targetNode.getChildren().length);
137         }
138         // Ensure that it's inserted in the right place
139         String actualId = targetNode.getChildren()[insertIndex].getStringAttr(
140                 ANDROID_URI, ATTR_ID);
141         if (!draggedButtonId.equals(actualId)) {
142             // Using assertEquals instead of fail to get nice diff view on test
143             // failure
144             List<String> childrenIds = new ArrayList<String>();
145             for (INode child : targetNode.getChildren()) {
146                 childrenIds.add(child.getStringAttr(ANDROID_URI, ATTR_ID));
147             }
148             int index = childrenIds.indexOf(draggedButtonId);
149             String message = "Button found at index " + index + " instead of " + insertIndex
150                     + " among " + childrenIds;
151             System.out.println(message);
152             assertEquals(message, draggedButtonId, actualId);
153         }
154 
155 
156         return targetNode.getChildren()[insertIndex];
157     }
158 
159     /**
160      * Utility method for asserting that two collections contain exactly the
161      * same elements (regardless of order)
162      * @param expected expected collection
163      * @param actual  actual collection
164      */
assertContainsSame(Collection<String> expected, Collection<String> actual)165     public static void assertContainsSame(Collection<String> expected, Collection<String> actual) {
166         if (expected.size() != actual.size()) {
167             fail("Collection sizes differ; expected " + expected.size() + " but was "
168                     + actual.size());
169         }
170 
171         // Sort prior to comparison to ensure we have the same elements
172         // regardless of order
173         List<String> expectedList = new ArrayList<String>(expected);
174         Collections.sort(expectedList);
175         List<String> actualList = new ArrayList<String>(actual);
176         Collections.sort(actualList);
177         // Instead of just assertEquals(expectedList, actualList);
178         // we iterate one element at a time so we can show the first
179         // -difference-.
180         for (int i = 0; i < expectedList.size(); i++) {
181             String expectedElement = expectedList.get(i);
182             String actualElement = actualList.get(i);
183             if (!expectedElement.equals(actualElement)) {
184                 System.out.println("Expected items: " + expectedList);
185                 System.out.println("Actual items  : " + actualList);
186             }
187             assertEquals("Collections differ; first difference:", expectedElement, actualElement);
188         }
189     }
190 
initialize(IViewRule rule, String fqn)191     protected void initialize(IViewRule rule, String fqn) {
192         rule.onInitialize(fqn, new TestRulesEngine(fqn));
193     }
194 
195     public static class TestRulesEngine implements IClientRulesEngine {
196         private final String mFqn;
197 
TestRulesEngine(String fqn)198         public TestRulesEngine(String fqn) {
199             mFqn = fqn;
200         }
201 
202         @Override
debugPrintf(@onNull String msg, Object... params)203         public void debugPrintf(@NonNull String msg, Object... params) {
204             fail("Not supported in tests yet");
205         }
206 
207         @Override
displayAlert(@onNull String message)208         public void displayAlert(@NonNull String message) {
209             fail("Not supported in tests yet");
210         }
211 
212         @Override
displayInput(@onNull String message, @Nullable String value, @Nullable IValidator filter)213         public String displayInput(@NonNull String message, @Nullable String value,
214                 @Nullable IValidator filter) {
215             fail("Not supported in tests yet");
216             return null;
217         }
218 
219         @Override
getFqcn()220         public @NonNull String getFqcn() {
221             return mFqn;
222         }
223 
224         @Override
getMetadata(final @NonNull String fqcn)225         public @NonNull IViewMetadata getMetadata(final @NonNull String fqcn) {
226             return new IViewMetadata() {
227                 @Override
228                 public @NonNull String getDisplayName() {
229                     // This also works when there is no "."
230                     return fqcn.substring(fqcn.lastIndexOf('.') + 1);
231                 }
232 
233                 @Override
234                 public @NonNull FillPreference getFillPreference() {
235                     return ViewMetadataRepository.get().getFillPreference(fqcn);
236                 }
237 
238                 @Override
239                 public @NonNull Margins getInsets() {
240                     return null;
241                 }
242 
243                 @Override
244                 public @NonNull List<String> getTopAttributes() {
245                     return ViewMetadataRepository.get().getTopAttributes(fqcn);
246                 }
247             };
248         }
249 
250         @Override
getMinApiLevel()251         public int getMinApiLevel() {
252             return 8;
253         }
254 
255         @Override
loadRule(@onNull String fqcn)256         public IViewRule loadRule(@NonNull String fqcn) {
257             fail("Not supported in tests yet");
258             return null;
259         }
260 
261         @Override
displayReferenceInput(String currentValue)262         public String displayReferenceInput(String currentValue) {
263             fail("Not supported in tests yet");
264             return null;
265         }
266 
267         @Override
getResourceValidator(String resourceTypeName, boolean uniqueInProject, boolean uniqueInLayout, boolean exists, String... allowed)268         public IValidator getResourceValidator(String resourceTypeName, boolean uniqueInProject,
269                 boolean uniqueInLayout, boolean exists, String... allowed) {
270             fail("Not supported in tests yet");
271             return null;
272         }
273 
274         @Override
displayResourceInput(@onNull String resourceTypeName, @Nullable String currentValue)275         public String displayResourceInput(@NonNull String resourceTypeName,
276                 @Nullable String currentValue) {
277             fail("Not supported in tests yet");
278             return null;
279         }
280 
281         @Override
displayMarginInput(@ullable String all, @Nullable String left, @Nullable String right, @Nullable String top, @Nullable String bottom)282         public String[] displayMarginInput(@Nullable String all, @Nullable String left,
283                 @Nullable String right, @Nullable String top, @Nullable String bottom) {
284             fail("Not supported in tests yet");
285             return null;
286         }
287 
288         @Override
displayIncludeSourceInput()289         public String displayIncludeSourceInput() {
290             fail("Not supported in tests yet");
291             return null;
292         }
293 
294         @Override
select(@onNull Collection<INode> nodes)295         public void select(@NonNull Collection<INode> nodes) {
296             fail("Not supported in tests yet");
297         }
298 
299         @Override
displayFragmentSourceInput()300         public String displayFragmentSourceInput() {
301             fail("Not supported in tests yet");
302             return null;
303         }
304 
305         @Override
layout()306         public void layout() {
307             fail("Not supported in tests yet");
308         }
309 
310         @Override
redraw()311         public void redraw() {
312             fail("Not supported in tests yet");
313         }
314 
315         @Override
measureChildren(@onNull INode parent, @Nullable AttributeFilter filter)316         public Map<INode, Rect> measureChildren(@NonNull INode parent,
317                 @Nullable AttributeFilter filter) {
318             return null;
319         }
320 
321         @Override
pxToDp(int px)322         public int pxToDp(int px) {
323             // Arbitrary conversion
324             return px / 3;
325         }
326 
327         @Override
dpToPx(int dp)328         public int dpToPx(int dp) {
329             // Arbitrary conversion
330             return 3 * dp;
331         }
332 
333         @Override
getUniqueId(@onNull String prefix)334         public @NonNull String getUniqueId(@NonNull String prefix) {
335             fail("Not supported in tests yet");
336             return null;
337         }
338 
339         @Override
screenToLayout(int pixels)340         public int screenToLayout(int pixels) {
341             fail("Not supported in tests yet");
342             return pixels;
343         }
344 
345         @Override
getAppNameSpace()346         public @NonNull String getAppNameSpace() {
347             fail("Not supported in tests yet");
348             return null;
349         }
350 
351         @Override
getViewObject(@onNull INode node)352         public @Nullable Object getViewObject(@NonNull INode node) {
353             fail("Not supported in tests yet");
354             return null;
355         }
356 
357         @Override
rename(INode node)358         public boolean rename(INode node) {
359             fail("Not supported in tests yet");
360             return false;
361         }
362 
363         @Override
364         @Nullable
displayCustomViewClassInput()365         public String displayCustomViewClassInput() {
366             fail("Not supported in tests yet");
367             return null;
368         }
369     }
370 
371     public void testDummy() {
372         // To avoid JUnit warning that this class contains no tests, even though
373         // this is an abstract class and JUnit shouldn't try
374     }
375 }
376