aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/AbsoluteLayoutRuleTest.java77
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/BaseLayoutRuleTest.java245
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/BaseViewRuleTest.java42
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/FrameLayoutRuleTest.java63
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/GravityHelperTest.java127
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/GridLayoutRuleTest.java23
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/LayoutTestBase.java375
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/LinearLayoutRuleTest.java466
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/RelativeLayoutRuleTest.java167
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestAttribute.java61
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestAttributeInfo.java81
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestColor.java36
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestDragElement.java158
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestGraphics.java181
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestNode.java477
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/ZoomControlsRuleTest.java50
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/grid/GridModelTest.java852
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/relative/DeletionHandlerTest.java445
18 files changed, 3926 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/AbsoluteLayoutRuleTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/AbsoluteLayoutRuleTest.java
new file mode 100644
index 000000000..1a14bdda4
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/AbsoluteLayoutRuleTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.layout;
+
+import static com.android.SdkConstants.ANDROID_URI;
+
+import com.android.ide.common.api.INode;
+import com.android.ide.common.api.Point;
+import com.android.ide.common.api.Rect;
+
+/** Test the {@link AbsoluteLayoutRule} */
+public class AbsoluteLayoutRuleTest extends LayoutTestBase {
+ // Utility for other tests
+ protected INode dragInto(Rect dragBounds, Point dragPoint, int insertIndex, int currentIndex,
+ String... graphicsFragments) {
+ INode layout = TestNode.create("android.widget.AbsoluteLayout").id("@+id/AbsoluteLayout01")
+ .bounds(new Rect(0, 0, 240, 480)).add(
+ TestNode.create("android.widget.Button").id("@+id/Button01").bounds(
+ new Rect(0, 0, 100, 80)),
+ TestNode.create("android.widget.Button").id("@+id/Button02").bounds(
+ new Rect(0, 100, 100, 80)),
+ TestNode.create("android.widget.Button").id("@+id/Button03").bounds(
+ new Rect(0, 200, 100, 80)),
+ TestNode.create("android.widget.Button").id("@+id/Button04").bounds(
+ new Rect(0, 300, 100, 80)));
+
+ return super.dragInto(new AbsoluteLayoutRule(), layout, dragBounds, dragPoint, null,
+ insertIndex, currentIndex, graphicsFragments);
+ }
+
+ public void testDragMiddle() {
+ INode inserted = dragInto(
+ // Bounds of the dragged item
+ new Rect(0, 0, 105, 80),
+ // Drag point
+ new Point(30, -10),
+ // Expected insert location: We just append in absolute layout
+ 4,
+ // Not dragging one of the existing children
+ -1,
+
+
+ // Bounds rectangle
+ "useStyle(DROP_RECIPIENT), drawRect(Rect[0,0,240,480])",
+
+ // Drop preview
+ "useStyle(DROP_PREVIEW), drawRect(30,-10,135,70)");
+
+ assertEquals("30dp", inserted.getStringAttr(ANDROID_URI, "layout_x"));
+ assertEquals("-10dp", inserted.getStringAttr(ANDROID_URI, "layout_y"));
+
+ // Without drag bounds we should just draw guide lines instead
+ inserted = dragInto(new Rect(0, 0, 0, 0), new Point(30, -10), 4, -1,
+ "useStyle(DROP_RECIPIENT), drawRect(Rect[0,0,240,480])",
+ // Guideline
+ "useStyle(GUIDELINE), drawLine(30,0,30,480), drawLine(0,-10,240,-10)",
+ // Drop preview
+ "useStyle(DROP_PREVIEW), drawLine(30,-10,240,-10), drawLine(30,-10,30,480)");
+ assertEquals("30dp", inserted.getStringAttr(ANDROID_URI, "layout_x"));
+ assertEquals("-10dp", inserted.getStringAttr(ANDROID_URI, "layout_y"));
+ }
+
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/BaseLayoutRuleTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/BaseLayoutRuleTest.java
new file mode 100644
index 000000000..d176fe7d1
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/BaseLayoutRuleTest.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.layout;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_ID;
+
+import com.android.ide.common.api.IDragElement;
+import com.android.ide.common.api.INode;
+import com.android.ide.common.api.Rect;
+import com.android.ide.common.layout.BaseLayoutRule.AttributeFilter;
+import com.android.utils.Pair;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+// TODO: Check assertions
+// TODO: Check equals() but not == strings by using new String("") to prevent interning
+// TODO: Rename BaseLayout to BaseLayoutRule, and tests too of course
+
+public class BaseLayoutRuleTest extends LayoutTestBase {
+
+ /** Provides test data used by other test cases */
+ private IDragElement[] createSampleElements() {
+ IDragElement[] elements = TestDragElement.create(TestDragElement.create(
+ "android.widget.Button", new Rect(0, 0, 100, 80)).id("@+id/Button01"),
+ TestDragElement.create("android.widget.LinearLayout", new Rect(0, 80, 100, 280))
+ .id("@+id/LinearLayout01").add(
+ TestDragElement.create("android.widget.Button",
+ new Rect(0, 80, 100, 80)).id("@+id/Button011"),
+ TestDragElement.create("android.widget.Button",
+ new Rect(0, 180, 100, 80)).id("@+id/Button012")),
+ TestDragElement.create("android.widget.Button", new Rect(100, 0, 100, 80)).id(
+ "@+id/Button02"));
+ return elements;
+ }
+
+ /** Test {@link BaseLayoutRule#collectIds}: Check that basic lookup of id works */
+ public final void testCollectIds1() {
+ IDragElement[] elements = TestDragElement.create(TestDragElement.create(
+ "android.widget.Button", new Rect(0, 0, 100, 80)).id("@+id/Button01"));
+ Map<String, Pair<String, String>> idMap = new HashMap<String, Pair<String, String>>();
+ Map<String, Pair<String, String>> ids = new BaseLayoutRule().collectIds(idMap, elements);
+ assertEquals(1, ids.size());
+ assertEquals("@+id/Button01", ids.keySet().iterator().next());
+ }
+
+ /**
+ * Test {@link BaseLayoutRule#collectIds}: Check that with the wrong URI we
+ * don't pick up the ID
+ */
+ public final void testCollectIds2() {
+ IDragElement[] elements = TestDragElement.create(TestDragElement.create(
+ "android.widget.Button", new Rect(0, 0, 100, 80)).set("myuri", ATTR_ID,
+ "@+id/Button01"));
+
+ Map<String, Pair<String, String>> idMap = new HashMap<String, Pair<String, String>>();
+ Map<String, Pair<String, String>> ids = new BaseLayoutRule().collectIds(idMap, elements);
+ assertEquals(0, ids.size());
+ }
+
+ /**
+ * Test {@link BaseLayoutRule#normalizeId(String)}
+ */
+ public final void testNormalizeId() {
+ assertEquals("foo", new BaseLayoutRule().normalizeId("foo"));
+ assertEquals("@+id/name", new BaseLayoutRule().normalizeId("@id/name"));
+ assertEquals("@+id/name", new BaseLayoutRule().normalizeId("@+id/name"));
+ }
+
+ /**
+ * Test {@link BaseLayoutRule#collectExistingIds}
+ */
+ public final void testCollectExistingIds1() {
+ Set<String> existing = new HashSet<String>();
+ INode node = TestNode.create("android.widget.Button").id("@+id/Button012").add(
+ TestNode.create("android.widget.Button").id("@+id/Button2"));
+
+ new BaseLayoutRule().collectExistingIds(node, existing);
+
+ assertEquals(2, existing.size());
+ assertContainsSame(Arrays.asList("@+id/Button2", "@+id/Button012"), existing);
+ }
+
+ /**
+ * Test {@link BaseLayoutRule#collectIds}: Check that with multiple elements and
+ * some children we still pick up all the right id's
+ */
+ public final void testCollectIds3() {
+ Map<String, Pair<String, String>> idMap = new HashMap<String, Pair<String, String>>();
+
+ IDragElement[] elements = createSampleElements();
+ Map<String, Pair<String, String>> ids = new BaseLayoutRule().collectIds(idMap, elements);
+ assertEquals(5, ids.size());
+ assertContainsSame(Arrays.asList("@+id/Button01", "@+id/Button02", "@+id/Button011",
+ "@+id/Button012", "@+id/LinearLayout01"), ids.keySet());
+
+ // Make sure the Pair has the right stuff too;
+ // (having the id again in the pair seems redundant; see if I really
+ // need it in the implementation)
+ assertEquals(Pair.of("@+id/LinearLayout01", "android.widget.LinearLayout"), ids
+ .get("@+id/LinearLayout01"));
+ }
+
+ /**
+ * Test {@link BaseLayoutRule#remapIds}: Ensure that it identifies a conflict
+ */
+ public final void testRemapIds1() {
+ Map<String, Pair<String, String>> idMap = new HashMap<String, Pair<String, String>>();
+ BaseLayoutRule baseLayout = new BaseLayoutRule();
+ IDragElement[] elements = createSampleElements();
+ baseLayout.collectIds(idMap, elements);
+ INode node = TestNode.create("android.widget.Button").id("@+id/Button012").add(
+ TestNode.create("android.widget.Button").id("@+id/Button2"));
+
+ assertEquals(5, idMap.size());
+ Map<String, Pair<String, String>> remapped = baseLayout.remapIds(node, idMap);
+ // 4 original from the sample elements, plus overlap with one
+ // (Button012) - one new
+ // button added in
+ assertEquals(6, remapped.size());
+
+ // TODO: I'm a little confused about what exactly this method should do;
+ // check with Raphael.
+ }
+
+
+ /**
+ * Test {@link BaseLayoutRule#getDropIdMap}
+ */
+ public final void testGetDropIdMap() {
+ BaseLayoutRule baseLayout = new BaseLayoutRule();
+ IDragElement[] elements = createSampleElements();
+ INode node = TestNode.create("android.widget.Button").id("@+id/Button012").add(
+ TestNode.create("android.widget.Button").id("@+id/Button2"));
+
+ Map<String, Pair<String, String>> idMap = baseLayout.getDropIdMap(node, elements, true);
+ assertContainsSame(Arrays.asList("@+id/Button01", "@+id/Button012", "@+id/Button011",
+ "@id/Button012", "@+id/Button02", "@+id/LinearLayout01"), idMap
+ .keySet());
+
+ // TODO: I'm a little confused about what exactly this method should do;
+ // check with Raphael.
+ }
+
+ public final void testAddAttributes1() {
+ BaseLayoutRule layout = new BaseLayoutRule();
+
+ // First try with no filter
+ IDragElement oldElement = TestDragElement.create("a.w.B").id("@+id/foo");
+ INode newNode = TestNode.create("a.w.B").id("@+id/foo").set("u", "key", "value").set("u",
+ "nothidden", "nothiddenvalue");
+ ;
+ AttributeFilter filter = null;
+ // No references in this test case
+ Map<String, Pair<String, String>> idMap = null;
+
+ layout.addAttributes(newNode, oldElement, idMap, filter);
+ assertEquals("value", newNode.getStringAttr("u", "key"));
+ assertEquals("nothiddenvalue", newNode.getStringAttr("u", "nothidden"));
+ }
+
+ public final void testAddAttributes2() {
+ // Test filtering
+ BaseLayoutRule layout = new BaseLayoutRule();
+
+ // First try with no filter
+ IDragElement oldElement = TestDragElement.create("a.w.B").id("@+id/foo");
+ INode newNode = TestNode.create("a.w.B").id("@+id/foo").set("u", "key", "value").set("u",
+ "hidden", "hiddenvalue");
+ AttributeFilter filter = new AttributeFilter() {
+
+ @Override
+ public String replace(String attributeUri, String attributeName,
+ String attributeValue) {
+ if (attributeName.equals("hidden")) {
+ return null;
+ }
+
+ return attributeValue;
+ }
+ };
+ // No references in this test case
+ Map<String, Pair<String, String>> idMap = null;
+
+ layout.addAttributes(newNode, oldElement, idMap, filter);
+ assertEquals("value", newNode.getStringAttr("u", "key"));
+ }
+
+ public final void testFindNewId() {
+ BaseLayoutRule baseLayout = new BaseLayoutRule();
+ Set<String> existing = new HashSet<String>();
+ assertEquals("@+id/Widget01", baseLayout.findNewId("a.w.Widget", existing));
+
+ existing.add("@+id/Widget01");
+ assertEquals("@+id/Widget02", baseLayout.findNewId("a.w.Widget", existing));
+
+ existing.add("@+id/Widget02");
+ assertEquals("@+id/Widget03", baseLayout.findNewId("a.w.Widget", existing));
+
+ existing.remove("@+id/Widget02");
+ assertEquals("@+id/Widget02", baseLayout.findNewId("a.w.Widget", existing));
+ }
+
+ public final void testDefaultAttributeFilter() {
+ assertEquals("true", BaseLayoutRule.DEFAULT_ATTR_FILTER.replace("myuri", "layout_alignRight",
+ "true"));
+ assertEquals(null, BaseLayoutRule.DEFAULT_ATTR_FILTER.replace(ANDROID_URI,
+ "layout_alignRight", "true"));
+ assertEquals("true", BaseLayoutRule.DEFAULT_ATTR_FILTER.replace(ANDROID_URI,
+ "myproperty", "true"));
+ }
+
+ public final void testAddInnerElements() {
+ IDragElement oldElement = TestDragElement.create("root").add(
+ TestDragElement.create("a.w.B").id("@+id/child1")
+ .set("uri", "childprop1", "value1"),
+ TestDragElement.create("a.w.B").id("@+id/child2").set("uri", "childprop2a",
+ "value2a").set("uri", "childprop2b", "value2b"));
+ INode newNode = TestNode.create("a.w.B").id("@+id/foo");
+ Map<String, Pair<String, String>> idMap = new HashMap<String, Pair<String, String>>();
+ BaseLayoutRule layout = new BaseLayoutRule();
+ layout.addInnerElements(newNode, oldElement, idMap);
+ assertEquals(2, newNode.getChildren().length);
+
+ assertEquals("value2b", newNode.getChildren()[1].getStringAttr("uri", "childprop2b"));
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/BaseViewRuleTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/BaseViewRuleTest.java
new file mode 100644
index 000000000..6d46b1efc
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/BaseViewRuleTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.layout;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import junit.framework.TestCase;
+
+public class BaseViewRuleTest extends TestCase {
+
+ public final void testGetAttributeDisplayName() {
+ assertEquals(null, BaseViewRule.getAttributeDisplayName(null));
+ assertEquals("", BaseViewRule.getAttributeDisplayName(""));
+ assertEquals("Foo", BaseViewRule.getAttributeDisplayName("foo"));
+ assertEquals("FooBar", BaseViewRule.getAttributeDisplayName("fooBar"));
+ assertEquals("Foo Bar", BaseViewRule.getAttributeDisplayName("foo_bar"));
+ // TBD: Should we also handle CamelCase properties?
+ // assertEquals("Foo Bar", BaseViewRule.getAttributeDisplayName("fooBar"));
+ }
+
+ public final void testJoin() {
+ assertEquals("foo", BaseViewRule.join('|', Arrays.asList("foo")));
+ assertEquals("", BaseViewRule.join('|', Collections.<String>emptyList()));
+ assertEquals("foo,bar", BaseViewRule.join(',', Arrays.asList("foo", "bar")));
+ assertEquals("foo|bar", BaseViewRule.join('|', Arrays.asList("foo", "bar")));
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/FrameLayoutRuleTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/FrameLayoutRuleTest.java
new file mode 100644
index 000000000..4a60566d9
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/FrameLayoutRuleTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.layout;
+
+import com.android.ide.common.api.INode;
+import com.android.ide.common.api.Point;
+import com.android.ide.common.api.Rect;
+
+/** Test the {@link FrameLayoutRule} */
+public class FrameLayoutRuleTest extends LayoutTestBase {
+ // Utility for other tests
+ protected void dragInto(Rect dragBounds, Point dragPoint, int insertIndex, int currentIndex,
+ String... graphicsFragments) {
+ INode layout = TestNode.create("android.widget.FrameLayout").id("@+id/FrameLayout01")
+ .bounds(new Rect(0, 0, 240, 480)).add(
+ TestNode.create("android.widget.Button").id("@+id/Button01").bounds(
+ new Rect(0, 0, 100, 80)),
+ TestNode.create("android.widget.Button").id("@+id/Button02").bounds(
+ new Rect(0, 100, 100, 80)),
+ TestNode.create("android.widget.Button").id("@+id/Button03").bounds(
+ new Rect(0, 200, 100, 80)),
+ TestNode.create("android.widget.Button").id("@+id/Button04").bounds(
+ new Rect(0, 300, 100, 80)));
+
+ super.dragInto(new FrameLayoutRule(), layout, dragBounds, dragPoint, null,
+ insertIndex, currentIndex, graphicsFragments);
+ }
+
+ public void testDragMiddle() {
+ dragInto(
+ // Bounds of the dragged item
+ new Rect(0, 0, 105, 80),
+ // Drag point
+ new Point(30, -10),
+ // Expected insert location: We just append in absolute layout
+ 4,
+ // Not dragging one of the existing children
+ -1,
+ // Bounds rectangle
+ "useStyle(DROP_RECIPIENT), drawRect(Rect[0,0,240,480])",
+
+ // Drop Preview
+ "useStyle(DROP_PREVIEW), drawRect(0,0,105,80)");
+ // Without drag bounds we should just draw guide lines instead
+ dragInto(new Rect(0, 0, 0, 0), new Point(30, -10), 4, -1,
+ "useStyle(DROP_RECIPIENT), drawRect(Rect[0,0,240,480])",
+ "useStyle(DROP_PREVIEW), drawLine(1,0,1,480), drawLine(0,1,240,1)");
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/GravityHelperTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/GravityHelperTest.java
new file mode 100644
index 000000000..8161babbe
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/GravityHelperTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.common.layout;
+
+import static com.android.ide.common.layout.GravityHelper.GRAVITY_BOTTOM;
+import static com.android.ide.common.layout.GravityHelper.GRAVITY_CENTER_HORIZ;
+import static com.android.ide.common.layout.GravityHelper.GRAVITY_CENTER_VERT;
+import static com.android.ide.common.layout.GravityHelper.GRAVITY_END;
+import static com.android.ide.common.layout.GravityHelper.GRAVITY_FILL_HORIZ;
+import static com.android.ide.common.layout.GravityHelper.GRAVITY_FILL_VERT;
+import static com.android.ide.common.layout.GravityHelper.GRAVITY_LEFT;
+import static com.android.ide.common.layout.GravityHelper.GRAVITY_RIGHT;
+import static com.android.ide.common.layout.GravityHelper.GRAVITY_START;
+import static com.android.ide.common.layout.GravityHelper.GRAVITY_TOP;
+import static com.android.ide.common.layout.GravityHelper.getGravity;
+
+import com.android.utils.XmlUtils;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import junit.framework.TestCase;
+
+@SuppressWarnings("javadoc")
+public class GravityHelperTest extends TestCase {
+ public void testGravity() throws Exception {
+ assertEquals(GRAVITY_BOTTOM, GravityHelper.getGravity("bottom", 0));
+ assertEquals(GRAVITY_BOTTOM | GRAVITY_LEFT, GravityHelper.getGravity("bottom|left", 0));
+ assertEquals(GRAVITY_BOTTOM | GRAVITY_LEFT | GRAVITY_START,
+ GravityHelper.getGravity("bottom|left|start", 0));
+ assertEquals(GRAVITY_BOTTOM | GRAVITY_LEFT | GRAVITY_START,
+ GravityHelper.getGravity("bottom|start|left", 0));
+ assertEquals(GRAVITY_BOTTOM | GRAVITY_RIGHT | GRAVITY_END,
+ GravityHelper.getGravity("bottom|right|end", 0));
+ assertEquals(GRAVITY_BOTTOM | GRAVITY_RIGHT | GRAVITY_END,
+ GravityHelper.getGravity("bottom|end|right", 0));
+ assertEquals(GRAVITY_CENTER_HORIZ | GRAVITY_CENTER_VERT,
+ GravityHelper.getGravity("center", 0));
+ }
+
+ public void testGravityString() throws Exception {
+ assertEquals("left", getGravity(GRAVITY_LEFT));
+ assertEquals("start", getGravity(GRAVITY_START));
+ assertEquals("right", getGravity(GRAVITY_RIGHT));
+ assertEquals("end", getGravity(GRAVITY_END));
+ assertEquals("top", getGravity(GRAVITY_TOP));
+ assertEquals("bottom", getGravity(GRAVITY_BOTTOM));
+ assertEquals("center_horizontal", getGravity(GRAVITY_CENTER_HORIZ));
+ assertEquals("center_vertical", getGravity(GRAVITY_CENTER_VERT));
+ assertEquals("fill_horizontal", getGravity(GRAVITY_FILL_HORIZ));
+ assertEquals("fill_vertical", getGravity(GRAVITY_FILL_VERT));
+
+ assertEquals("center", getGravity(GRAVITY_CENTER_HORIZ|GRAVITY_CENTER_VERT));
+
+ assertEquals("left|bottom", getGravity(GRAVITY_LEFT|GRAVITY_BOTTOM));
+ assertEquals("left|start|bottom", getGravity(GRAVITY_LEFT|GRAVITY_START|GRAVITY_BOTTOM));
+ assertEquals("center_horizontal|top", getGravity(GRAVITY_CENTER_HORIZ|GRAVITY_TOP));
+ }
+
+ public void testConstrained() throws Exception {
+ assertTrue(GravityHelper.isConstrainedHorizontally(GRAVITY_LEFT));
+ assertTrue(GravityHelper.isConstrainedHorizontally(GRAVITY_START));
+ assertTrue(GravityHelper.isConstrainedHorizontally(GRAVITY_RIGHT));
+ assertTrue(GravityHelper.isConstrainedHorizontally(GRAVITY_END));
+ assertTrue(GravityHelper.isConstrainedHorizontally(GRAVITY_CENTER_HORIZ));
+ assertTrue(GravityHelper.isConstrainedHorizontally(GRAVITY_FILL_HORIZ));
+
+ assertFalse(GravityHelper.isConstrainedVertically(GRAVITY_LEFT));
+ assertFalse(GravityHelper.isConstrainedVertically(GRAVITY_START));
+ assertFalse(GravityHelper.isConstrainedVertically(GRAVITY_RIGHT));
+ assertFalse(GravityHelper.isConstrainedVertically(GRAVITY_END));
+ assertFalse(GravityHelper.isConstrainedVertically(GRAVITY_CENTER_HORIZ));
+ assertFalse(GravityHelper.isConstrainedVertically(GRAVITY_FILL_HORIZ));
+
+ assertTrue(GravityHelper.isConstrainedVertically(GRAVITY_TOP));
+ assertTrue(GravityHelper.isConstrainedVertically(GRAVITY_BOTTOM));
+ assertTrue(GravityHelper.isConstrainedVertically(GRAVITY_CENTER_VERT));
+ assertTrue(GravityHelper.isConstrainedVertically(GRAVITY_FILL_VERT));
+
+ assertFalse(GravityHelper.isConstrainedHorizontally(GRAVITY_TOP));
+ assertFalse(GravityHelper.isConstrainedHorizontally(GRAVITY_BOTTOM));
+ assertFalse(GravityHelper.isConstrainedHorizontally(GRAVITY_CENTER_VERT));
+ assertFalse(GravityHelper.isConstrainedHorizontally(GRAVITY_FILL_VERT));
+ }
+
+ public void testAligned() throws Exception {
+ assertTrue(GravityHelper.isLeftAligned(GRAVITY_LEFT|GRAVITY_TOP));
+ assertTrue(GravityHelper.isLeftAligned(GRAVITY_START|GRAVITY_TOP));
+ assertTrue(GravityHelper.isLeftAligned(GRAVITY_LEFT));
+ assertTrue(GravityHelper.isLeftAligned(GRAVITY_START));
+ assertFalse(GravityHelper.isLeftAligned(GRAVITY_RIGHT));
+ assertFalse(GravityHelper.isLeftAligned(GRAVITY_END));
+
+ assertTrue(GravityHelper.isTopAligned(GRAVITY_LEFT|GRAVITY_TOP));
+ assertTrue(GravityHelper.isTopAligned(GRAVITY_START|GRAVITY_TOP));
+ assertTrue(GravityHelper.isTopAligned(GRAVITY_TOP));
+ assertFalse(GravityHelper.isTopAligned(GRAVITY_BOTTOM));
+ }
+
+ public void testElement() throws Exception {
+ Document document = XmlUtils.parseDocumentSilently(""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " android:layout_width=\"match_parent\"\n"
+ + " android:layout_height=\"wrap_content\"\n"
+ + " android:layout_gravity=\"start|center_vertical|left\""
+ + " android:orientation=\"vertical\" />\n", true);
+ assertNotNull(document);
+ Element element = document.getDocumentElement();
+ assertNotNull(element);
+ int gravity = GravityHelper.getGravity(element);
+ assertEquals("left|start|center_vertical", GravityHelper.getGravity(gravity));
+ }
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/GridLayoutRuleTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/GridLayoutRuleTest.java
new file mode 100644
index 000000000..f0483655d
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/GridLayoutRuleTest.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.common.layout;
+
+
+public class GridLayoutRuleTest extends LayoutTestBase {
+ @Override
+ public void testDummy() {
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/LayoutTestBase.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/LayoutTestBase.java
new file mode 100644
index 000000000..4b4bb814a
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/LayoutTestBase.java
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.layout;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_ID;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.api.DropFeedback;
+import com.android.ide.common.api.IClientRulesEngine;
+import com.android.ide.common.api.IDragElement;
+import com.android.ide.common.api.INode;
+import com.android.ide.common.api.IValidator;
+import com.android.ide.common.api.IViewMetadata;
+import com.android.ide.common.api.IViewRule;
+import com.android.ide.common.api.Margins;
+import com.android.ide.common.api.Point;
+import com.android.ide.common.api.Rect;
+import com.android.ide.eclipse.adt.internal.editors.layout.gre.ViewMetadataRepository;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+/**
+ * Common layout helpers from LayoutRule tests
+ */
+@SuppressWarnings("javadoc")
+public class LayoutTestBase extends TestCase {
+ /**
+ * Helper function used by tests to drag a button into a canvas containing
+ * the given children.
+ *
+ * @param rule The rule to test on
+ * @param targetNode The target layout node to drag into
+ * @param dragBounds The (original) bounds of the dragged item
+ * @param dropPoint The drag point we should drag to and drop
+ * @param secondDropPoint An optional second drag point to drag to before
+ * drawing graphics and dropping (or null if not applicable)
+ * @param insertIndex The expected insert position we end up with after
+ * dropping at the dropPoint
+ * @param currentIndex If the dragged widget is already in the canvas this
+ * should be its child index; if not, pass in -1
+ * @param graphicsFragments This is a varargs array of String fragments
+ * we expect to see in the graphics output on the drag over
+ * event.
+ * @return The inserted node
+ */
+ protected INode dragInto(IViewRule rule, INode targetNode, Rect dragBounds, Point dropPoint,
+ Point secondDropPoint, int insertIndex, int currentIndex,
+ String... graphicsFragments) {
+
+ String draggedButtonId = (currentIndex == -1) ? "@+id/DraggedButton" : targetNode
+ .getChildren()[currentIndex].getStringAttr(ANDROID_URI, ATTR_ID);
+
+ IDragElement[] elements = TestDragElement.create(TestDragElement.create(
+ "android.widget.Button", dragBounds).id(draggedButtonId));
+
+ // Enter target
+ DropFeedback feedback = rule.onDropEnter(targetNode, null/*targetView*/, elements);
+ assertNotNull(feedback);
+ assertFalse(feedback.invalidTarget);
+ assertNotNull(feedback.painter);
+
+ if (currentIndex != -1) {
+ feedback.sameCanvas = true;
+ }
+
+ // Move near top left corner of the target
+ feedback = rule.onDropMove(targetNode, elements, feedback, dropPoint);
+ assertNotNull(feedback);
+
+ if (secondDropPoint != null) {
+ feedback = rule.onDropMove(targetNode, elements, feedback, secondDropPoint);
+ assertNotNull(feedback);
+ }
+
+ if (insertIndex == -1) {
+ assertTrue(feedback.invalidTarget);
+ } else {
+ assertFalse(feedback.invalidTarget);
+ }
+
+ // Paint feedback and make sure it's what we expect
+ TestGraphics graphics = new TestGraphics();
+ assertNotNull(feedback.painter);
+ feedback.painter.paint(graphics, targetNode, feedback);
+ String drawn = graphics.getDrawn().toString();
+
+ // Check that each graphics fragment is drawn
+ for (String fragment : graphicsFragments) {
+ if (!drawn.contains(fragment)) {
+ // Get drawn-output since unit test truncates message in below
+ // contains-assertion
+ System.out.println("Could not find: " + fragment);
+ System.out.println("Full graphics output: " + drawn);
+ }
+ assertTrue(fragment + " not found; full=" + drawn, drawn.contains(fragment));
+ }
+
+ // Attempt a drop?
+ if (insertIndex == -1) {
+ // No, not expected to succeed (for example, when drop point is over an
+ // invalid region in RelativeLayout) - just return.
+ return null;
+ }
+ int childrenCountBefore = targetNode.getChildren().length;
+ rule.onDropped(targetNode, elements, feedback, dropPoint);
+
+ if (currentIndex == -1) {
+ // Inserting new from outside
+ assertEquals(childrenCountBefore+1, targetNode.getChildren().length);
+ } else {
+ // Moving from existing; must remove in old position first
+ ((TestNode) targetNode).removeChild(currentIndex);
+
+ assertEquals(childrenCountBefore, targetNode.getChildren().length);
+ }
+ // Ensure that it's inserted in the right place
+ String actualId = targetNode.getChildren()[insertIndex].getStringAttr(
+ ANDROID_URI, ATTR_ID);
+ if (!draggedButtonId.equals(actualId)) {
+ // Using assertEquals instead of fail to get nice diff view on test
+ // failure
+ List<String> childrenIds = new ArrayList<String>();
+ for (INode child : targetNode.getChildren()) {
+ childrenIds.add(child.getStringAttr(ANDROID_URI, ATTR_ID));
+ }
+ int index = childrenIds.indexOf(draggedButtonId);
+ String message = "Button found at index " + index + " instead of " + insertIndex
+ + " among " + childrenIds;
+ System.out.println(message);
+ assertEquals(message, draggedButtonId, actualId);
+ }
+
+
+ return targetNode.getChildren()[insertIndex];
+ }
+
+ /**
+ * Utility method for asserting that two collections contain exactly the
+ * same elements (regardless of order)
+ * @param expected expected collection
+ * @param actual actual collection
+ */
+ public static void assertContainsSame(Collection<String> expected, Collection<String> actual) {
+ if (expected.size() != actual.size()) {
+ fail("Collection sizes differ; expected " + expected.size() + " but was "
+ + actual.size());
+ }
+
+ // Sort prior to comparison to ensure we have the same elements
+ // regardless of order
+ List<String> expectedList = new ArrayList<String>(expected);
+ Collections.sort(expectedList);
+ List<String> actualList = new ArrayList<String>(actual);
+ Collections.sort(actualList);
+ // Instead of just assertEquals(expectedList, actualList);
+ // we iterate one element at a time so we can show the first
+ // -difference-.
+ for (int i = 0; i < expectedList.size(); i++) {
+ String expectedElement = expectedList.get(i);
+ String actualElement = actualList.get(i);
+ if (!expectedElement.equals(actualElement)) {
+ System.out.println("Expected items: " + expectedList);
+ System.out.println("Actual items : " + actualList);
+ }
+ assertEquals("Collections differ; first difference:", expectedElement, actualElement);
+ }
+ }
+
+ protected void initialize(IViewRule rule, String fqn) {
+ rule.onInitialize(fqn, new TestRulesEngine(fqn));
+ }
+
+ public static class TestRulesEngine implements IClientRulesEngine {
+ private final String mFqn;
+
+ public TestRulesEngine(String fqn) {
+ mFqn = fqn;
+ }
+
+ @Override
+ public void debugPrintf(@NonNull String msg, Object... params) {
+ fail("Not supported in tests yet");
+ }
+
+ @Override
+ public void displayAlert(@NonNull String message) {
+ fail("Not supported in tests yet");
+ }
+
+ @Override
+ public String displayInput(@NonNull String message, @Nullable String value,
+ @Nullable IValidator filter) {
+ fail("Not supported in tests yet");
+ return null;
+ }
+
+ @Override
+ public @NonNull String getFqcn() {
+ return mFqn;
+ }
+
+ @Override
+ public @NonNull IViewMetadata getMetadata(final @NonNull String fqcn) {
+ return new IViewMetadata() {
+ @Override
+ public @NonNull String getDisplayName() {
+ // This also works when there is no "."
+ return fqcn.substring(fqcn.lastIndexOf('.') + 1);
+ }
+
+ @Override
+ public @NonNull FillPreference getFillPreference() {
+ return ViewMetadataRepository.get().getFillPreference(fqcn);
+ }
+
+ @Override
+ public @NonNull Margins getInsets() {
+ return null;
+ }
+
+ @Override
+ public @NonNull List<String> getTopAttributes() {
+ return ViewMetadataRepository.get().getTopAttributes(fqcn);
+ }
+ };
+ }
+
+ @Override
+ public int getMinApiLevel() {
+ return 8;
+ }
+
+ @Override
+ public IViewRule loadRule(@NonNull String fqcn) {
+ fail("Not supported in tests yet");
+ return null;
+ }
+
+ @Override
+ public String displayReferenceInput(String currentValue) {
+ fail("Not supported in tests yet");
+ return null;
+ }
+
+ @Override
+ public IValidator getResourceValidator(String resourceTypeName, boolean uniqueInProject,
+ boolean uniqueInLayout, boolean exists, String... allowed) {
+ fail("Not supported in tests yet");
+ return null;
+ }
+
+ @Override
+ public String displayResourceInput(@NonNull String resourceTypeName,
+ @Nullable String currentValue) {
+ fail("Not supported in tests yet");
+ return null;
+ }
+
+ @Override
+ public String[] displayMarginInput(@Nullable String all, @Nullable String left,
+ @Nullable String right, @Nullable String top, @Nullable String bottom) {
+ fail("Not supported in tests yet");
+ return null;
+ }
+
+ @Override
+ public String displayIncludeSourceInput() {
+ fail("Not supported in tests yet");
+ return null;
+ }
+
+ @Override
+ public void select(@NonNull Collection<INode> nodes) {
+ fail("Not supported in tests yet");
+ }
+
+ @Override
+ public String displayFragmentSourceInput() {
+ fail("Not supported in tests yet");
+ return null;
+ }
+
+ @Override
+ public void layout() {
+ fail("Not supported in tests yet");
+ }
+
+ @Override
+ public void redraw() {
+ fail("Not supported in tests yet");
+ }
+
+ @Override
+ public Map<INode, Rect> measureChildren(@NonNull INode parent,
+ @Nullable AttributeFilter filter) {
+ return null;
+ }
+
+ @Override
+ public int pxToDp(int px) {
+ // Arbitrary conversion
+ return px / 3;
+ }
+
+ @Override
+ public int dpToPx(int dp) {
+ // Arbitrary conversion
+ return 3 * dp;
+ }
+
+ @Override
+ public @NonNull String getUniqueId(@NonNull String prefix) {
+ fail("Not supported in tests yet");
+ return null;
+ }
+
+ @Override
+ public int screenToLayout(int pixels) {
+ fail("Not supported in tests yet");
+ return pixels;
+ }
+
+ @Override
+ public @NonNull String getAppNameSpace() {
+ fail("Not supported in tests yet");
+ return null;
+ }
+
+ @Override
+ public @Nullable Object getViewObject(@NonNull INode node) {
+ fail("Not supported in tests yet");
+ return null;
+ }
+
+ @Override
+ public boolean rename(INode node) {
+ fail("Not supported in tests yet");
+ return false;
+ }
+
+ @Override
+ @Nullable
+ public String displayCustomViewClassInput() {
+ fail("Not supported in tests yet");
+ return null;
+ }
+ }
+
+ public void testDummy() {
+ // To avoid JUnit warning that this class contains no tests, even though
+ // this is an abstract class and JUnit shouldn't try
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/LinearLayoutRuleTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/LinearLayoutRuleTest.java
new file mode 100644
index 000000000..9c4c934c0
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/LinearLayoutRuleTest.java
@@ -0,0 +1,466 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.layout;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_ID;
+import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
+import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
+import static com.android.SdkConstants.ATTR_ORIENTATION;
+import static com.android.SdkConstants.VALUE_HORIZONTAL;
+import static com.android.SdkConstants.VALUE_VERTICAL;
+
+import com.android.ide.common.api.DropFeedback;
+import com.android.ide.common.api.IAttributeInfo.Format;
+import com.android.ide.common.api.IDragElement;
+import com.android.ide.common.api.IMenuCallback;
+import com.android.ide.common.api.INode;
+import com.android.ide.common.api.IViewRule;
+import com.android.ide.common.api.Point;
+import com.android.ide.common.api.Rect;
+import com.android.ide.common.api.RuleAction;
+import com.android.ide.common.api.RuleAction.NestedAction;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/** Test the {@link LinearLayoutRule} */
+public class LinearLayoutRuleTest extends LayoutTestBase {
+ // Utility for other tests
+ protected void dragIntoEmpty(Rect dragBounds) {
+ boolean haveBounds = dragBounds.isValid();
+
+ IViewRule rule = new LinearLayoutRule();
+
+ INode targetNode = TestNode.create("android.widget.LinearLayout").id(
+ "@+id/LinearLayout01").bounds(new Rect(0, 0, 240, 480));
+ Point dropPoint = new Point(10, 5);
+
+ IDragElement[] elements = TestDragElement.create(TestDragElement.create(
+ "android.widget.Button", dragBounds).id("@+id/Button01"));
+
+ // Enter target
+ DropFeedback feedback = rule.onDropEnter(targetNode, null/*targetView*/, elements);
+ assertNotNull(feedback);
+ assertFalse(feedback.invalidTarget);
+ assertNotNull(feedback.painter);
+
+ feedback = rule.onDropMove(targetNode, elements, feedback, dropPoint);
+ assertNotNull(feedback);
+ assertFalse(feedback.invalidTarget);
+
+ // Paint feedback and make sure it's what we expect
+ TestGraphics graphics = new TestGraphics();
+ assertNotNull(feedback.painter);
+ feedback.painter.paint(graphics, targetNode, feedback);
+ assertEquals(
+ // Expect to see a recipient rectangle around the bounds of the
+ // LinearLayout,
+ // as well as a single vertical line as a drop preview located
+ // along the left
+ // edge (for this horizontal linear layout) showing insert
+ // position at index 0,
+ // and finally a rectangle for the bounds of the inserted button
+ // centered over
+ // the middle
+ "[useStyle(DROP_RECIPIENT), "
+ +
+ // Bounds rectangle
+ "drawRect(Rect[0,0,240,480]), "
+ + "useStyle(DROP_ZONE), drawLine(1,0,1,480), "
+ + "useStyle(DROP_ZONE_ACTIVE), " + "useStyle(DROP_PREVIEW), " +
+ // Insert position line
+ "drawLine(1,0,1,480)" + (haveBounds ?
+ // Outline of dragged node centered over position line
+ ", useStyle(DROP_PREVIEW), " + "drawRect(1,0,101,80)"
+ // Nothing when we don't have bounds
+ : "") + "]", graphics.getDrawn().toString());
+
+ // Attempt a drop
+ assertEquals(0, targetNode.getChildren().length);
+ rule.onDropped(targetNode, elements, feedback, dropPoint);
+ assertEquals(1, targetNode.getChildren().length);
+ assertEquals("@+id/Button01", targetNode.getChildren()[0].getStringAttr(
+ ANDROID_URI, ATTR_ID));
+ }
+
+ // Utility for other tests
+ protected INode dragInto(boolean vertical, Rect dragBounds, Point dragPoint,
+ int insertIndex, int currentIndex,
+ String... graphicsFragments) {
+ INode linearLayout = TestNode.create("android.widget.LinearLayout").id(
+ "@+id/LinearLayout01").bounds(new Rect(0, 0, 240, 480)).set(ANDROID_URI,
+ ATTR_ORIENTATION,
+ vertical ? VALUE_VERTICAL : VALUE_HORIZONTAL)
+ .add(
+ TestNode.create("android.widget.Button").id("@+id/Button01").bounds(
+ new Rect(0, 0, 100, 80)),
+ TestNode.create("android.widget.Button").id("@+id/Button02").bounds(
+ new Rect(0, 100, 100, 80)),
+ TestNode.create("android.widget.Button").id("@+id/Button03").bounds(
+ new Rect(0, 200, 100, 80)),
+ TestNode.create("android.widget.Button").id("@+id/Button04").bounds(
+ new Rect(0, 300, 100, 80)));
+
+ return super.dragInto(new LinearLayoutRule(), linearLayout, dragBounds, dragPoint, null,
+ insertIndex, currentIndex, graphicsFragments);
+ }
+
+ // Check that the context menu registers the expected menu items
+ public void testContextMenu() {
+ LinearLayoutRule rule = new LinearLayoutRule();
+ initialize(rule, "android.widget.LinearLayout");
+ INode node = TestNode.create("android.widget.Button").id("@+id/Button012");
+
+ List<RuleAction> contextMenu = new ArrayList<RuleAction>();
+ rule.addContextMenuActions(contextMenu, node);
+ assertEquals(6, contextMenu.size());
+ assertEquals("Edit ID...", contextMenu.get(0).getTitle());
+ assertTrue(contextMenu.get(1) instanceof RuleAction.Separator);
+ assertEquals("Layout Width", contextMenu.get(2).getTitle());
+ assertEquals("Layout Height", contextMenu.get(3).getTitle());
+ assertTrue(contextMenu.get(4) instanceof RuleAction.Separator);
+ assertEquals("Other Properties", contextMenu.get(5).getTitle());
+
+ RuleAction propertiesMenu = contextMenu.get(5);
+ assertTrue(propertiesMenu.getClass().getName(),
+ propertiesMenu instanceof NestedAction);
+ }
+
+ public void testContextMenuCustom() {
+ LinearLayoutRule rule = new LinearLayoutRule();
+ initialize(rule, "android.widget.LinearLayout");
+ INode node = TestNode.create("android.widget.LinearLayout").id("@+id/LinearLayout")
+ .set(ANDROID_URI, ATTR_LAYOUT_WIDTH, "42dip")
+ .set(ANDROID_URI, ATTR_LAYOUT_HEIGHT, "50sp");
+
+ List<RuleAction> contextMenu = new ArrayList<RuleAction>();
+ rule.addContextMenuActions(contextMenu, node);
+ assertEquals(6, contextMenu.size());
+ assertEquals("Layout Width", contextMenu.get(2).getTitle());
+ RuleAction menuAction = contextMenu.get(2);
+ assertTrue(menuAction instanceof RuleAction.Choices);
+ RuleAction.Choices choices = (RuleAction.Choices) menuAction;
+ List<String> titles = choices.getTitles();
+ List<String> ids = choices.getIds();
+ assertEquals("Wrap Content", titles.get(0));
+ assertEquals("wrap_content", ids.get(0));
+ assertEquals("Match Parent", titles.get(1));
+ assertEquals("match_parent", ids.get(1));
+ assertEquals("42dip", titles.get(2));
+ assertEquals("42dip", ids.get(2));
+ assertEquals("42dip", choices.getCurrent());
+ }
+
+ // Check that the context menu manipulates the orientation attribute
+ public void testOrientation() {
+ LinearLayoutRule rule = new LinearLayoutRule();
+ initialize(rule, "android.widget.LinearLayout");
+ TestNode node = TestNode.create("android.widget.LinearLayout").id("@+id/LinearLayout012");
+ node.putAttributeInfo(ANDROID_URI, "orientation",
+ new TestAttributeInfo(ATTR_ORIENTATION, Format.ENUM_SET,
+ "android.widget.LinearLayout",
+ new String[] {"horizontal", "vertical"}, null, null));
+
+ assertNull(node.getStringAttr(ANDROID_URI, ATTR_ORIENTATION));
+
+ List<RuleAction> contextMenu = new ArrayList<RuleAction>();
+ rule.addContextMenuActions(contextMenu, node);
+ assertEquals(7, contextMenu.size());
+ RuleAction orientationAction = contextMenu.get(1);
+ assertEquals("Orientation", orientationAction.getTitle());
+
+ assertTrue(orientationAction.getClass().getName(),
+ orientationAction instanceof RuleAction.Choices);
+
+ RuleAction.Choices choices = (RuleAction.Choices) orientationAction;
+ IMenuCallback callback = choices.getCallback();
+ callback.action(orientationAction, Collections.singletonList(node), VALUE_VERTICAL, true);
+
+ String orientation = node.getStringAttr(ANDROID_URI,
+ ATTR_ORIENTATION);
+ assertEquals(VALUE_VERTICAL, orientation);
+ callback.action(orientationAction, Collections.singletonList(node), VALUE_HORIZONTAL,
+ true);
+ orientation = node.getStringAttr(ANDROID_URI, ATTR_ORIENTATION);
+ assertEquals(VALUE_HORIZONTAL, orientation);
+ }
+
+ // Check that the context menu manipulates the orientation attribute
+ public void testProperties() {
+ LinearLayoutRule rule = new LinearLayoutRule();
+ initialize(rule, "android.widget.LinearLayout");
+ TestNode node = TestNode.create("android.widget.LinearLayout").id("@+id/LinearLayout012");
+ node.putAttributeInfo(ANDROID_URI, "orientation",
+ new TestAttributeInfo(ATTR_ORIENTATION, Format.ENUM_SET,
+ "android.widget.LinearLayout",
+ new String[] {"horizontal", "vertical"}, null, null));
+ node.setAttributeSources(Arrays.asList("android.widget.LinearLayout",
+ "android.view.ViewGroup", "android.view.View"));
+ node.putAttributeInfo(ANDROID_URI, "gravity",
+ new TestAttributeInfo("gravity", Format.INTEGER_SET,
+ "android.widget.LinearLayout", null, null, null));
+
+
+ assertNull(node.getStringAttr(ANDROID_URI, ATTR_ORIENTATION));
+
+ List<RuleAction> contextMenu = new ArrayList<RuleAction>();
+ rule.addContextMenuActions(contextMenu, node);
+ assertEquals(8, contextMenu.size());
+
+ assertEquals("Orientation", contextMenu.get(1).getTitle());
+ assertEquals("Edit Gravity...", contextMenu.get(2).getTitle());
+
+ assertEquals("Other Properties", contextMenu.get(7).getTitle());
+
+ RuleAction propertiesMenu = contextMenu.get(7);
+ assertTrue(propertiesMenu.getClass().getName(),
+ propertiesMenu instanceof NestedAction);
+ NestedAction nested = (NestedAction) propertiesMenu;
+ List<RuleAction> nestedActions = nested.getNestedActions(node);
+ assertEquals(9, nestedActions.size());
+ assertEquals("Recent", nestedActions.get(0).getTitle());
+ assertTrue(nestedActions.get(1) instanceof RuleAction.Separator);
+ assertEquals("Defined by LinearLayout", nestedActions.get(2).getTitle());
+ assertEquals("Inherited from ViewGroup", nestedActions.get(3).getTitle());
+ assertEquals("Inherited from View", nestedActions.get(4).getTitle());
+ assertTrue(nestedActions.get(5) instanceof RuleAction.Separator);
+ assertEquals("Layout Parameters", nestedActions.get(6).getTitle());
+ assertTrue(nestedActions.get(7) instanceof RuleAction.Separator);
+ assertEquals("All By Name", nestedActions.get(8).getTitle());
+
+ BaseViewRule.editedProperty(ATTR_ORIENTATION);
+
+ RuleAction recentAction = nestedActions.get(0);
+ assertTrue(recentAction instanceof NestedAction);
+ NestedAction recentChoices = (NestedAction) recentAction;
+ List<RuleAction> recentItems = recentChoices.getNestedActions(node);
+
+ assertEquals(1, recentItems.size());
+ assertEquals("Orientation", recentItems.get(0).getTitle());
+
+ BaseViewRule.editedProperty("gravity");
+ recentItems = recentChoices.getNestedActions(node);
+ assertEquals(2, recentItems.size());
+ assertEquals("Gravity...", recentItems.get(0).getTitle());
+ assertEquals("Orientation", recentItems.get(1).getTitle());
+
+ BaseViewRule.editedProperty(ATTR_ORIENTATION);
+ recentItems = recentChoices.getNestedActions(node);
+ assertEquals(2, recentItems.size());
+ assertEquals("Orientation", recentItems.get(0).getTitle());
+ assertEquals("Gravity...", recentItems.get(1).getTitle());
+
+ // Lots of other properties -- flushes out properties that apply to this view
+ for (int i = 0; i < 30; i++) {
+ BaseViewRule.editedProperty("dummy_" + i);
+ }
+ recentItems = recentChoices.getNestedActions(node);
+ assertEquals(0, recentItems.size());
+
+ BaseViewRule.editedProperty("gravity");
+ recentItems = recentChoices.getNestedActions(node);
+ assertEquals(1, recentItems.size());
+ assertEquals("Gravity...", recentItems.get(0).getTitle());
+ }
+
+ public void testDragInEmptyWithBounds() {
+ dragIntoEmpty(new Rect(0, 0, 100, 80));
+ }
+
+ public void testDragInEmptyWithoutBounds() {
+ dragIntoEmpty(new Rect(0, 0, 0, 0));
+ }
+
+ public void testDragInVerticalTop() {
+ dragInto(true,
+ // Bounds of the dragged item
+ new Rect(0, 0, 105, 80),
+ // Drag point
+ new Point(30, -10),
+ // Expected insert location
+ 0,
+ // Not dragging one of the existing children
+ -1,
+ // Bounds rectangle
+ "useStyle(DROP_RECIPIENT), drawRect(Rect[0,0,240,480])",
+
+ // Drop zones
+ "useStyle(DROP_ZONE), drawLine(0,0,240,0), drawLine(0,90,240,90), "
+ + "drawLine(0,190,240,190), drawLine(0,290,240,290), "
+ + "drawLine(0,381,240,381)",
+
+ // Active nearest line
+ "useStyle(DROP_ZONE_ACTIVE), useStyle(DROP_PREVIEW), drawLine(0,0,240,0)",
+
+ // Preview of the dropped rectangle
+ "useStyle(DROP_PREVIEW), drawRect(0,-40,105,40)");
+
+ // Without drag bounds it should be identical except no preview
+ // rectangle
+ dragInto(true,
+ new Rect(0, 0, 0, 0), // Invalid
+ new Point(30, -10), 0, -1,
+ "useStyle(DROP_ZONE_ACTIVE), useStyle(DROP_PREVIEW), drawLine(0,0,240,0)");
+ }
+
+ public void testDragInVerticalBottom() {
+ dragInto(true,
+ // Bounds of the dragged item
+ new Rect(0, 0, 105, 80),
+ // Drag point
+ new Point(30, 500),
+ // Expected insert location
+ 4,
+ // Not dragging one of the existing children
+ -1,
+ // Bounds rectangle
+ "useStyle(DROP_RECIPIENT), drawRect(Rect[0,0,240,480])",
+
+ // Drop zones
+ "useStyle(DROP_ZONE), drawLine(0,0,240,0), drawLine(0,90,240,90), "
+ + "drawLine(0,190,240,190), drawLine(0,290,240,290), drawLine(0,381,240,381), ",
+
+ // Active nearest line
+ "useStyle(DROP_ZONE_ACTIVE), useStyle(DROP_PREVIEW), drawLine(0,381,240,381)",
+
+ // Preview of the dropped rectangle
+ "useStyle(DROP_PREVIEW), drawRect(0,381,105,461)");
+
+ // Check without bounds too
+ dragInto(true, new Rect(0, 0, 105, 80), new Point(30, 500), 4, -1,
+ "useStyle(DROP_PREVIEW), drawRect(0,381,105,461)");
+ }
+
+ public void testDragInVerticalMiddle() {
+ dragInto(true,
+ // Bounds of the dragged item
+ new Rect(0, 0, 105, 80),
+ // Drag point
+ new Point(0, 170),
+ // Expected insert location
+ 2,
+ // Not dragging one of the existing children
+ -1,
+ // Bounds rectangle
+ "useStyle(DROP_RECIPIENT), drawRect(Rect[0,0,240,480])",
+
+ // Drop zones
+ "useStyle(DROP_ZONE), drawLine(0,0,240,0), drawLine(0,90,240,90), "
+ + "drawLine(0,190,240,190), drawLine(0,290,240,290)",
+
+ // Active nearest line
+ "useStyle(DROP_ZONE_ACTIVE), useStyle(DROP_PREVIEW), drawLine(0,190,240,190)",
+
+ // Preview of the dropped rectangle
+ "useStyle(DROP_PREVIEW), drawRect(0,150,105,230)");
+
+ // Check without bounds too
+ dragInto(true, new Rect(0, 0, 105, 80), new Point(0, 170), 2, -1,
+ "useStyle(DROP_PREVIEW), drawRect(0,150,105,230)");
+ }
+
+ public void testDragInVerticalMiddleSelfPos() {
+ // Drag the 2nd button, down to the position between 3rd and 4th
+ dragInto(true,
+ // Bounds of the dragged item
+ new Rect(0, 100, 100, 80),
+ // Drag point
+ new Point(0, 250),
+ // Expected insert location
+ 2,
+ // Dragging 1st item
+ 1,
+ // Bounds rectangle
+
+ "useStyle(DROP_RECIPIENT), drawRect(Rect[0,0,240,480])",
+
+ // Drop zones - these are different because we exclude drop
+ // zones around the
+ // dragged item itself (it doesn't make sense to insert directly
+ // before or after
+ // myself
+ "useStyle(DROP_ZONE), drawLine(0,0,240,0), drawLine(0,290,240,290), "
+ + "drawLine(0,381,240,381)",
+
+ // Preview line along insert axis
+ "useStyle(DROP_ZONE_ACTIVE), useStyle(DROP_PREVIEW), drawLine(0,290,240,290)",
+
+ // Preview of dropped rectangle
+ "useStyle(DROP_PREVIEW), drawRect(0,250,100,330)");
+
+ // Test dropping on self (no position change):
+ dragInto(true,
+ // Bounds of the dragged item
+ new Rect(0, 100, 100, 80),
+ // Drag point
+ new Point(0, 210),
+ // Expected insert location
+ 1,
+ // Dragging from same pos
+ 1,
+ // Bounds rectangle
+ "useStyle(DROP_RECIPIENT), drawRect(Rect[0,0,240,480])",
+
+ // Drop zones - these are different because we exclude drop
+ // zones around the
+ // dragged item itself (it doesn't make sense to insert directly
+ // before or after
+ // myself
+ "useStyle(DROP_ZONE), drawLine(0,0,240,0), drawLine(0,290,240,290), "
+ + "drawLine(0,381,240,381)",
+
+ // No active nearest line when you're over the self pos!
+
+ // Preview of the dropped rectangle
+ "useStyle(DROP_ZONE_ACTIVE), useStyle(DROP_PREVIEW), drawRect(0,100,100,180)");
+ }
+
+ public void testDragToLastPosition() {
+ // Drag a button to the last position -- and confirm that the preview rectangle
+ // is now shown midway between the second to last and last positions, but fully
+ // below the drop zone line:
+ dragInto(true,
+ // Bounds of the dragged item
+ new Rect(0, 100, 100, 80),
+ // Drag point
+ new Point(0, 400),
+ // Expected insert location
+ 3,
+ // Dragging 1st item
+ 1,
+
+ // Bounds rectangle
+ "useStyle(DROP_RECIPIENT), drawRect(Rect[0,0,240,480])",
+
+ // Drop Zones
+ "useStyle(DROP_ZONE), drawLine(0,0,240,0), drawLine(0,290,240,290), " +
+ "drawLine(0,381,240,381), ",
+
+ // Active Drop Zone
+ "useStyle(DROP_ZONE_ACTIVE), useStyle(DROP_PREVIEW), drawLine(0,381,240,381)",
+
+ // Drop Preview
+ "useStyle(DROP_PREVIEW), drawRect(0,381,100,461)");
+ }
+
+ // Left to test:
+ // Check inserting at last pos with multiple children
+ // Check inserting with no bounds rectangle for dragged element
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/RelativeLayoutRuleTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/RelativeLayoutRuleTest.java
new file mode 100644
index 000000000..f9747f39d
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/RelativeLayoutRuleTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.layout;
+
+
+import static com.android.SdkConstants.ANDROID_URI;
+
+import com.android.ide.common.api.INode;
+import com.android.ide.common.api.Point;
+import com.android.ide.common.api.Rect;
+
+/** Test the {@link RelativeLayoutRule} */
+public class RelativeLayoutRuleTest extends LayoutTestBase {
+ // Utility for other tests
+ protected INode dragInto(Rect dragBounds, Point dragPoint, Point secondDragPoint,
+ int insertIndex, int currentIndex, String... graphicsFragments) {
+ INode layout = TestNode.create("android.widget.RelativeLayout").id("@+id/RelativeLayout01")
+ .bounds(new Rect(0, 0, 240, 480)).add(
+ // Add centered button as the anchor
+ TestNode.create("android.widget.Button").id("@+id/Centered").bounds(
+ new Rect(70, 200, 100, 80)).set(ANDROID_URI,
+ "layout_centerInParent", "true"),
+ // Add a second button anchored to it
+ TestNode.create("android.widget.Button").id("@+id/Below").bounds(
+ new Rect(70, 280, 100, 80)).set(ANDROID_URI, "layout_below",
+ "@+id/Centered").set(ANDROID_URI, "layout_alignLeft",
+ "@+id/Centered"));
+
+ return super.dragInto(new RelativeLayoutRule(), layout, dragBounds, dragPoint,
+ secondDragPoint, insertIndex, currentIndex, graphicsFragments);
+ }
+
+ protected INode dragInto(Rect dragBounds, Point dragPoint, Point secondDragPoint,
+ int insertIndex, int currentIndex, String[] extraFragments,
+ String... graphicsFragments) {
+
+ // When we switch to JDK6, use Arrays#copyOf instead
+ String[] combined = new String[extraFragments.length + graphicsFragments.length];
+ System.arraycopy(graphicsFragments, 0, combined, 0, graphicsFragments.length);
+ System.arraycopy(extraFragments, 0, combined, graphicsFragments.length,
+ extraFragments.length);
+
+ return dragInto(dragBounds, dragPoint, secondDragPoint, insertIndex,
+ currentIndex, combined);
+ }
+
+ /* This needs to be updated for the new interaction
+ public void testDropTopEdge() {
+ // If we drag right into the button itself, not a valid drop position
+ INode inserted = dragInto(
+ new Rect(0, 0, 105, 80), new Point(30, -10), null, 2, -1,
+ // Bounds rectangle
+ "useStyle(DROP_RECIPIENT), drawRect(Rect[0,0,240,480])",
+
+ // Preview line + drop zone rectangle along the top
+ "useStyle(DROP_ZONE), drawRect(Rect[0,-10,240,20])",
+ "useStyle(DROP_ZONE_ACTIVE), fillRect(Rect[0,-10,240,20])",
+ "useStyle(DROP_PREVIEW), drawLine(0,0,240,0)",
+
+ // Tip
+ "useStyle(HELP), drawBoxedStrings(5,15,[alignParentTop])",
+
+ // Drop preview
+ "useStyle(DROP_PREVIEW), drawRect(Rect[0,0,105,80])");
+
+ assertEquals("true", inserted.getStringAttr(ANDROID_URI,
+ "layout_alignParentTop"));
+ }
+
+ public void testDropZones() {
+ List<Pair<Point,String[]>> zones = new ArrayList<Pair<Point,String[]>>();
+
+ zones.add(Pair.of(new Point(51+10, 181+10),
+ new String[] {"above=@+id/Centered", "toLeftOf=@+id/Centered"}));
+ zones.add(Pair.of(new Point(71+10, 181+10),
+ new String[] {"above=@+id/Centered", "alignLeft=@+id/Centered"}));
+ zones.add(Pair.of(new Point(104+10, 181+10),
+ new String[] {"above=@+id/Centered", "alignRight=@+id/Centered"}));
+ zones.add(Pair.of(new Point(137+10, 181+10),
+ new String[] {"above=@+id/Centered", "alignRight=@+id/Centered"}));
+ zones.add(Pair.of(new Point(170+10, 181+10),
+ new String[] {"above=@+id/Centered", "toRightOf=@+id/Centered"}));
+ zones.add(Pair.of(new Point(51+10, 279+10),
+ new String[] {"below=@+id/Centered", "toLeftOf=@+id/Centered"}));
+ zones.add(Pair.of(new Point(71+10, 279+10),
+ new String[] {"below=@+id/Centered", "alignLeft=@+id/Centered"}));
+ zones.add(Pair.of(new Point(104+10, 279+10),
+ new String[] {"below=@+id/Centered", "alignLeft=@+id/Centered"}));
+ zones.add(Pair.of(new Point(137+10, 279+10),
+ new String[] {"below=@+id/Centered", "alignRight=@+id/Centered"}));
+ zones.add(Pair.of(new Point(170+10, 279+10),
+ new String[] {"below=@+id/Centered", "toRightOf=@+id/Centered"}));
+ zones.add(Pair.of(new Point(51+10, 201+10),
+ new String[] {"toLeftOf=@+id/Centered", "alignTop=@+id/Centered"}));
+ zones.add(Pair.of(new Point(51+10, 227+10),
+ new String[] {"toLeftOf=@+id/Centered", "alignTop=@+id/Centered"}));
+ zones.add(Pair.of(new Point(170+10, 201+10),
+ new String[] {"toRightOf=@+id/Centered", "alignTop=@+id/Centered"}));
+ zones.add(Pair.of(new Point(51+10, 253+10),
+ new String[] {"toLeftOf=@+id/Centered", "alignBottom=@+id/Centered"}));
+ zones.add(Pair.of(new Point(170+10, 227+10),
+ new String[] {"toRightOf=@+id/Centered", "alignTop=@+id/Centered",
+ "alignBottom=@+id/Centered"}));
+ zones.add(Pair.of(new Point(170+10, 253+10),
+ new String[] {"toRightOf=@+id/Centered", "alignBottom=@+id/Centered"}));
+
+ for (Pair<Point,String[]> zonePair : zones) {
+ Point dropPoint = zonePair.getFirst();
+ String[] attachments = zonePair.getSecond();
+ // If we drag right into the button itself, not a valid drop position
+
+ INode inserted = dragInto(
+ new Rect(0, 0, 105, 80), new Point(120, 240), dropPoint, 1, -1,
+ attachments,
+
+ // Bounds rectangle
+ "useStyle(DROP_RECIPIENT), drawRect(Rect[0,0,240,480])",
+
+ // Drop zones
+ "useStyle(DROP_ZONE), "
+ + "drawRect(Rect[51,181,20,20]), drawRect(Rect[71,181,33,20]), "
+ + "drawRect(Rect[104,181,33,20]), drawRect(Rect[137,181,33,20]), "
+ + "drawRect(Rect[170,181,20,20]), drawRect(Rect[51,279,20,20]), "
+ + "drawRect(Rect[71,279,33,20]), drawRect(Rect[104,279,33,20]), "
+ + "drawRect(Rect[137,279,33,20]), drawRect(Rect[170,279,20,20]), "
+ + "drawRect(Rect[51,201,20,26]), drawRect(Rect[51,227,20,26]), "
+ + "drawRect(Rect[51,253,20,26]), drawRect(Rect[170,201,20,26]), "
+ + "drawRect(Rect[170,227,20,26]), drawRect(Rect[170,253,20,26])");
+
+ for (String attachment : attachments) {
+ String[] elements = attachment.split("=");
+ String name = "layout_" + elements[0];
+ String value = elements[1];
+ assertEquals(value, inserted.getStringAttr(ANDROID_URI, name));
+ }
+ }
+ }
+
+
+ public void testDragInvalid() {
+ // If we drag right into the button itself, not a valid drop position
+ dragInto(new Rect(70, 200, 100, 80), new Point(120, 240), new Point(120, 240), -1, 0,
+ // Bounds rectangle
+ "useStyle(DROP_RECIPIENT), drawRect(Rect[0,0,240,480])",
+
+ // Invalid marker
+ "useStyle(INVALID), fillRect(Rect[70,200,100,80]), drawLine(70,200,170,280), "
+ + "drawLine(70,280,170,200)");
+ }
+
+ // TODO: Test error (dragging on ancestor)
+ */
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestAttribute.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestAttribute.java
new file mode 100644
index 000000000..752d61c77
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestAttribute.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.common.layout;
+
+import com.android.annotations.NonNull;
+import com.android.ide.common.api.IDragElement.IDragAttribute;
+import com.android.ide.common.api.INode.IAttribute;
+import com.android.ide.common.xml.XmlAttributeSortOrder;
+
+/** Test/mock implementation of {@link IAttribute} and {@link IDragAttribute} */
+public class TestAttribute implements IAttribute, IDragAttribute {
+ private String mUri;
+
+ private String mName;
+
+ private String mValue;
+
+ public TestAttribute(String mUri, String mName, String mValue) {
+ super();
+ this.mName = mName;
+ this.mUri = mUri;
+ this.mValue = mValue;
+ }
+
+ @Override
+ public @NonNull String getName() {
+ return mName;
+ }
+
+ @Override
+ public @NonNull String getUri() {
+ return mUri;
+ }
+
+ @Override
+ public @NonNull String getValue() {
+ return mValue;
+ }
+
+ @Override
+ public String toString() {
+ return "TestAttribute [name=" + mName + ", uri=" + mUri + ", value=" + mValue + "]";
+ }
+
+ public int compareTo(IDragAttribute o) {
+ return XmlAttributeSortOrder.compareAttributes(mName, o.getName());
+ }
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestAttributeInfo.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestAttributeInfo.java
new file mode 100644
index 000000000..69984bdbf
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestAttributeInfo.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.common.layout;
+
+import com.android.annotations.NonNull;
+import com.android.ide.common.api.IAttributeInfo;
+
+import java.util.EnumSet;
+
+/** Test/mock implementation of {@link IAttributeInfo} */
+public class TestAttributeInfo implements IAttributeInfo {
+ private final String mName;
+ private final EnumSet<Format> mFormats;
+ private final String mDefinedBy;
+ private final String[] mEnumValues;
+ private final String[] mFlagValues;
+ private final String mJavadoc;
+
+ public TestAttributeInfo(String name) {
+ this(name, null, null, null, null, null);
+ }
+
+ public TestAttributeInfo(String name, EnumSet<Format> formats, String definedBy,
+ String[] enumValues, String[] flagValues, String javadoc) {
+ super();
+ this.mName = name;
+ this.mFormats = formats;
+ this.mDefinedBy = definedBy;
+ this.mEnumValues = enumValues;
+ this.mFlagValues = flagValues;
+ this.mJavadoc = javadoc;
+ }
+
+ @Override
+ public String getDeprecatedDoc() {
+ return null;
+ }
+
+ @Override
+ public String[] getEnumValues() {
+ return mEnumValues;
+ }
+
+ @Override
+ public String[] getFlagValues() {
+ return mFlagValues;
+ }
+
+ @Override
+ public @NonNull EnumSet<Format> getFormats() {
+ return mFormats;
+ }
+
+ @Override
+ public @NonNull String getJavaDoc() {
+ return mJavadoc;
+ }
+
+ @Override
+ public @NonNull String getName() {
+ return mName;
+ }
+
+ @Override
+ public @NonNull String getDefinedBy() {
+ return mDefinedBy;
+ }
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestColor.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestColor.java
new file mode 100644
index 000000000..449ad5ebb
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestColor.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.layout;
+
+import com.android.ide.common.api.IColor;
+
+public class TestColor implements IColor {
+ private int mRgb;
+
+ public TestColor(int rgb) {
+ this.mRgb = rgb;
+ }
+
+ public int getRgb() {
+ return mRgb;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("#%6x", mRgb);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestDragElement.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestDragElement.java
new file mode 100644
index 000000000..b96de60c7
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestDragElement.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.common.layout;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_ID;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.api.IDragElement;
+import com.android.ide.common.api.INode;
+import com.android.ide.common.api.Rect;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** Test/mock implementation of {@link IDragElement} */
+public class TestDragElement implements IDragElement {
+ private Rect mRect;
+
+ private final String mFqcn;
+
+ private Map<String, TestAttribute> mAttributes = new HashMap<String, TestAttribute>();
+
+ private List<TestDragElement> mChildren = new ArrayList<TestDragElement>();
+
+ private TestDragElement mParent;
+
+ public TestDragElement(String mFqcn, Rect mRect, List<TestDragElement> mChildren,
+ TestDragElement mParent) {
+ super();
+ this.mRect = mRect;
+ this.mFqcn = mFqcn;
+ this.mChildren = mChildren;
+ this.mParent = mParent;
+ }
+
+ public TestDragElement(String fqn) {
+ this(fqn, null, null, null);
+ }
+
+ public TestDragElement setBounds(Rect bounds) {
+ this.mRect = bounds;
+
+ return this;
+ }
+
+ // Builder stuff
+ public TestDragElement set(String uri, String name, String value) {
+ if (mAttributes == null) {
+ mAttributes = new HashMap<String, TestAttribute>();
+ }
+
+ mAttributes.put(uri + name, new TestAttribute(uri, name, value));
+
+ return this;
+ }
+
+ public TestDragElement add(TestDragElement... children) {
+ if (mChildren == null) {
+ mChildren = new ArrayList<TestDragElement>();
+ }
+
+ for (TestDragElement child : children) {
+ mChildren.add(child);
+ child.mParent = this;
+ }
+
+ return this;
+ }
+
+ public TestDragElement id(String id) {
+ return set(ANDROID_URI, ATTR_ID, id);
+ }
+
+ public static TestDragElement create(String fqn, Rect bounds) {
+ return create(fqn).setBounds(bounds);
+ }
+
+ public static TestDragElement create(String fqn) {
+ return new TestDragElement(fqn);
+ }
+
+ public static IDragElement[] create(TestDragElement... elements) {
+ return elements;
+ }
+
+ // ==== IDragElement ====
+
+ @Override
+ public IDragAttribute getAttribute(@Nullable String uri, @NonNull String localName) {
+ if (mAttributes == null) {
+ return new TestAttribute(uri, localName, "");
+ }
+
+ return mAttributes.get(uri + localName);
+ }
+
+ @Override
+ public @NonNull IDragAttribute[] getAttributes() {
+ return mAttributes.values().toArray(new IDragAttribute[mAttributes.size()]);
+ }
+
+ @Override
+ public @NonNull Rect getBounds() {
+ return mRect;
+ }
+
+ @Override
+ public @NonNull String getFqcn() {
+ return mFqcn;
+ }
+
+ @Override
+ public @NonNull IDragElement[] getInnerElements() {
+ if (mChildren == null) {
+ return new IDragElement[0];
+ }
+
+ return mChildren.toArray(new IDragElement[mChildren.size()]);
+ }
+
+ @Override
+ public @NonNull Rect getParentBounds() {
+ return mParent != null ? mParent.getBounds() : null;
+ }
+
+ @Override
+ public String getParentFqcn() {
+ return mParent != null ? mParent.getFqcn() : null;
+ }
+
+ @Override
+ public String toString() {
+ return "TestDragElement [fqn=" + mFqcn + ", attributes=" + mAttributes + ", bounds="
+ + mRect + "]";
+ }
+
+ @Override
+ public boolean isSame(INode node) {
+ return node.getBounds().equals(getBounds());
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestGraphics.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestGraphics.java
new file mode 100644
index 000000000..ec9321003
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestGraphics.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.layout;
+
+import com.android.annotations.NonNull;
+import com.android.ide.common.api.DrawingStyle;
+import com.android.ide.common.api.IColor;
+import com.android.ide.common.api.IGraphics;
+import com.android.ide.common.api.Point;
+import com.android.ide.common.api.Rect;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+// TODO: Create box of ascii art
+
+public class TestGraphics implements IGraphics {
+ /** List of things we have drawn */
+ private List<String> mDrawn = new ArrayList<String>();
+
+ private IColor mBackground = new TestColor(0x000000);
+
+ private IColor mForeground = new TestColor(0xFFFFFF);
+
+ private int mAlpha = 128;
+
+ /** Return log of graphics calls */
+ public List<String> getDrawn() {
+ return Collections.unmodifiableList(mDrawn);
+ }
+
+ /** Wipe out log of graphics calls */
+ public void clear() {
+ mDrawn.clear();
+ }
+
+ // ==== IGraphics ====
+
+ @Override
+ public void drawBoxedStrings(int x, int y, @NonNull List<?> strings) {
+ mDrawn.add("drawBoxedStrings(" + x + "," + y + "," + strings + ")");
+ }
+
+ @Override
+ public void drawLine(int x1, int y1, int x2, int y2) {
+ mDrawn.add("drawLine(" + x1 + "," + y1 + "," + x2 + "," + y2 + ")");
+ }
+
+ @Override
+ public void drawLine(@NonNull Point p1, @NonNull Point p2) {
+ mDrawn.add("drawLine(" + p1 + "," + p2 + ")");
+ }
+
+ @Override
+ public void drawRect(int x1, int y1, int x2, int y2) {
+ mDrawn.add("drawRect(" + x1 + "," + y1 + "," + x2 + "," + y2 + ")");
+ }
+
+ @Override
+ public void drawRect(@NonNull Point p1, @NonNull Point p2) {
+ mDrawn.add("drawRect(" + p1 + "," + p2 + ")");
+ }
+
+ @Override
+ public void drawRect(@NonNull Rect r) {
+ mDrawn.add("drawRect(" + rectToString(r) + ")");
+ }
+
+ @Override
+ public void drawString(@NonNull String string, int x, int y) {
+ mDrawn.add("drawString(" + x + "," + y + "," + string + ")");
+ }
+
+ @Override
+ public void drawString(@NonNull String string, @NonNull Point topLeft) {
+ mDrawn.add("drawString(" + string + "," + topLeft + ")");
+ }
+
+ @Override
+ public void fillRect(int x1, int y1, int x2, int y2) {
+ mDrawn.add("fillRect(" + x1 + "," + y1 + "," + x2 + "," + y2 + ")");
+ }
+
+ @Override
+ public void fillRect(@NonNull Point p1, @NonNull Point p2) {
+ mDrawn.add("fillRect(" + p1 + "," + p2 + ")");
+ }
+
+ @Override
+ public void fillRect(@NonNull Rect r) {
+ mDrawn.add("fillRect(" + rectToString(r) + ")");
+ }
+
+ @Override
+ public int getAlpha() {
+ return mAlpha;
+ }
+
+ @Override
+ public @NonNull IColor getBackground() {
+ return mBackground;
+ }
+
+ @Override
+ public int getFontHeight() {
+ return 12;
+ }
+
+ @Override
+ public @NonNull IColor getForeground() {
+ return mForeground;
+ }
+
+ @Override
+ public @NonNull IColor registerColor(int rgb) {
+ mDrawn.add("registerColor(" + Integer.toHexString(rgb) + ")");
+ return new TestColor(rgb);
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ mAlpha = alpha;
+ mDrawn.add("setAlpha(" + alpha + ")");
+ }
+
+ @Override
+ public void setBackground(@NonNull IColor color) {
+ mDrawn.add("setBackground(" + color + ")");
+ mBackground = color;
+ }
+
+ @Override
+ public void setForeground(@NonNull IColor color) {
+ mDrawn.add("setForeground(" + color + ")");
+ mForeground = color;
+ }
+
+ @Override
+ public void setLineStyle(@NonNull LineStyle style) {
+ mDrawn.add("setLineStyle(" + style + ")");
+ }
+
+ @Override
+ public void setLineWidth(int width) {
+ mDrawn.add("setLineWidth(" + width + ")");
+ }
+
+ @Override
+ public void useStyle(@NonNull DrawingStyle style) {
+ mDrawn.add("useStyle(" + style + ")");
+ }
+
+ @Override
+ public void drawArrow(int x1, int y1, int x2, int y2, int size) {
+ mDrawn.add("drawArrow(" + x1 + "," + y1 + "," + x2 + "," + y2 + ")");
+ }
+
+ @Override
+ public void drawPoint(int x, int y) {
+ mDrawn.add("drawPoint(" + x + "," + y + ")");
+ }
+
+ private static String rectToString(Rect rect) {
+ return "Rect[" + rect.x + "," + rect.y + "," + rect.w + "," + rect.h + "]";
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestNode.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestNode.java
new file mode 100644
index 000000000..30886e4fa
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestNode.java
@@ -0,0 +1,477 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.common.layout;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ANDROID_WIDGET_PREFIX;
+import static com.android.SdkConstants.ATTR_ID;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.api.IAttributeInfo;
+import com.android.ide.common.api.INode;
+import com.android.ide.common.api.INodeHandler;
+import com.android.ide.common.api.Margins;
+import com.android.ide.common.api.Rect;
+import com.android.ide.common.xml.XmlFormatStyle;
+import com.android.ide.common.xml.XmlPrettyPrinter;
+import com.android.ide.eclipse.adt.internal.editors.formatting.EclipseXmlFormatPreferences;
+import com.android.ide.eclipse.adt.internal.editors.formatting.EclipseXmlPrettyPrinter;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
+import com.google.common.base.Splitter;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/** Test/mock implementation of {@link INode} */
+@SuppressWarnings("javadoc")
+public class TestNode implements INode {
+ private TestNode mParent;
+
+ private final List<TestNode> mChildren = new ArrayList<TestNode>();
+
+ private final String mFqcn;
+
+ private Rect mBounds = new Rect(); // Invalid bounds initially
+
+ private Map<String, IAttribute> mAttributes = new HashMap<String, IAttribute>();
+
+ private Map<String, IAttributeInfo> mAttributeInfos = new HashMap<String, IAttributeInfo>();
+
+ private List<String> mAttributeSources;
+
+ public TestNode(String fqcn) {
+ this.mFqcn = fqcn;
+ }
+
+ public TestNode bounds(Rect bounds) {
+ this.mBounds = bounds;
+
+ return this;
+ }
+
+ public TestNode id(String id) {
+ return set(ANDROID_URI, ATTR_ID, id);
+ }
+
+ public TestNode set(String uri, String name, String value) {
+ setAttribute(uri, name, value);
+
+ return this;
+ }
+
+ public TestNode add(TestNode child) {
+ mChildren.add(child);
+ child.mParent = this;
+
+ return this;
+ }
+
+ public TestNode add(TestNode... children) {
+ for (TestNode child : children) {
+ mChildren.add(child);
+ child.mParent = this;
+ }
+
+ return this;
+ }
+
+ public static TestNode create(String fcqn) {
+ return new TestNode(fcqn);
+ }
+
+ public void removeChild(int index) {
+ TestNode removed = mChildren.remove(index);
+ removed.mParent = null;
+ }
+
+ // ==== INODE ====
+
+ @Override
+ public @NonNull INode appendChild(@NonNull String viewFqcn) {
+ return insertChildAt(viewFqcn, mChildren.size());
+ }
+
+ @Override
+ public void editXml(@NonNull String undoName, @NonNull INodeHandler callback) {
+ callback.handle(this);
+ }
+
+ public void putAttributeInfo(String uri, String attrName, IAttributeInfo info) {
+ mAttributeInfos.put(uri + attrName, info);
+ }
+
+ @Override
+ public IAttributeInfo getAttributeInfo(@Nullable String uri, @NonNull String attrName) {
+ return mAttributeInfos.get(uri + attrName);
+ }
+
+ @Override
+ public @NonNull Rect getBounds() {
+ return mBounds;
+ }
+
+ @Override
+ public @NonNull INode[] getChildren() {
+ return mChildren.toArray(new INode[mChildren.size()]);
+ }
+
+ @Override
+ public @NonNull IAttributeInfo[] getDeclaredAttributes() {
+ return mAttributeInfos.values().toArray(new IAttributeInfo[mAttributeInfos.size()]);
+ }
+
+ @Override
+ public @NonNull String getFqcn() {
+ return mFqcn;
+ }
+
+ @Override
+ public @NonNull IAttribute[] getLiveAttributes() {
+ return mAttributes.values().toArray(new IAttribute[mAttributes.size()]);
+ }
+
+ @Override
+ public INode getParent() {
+ return mParent;
+ }
+
+ @Override
+ public INode getRoot() {
+ TestNode curr = this;
+ while (curr.mParent != null) {
+ curr = curr.mParent;
+ }
+
+ return curr;
+ }
+
+ @Override
+ public String getStringAttr(@Nullable String uri, @NonNull String attrName) {
+ IAttribute attr = mAttributes.get(uri + attrName);
+ if (attr == null) {
+ return null;
+ }
+
+ return attr.getValue();
+ }
+
+ @Override
+ public @NonNull INode insertChildAt(@NonNull String viewFqcn, int index) {
+ TestNode child = new TestNode(viewFqcn);
+ if (index == -1) {
+ mChildren.add(child);
+ } else {
+ mChildren.add(index, child);
+ }
+ child.mParent = this;
+ return child;
+ }
+
+ @Override
+ public void removeChild(@NonNull INode node) {
+ int index = mChildren.indexOf(node);
+ if (index != -1) {
+ removeChild(index);
+ }
+ }
+
+ @Override
+ public boolean setAttribute(@Nullable String uri, @NonNull String localName,
+ @Nullable String value) {
+ mAttributes.put(uri + localName, new TestAttribute(uri, localName, value));
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ String id = getStringAttr(ANDROID_URI, ATTR_ID);
+ return "TestNode [id=" + (id != null ? id : "?") + ", fqn=" + mFqcn + ", infos="
+ + mAttributeInfos + ", attributes=" + mAttributes + ", bounds=" + mBounds + "]";
+ }
+
+ @Override
+ public int getBaseline() {
+ return -1;
+ }
+
+ @Override
+ public @NonNull Margins getMargins() {
+ return null;
+ }
+
+ @Override
+ public @NonNull List<String> getAttributeSources() {
+ return mAttributeSources != null ? mAttributeSources : Collections.<String>emptyList();
+ }
+
+ public void setAttributeSources(List<String> attributeSources) {
+ mAttributeSources = attributeSources;
+ }
+
+ /** Create a test node from the given XML */
+ public static TestNode createFromXml(String xml) {
+ Document document = DomUtilities.parseDocument(xml, false);
+ assertNotNull(document);
+ assertNotNull(document.getDocumentElement());
+
+ return createFromNode(document.getDocumentElement());
+ }
+
+ public static String toXml(TestNode node) {
+ assertTrue("This method only works with nodes constructed from XML",
+ node instanceof TestXmlNode);
+ Document document = ((TestXmlNode) node).mElement.getOwnerDocument();
+ // Insert new whitespace nodes etc
+ String xml = dumpDocument(document);
+ document = DomUtilities.parseDocument(xml, false);
+
+ XmlPrettyPrinter printer = new EclipseXmlPrettyPrinter(EclipseXmlFormatPreferences.create(),
+ XmlFormatStyle.LAYOUT, "\n");
+ StringBuilder sb = new StringBuilder(1000);
+ sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
+ printer.prettyPrint(-1, document, null, null, sb, false);
+ return sb.toString();
+ }
+
+ @SuppressWarnings("deprecation")
+ private static String dumpDocument(Document document) {
+ // Diagnostics: print out the XML that we're about to render
+ org.apache.xml.serialize.OutputFormat outputFormat =
+ new org.apache.xml.serialize.OutputFormat(
+ "XML", "ISO-8859-1", true); //$NON-NLS-1$ //$NON-NLS-2$
+ outputFormat.setIndent(2);
+ outputFormat.setLineWidth(100);
+ outputFormat.setIndenting(true);
+ outputFormat.setOmitXMLDeclaration(true);
+ outputFormat.setOmitDocumentType(true);
+ StringWriter stringWriter = new StringWriter();
+ // Using FQN here to avoid having an import above, which will result
+ // in a deprecation warning, and there isn't a way to annotate a single
+ // import element with a SuppressWarnings.
+ org.apache.xml.serialize.XMLSerializer serializer =
+ new org.apache.xml.serialize.XMLSerializer(stringWriter, outputFormat);
+ serializer.setNamespaces(true);
+ try {
+ serializer.serialize(document.getDocumentElement());
+ return stringWriter.toString();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ private static TestNode createFromNode(Element element) {
+ String fqcn = ANDROID_WIDGET_PREFIX + element.getTagName();
+ TestNode node = new TestXmlNode(fqcn, element);
+
+ for (Element child : DomUtilities.getChildren(element)) {
+ node.add(createFromNode(child));
+ }
+
+ return node;
+ }
+
+ @Nullable
+ public static TestNode findById(TestNode node, String id) {
+ id = BaseLayoutRule.stripIdPrefix(id);
+ return node.findById(id);
+ }
+
+ private TestNode findById(String targetId) {
+ String id = getStringAttr(ANDROID_URI, ATTR_ID);
+ if (id != null && targetId.equals(BaseLayoutRule.stripIdPrefix(id))) {
+ return this;
+ }
+
+ for (TestNode child : mChildren) {
+ TestNode result = child.findById(targetId);
+ if (result != null) {
+ return result;
+ }
+ }
+
+ return null;
+ }
+
+ private static String getTagName(String fqcn) {
+ return fqcn.substring(fqcn.lastIndexOf('.') + 1);
+ }
+
+ private static class TestXmlNode extends TestNode {
+ private final Element mElement;
+
+ public TestXmlNode(String fqcn, Element element) {
+ super(fqcn);
+ mElement = element;
+ }
+
+ @Override
+ public @NonNull IAttribute[] getLiveAttributes() {
+ List<IAttribute> result = new ArrayList<IAttribute>();
+
+ NamedNodeMap attributes = mElement.getAttributes();
+ for (int i = 0, n = attributes.getLength(); i < n; i++) {
+ Attr attribute = (Attr) attributes.item(i);
+ result.add(new TestXmlAttribute(attribute));
+ }
+ return result.toArray(new IAttribute[result.size()]);
+ }
+
+ @Override
+ public boolean setAttribute(String uri, String localName, String value) {
+ if (value == null) {
+ mElement.removeAttributeNS(uri, localName);
+ } else {
+ mElement.setAttributeNS(uri, localName, value);
+ }
+ return super.setAttribute(uri, localName, value);
+ }
+
+ @Override
+ public INode appendChild(String viewFqcn) {
+ Element child = mElement.getOwnerDocument().createElement(getTagName(viewFqcn));
+ mElement.appendChild(child);
+ return new TestXmlNode(viewFqcn, child);
+ }
+
+ @Override
+ public INode insertChildAt(String viewFqcn, int index) {
+ if (index == -1) {
+ return appendChild(viewFqcn);
+ }
+ Element child = mElement.getOwnerDocument().createElement(getTagName(viewFqcn));
+ List<Element> children = DomUtilities.getChildren(mElement);
+ if (children.size() >= index) {
+ Element before = children.get(index);
+ mElement.insertBefore(child, before);
+ } else {
+ fail("Unexpected index");
+ mElement.appendChild(child);
+ }
+ return new TestXmlNode(viewFqcn, child);
+ }
+
+ @Override
+ public String getStringAttr(String uri, String name) {
+ String value;
+ if (uri == null) {
+ value = mElement.getAttribute(name);
+ } else {
+ value = mElement.getAttributeNS(uri, name);
+ }
+ if (value.isEmpty()) {
+ value = null;
+ }
+
+ return value;
+ }
+
+ @Override
+ public void removeChild(INode node) {
+ assert node instanceof TestXmlNode;
+ mElement.removeChild(((TestXmlNode) node).mElement);
+ }
+
+ @Override
+ public void removeChild(int index) {
+ List<Element> children = DomUtilities.getChildren(mElement);
+ assertTrue(index < children.size());
+ Element oldChild = children.get(index);
+ mElement.removeChild(oldChild);
+ }
+ }
+
+ public static class TestXmlAttribute implements IAttribute {
+ private Attr mAttribute;
+
+ public TestXmlAttribute(Attr attribute) {
+ this.mAttribute = attribute;
+ }
+
+ @Override
+ public String getUri() {
+ return mAttribute.getNamespaceURI();
+ }
+
+ @Override
+ public String getName() {
+ String name = mAttribute.getLocalName();
+ if (name == null) {
+ name = mAttribute.getName();
+ }
+ return name;
+ }
+
+ @Override
+ public String getValue() {
+ return mAttribute.getValue();
+ }
+ }
+
+ // Recursively initialize this node with the bounds specified in the given hierarchy
+ // dump (from ViewHierarchy's DUMP_INFO flag
+ public void assignBounds(String bounds) {
+ Iterable<String> split = Splitter.on('\n').trimResults().split(bounds);
+ assignBounds(split.iterator());
+ }
+
+ private void assignBounds(Iterator<String> iterator) {
+ assertTrue(iterator.hasNext());
+ String desc = iterator.next();
+
+ Pattern pattern = Pattern.compile("^\\s*(.+)\\s+\\[(.+)\\]\\s*(<.+>)?\\s*(\\S+)?\\s*$");
+ Matcher matcher = pattern.matcher(desc);
+ assertTrue(matcher.matches());
+ String fqn = matcher.group(1);
+ assertEquals(getFqcn(), fqn);
+ String boundsString = matcher.group(2);
+ String[] bounds = boundsString.split(",");
+ assertEquals(boundsString, 4, bounds.length);
+ try {
+ int left = Integer.parseInt(bounds[0]);
+ int top = Integer.parseInt(bounds[1]);
+ int right = Integer.parseInt(bounds[2]);
+ int bottom = Integer.parseInt(bounds[3]);
+ mBounds = new Rect(left, top, right - left, bottom - top);
+ } catch (NumberFormatException nufe) {
+ fail(nufe.getLocalizedMessage());
+ }
+ String tag = matcher.group(3);
+
+ for (INode child : getChildren()) {
+ assertTrue(iterator.hasNext());
+ ((TestNode) child).assignBounds(iterator);
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/ZoomControlsRuleTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/ZoomControlsRuleTest.java
new file mode 100644
index 000000000..daa487ed4
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/ZoomControlsRuleTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.layout;
+
+import com.android.ide.common.api.DropFeedback;
+import com.android.ide.common.api.IDragElement;
+import com.android.ide.common.api.INode;
+import com.android.ide.common.api.Rect;
+
+/** Test the {@link ZoomControlsRule} */
+public class ZoomControlsRuleTest extends LayoutTestBase {
+ public void testDoNothing() {
+ String draggedButtonId = "@+id/DraggedButton";
+
+ IDragElement[] elements = TestDragElement.create(TestDragElement.create(
+ "android.widget.Button").id(draggedButtonId));
+
+ INode layout = TestNode.create("android.widget.ZoomControls").id("@+id/ZoomControls01")
+ .bounds(new Rect(0, 0, 240, 480)).add(
+ TestNode.create("android.widget.Button").id("@+id/Button01").bounds(
+ new Rect(0, 0, 100, 80)),
+ TestNode.create("android.widget.Button").id("@+id/Button02").bounds(
+ new Rect(0, 100, 100, 80)),
+ TestNode.create("android.widget.Button").id("@+id/Button03").bounds(
+ new Rect(0, 200, 100, 80)),
+ TestNode.create("android.widget.Button").id("@+id/Button04").bounds(
+ new Rect(0, 300, 100, 80)));
+
+ ZoomControlsRule rule = new ZoomControlsRule();
+
+ // Enter target
+ DropFeedback feedback = rule.onDropEnter(layout, null/*targetView*/, elements);
+ // Zoom controls don't respond to drags
+ assertNull(feedback);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/grid/GridModelTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/grid/GridModelTest.java
new file mode 100644
index 000000000..c4a58c6e4
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/grid/GridModelTest.java
@@ -0,0 +1,852 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.common.layout.grid;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_COLUMN_COUNT;
+import static com.android.SdkConstants.ATTR_LAYOUT_COLUMN;
+import static com.android.SdkConstants.ATTR_LAYOUT_COLUMN_SPAN;
+import static com.android.SdkConstants.ATTR_LAYOUT_ROW;
+import static com.android.SdkConstants.FQCN_BUTTON;
+
+import com.android.ide.common.api.INode;
+import com.android.ide.common.api.Rect;
+import com.android.ide.common.layout.LayoutTestBase;
+import com.android.ide.common.layout.TestNode;
+import com.android.ide.common.layout.grid.GridModel.ViewData;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+
+@SuppressWarnings("javadoc")
+public class GridModelTest extends LayoutTestBase {
+ public void testRemoveFlag() {
+ assertEquals("left", GridModel.removeFlag("top", "top|left"));
+ assertEquals("left", GridModel.removeFlag("top", "top | left"));
+ assertEquals("top", GridModel.removeFlag("left", "top|left"));
+ assertEquals("top", GridModel.removeFlag("left", "top | left"));
+ assertEquals("left | center", GridModel.removeFlag("top", "top | left | center"));
+ assertEquals(null, GridModel.removeFlag("top", "top"));
+ }
+
+ public void testReadModel1() {
+ TestNode targetNode = TestNode.create("android.widget.GridLayout").id("@+id/GridLayout1")
+ .bounds(new Rect(0, 0, 240, 480)).set(ANDROID_URI, ATTR_COLUMN_COUNT, "3");
+
+ GridModel model = GridModel.get(null, targetNode, null);
+ assertEquals(3, model.declaredColumnCount);
+ assertEquals(1, model.actualColumnCount);
+ assertEquals(1, model.actualRowCount);
+
+ targetNode.add(TestNode.create(FQCN_BUTTON).id("@+id/Button1"));
+ targetNode.add(TestNode.create(FQCN_BUTTON).id("@+id/Button2"));
+ targetNode.add(TestNode.create(FQCN_BUTTON).id("@+id/Button3"));
+ targetNode.add(TestNode.create(FQCN_BUTTON).id("@+id/Button4"));
+
+ model = GridModel.get(null, targetNode, null);
+ assertEquals(3, model.declaredColumnCount);
+ assertEquals(3, model.actualColumnCount);
+ assertEquals(2, model.actualRowCount);
+ }
+
+ public void testSplitColumn() {
+ TestNode targetNode = TestNode.create("android.widget.GridLayout").id("@+id/GridLayout1")
+ .bounds(new Rect(0, 0, 240, 480)).set(ANDROID_URI, ATTR_COLUMN_COUNT, "3");
+ TestNode b1 = TestNode.create(FQCN_BUTTON).id("@+id/Button1");
+ TestNode b2 = TestNode.create(FQCN_BUTTON).id("@+id/Button2");
+ TestNode b3 = TestNode.create(FQCN_BUTTON).id("@+id/Button3");
+ TestNode b4 = TestNode.create(FQCN_BUTTON).id("@+id/Button4");
+ targetNode.add(b1);
+ targetNode.add(b2);
+ targetNode.add(b3);
+ targetNode.add(b4);
+ b4.setAttribute(ANDROID_URI, ATTR_LAYOUT_COLUMN_SPAN, "2");
+
+ GridModel model = GridModel.get(new LayoutTestBase.TestRulesEngine(targetNode.getFqcn()),
+ targetNode, null);
+ assertEquals(3, model.declaredColumnCount);
+ assertEquals(3, model.actualColumnCount);
+ assertEquals(2, model.actualRowCount);
+
+ model.applyPositionAttributes();
+ assertEquals("0", b1.getStringAttr(ANDROID_URI, ATTR_LAYOUT_COLUMN));
+ assertEquals("0", b1.getStringAttr(ANDROID_URI, ATTR_LAYOUT_ROW));
+
+ assertEquals("1", b2.getStringAttr(ANDROID_URI, ATTR_LAYOUT_COLUMN));
+ assertEquals("0", b2.getStringAttr(ANDROID_URI, ATTR_LAYOUT_ROW));
+
+ assertEquals("2", b3.getStringAttr(ANDROID_URI, ATTR_LAYOUT_COLUMN));
+ assertEquals("0", b3.getStringAttr(ANDROID_URI, ATTR_LAYOUT_ROW));
+
+ assertEquals("0", b4.getStringAttr(ANDROID_URI, ATTR_LAYOUT_COLUMN));
+ assertEquals("1", b4.getStringAttr(ANDROID_URI, ATTR_LAYOUT_ROW));
+ assertEquals("2", b4.getStringAttr(ANDROID_URI, ATTR_LAYOUT_COLUMN_SPAN));
+
+ model.splitColumn(1, false /*insertMarginColumn*/, 100 /*columnWidthDp*/, 300 /* x */);
+ model.applyPositionAttributes();
+
+ assertEquals(4, model.declaredColumnCount);
+ assertEquals(4, model.actualColumnCount);
+ assertEquals(2, model.actualRowCount);
+
+ assertEquals("0", b1.getStringAttr(ANDROID_URI, ATTR_LAYOUT_COLUMN));
+ assertEquals("0", b1.getStringAttr(ANDROID_URI, ATTR_LAYOUT_ROW));
+
+ assertEquals("1", b2.getStringAttr(ANDROID_URI, ATTR_LAYOUT_COLUMN));
+ assertEquals("2", b2.getStringAttr(ANDROID_URI, ATTR_LAYOUT_COLUMN_SPAN));
+ assertEquals("0", b2.getStringAttr(ANDROID_URI, ATTR_LAYOUT_ROW));
+
+ assertEquals("3", b3.getStringAttr(ANDROID_URI, ATTR_LAYOUT_COLUMN));
+ assertEquals("0", b3.getStringAttr(ANDROID_URI, ATTR_LAYOUT_ROW));
+
+ assertEquals("0", b4.getStringAttr(ANDROID_URI, ATTR_LAYOUT_COLUMN));
+ assertEquals("1", b4.getStringAttr(ANDROID_URI, ATTR_LAYOUT_ROW));
+ assertEquals("3", b4.getStringAttr(ANDROID_URI, ATTR_LAYOUT_COLUMN_SPAN));
+ }
+
+ public void testDeletion1() {
+ String xml =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<GridLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\"\n" +
+ " android:columnCount=\"4\" >\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button1\"\n" +
+ " android:layout_column=\"1\"\n" +
+ " android:layout_gravity=\"left|top\"\n" +
+ " android:layout_row=\"1\"\n" +
+ " android:text=\"Button\" />\n" +
+ "\n" +
+ " <TextView\n" +
+ " android:id=\"@+id/TextView1\"\n" +
+ " android:layout_column=\"3\"\n" +
+ " android:layout_gravity=\"left|top\"\n" +
+ " android:layout_row=\"1\"\n" +
+ " android:text=\"Text\" />\n" +
+ "\n" +
+ " <Space\n" +
+ " android:id=\"@+id/wspace1\"\n" +
+ " android:layout_width=\"21dp\"\n" +
+ " android:layout_height=\"1dp\"\n" +
+ " android:layout_column=\"0\"\n" +
+ " android:layout_row=\"0\" />\n" +
+ "\n" +
+ " <Space\n" +
+ " android:id=\"@+id/hspace1\"\n" +
+ " android:layout_width=\"1dp\"\n" +
+ " android:layout_height=\"55dp\"\n" +
+ " android:layout_column=\"0\"\n" +
+ " android:layout_row=\"0\" />\n" +
+ "\n" +
+ " <Space\n" +
+ " android:id=\"@+id/wspace2\"\n" +
+ " android:layout_width=\"10dp\"\n" +
+ " android:layout_height=\"1dp\"\n" +
+ " android:layout_column=\"2\"\n" +
+ " android:layout_row=\"0\" />\n" +
+ "\n" +
+ "</GridLayout>";
+
+ TestNode targetNode = TestNode.createFromXml(xml);
+ assertNotNull(targetNode);
+
+ TestNode button1 = TestNode.findById(targetNode, "@+id/button1");
+ TestNode textView1 = TestNode.findById(targetNode, "@+id/TextView1");
+ TestNode wspace1 = TestNode.findById(targetNode, "@+id/wspace1");
+ TestNode wspace2 = TestNode.findById(targetNode, "@+id/wspace2");
+ TestNode hspace1 = TestNode.findById(targetNode, "@+id/hspace1");
+ assertNotNull(wspace1);
+ assertNotNull(hspace1);
+ assertNotNull(wspace2);
+ assertNotNull(button1);
+ assertNotNull(textView1);
+
+ // Assign some bounds such that the model makes sense when merging spacer sizes
+ // TODO: MAke test utility method to automatically assign half divisions!!
+ button1.bounds(new Rect(90, 10, 100, 40));
+ textView1.bounds(new Rect(200, 10, 100, 40));
+ wspace1.bounds(new Rect(0, 0, 90, 1));
+ wspace1.bounds(new Rect(190, 0, 10, 1));
+ hspace1.bounds(new Rect(0, 0, 1, 10));
+
+ GridModel model = GridModel.get(new LayoutTestBase.TestRulesEngine(targetNode.getFqcn()),
+ targetNode, null);
+ assertEquals(4, model.declaredColumnCount);
+ assertEquals(4, model.actualColumnCount);
+ assertEquals(2, model.actualRowCount);
+
+ ViewData textViewData = model.getView(textView1);
+ assertEquals(3, textViewData.column);
+
+ // Delete button1
+ button1.getParent().removeChild(button1);
+ model.onDeleted(Arrays.<INode>asList(button1));
+ model.applyPositionAttributes();
+
+ assertEquals(2, model.declaredColumnCount);
+ assertEquals(2, model.actualColumnCount);
+ assertEquals(2, model.actualRowCount);
+ assertNotNull(model.getView(textView1));
+ assertNull(model.getView(button1));
+
+ assertEquals(
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<GridLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\"\n" +
+ " android:columnCount=\"2\">\n" +
+ "\n" +
+ " <TextView\n" +
+ " android:id=\"@+id/TextView1\"\n" +
+ " android:layout_column=\"1\"\n" +
+ " android:layout_gravity=\"left|top\"\n" +
+ " android:layout_row=\"1\"\n" +
+ " android:text=\"Text\">\n" +
+ " </TextView>\n" +
+ "\n" +
+ " <Space\n" +
+ " android:id=\"@+id/wspace1\"\n" +
+ " android:layout_width=\"66dp\"\n" +
+ " android:layout_height=\"1dp\"\n" +
+ " android:layout_column=\"0\"\n" +
+ " android:layout_row=\"0\">\n" +
+ " </Space>\n" +
+ "\n" +
+ " <Space\n" +
+ " android:id=\"@+id/hspace1\"\n" +
+ " android:layout_width=\"1dp\"\n" +
+ " android:layout_height=\"55dp\"\n" +
+ " android:layout_column=\"0\"\n" +
+ " android:layout_row=\"0\">\n" +
+ " </Space>\n" +
+ "\n" +
+ "</GridLayout>", TestNode.toXml(targetNode));
+
+ // Delete textView1
+
+ textView1.getParent().removeChild(textView1);
+ model.onDeleted(Arrays.<INode>asList(textView1));
+ model.applyPositionAttributes();
+
+ assertEquals(2, model.declaredColumnCount);
+ assertEquals(0, model.actualColumnCount);
+ assertEquals(0, model.actualRowCount);
+ assertNull(model.getView(textView1));
+ assertNull(model.getView(button1));
+
+ assertEquals(
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<GridLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\"\n" +
+ " android:columnCount=\"0\">\n" +
+ "\n" +
+ "</GridLayout>", TestNode.toXml(targetNode));
+
+ }
+
+ public void testDelete2() throws Exception {
+ String xml =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<GridLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\"\n" +
+ " android:columnCount=\"4\"\n" +
+ " android:orientation=\"vertical\" >\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button1\"\n" +
+ " android:layout_column=\"0\"\n" +
+ " android:layout_columnSpan=\"2\"\n" +
+ " android:layout_gravity=\"left|top\"\n" +
+ " android:layout_row=\"0\"\n" +
+ " android:text=\"Button1\" />\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button3\"\n" +
+ " android:layout_column=\"1\"\n" +
+ " android:layout_columnSpan=\"2\"\n" +
+ " android:layout_gravity=\"left|top\"\n" +
+ " android:layout_row=\"1\"\n" +
+ " android:text=\"Button2\" />\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button2\"\n" +
+ " android:layout_column=\"2\"\n" +
+ " android:layout_gravity=\"left|top\"\n" +
+ " android:layout_row=\"0\"\n" +
+ " android:text=\"Button3\" />\n" +
+ "\n" +
+ " <Space\n" +
+ " android:id=\"@+id/spacer_177\"\n" +
+ " android:layout_width=\"46dp\"\n" +
+ " android:layout_height=\"1dp\"\n" +
+ " android:layout_column=\"0\"\n" +
+ " android:layout_row=\"0\" />\n" +
+ "\n" +
+ "</GridLayout>";
+
+ TestNode targetNode = TestNode.createFromXml(xml);
+ assertNotNull(targetNode);
+
+ TestNode button1 = TestNode.findById(targetNode, "@+id/button1");
+ TestNode button2 = TestNode.findById(targetNode, "@+id/button2");
+ TestNode button3 = TestNode.findById(targetNode, "@+id/button3");
+ TestNode hspacer = TestNode.findById(targetNode, "@+id/spacer_177");
+ assertNotNull(button1);
+ assertNotNull(button2);
+ assertNotNull(button3);
+ assertNotNull(hspacer);
+
+ // Assign some bounds such that the model makes sense when merging spacer sizes
+ // TODO: MAke test utility method to automatically assign half divisions!!
+ button1.bounds(new Rect(0, 0, 100, 40));
+ button2.bounds(new Rect(100, 0, 100, 40));
+ button3.bounds(new Rect(50, 40, 100, 40));
+ hspacer.bounds(new Rect(0, 0, 50, 1));
+
+ GridModel model = GridModel.get(new LayoutTestBase.TestRulesEngine(targetNode.getFqcn()),
+ targetNode, null);
+ assertEquals(4, model.declaredColumnCount);
+ assertEquals(3, model.actualColumnCount);
+ assertEquals(2, model.actualRowCount);
+
+ ViewData buttonData = model.getView(button1);
+ assertEquals(0, buttonData.column);
+
+ // Delete button1
+ button1.getParent().removeChild(button1);
+ model.onDeleted(Arrays.<INode>asList(button1));
+ model.applyPositionAttributes();
+
+ assertEquals(3, model.declaredColumnCount);
+ assertEquals(3, model.actualColumnCount);
+ assertEquals(2, model.actualRowCount);
+ assertNull(model.getView(button1));
+
+ assertEquals(
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<GridLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\"\n" +
+ " android:columnCount=\"3\"\n" +
+ " android:orientation=\"vertical\">\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button3\"\n" +
+ " android:layout_column=\"1\"\n" +
+ " android:layout_columnSpan=\"2\"\n" +
+ " android:layout_gravity=\"left|top\"\n" +
+ " android:layout_row=\"1\"\n" +
+ " android:text=\"Button2\">\n" +
+ " </Button>\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button2\"\n" +
+ " android:layout_column=\"2\"\n" +
+ " android:layout_gravity=\"left|top\"\n" +
+ " android:layout_row=\"0\"\n" +
+ " android:text=\"Button3\">\n" +
+ " </Button>\n" +
+ "\n" +
+ " <Space\n" +
+ " android:id=\"@+id/spacer_177\"\n" +
+ " android:layout_width=\"46dp\"\n" +
+ " android:layout_height=\"1dp\"\n" +
+ " android:layout_column=\"0\"\n" +
+ " android:layout_row=\"0\">\n" +
+ " </Space>\n" +
+ "\n" +
+ "</GridLayout>", TestNode.toXml(targetNode));
+ }
+
+ public void testDelete3_INCOMPLETE() throws Exception {
+ String xml =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<GridLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " android:layout_width=\"match_parent\" android:layout_height=\"match_parent\"\n" +
+ " android:columnCount=\"6\">\n" +
+ " <Button android:id=\"@+id/button1\" android:layout_column=\"1\"\n" +
+ " android:layout_columnSpan=\"2\" android:layout_gravity=\"left|top\"\n" +
+ " android:layout_row=\"1\" android:layout_rowSpan=\"2\" android:text=\"Button\" />\n" +
+ " <TextView android:id=\"@+id/TextView1\" android:layout_column=\"4\"\n" +
+ " android:layout_gravity=\"left|top\" android:layout_row=\"1\"\n" +
+ " android:text=\"Text\" />\n" +
+ " <Button android:id=\"@+id/button3\" android:layout_column=\"5\"\n" +
+ " android:layout_gravity=\"left|top\" android:layout_row=\"2\"\n" +
+ " android:layout_rowSpan=\"2\" android:text=\"Button\" />\n" +
+ " <Button android:id=\"@+id/button2\" android:layout_column=\"2\"\n" +
+ " android:layout_columnSpan=\"3\" android:layout_gravity=\"left|top\"\n" +
+ " android:layout_row=\"4\" android:text=\"Button\" />\n" +
+ " <Space android:id=\"@+id/wspace1\" android:layout_width=\"21dp\"\n" +
+ " android:layout_height=\"1dp\" android:layout_column=\"0\"\n" +
+ " android:layout_row=\"0\" />\n" +
+ " <Space android:id=\"@+id/spacer_630\" android:layout_width=\"1dp\"\n" +
+ " android:layout_height=\"55dp\" android:layout_column=\"0\"\n" +
+ " android:layout_row=\"0\" />\n" +
+ " <Space android:id=\"@+id/wspace2\" android:layout_width=\"10dp\"\n" +
+ " android:layout_height=\"1dp\" android:layout_column=\"3\"\n" +
+ " android:layout_row=\"0\" />\n" +
+ " <Space android:id=\"@+id/spacer_619\" android:layout_width=\"59dp\"\n" +
+ " android:layout_height=\"1dp\" android:layout_column=\"1\"\n" +
+ " android:layout_row=\"0\" />\n" +
+ " <Space android:id=\"@+id/spacer_102\" android:layout_width=\"1dp\"\n" +
+ " android:layout_height=\"30dp\" android:layout_column=\"0\"\n" +
+ " android:layout_row=\"1\" />\n" +
+ " <Space android:id=\"@+id/spacer_109\" android:layout_width=\"1dp\"\n" +
+ " android:layout_height=\"28dp\" android:layout_column=\"0\"\n" +
+ " android:layout_row=\"2\" />\n" +
+ " <Space android:id=\"@+id/spacer_146\" android:layout_width=\"1dp\"\n" +
+ " android:layout_height=\"70dp\" android:layout_column=\"0\"\n" +
+ " android:layout_row=\"3\" />\n" +
+ "</GridLayout>";
+ TestNode targetNode = TestNode.createFromXml(xml);
+ assertNotNull(targetNode);
+ targetNode.assignBounds(
+ "android.widget.GridLayout [0,109,480,800] <GridLayout>\n" +
+ " android.widget.Button [32,83,148,155] <Button> @+id/button1\n" +
+ " android.widget.TextView [163,83,205,109] <TextView> @+id/TextView1\n" +
+ " android.widget.Button [237,128,353,200] <Button> @+id/button3\n" +
+ " android.widget.Button [121,275,237,347] <Button> @+id/button2\n" +
+ " android.widget.Space [0,0,32,2] <Space> @+id/wspace1\n" +
+ " android.widget.Space [0,0,2,83] <Space> @+id/spacer_630\n" +
+ " android.widget.Space [148,0,163,2] <Space> @+id/wspace2\n" +
+ " android.widget.Space [32,0,121,2] <Space> @+id/spacer_619\n" +
+ " android.widget.Space [0,83,2,128] <Space> @+id/spacer_102\n" +
+ " android.widget.Space [0,128,2,170] <Space> @+id/spacer_109\n" +
+ " android.widget.Space [0,170,2,275] <Space> @+id/spacer_146\n");
+ TestNode layout = TestNode.findById(targetNode, "@+id/GridLayout1");
+ //TestNode button1 = TestNode.findById(targetNode, "@+id/button1");
+ TestNode button2 = TestNode.findById(targetNode, "@+id/button2");
+ TestNode button3 = TestNode.findById(targetNode, "@+id/button3");
+
+ GridModel model = GridModel.get(new LayoutTestBase.TestRulesEngine(targetNode.getFqcn()),
+ targetNode, null);
+ assertEquals(6, model.declaredColumnCount);
+ assertEquals(6, model.actualColumnCount);
+ assertEquals(5, model.actualRowCount);
+
+ // TODO: Delete button2 or button3: bad stuff happens visually
+ fail("Finish test");
+ }
+
+ public void testDelete4_INCOMPLETE() {
+ String xml = "" +
+ "<GridLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " xmlns:tools=\"http://schemas.android.com/tools\" " +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\" android:columnCount=\"3\"\n" +
+ " android:gravity=\"center\" android:text=\"@string/hello_world\"\n" +
+ " tools:context=\".MainActivity\">\n" +
+ " <Button android:id=\"@+id/button2\" android:layout_column=\"1\"\n" +
+ " android:layout_columnSpan=\"2\" android:layout_gravity=\"left|top\"\n" +
+ " android:layout_row=\"1\" android:text=\"Button\" />\n" +
+ " <Button android:id=\"@+id/button1\" android:layout_column=\"1\"\n" +
+ " android:layout_columnSpan=\"2\" android:layout_gravity=\"left|top\"\n" +
+ " android:layout_row=\"3\" android:text=\"Button\" />\n" +
+ " <Space android:id=\"@+id/spacer_167\" android:layout_width=\"74dp\"\n" +
+ " android:layout_height=\"1dp\" android:layout_column=\"0\"\n" +
+ " android:layout_row=\"0\" />\n" +
+ " <Space android:id=\"@+id/spacer_133\" android:layout_width=\"1dp\"\n" +
+ " android:layout_height=\"21dp\" android:layout_column=\"0\"\n" +
+ " android:layout_row=\"0\" />\n" +
+ " <Space android:id=\"@+id/spacer_142\" android:layout_width=\"1dp\"\n" +
+ " android:layout_height=\"26dp\" android:layout_column=\"0\"\n" +
+ " android:layout_row=\"2\" />\n" +
+ " <Space android:id=\"@+id/spacer_673\" android:layout_width=\"43dp\"\n" +
+ " android:layout_height=\"1dp\" android:layout_column=\"1\"\n" +
+ " android:layout_row=\"0\" />\n" +
+ " <Space android:id=\"@+id/spacer_110\" android:layout_width=\"202dp\"\n" +
+ " android:layout_height=\"15dp\" android:layout_column=\"2\" />\n" +
+ "</GridLayout>";
+ TestNode targetNode = TestNode.createFromXml(xml);
+ assertNotNull(targetNode);
+ targetNode.assignBounds(
+ "android.widget.GridLayout [0,109,480,800] <GridLayout>\n" +
+ " android.widget.Button [111,32,227,104] <Button> @+id/button2\n" +
+ " android.widget.Button [111,143,227,215] <Button> @+id/button1\n" +
+ " android.widget.Space [0,0,111,2] <Space> @+id/spacer_167\n" +
+ " android.widget.Space [0,0,2,32] <Space> @+id/spacer_133\n" +
+ " android.widget.Space [0,104,2,143] <Space> @+id/spacer_142\n" +
+ " android.widget.Space [111,0,176,2] <Space> @+id/spacer_673\n" +
+ " android.widget.Space [176,668,479,691] <Space> @+id/spacer_110");
+
+
+ // Remove button2; button1 shifts to the right!
+
+ //TestNode layout = TestNode.findById(targetNode, "@+id/GridLayout1");
+ //TestNode button1 = TestNode.findById(targetNode, "@+id/button1");
+ TestNode button2 = TestNode.findById(targetNode, "@+id/button2");
+ TestNode button3 = TestNode.findById(targetNode, "@+id/button3");
+ assertEquals(new Rect(111, 32, 227 - 111, 104 - 32), button2.getBounds());
+
+ GridModel model = GridModel.get(new LayoutTestBase.TestRulesEngine(targetNode.getFqcn()),
+ targetNode, null);
+ assertEquals(3, model.declaredColumnCount);
+ assertEquals(3, model.actualColumnCount);
+ assertEquals(4, model.actualRowCount);
+ fail("Finish test");
+ }
+
+ public void testDelete5_INCOMPLETE() {
+ String xml =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<GridLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " android:id=\"@+id/GridLayout1\" android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\" android:columnCount=\"4\"\n" +
+ " android:orientation=\"vertical\">\n" +
+ " <Button android:id=\"@+id/button1\" android:layout_column=\"0\"\n" +
+ " android:layout_gravity=\"center_horizontal|bottom\"\n" +
+ " android:layout_row=\"0\" android:text=\"Button\" />\n" +
+ " <Space android:layout_width=\"66dp\" android:layout_height=\"1dp\"\n" +
+ " android:layout_column=\"0\" android:layout_row=\"0\" />\n" +
+ " <Button android:id=\"@+id/button3\" android:layout_column=\"2\"\n" +
+ " android:layout_gravity=\"left|bottom\" android:layout_row=\"0\"\n" +
+ " android:text=\"Button\" />\n" +
+ " <Button android:id=\"@+id/button2\" android:layout_column=\"3\"\n" +
+ " android:layout_columnSpan=\"2\" android:layout_gravity=\"left|bottom\"\n" +
+ " android:layout_row=\"0\" android:text=\"Button\" />\n" +
+ " <Space android:id=\"@+id/spacer_109\" android:layout_width=\"51dp\"\n" +
+ " android:layout_height=\"1dp\" android:layout_column=\"1\"\n" +
+ " android:layout_row=\"0\" />\n" +
+ " <Space android:layout_width=\"129dp\" android:layout_height=\"1dp\"\n" +
+ " android:layout_column=\"2\" android:layout_row=\"0\" />\n" +
+ " <Space android:layout_width=\"62dp\" android:layout_height=\"1dp\"\n" +
+ " android:layout_column=\"3\" android:layout_row=\"0\" />\n" +
+ " <Space android:id=\"@+id/spacer_397\" android:layout_width=\"1dp\"\n" +
+ " android:layout_height=\"103dp\" android:layout_column=\"0\"\n" +
+ " android:layout_row=\"0\" />\n" +
+ " <Space android:layout_width=\"1dp\" android:layout_height=\"356dp\"\n" +
+ " android:layout_column=\"0\" android:layout_row=\"1\" />\n" +
+ "</GridLayout>";
+ TestNode targetNode = TestNode.createFromXml(xml);
+ assertNotNull(targetNode);
+
+ targetNode.assignBounds(
+ "android.widget.GridLayout [0,109,480,800] <GridLayout> @+id/GridLayout1\n" +
+ " android.widget.Button [0,83,116,155] <Button> @+id/button1\n" +
+ " android.widget.Space [0,0,99,2] <Space>\n" +
+ " android.widget.Button [193,83,309,155] <Button> @+id/button3\n" +
+ " android.widget.Button [387,83,503,155] <Button> @+id/button2\n" +
+ " android.widget.Space [116,0,193,2] <Space> @+id/spacer_109\n" +
+ " android.widget.Space [193,0,387,2] <Space>\n" +
+ " android.widget.Space [387,0,480,2] <Space>\n" +
+ " android.widget.Space [0,0,2,155] <Space> @+id/spacer_397\n" +
+ " android.widget.Space [0,155,2,689] <Space>");
+
+ // Delete button3. This causes an array out of bounds exception currently.
+
+ //TestNode layout = TestNode.findById(targetNode, "@+id/GridLayout1");
+ //TestNode button1 = TestNode.findById(targetNode, "@+id/button1");
+ TestNode button2 = TestNode.findById(targetNode, "@+id/button2");
+ TestNode button3 = TestNode.findById(targetNode, "@+id/button3");
+ assertEquals(new Rect(387, 83, 503 - 387, 155- 83), button2.getBounds());
+
+ GridModel model = GridModel.get(new LayoutTestBase.TestRulesEngine(targetNode.getFqcn()),
+ targetNode, null);
+ assertEquals(4, model.declaredColumnCount);
+ assertEquals(4, model.actualColumnCount);
+ assertEquals(2, model.actualRowCount);
+
+ model.onDeleted(Collections.<INode>singletonList(button3));
+ // Exception fixed; todo: Test that the model updates are correct.
+
+ assertEquals(3, model.declaredColumnCount);
+ assertEquals(3, model.actualColumnCount);
+ assertEquals(2, model.actualRowCount);
+
+ fail("Finish test");
+ }
+
+ public void testInsert1() throws Exception {
+ String xml =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<GridLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " android:id=\"@+id/GridLayout1\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\"\n" +
+ " android:columnCount=\"4\"\n" +
+ " android:orientation=\"vertical\" >\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button1\"\n" +
+ " android:layout_column=\"0\"\n" +
+ " android:layout_columnSpan=\"4\"\n" +
+ " android:layout_gravity=\"center_horizontal|bottom\"\n" +
+ " android:layout_row=\"0\"\n" +
+ " android:text=\"Button\" />\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button2\"\n" +
+ " android:layout_column=\"2\"\n" +
+ " android:layout_gravity=\"left|top\"\n" +
+ " android:layout_row=\"1\"\n" +
+ " android:text=\"Button\" />\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button3\"\n" +
+ " android:layout_column=\"3\"\n" +
+ " android:layout_gravity=\"left|top\"\n" +
+ " android:layout_row=\"1\"\n" +
+ " android:text=\"Button\" />\n" +
+ "\n" +
+ " <Space\n" +
+ " android:id=\"@+id/spacer_393\"\n" +
+ " android:layout_width=\"81dp\"\n" +
+ " android:layout_height=\"1dp\"\n" +
+ " android:layout_column=\"1\"\n" +
+ " android:layout_row=\"0\" />\n" +
+ "\n" +
+ " <Space\n" +
+ " android:id=\"@+id/spacer_397\"\n" +
+ " android:layout_width=\"1dp\"\n" +
+ " android:layout_height=\"103dp\"\n" +
+ " android:layout_column=\"0\"\n" +
+ " android:layout_row=\"0\" />\n" +
+ "\n" +
+ "</GridLayout>";
+
+ TestNode targetNode = TestNode.createFromXml(xml);
+ assertNotNull(targetNode);
+
+ TestNode layout = TestNode.findById(targetNode, "@+id/GridLayout1");
+ TestNode button1 = TestNode.findById(targetNode, "@+id/button1");
+ TestNode button2 = TestNode.findById(targetNode, "@+id/button2");
+ TestNode button3 = TestNode.findById(targetNode, "@+id/button3");
+ TestNode hspacer = TestNode.findById(targetNode, "@+id/spacer_393");
+ TestNode vspacer = TestNode.findById(targetNode, "@+id/spacer_397");
+ assertNotNull(layout);
+ assertNotNull(button1);
+ assertNotNull(button2);
+ assertNotNull(button3);
+ assertNotNull(hspacer);
+
+ // Obtained by setting ViewHierarchy.DUMP_INFO=true:
+ layout.bounds(new Rect(0, 109, 480, 800-109));
+ button1.bounds(new Rect(182, 83, 298-182, 155-83));
+ button2.bounds(new Rect(124, 155, 240-124, 227-155));
+ button3.bounds(new Rect(240, 155, 356-240, 227-155));
+ hspacer.bounds(new Rect(2, 0, 124-2, 2));
+ vspacer.bounds(new Rect(0, 0, 2, 155));
+
+ GridModel model = GridModel.get(new LayoutTestBase.TestRulesEngine(targetNode.getFqcn()),
+ targetNode, null);
+ assertEquals(4, model.declaredColumnCount);
+ assertEquals(4, model.actualColumnCount);
+ assertEquals(2, model.actualRowCount);
+
+
+ model.splitColumn(1, false, 21, 32);
+ int index = model.getInsertIndex(2, 1);
+ GridModel.ViewData next = model.getView(index);
+ INode newChild = targetNode.insertChildAt(FQCN_BUTTON, index);
+ next.applyPositionAttributes();
+ model.setGridAttribute(newChild, ATTR_LAYOUT_COLUMN, 1);
+ model.setGridAttribute(newChild, ATTR_LAYOUT_COLUMN_SPAN, 3);
+ }
+
+ public void testInsert2() throws Exception {
+ // Drop into a view where there is a centered view: when dropping to the right of
+ // it (on a row further down), ensure that the row span is increased for the
+ // non-left-justified centered view which does not horizontally overlap the view
+ String xml =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<GridLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " android:id=\"@+id/GridLayout1\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\"\n" +
+ " android:columnCount=\"3\"\n" +
+ " android:orientation=\"vertical\" >\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button1\"\n" +
+ " android:layout_column=\"0\"\n" +
+ " android:layout_columnSpan=\"3\"\n" +
+ " android:layout_gravity=\"center_horizontal|bottom\"\n" +
+ " android:layout_row=\"0\"\n" +
+ " android:text=\"Button\" />\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button2\"\n" +
+ " android:layout_column=\"1\"\n" +
+ " android:layout_gravity=\"left|top\"\n" +
+ " android:layout_row=\"1\"\n" +
+ " android:text=\"Button\" />\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button3\"\n" +
+ " android:layout_column=\"2\"\n" +
+ " android:layout_gravity=\"left|top\"\n" +
+ " android:layout_row=\"1\"\n" +
+ " android:text=\"Button\" />\n" +
+ "\n" +
+ " <Space\n" +
+ " android:id=\"@+id/spacer_393\"\n" +
+ " android:layout_width=\"81dp\"\n" +
+ " android:layout_height=\"1dp\"\n" +
+ " android:layout_column=\"0\"\n" +
+ " android:layout_row=\"0\" />\n" +
+ "\n" +
+ " \n" +
+ " <Space\n" +
+ " android:id=\"@+id/spacer_397\"\n" +
+ " android:layout_width=\"1dp\"\n" +
+ " android:layout_height=\"103dp\"\n" +
+ " android:layout_column=\"0\"\n" +
+ " android:layout_row=\"0\" />\n" +
+ "\n" +
+ " \n" +
+ "</GridLayout>";
+
+ TestNode targetNode = TestNode.createFromXml(xml);
+ assertNotNull(targetNode);
+
+ TestNode layout = TestNode.findById(targetNode, "@+id/GridLayout1");
+ TestNode button1 = TestNode.findById(targetNode, "@+id/button1");
+ TestNode button2 = TestNode.findById(targetNode, "@+id/button2");
+ TestNode button3 = TestNode.findById(targetNode, "@+id/button3");
+ TestNode hspacer = TestNode.findById(targetNode, "@+id/spacer_393");
+ TestNode vspacer = TestNode.findById(targetNode, "@+id/spacer_397");
+ assertNotNull(layout);
+ assertNotNull(button1);
+ assertNotNull(button2);
+ assertNotNull(button3);
+ assertNotNull(hspacer);
+
+ // Obtained by setting ViewHierarchy.DUMP_INFO=true:
+ layout.bounds(new Rect(0, 109, 480, 800-109));
+ button1.bounds(new Rect(182, 83, 298-182, 155-83));
+ button2.bounds(new Rect(122, 155, 238-122, 227-155));
+ button3.bounds(new Rect(238, 155, 354-238, 227-155));
+ hspacer.bounds(new Rect(0, 0, 122, 2));
+ vspacer.bounds(new Rect(0, 0, 2, 155));
+
+ GridModel model = GridModel.get(new LayoutTestBase.TestRulesEngine(targetNode.getFqcn()),
+ targetNode, null);
+ assertEquals(3, model.declaredColumnCount);
+ assertEquals(3, model.actualColumnCount);
+ assertEquals(2, model.actualRowCount);
+
+ ViewData view = model.getView(button1);
+ assertNotNull(view);
+ assertEquals(0, view.column);
+ assertEquals(3, view.columnSpan);
+ assertEquals("3", view.node.getStringAttr(ANDROID_URI, ATTR_LAYOUT_COLUMN_SPAN));
+
+ model.splitColumn(3, false, 53, 318);
+ assertEquals(0, view.column);
+ assertEquals(4, view.columnSpan);
+ assertEquals("4", view.node.getStringAttr(ANDROID_URI, ATTR_LAYOUT_COLUMN_SPAN));
+ }
+
+ public void testInsert3_BROKEN() throws Exception {
+ // Check that when we insert a new gap column near an existing column, the
+ // view in that new column does not get moved
+ String xml =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<GridLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " android:id=\"@+id/GridLayout1\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\"\n" +
+ " android:columnCount=\"3\"\n" +
+ " android:orientation=\"vertical\" >\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button1\"\n" +
+ " android:layout_column=\"0\"\n" +
+ " android:layout_columnSpan=\"3\"\n" +
+ " android:layout_gravity=\"center_horizontal|bottom\"\n" +
+ " android:layout_row=\"0\"\n" +
+ " android:text=\"Button\" />\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button2\"\n" +
+ " android:layout_column=\"1\"\n" +
+ " android:layout_gravity=\"left|top\"\n" +
+ " android:layout_row=\"1\"\n" +
+ " android:text=\"Button\" />\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button3\"\n" +
+ " android:layout_column=\"2\"\n" +
+ " android:layout_gravity=\"left|top\"\n" +
+ " android:layout_row=\"1\"\n" +
+ " android:text=\"Button\" />\n" +
+ "\n" +
+ " <Space\n" +
+ " android:id=\"@+id/spacer_393\"\n" +
+ " android:layout_width=\"81dp\"\n" +
+ " android:layout_height=\"1dp\"\n" +
+ " android:layout_column=\"0\"\n" +
+ " android:layout_row=\"0\" />\n" +
+ "\n" +
+ " \n" +
+ " <Space\n" +
+ " android:id=\"@+id/spacer_397\"\n" +
+ " android:layout_width=\"1dp\"\n" +
+ " android:layout_height=\"103dp\"\n" +
+ " android:layout_column=\"0\"\n" +
+ " android:layout_row=\"0\" />\n" +
+ "\n" +
+ " \n" +
+ "</GridLayout>";
+
+ TestNode targetNode = TestNode.createFromXml(xml);
+ assertNotNull(targetNode);
+
+ TestNode layout = TestNode.findById(targetNode, "@+id/GridLayout1");
+ TestNode button1 = TestNode.findById(targetNode, "@+id/button1");
+ TestNode button2 = TestNode.findById(targetNode, "@+id/button2");
+ TestNode button3 = TestNode.findById(targetNode, "@+id/button3");
+ TestNode hspacer = TestNode.findById(targetNode, "@+id/spacer_393");
+ TestNode vspacer = TestNode.findById(targetNode, "@+id/spacer_397");
+ assertNotNull(layout);
+ assertNotNull(button1);
+ assertNotNull(button2);
+ assertNotNull(button3);
+ assertNotNull(hspacer);
+
+ // Obtained by setting ViewHierarchy.DUMP_INFO=true:
+ layout.bounds(new Rect(0, 109, 480, 800-109));
+ button1.bounds(new Rect(182, 83, 298-182, 155-83));
+ button2.bounds(new Rect(122, 155, 238-122, 227-155));
+ button3.bounds(new Rect(238, 155, 354-238, 227-155));
+ hspacer.bounds(new Rect(0, 0, 122, 2));
+ vspacer.bounds(new Rect(0, 0, 2, 155));
+
+ GridModel model = GridModel.get(new LayoutTestBase.TestRulesEngine(targetNode.getFqcn()),
+ targetNode, null);
+ assertEquals(3, model.declaredColumnCount);
+ assertEquals(3, model.actualColumnCount);
+ assertEquals(2, model.actualRowCount);
+
+ ViewData view = model.getView(button3);
+ assertNotNull(view);
+ assertEquals(2, view.column);
+ assertEquals(1, view.columnSpan);
+ assertNull("1", view.node.getStringAttr(ANDROID_URI, ATTR_LAYOUT_COLUMN_SPAN));
+
+ model.splitColumn(2, true, 10, 253);
+ // TODO: Finish this test: Assert that the cells are in the right place
+ //assertEquals(4, view.column);
+ //assertEquals(1, view.columnSpan);
+ //assertEquals("4", view.node.getStringAttr(ANDROID_URI, ATTR_LAYOUT_COLUMN_SPAN));
+ fail("Finish test");
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/relative/DeletionHandlerTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/relative/DeletionHandlerTest.java
new file mode 100644
index 000000000..d629a3897
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/relative/DeletionHandlerTest.java
@@ -0,0 +1,445 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.common.layout.relative;
+
+import static com.android.SdkConstants.ATTR_ID;
+import static com.android.SdkConstants.ANDROID_URI;
+
+import com.android.ide.common.api.INode;
+import com.android.ide.common.layout.BaseViewRule;
+import com.android.ide.common.layout.TestNode;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+@SuppressWarnings("javadoc")
+public class DeletionHandlerTest extends TestCase {
+ public void testSimple() {
+ String xml = "" +
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " xmlns:tools=\"http://schemas.android.com/tools\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\"\n" +
+ " tools:ignore=\"HardcodedText\" >\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button1\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_alignParentLeft=\"true\"\n" +
+ " android:text=\"A\" />\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button2\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_alignBaseline=\"@+id/button1\"\n" +
+ " android:layout_alignBottom=\"@+id/button1\"\n" +
+ " android:layout_toRightOf=\"@+id/button1\"\n" +
+ " android:text=\"B\" />\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button3\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_alignBottom=\"@+id/button2\"\n" +
+ " android:layout_toRightOf=\"@+id/button2\"\n" +
+ " android:text=\"C\" />\n" +
+ "\n" +
+ "</RelativeLayout>";
+ TestNode targetNode = TestNode.createFromXml(xml);
+ assertNotNull(targetNode);
+
+ TestNode button2 = TestNode.findById(targetNode, "@+id/button2");
+
+ INode layout = button2.getParent();
+ List<INode> deletedNodes = Collections.<INode>singletonList(button2);
+ List<INode> movedNodes = Collections.<INode>emptyList();
+ assertSame(layout, targetNode);
+ layout.removeChild(button2);
+
+ DeletionHandler handler = new DeletionHandler(deletedNodes, movedNodes, layout);
+ handler.updateConstraints();
+
+ String updated = TestNode.toXml(targetNode);
+ assertEquals(
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " xmlns:tools=\"http://schemas.android.com/tools\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\"\n" +
+ " tools:ignore=\"HardcodedText\">\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button1\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_alignParentLeft=\"true\"\n" +
+ " android:text=\"A\">\n" +
+ " </Button>\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button3\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_alignBaseline=\"@+id/button1\"\n" +
+ " android:layout_alignBottom=\"@+id/button1\"\n" +
+ " android:layout_toRightOf=\"@+id/button1\"\n" +
+ " android:text=\"C\">\n" +
+ " </Button>\n" +
+ "\n" +
+ "</RelativeLayout>",
+ updated);
+ assertFalse(updated.contains(BaseViewRule.stripIdPrefix(button2.getStringAttr(ANDROID_URI,
+ ATTR_ID))));
+ }
+
+ public void testTransitive() {
+ String xml = "" +
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " xmlns:tools=\"http://schemas.android.com/tools\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\"\n" +
+ " tools:ignore=\"HardcodedText\" >\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button1\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_alignParentLeft=\"true\"\n" +
+ " android:layout_alignParentTop=\"true\"\n" +
+ " android:text=\"Above\" />\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button2\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_alignParentLeft=\"true\"\n" +
+ " android:layout_below=\"@+id/button1\"\n" +
+ " android:text=\"A\" />\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button3\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_alignBaseline=\"@+id/button2\"\n" +
+ " android:layout_alignBottom=\"@+id/button2\"\n" +
+ " android:layout_toRightOf=\"@+id/button2\"\n" +
+ " android:text=\"B\" />\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button4\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_alignBottom=\"@+id/button3\"\n" +
+ " android:layout_toRightOf=\"@+id/button3\"\n" +
+ " android:text=\"C\" />\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button5\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_alignBaseline=\"@+id/button4\"\n" +
+ " android:layout_alignBottom=\"@+id/button4\"\n" +
+ " android:layout_toRightOf=\"@+id/button4\"\n" +
+ " android:text=\"D\" />\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button6\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_alignBottom=\"@+id/button5\"\n" +
+ " android:layout_toRightOf=\"@+id/button5\"\n" +
+ " android:text=\"E\" />\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button7\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_alignLeft=\"@+id/button3\"\n" +
+ " android:layout_below=\"@+id/button3\"\n" +
+ " android:text=\"Button\" />\n" +
+ "\n" +
+ " <CheckBox\n" +
+ " android:id=\"@+id/checkBox1\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_alignBaseline=\"@+id/button7\"\n" +
+ " android:layout_alignBottom=\"@+id/button7\"\n" +
+ " android:layout_toRightOf=\"@+id/button7\"\n" +
+ " android:text=\"CheckBox\" />\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button8\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_below=\"@+id/checkBox1\"\n" +
+ " android:layout_toRightOf=\"@+id/checkBox1\"\n" +
+ " android:text=\"Button\" />\n" +
+ "\n" +
+ "</RelativeLayout>";
+ TestNode targetNode = TestNode.createFromXml(xml);
+ assertNotNull(targetNode);
+ TestNode button7 = TestNode.findById(targetNode, "@+id/button7");
+ TestNode checkBox = TestNode.findById(targetNode, "@+id/checkBox1");
+
+ INode layout = button7.getParent();
+ List<INode> deletedNodes = Arrays.<INode>asList(button7, checkBox);
+ List<INode> movedNodes = Collections.<INode>emptyList();
+ assertSame(layout, targetNode);
+ layout.removeChild(button7);
+ layout.removeChild(checkBox);
+
+ DeletionHandler handler = new DeletionHandler(deletedNodes, movedNodes, layout);
+ handler.updateConstraints();
+
+ String updated = TestNode.toXml(targetNode);
+ assertEquals(
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " xmlns:tools=\"http://schemas.android.com/tools\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\"\n" +
+ " tools:ignore=\"HardcodedText\">\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button1\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_alignParentLeft=\"true\"\n" +
+ " android:layout_alignParentTop=\"true\"\n" +
+ " android:text=\"Above\">\n" +
+ " </Button>\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button2\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_alignParentLeft=\"true\"\n" +
+ " android:layout_below=\"@+id/button1\"\n" +
+ " android:text=\"A\">\n" +
+ " </Button>\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button3\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_alignBaseline=\"@+id/button2\"\n" +
+ " android:layout_alignBottom=\"@+id/button2\"\n" +
+ " android:layout_toRightOf=\"@+id/button2\"\n" +
+ " android:text=\"B\">\n" +
+ " </Button>\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button4\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_alignBottom=\"@+id/button3\"\n" +
+ " android:layout_toRightOf=\"@+id/button3\"\n" +
+ " android:text=\"C\">\n" +
+ " </Button>\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button5\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_alignBaseline=\"@+id/button4\"\n" +
+ " android:layout_alignBottom=\"@+id/button4\"\n" +
+ " android:layout_toRightOf=\"@+id/button4\"\n" +
+ " android:text=\"D\">\n" +
+ " </Button>\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button6\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_alignBottom=\"@+id/button5\"\n" +
+ " android:layout_toRightOf=\"@+id/button5\"\n" +
+ " android:text=\"E\">\n" +
+ " </Button>\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button8\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_alignLeft=\"@+id/button3\"\n" +
+ " android:layout_below=\"@+id/button3\"\n" +
+ " android:text=\"Button\">\n" +
+ " </Button>\n" +
+ "\n" +
+ "</RelativeLayout>",
+ updated);
+ assertFalse(updated.contains(BaseViewRule.stripIdPrefix(button7.getStringAttr(ANDROID_URI,
+ ATTR_ID))));
+ }
+
+ public void testCenter() {
+ String xml =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " xmlns:tools=\"http://schemas.android.com/tools\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\"\n" +
+ " tools:ignore=\"HardcodedText\" >\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button1\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_centerInParent=\"true\"\n" +
+ " android:text=\"Button\" />\n" +
+ "\n" +
+ " <CheckBox\n" +
+ " android:id=\"@+id/checkBox1\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_below=\"@+id/button1\"\n" +
+ " android:layout_toRightOf=\"@+id/button1\"\n" +
+ " android:text=\"CheckBox\" />\n" +
+ "\n" +
+ "</RelativeLayout>";
+
+ TestNode targetNode = TestNode.createFromXml(xml);
+ assertNotNull(targetNode);
+ TestNode button1 = TestNode.findById(targetNode, "@+id/button1");
+
+ INode layout = button1.getParent();
+ List<INode> deletedNodes = Collections.<INode>singletonList(button1);
+ List<INode> movedNodes = Collections.<INode>emptyList();
+ assertSame(layout, targetNode);
+ layout.removeChild(button1);
+
+ DeletionHandler handler = new DeletionHandler(deletedNodes, movedNodes, layout);
+ handler.updateConstraints();
+
+ String updated = TestNode.toXml(targetNode);
+ assertEquals(
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " xmlns:tools=\"http://schemas.android.com/tools\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\"\n" +
+ " tools:ignore=\"HardcodedText\">\n" +
+ "\n" +
+ " <CheckBox\n" +
+ " android:id=\"@+id/checkBox1\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_centerInParent=\"true\"\n" +
+ " android:text=\"CheckBox\">\n" +
+ " </CheckBox>\n" +
+ "\n" +
+ "</RelativeLayout>",
+ updated);
+ assertFalse(updated.contains(BaseViewRule.stripIdPrefix(button1.getStringAttr(ANDROID_URI,
+ ATTR_ID))));
+
+ }
+
+ public void testMove() {
+ String xml = "" +
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " xmlns:tools=\"http://schemas.android.com/tools\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\"\n" +
+ " tools:ignore=\"HardcodedText\" >\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button1\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_alignParentLeft=\"true\"\n" +
+ " android:text=\"A\" />\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button2\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_alignBaseline=\"@+id/button1\"\n" +
+ " android:layout_alignBottom=\"@+id/button1\"\n" +
+ " android:layout_toRightOf=\"@+id/button1\"\n" +
+ " android:text=\"B\" />\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button3\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_alignBottom=\"@+id/button2\"\n" +
+ " android:layout_toRightOf=\"@+id/button2\"\n" +
+ " android:text=\"C\" />\n" +
+ "\n" +
+ "</RelativeLayout>";
+ TestNode targetNode = TestNode.createFromXml(xml);
+ assertNotNull(targetNode);
+
+ TestNode button2 = TestNode.findById(targetNode, "@+id/button2");
+
+ INode layout = button2.getParent();
+ List<INode> deletedNodes = Collections.<INode>singletonList(button2);
+ List<INode> movedNodes = Collections.<INode>singletonList(button2);
+ assertSame(layout, targetNode);
+
+ DeletionHandler handler = new DeletionHandler(deletedNodes, movedNodes, layout);
+ handler.updateConstraints();
+
+ String updated = TestNode.toXml(targetNode);
+ assertEquals(
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " xmlns:tools=\"http://schemas.android.com/tools\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\"\n" +
+ " tools:ignore=\"HardcodedText\">\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button1\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_alignParentLeft=\"true\"\n" +
+ " android:text=\"A\">\n" +
+ " </Button>\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button2\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_alignBaseline=\"@+id/button1\"\n" +
+ " android:layout_alignBottom=\"@+id/button1\"\n" +
+ " android:layout_toRightOf=\"@+id/button1\"\n" +
+ " android:text=\"B\">\n" +
+ " </Button>\n" +
+ "\n" +
+ " <Button\n" +
+ " android:id=\"@+id/button3\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_alignBottom=\"@+id/button2\"\n" +
+ " android:layout_toRightOf=\"@+id/button2\"\n" +
+ " android:text=\"C\">\n" +
+ " </Button>\n" +
+ "\n" +
+ "</RelativeLayout>",
+ updated);
+ assertTrue(updated.contains(BaseViewRule.stripIdPrefix(button2.getStringAttr(ANDROID_URI,
+ ATTR_ID))));
+ }
+}