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 */ 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 */ 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 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 198 public TestRulesEngine(String fqn) { 199 mFqn = fqn; 200 } 201 202 @Override 203 public void debugPrintf(@NonNull String msg, Object... params) { 204 fail("Not supported in tests yet"); 205 } 206 207 @Override 208 public void displayAlert(@NonNull String message) { 209 fail("Not supported in tests yet"); 210 } 211 212 @Override 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 220 public @NonNull String getFqcn() { 221 return mFqn; 222 } 223 224 @Override 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 251 public int getMinApiLevel() { 252 return 8; 253 } 254 255 @Override 256 public IViewRule loadRule(@NonNull String fqcn) { 257 fail("Not supported in tests yet"); 258 return null; 259 } 260 261 @Override 262 public String displayReferenceInput(String currentValue) { 263 fail("Not supported in tests yet"); 264 return null; 265 } 266 267 @Override 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 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 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 289 public String displayIncludeSourceInput() { 290 fail("Not supported in tests yet"); 291 return null; 292 } 293 294 @Override 295 public void select(@NonNull Collection<INode> nodes) { 296 fail("Not supported in tests yet"); 297 } 298 299 @Override 300 public String displayFragmentSourceInput() { 301 fail("Not supported in tests yet"); 302 return null; 303 } 304 305 @Override 306 public void layout() { 307 fail("Not supported in tests yet"); 308 } 309 310 @Override 311 public void redraw() { 312 fail("Not supported in tests yet"); 313 } 314 315 @Override 316 public Map<INode, Rect> measureChildren(@NonNull INode parent, 317 @Nullable AttributeFilter filter) { 318 return null; 319 } 320 321 @Override 322 public int pxToDp(int px) { 323 // Arbitrary conversion 324 return px / 3; 325 } 326 327 @Override 328 public int dpToPx(int dp) { 329 // Arbitrary conversion 330 return 3 * dp; 331 } 332 333 @Override 334 public @NonNull String getUniqueId(@NonNull String prefix) { 335 fail("Not supported in tests yet"); 336 return null; 337 } 338 339 @Override 340 public int screenToLayout(int pixels) { 341 fail("Not supported in tests yet"); 342 return pixels; 343 } 344 345 @Override 346 public @NonNull String getAppNameSpace() { 347 fail("Not supported in tests yet"); 348 return null; 349 } 350 351 @Override 352 public @Nullable Object getViewObject(@NonNull INode node) { 353 fail("Not supported in tests yet"); 354 return null; 355 } 356 357 @Override 358 public boolean rename(INode node) { 359 fail("Not supported in tests yet"); 360 return false; 361 } 362 363 @Override 364 @Nullable 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