In my application, I have a feature such that a user is able to re-order items in a list, by long clicking to create a drag shadow, which the user can then drag and insert at a position of choice. Once dropped, the items are re-ordered.
I am struggling to develop a UI test for this. I am able to either successfully long click on the item, to create the drag shadow OR implement a dragging motion. I seem unable to combine the two, into one motion.
I am using Espresso and Barista in my Android UI Tests.
For the long click I used Barista's API:
longClickOn("ITEM");
For the dragging motion, I attempted to create my own Espresso ViewAction:
return new ViewAction() {
@Override
public Matcher<View> getConstraints() {
return isAssignableFrom(ViewGroup.class);
}
@Override
public String getDescription() {
return "Swiping child " + srcIndex + " to child " + destIndex;
}
@Override
public void perform(UiController uiController, View view) {
ViewGroup parent = (ViewGroup) view;
final View srcChild = parent.getChildAt(srcIndex);
final View destChild = parent.getChildAt(destIndex);
final CoordinatesProvider srcCoordinatesProvider = new CoordinatesProvider() {
@Override
public float[] calculateCoordinates(View view) {
int[] location = new int[2];
srcChild.getLocationInWindow(location);
float x = location[0] + (view.getMeasuredWidth() / 2);
float y = location[1] + (view.getMeasuredHeight() / 2);
return new float[] {x, y};
}
};
final CoordinatesProvider destCoordinatesProvider = new CoordinatesProvider() {
@Override
public float[] calculateCoordinates(View view) {
int[] location = new int[2];
destChild.getLocationInWindow(location);
float x = location[0] + (view.getMeasuredWidth() / 2);
float y = location[1] + (view.getMeasuredHeight() / 2);
return new float[] {x, y};
}
};
GeneralSwipeAction swipe = new GeneralSwipeAction(Swipe.FAST,
srcCoordinatesProvider, destCoordinatesProvider, Press.FINGER);
swipe.perform(uiController, parent);
}
};
EDIT:
Following the answer from @Be_Negative, I've customised the given answer and came up with this:
private static ViewAction drag(final int srcIndex, final int destIndex) {
return new ViewAction() {
@Override
public Matcher<View> getConstraints() {
return isAssignableFrom(ViewGroup.class);
}
@Override
public String getDescription() {
return "Swiping child " + srcIndex + " to child " + destIndex;
}
@Override
public void perform(UiController uiController, View view) {
ViewGroup parent = (ViewGroup) view;
uiController.loopMainThreadUntilIdle();
final View srcChild = parent.getChildAt(srcIndex);
final View destChild = parent.getChildAt(destIndex);
final CoordinatesProvider coordinatesProvider = getCoordinatesProdvider();
float[] precision = Press.PINPOINT.describePrecision();
MotionEvent downEvent = MotionEvents.sendDown(uiController, coordinatesProvider.calculateCoordinates(srcChild), precision).down;
try {
long longPressTimeout = (long) (ViewConfiguration.getLongPressTimeout() * 1.5);
uiController.loopMainThreadForAtLeast(longPressTimeout);
float[][] steps = interpolateDragging(
coordinatesProvider.calculateCoordinates(srcChild),
coordinatesProvider.calculateCoordinates(destChild)
);
uiController.loopMainThreadUntilIdle();
for(float[] step : steps) {
if( !MotionEvents.sendMovement(uiController, downEvent, step)) {
MotionEvents.sendCancel(uiController, downEvent);
}
}
if(!MotionEvents.sendUp(uiController, downEvent, coordinatesProvider.calculateCoordinates(destChild))) {
MotionEvents.sendCancel(uiController, downEvent);
}
} catch(Exception e) {
System.out.println(e);
} finally {
downEvent.recycle();
}
}
};
}
Unforunately, it throws an error at MotionEvents.sendMovement
as follows:
Error performing 'inject motion event (corresponding down event: MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=540.0, y[0]=302.0, toolType[0]=TOOL_TYPE_UNKNOWN, buttonState=BUTTON_PRIMARY, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=34586525, downTime=34586525, deviceId=0, source=0x1002 })' on view 'unknown'.