Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(33)

Unified Diff: content/public/android/javatests/src/org/chromium/content/browser/input/SelectionHandleTest.java

Issue 21044008: [Android] Add selection handle tests (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 7 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: content/public/android/javatests/src/org/chromium/content/browser/input/SelectionHandleTest.java
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/input/SelectionHandleTest.java b/content/public/android/javatests/src/org/chromium/content/browser/input/SelectionHandleTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..162f47816a49795bf5ba6cc6f4e79334f6239906
--- /dev/null
+++ b/content/public/android/javatests/src/org/chromium/content/browser/input/SelectionHandleTest.java
@@ -0,0 +1,448 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.content.browser.input;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.text.Editable;
+import android.text.Selection;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+
+import java.util.concurrent.Callable;
+
+import org.chromium.base.test.util.Feature;
+import org.chromium.base.test.util.UrlUtils;
+import org.chromium.base.ThreadUtils;
+import org.chromium.content.browser.ContentView;
+import org.chromium.content.browser.RenderCoordinates;
+import org.chromium.content.browser.test.util.CriteriaHelper;
+import org.chromium.content.browser.test.util.Criteria;
+import org.chromium.content.browser.test.util.DOMUtils;
+import org.chromium.content.browser.test.util.TestCallbackHelperContainer;
+import org.chromium.content.browser.test.util.TestInputMethodManagerWrapper;
+import org.chromium.content.browser.test.util.TestTouchUtils;
+import org.chromium.content.browser.test.util.TouchCommon;
+import org.chromium.content_shell_apk.ContentShellTestBase;
+
+public class SelectionHandleTest extends ContentShellTestBase {
+ private static final String META_DISABLE_ZOOM =
+ "<meta name=\"viewport\" content=\"" +
+ "height=device-height," +
+ "width=device-width," +
+ "initial-scale=1.0," +
+ "minimum-scale=1.0," +
+ "maximum-scale=1.0," +
+ "\" />";
+
+ // For these we use a tiny font-size so that we can be more strict on the expected handle
+ // positions.
+ private static final String TEXTAREA_ID = "textarea";
+ private static final String TEXTAREA_DATA_URL = UrlUtils.encodeHtmlDataUri(
+ "<html><head>" + META_DISABLE_ZOOM + "</head><body>" +
+ "<textarea id=\"" + TEXTAREA_ID +
+ "\" cols=\"40\" rows=\"20\" style=\"font-size:6px\">" +
+ "L r m i s m d l r s t a e , c n e t t r a i i i i g e i , s d d e u m d t m o " +
+ "i c d d n u l b r e d l r m g a l q a U e i a m n m e i m q i n s r d " +
+ "e e c t t o u l m o a o i n s u a i u p x a o m d c n e u t D i a t " +
+ "i u e o o i r p e e d r t n o u t t v l t s e i l m o o e u u i t u l " +
+ "p r a u . x e t u s n o c e a c p d t t o p o d n , u t n u p q i " +
+ "o f c a e e u t o l t n m d s l b r m." +
+ "L r m i s m d l r s t a e , c n e t t r a i i i i g e i , s d d e u m d t m o " +
+ "i c d d n u l b r e d l r m g a l q a U e i a m n m e i m q i n s r d " +
+ "e e c t t o u l m o a o i n s u a i u p x a o m d c n e u t D i a t " +
+ "i u e o o i r p e e d r t n o u t t v l t s e i l m o o e u u i t u l " +
+ "p r a u . x e t u s n o c e a c p d t t o p o d n , u t n u p q i " +
+ "o f c a e e u t o l t n m d s l b r m." +
+ "</textarea>" +
+ "</body></html>");
+
+ private static final String NONEDITABLE_DIV_ID = "noneditable";
+ private static final String NONEDITABLE_DATA_URL = UrlUtils.encodeHtmlDataUri(
+ "<html><head>" + META_DISABLE_ZOOM + "</head><body>" +
+ "<div id=\"" + NONEDITABLE_DIV_ID + "\" style=\"width:200; font-size:6px\">" +
+ "L r m i s m d l r s t a e , c n e t t r a i i i i g e i , s d d e u m d t m o " +
+ "i c d d n u l b r e d l r m g a l q a U e i a m n m e i m q i n s r d " +
+ "e e c t t o u l m o a o i n s u a i u p x a o m d c n e u t D i a t " +
+ "i u e o o i r p e e d r t n o u t t v l t s e i l m o o e u u i t u l " +
+ "p r a u . x e t u s n o c e a c p d t t o p o d n , u t n u p q i " +
+ "o f c a e e u t o l t n m d s l b r m." +
+ "L r m i s m d l r s t a e , c n e t t r a i i i i g e i , s d d e u m d t m o " +
+ "i c d d n u l b r e d l r m g a l q a U e i a m n m e i m q i n s r d " +
+ "e e c t t o u l m o a o i n s u a i u p x a o m d c n e u t D i a t " +
+ "i u e o o i r p e e d r t n o u t t v l t s e i l m o o e u u i t u l " +
+ "p r a u . x e t u s n o c e a c p d t t o p o d n , u t n u p q i " +
+ "o f c a e e u t o l t n m d s l b r m." +
+ "</div>" +
+ "</body></html>");
+
+ // TODO(cjhopman): These tolerances should be based on the actual width/height of a
+ // character/line.
+ private static final int HANDLE_POSITION_X_TOLERANCE_PIX = 20;
+ private static final int HANDLE_POSITION_Y_TOLERANCE_PIX = 30;
+
+ private enum TestPageType {
+ EDITABLE(TEXTAREA_ID, TEXTAREA_DATA_URL, true),
+ NONEDITABLE(NONEDITABLE_DIV_ID, NONEDITABLE_DATA_URL, false);
+
+ final String nodeId;
+ final String dataUrl;
+ final boolean selectionShouldBeEditable;
+
+ TestPageType(String nodeId, String dataUrl, boolean selectionShouldBeEditable) {
+ this.nodeId = nodeId;
+ this.dataUrl = dataUrl;
+ this.selectionShouldBeEditable = selectionShouldBeEditable;
+ }
+ }
+
+ private void launchWithUrl(String url) throws Throwable {
+ launchContentShellWithUrl(url);
+ assertTrue("Page failed to load", waitForActiveShellToBeDoneLoading());
+ assertWaitForPageScaleFactorMatch(1.0f);
+
+ // The TestInputMethodManagerWrapper intercepts showSoftInput so that a keyboard is never
+ // brought up.
+ getImeAdapter().setInputMethodManagerWrapper(
+ new TestInputMethodManagerWrapper(getContentViewCore()));
+ }
+
+ private void assertWaitForHasSelectionPosition()
+ throws Throwable {
+ assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
+ @Override
+ public boolean isSatisfied() {
+ int start = getSelectionStart();
+ int end = getSelectionEnd();
+ return start > 0 && start == end;
+ }
+ }));
+ }
+
+ /**
+ * Verifies that when a long-press is performed on static page text,
+ * selection handles appear and that handles can be dragged to extend the
+ * selection. Does not check exact handle position as this will depend on
+ * screen size; instead, position is expected to be correct within
+ * HANDLE_POSITION_TOLERANCE_PIX.
+ */
+ @MediumTest
+ @Feature({ "TextSelection", "Main" })
+ public void testNoneditableSelectionHandles() throws Throwable {
+ doSelectionHandleTest(TestPageType.NONEDITABLE);
+ }
+
+ /**
+ * Verifies that when a long-press is performed on editable text (within a
+ * textarea), selection handles appear and that handles can be dragged to
+ * extend the selection. Does not check exact handle position as this will
+ * depend on screen size; instead, position is expected to be correct within
+ * HANDLE_POSITION_TOLERANCE_PIX.
+ */
+ @MediumTest
+ @Feature({ "TextSelection" })
+ public void testEditableSelectionHandles() throws Throwable {
+ doSelectionHandleTest(TestPageType.EDITABLE);
+ }
+
+ private void doSelectionHandleTest(TestPageType pageType) throws Throwable {
+ launchWithUrl(pageType.dataUrl);
+
+ clickNodeToShowSelectionHandles(pageType.nodeId);
+ assertWaitForSelectionEditableEquals(pageType.selectionShouldBeEditable);
+
+ HandleView startHandle = getStartHandle();
+ HandleView endHandle = getEndHandle();
+
+ Rect nodeWindowBounds = getNodeBoundsPix(pageType.nodeId);
+
+ int leftX = (nodeWindowBounds.left + nodeWindowBounds.centerX()) / 2;
+ int centerX = nodeWindowBounds.centerX();
+ int rightX = (nodeWindowBounds.right + nodeWindowBounds.centerX()) / 2;
+
+ int topY = (nodeWindowBounds.top + nodeWindowBounds.centerY()) / 2;
+ int centerY = nodeWindowBounds.centerY();
+ int bottomY = (nodeWindowBounds.bottom + nodeWindowBounds.centerY()) / 2;
+
+ // Drag start handle up and to the left. The selection start should decrease.
+ dragHandleAndCheckSelectionChange(startHandle, leftX, topY, -1, 0);
+ // Drag end handle down and to the right. The selection end should increase.
+ dragHandleAndCheckSelectionChange(endHandle, rightX, bottomY, 0, 1);
+ // Drag start handle back to the middle. The selection start should increase.
+ dragHandleAndCheckSelectionChange(startHandle, centerX, centerY, 1, 0);
+ // Drag end handle up and to the left past the start handle. Both selection start and end
+ // should decrease.
+ dragHandleAndCheckSelectionChange(endHandle, leftX, topY, -1, -1);
+ // Drag start handle down and to the right past the end handle. Both selection start and end
+ // should increase.
+ dragHandleAndCheckSelectionChange(startHandle, rightX, bottomY, 1, 1);
+
+ clickToDismissHandles();
+ }
+
+ private void dragHandleAndCheckSelectionChange(HandleView handle, int dragToX, int dragToY,
+ final int expectedStartChange, final int expectedEndChange) throws Throwable {
+ String initialText = getContentViewCore().getSelectedText();
+ final int initialSelectionEnd = getSelectionEnd();
+ final int initialSelectionStart = getSelectionStart();
+
+ dragHandleTo(handle, dragToX, dragToY, 10);
+ assertWaitForEitherHandleNear(dragToX, dragToY);
+
+ if (getContentViewCore().isSelectionEditable()) {
+ assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
+ @Override
+ public boolean isSatisfied() {
+ int startChange = getSelectionStart() - initialSelectionStart;
+ // TODO(cjhopman): Due to http://crbug.com/244633 we can't really assert that
+ // there is no change when we expect to be able to.
+ if (expectedStartChange != 0) {
+ if ((int) Math.signum(startChange) != expectedStartChange) return false;
+ }
+
+ int endChange = getSelectionEnd() - initialSelectionEnd;
+ if (expectedEndChange != 0) {
+ if ((int) Math.signum(endChange) != expectedEndChange) return false;
+ }
+
+ return true;
+ }
+ }));
+ }
+
+ assertWaitForHandleViewStopped(getStartHandle());
+ assertWaitForHandleViewStopped(getEndHandle());
+ }
+
+ private void assertWaitForSelectionEditableEquals(final boolean expected) throws Throwable {
+ assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
+ @Override
+ public boolean isSatisfied() {
+ return getContentViewCore().isSelectionEditable() == expected;
+ }
+ }));
+ }
+
+ private void assertWaitForHandleViewStopped(final HandleView handle) throws Throwable {
+ assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
+ private Point position = new Point(-1, -1);
+ @Override
+ public boolean isSatisfied() {
+ Point lastPosition = position;
+ position = getHandlePosition(handle);
+ return !handle.isDragging() &&
+ position.equals(lastPosition);
+ }
+ }));
+ }
+
+ /**
+ * Verifies that when a selection is made within static page text, that the
+ * contextual action bar of the correct type is displayed. Also verified
+ * that the bar disappears upon deselection.
+ */
+ @MediumTest
+ @Feature({ "TextSelection" })
+ public void testNoneditableSelectionActionBar() throws Throwable {
+ doSelectionActionBarTest(TestPageType.NONEDITABLE);
+ }
+
+ /**
+ * Verifies that when a selection is made within editable text, that the
+ * contextual action bar of the correct type is displayed. Also verified
+ * that the bar disappears upon deselection.
+ */
+ @MediumTest
+ @Feature({ "TextSelection" })
+ public void testEditableSelectionActionBar() throws Throwable {
+ doSelectionActionBarTest(TestPageType.EDITABLE);
+ }
+
+ private void doSelectionActionBarTest(TestPageType pageType) throws Throwable {
+ launchWithUrl(pageType.dataUrl);
+ assertFalse(getContentViewCore().isSelectActionBarShowing());
+ clickNodeToShowSelectionHandles(pageType.nodeId);
+ assertWaitForSelectActionBarShowingEquals(true);
+ clickToDismissHandles();
+ assertWaitForSelectActionBarShowingEquals(false);
+ }
+
+ private static Point getHandlePosition(final HandleView handle) {
+ return ThreadUtils.runOnUiThreadBlockingNoException(new Callable<Point>() {
+ @Override
+ public Point call() {
+ return new Point(handle.getAdjustedPositionX(), handle.getAdjustedPositionY());
+ }
+ });
+ }
+
+ private static boolean isHandleNear(HandleView handle, int x, int y) {
+ Point position = getHandlePosition(handle);
+ return (Math.abs(position.x - x) < HANDLE_POSITION_X_TOLERANCE_PIX) &&
+ (Math.abs(position.y - y) < HANDLE_POSITION_Y_TOLERANCE_PIX);
+ }
+
+ private void assertWaitForHandleNear(final HandleView handle, final int x, final int y)
+ throws Throwable {
+ assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
+ @Override
+ public boolean isSatisfied() {
+ return isHandleNear(handle, x, y);
+ }
+ }));
+ }
+
+ private void assertWaitForEitherHandleNear(final int x, final int y) throws Throwable {
+ final HandleView startHandle = getStartHandle();
+ final HandleView endHandle = getEndHandle();
+ assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
+ @Override
+ public boolean isSatisfied() {
+ return isHandleNear(startHandle, x, y) || isHandleNear(endHandle, x, y);
+ }
+ }));
+ }
+
+ private void assertWaitForHandlesShowingEquals(final boolean shouldBeShowing) throws Throwable {
+ assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
+ @Override
+ public boolean isSatisfied() {
+ SelectionHandleController shc =
+ getContentViewCore().getSelectionHandleControllerForTest();
+ boolean isShowing = shc != null && shc.isShowing();
+ return shouldBeShowing == isShowing;
+ }
+ }));
+ }
+
+
+ private void dragHandleTo(final HandleView handle, final int dragToX, final int dragToY,
+ final int steps) throws Throwable {
+ ContentView view = getContentView();
+ assertTrue(ThreadUtils.runOnUiThreadBlocking(new Callable<Boolean>() {
+ @Override
+ public Boolean call() {
+ int adjustedX = handle.getAdjustedPositionX();
+ int adjustedY = handle.getAdjustedPositionY();
+ int realX = handle.getPositionX();
+ int realY = handle.getPositionY();
+
+ int realDragToX = dragToX + (realX - adjustedX);
+ int realDragToY = dragToY + (realY - adjustedY);
+
+ ContentView view = getContentView();
+ int[] fromLocation = TestTouchUtils.getAbsoluteLocationFromRelative(
+ view, realX, realY);
+ int[] toLocation = TestTouchUtils.getAbsoluteLocationFromRelative(
+ view, realDragToX, realDragToY);
+
+ long downTime = SystemClock.uptimeMillis();
+ MotionEvent event = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN,
+ fromLocation[0], fromLocation[1], 0);
+ handle.dispatchTouchEvent(event);
+
+ if (!handle.isDragging()) return false;
+
+ for (int i = 0; i < steps; i++) {
+ float scale = (float) (i + 1) / steps;
+ int x = fromLocation[0] + (int) (scale * (toLocation[0] - fromLocation[0]));
+ int y = fromLocation[1] + (int) (scale * (toLocation[1] - fromLocation[1]));
+ long eventTime = SystemClock.uptimeMillis();
+ event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE,
+ x, y, 0);
+ handle.dispatchTouchEvent(event);
+ }
+ long upTime = SystemClock.uptimeMillis();
+ event = MotionEvent.obtain(downTime, upTime, MotionEvent.ACTION_UP,
+ toLocation[0], toLocation[1], 0);
+ handle.dispatchTouchEvent(event);
+
+ return !handle.isDragging();
+ }
+ }));
+ }
+
+ private Rect getNodeBoundsPix(String nodeId) throws Throwable {
+ Rect nodeBounds = DOMUtils.getNodeBounds(getContentView(),
+ new TestCallbackHelperContainer(getContentView()), nodeId);
+
+ RenderCoordinates renderCoordinates = getContentView().getRenderCoordinates();
+ int offsetX = getContentView().getContentViewCore().getViewportSizeOffsetWidthPix();
+ int offsetY = getContentView().getContentViewCore().getViewportSizeOffsetHeightPix();
+
+ int left = (int) renderCoordinates.fromLocalCssToPix(nodeBounds.left) + offsetX;
+ int right = (int) renderCoordinates.fromLocalCssToPix(nodeBounds.right) + offsetX;
+ int top = (int) renderCoordinates.fromLocalCssToPix(nodeBounds.top) + offsetY;
+ int bottom = (int) renderCoordinates.fromLocalCssToPix(nodeBounds.bottom) + offsetY;
+
+ return new Rect(left, top, right, bottom);
+ }
+
+ private void clickNodeToShowSelectionHandles(String nodeId) throws Throwable {
+ Rect nodeWindowBounds = getNodeBoundsPix(nodeId);
+
+ TouchCommon touchCommon = new TouchCommon(this);
+ int centerX = nodeWindowBounds.centerX();
+ int centerY = nodeWindowBounds.centerY();
+ touchCommon.longPressView(getContentView(), centerX, centerY);
+
+ assertWaitForHandlesShowingEquals(true);
+
+ // No words wrap in the sample text so handles should be at the same y
+ // position.
+ assertEquals(getStartHandle().getPositionY(), getEndHandle().getPositionY());
+
+ // In ContentShell, the handles are initially misplaced when they first appear. This is
+ // fixed after the first time they are dragged (or the page is scrolled).
+ // TODO(cjhopman): Fix this problem in ContentShell: http://crbug.com/243836
+ dragHandleTo(getStartHandle(), centerX - 40, centerY - 40, 1);
+ assertWaitForHandleViewStopped(getStartHandle());
+ }
+
+ private void clickToDismissHandles() throws Throwable {
+ TestTouchUtils.sleepForDoubleTapTimeout(getInstrumentation());
+ new TouchCommon(this).singleClickView(getContentView(), 0, 0);
+ assertWaitForHandlesShowingEquals(false);
+ }
+
+ private void assertWaitForSelectActionBarShowingEquals(final boolean shouldBeShowing)
+ throws InterruptedException {
+ assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
+ @Override
+ public boolean isSatisfied() {
+ return shouldBeShowing == getContentViewCore().isSelectActionBarShowing();
+ }
+ }));
+ }
+
+ private ImeAdapter getImeAdapter() {
+ return getContentViewCore().getImeAdapterForTest();
+ }
+
+ private int getSelectionStart() {
+ return Selection.getSelectionStart(getEditable());
+ }
+
+ private int getSelectionEnd() {
+ return Selection.getSelectionEnd(getEditable());
+ }
+
+ private Editable getEditable() {
+ return getContentViewCore().getEditableForTest();
+ }
+
+ private HandleView getStartHandle() {
+ SelectionHandleController shc = getContentViewCore().getSelectionHandleControllerForTest();
+ return shc.getStartHandleViewForTest();
+ }
+
+ private HandleView getEndHandle() {
+ SelectionHandleController shc = getContentViewCore().getSelectionHandleControllerForTest();
+ return shc.getEndHandleViewForTest();
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698