| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 |
| * |
| * 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.google.android.apps.common.testing.ui.espresso.action; |
| |
| import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.isAssignableFrom; |
| import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.isDisplayed; |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static com.google.common.base.Preconditions.checkState; |
| import static org.hamcrest.Matchers.allOf; |
| |
| import com.google.android.apps.common.testing.ui.espresso.PerformException; |
| import com.google.android.apps.common.testing.ui.espresso.UiController; |
| import com.google.android.apps.common.testing.ui.espresso.ViewAction; |
| import com.google.android.apps.common.testing.ui.espresso.util.HumanReadables; |
| import com.google.common.base.Optional; |
| import com.google.common.collect.Lists; |
| |
| import android.view.View; |
| import android.widget.Adapter; |
| import android.widget.AdapterView; |
| |
| import org.hamcrest.Matcher; |
| import org.hamcrest.StringDescription; |
| |
| import java.util.List; |
| |
| /** |
| * Forces an AdapterView to ensure that the data matching a provided data matcher |
| * is loaded into the current view hierarchy. |
| * |
| */ |
| public final class AdapterDataLoaderAction implements ViewAction { |
| private final Matcher<Object> dataToLoadMatcher; |
| private final AdapterViewProtocol adapterViewProtocol; |
| private final Optional<Integer> atPosition; |
| private AdapterViewProtocol.AdaptedData adaptedData; |
| private boolean performed = false; |
| private Object dataLock = new Object(); |
| |
| public AdapterDataLoaderAction(Matcher<Object> dataToLoadMatcher, Optional<Integer> atPosition, |
| AdapterViewProtocol adapterViewProtocol) { |
| this.dataToLoadMatcher = checkNotNull(dataToLoadMatcher); |
| this.atPosition = checkNotNull(atPosition); |
| this.adapterViewProtocol = checkNotNull(adapterViewProtocol); |
| } |
| |
| public AdapterViewProtocol.AdaptedData getAdaptedData() { |
| synchronized (dataLock) { |
| checkState(performed, "perform hasn't been called yet!"); |
| return adaptedData; |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| @Override |
| public Matcher<View> getConstraints() { |
| return allOf(isAssignableFrom(AdapterView.class), isDisplayed()); |
| } |
| |
| @SuppressWarnings("unchecked") |
| @Override |
| public void perform(UiController uiController, View view) { |
| AdapterView<? extends Adapter> adapterView = (AdapterView<? extends Adapter>) view; |
| List<AdapterViewProtocol.AdaptedData> matchedDataItems = Lists.newArrayList(); |
| |
| for (AdapterViewProtocol.AdaptedData data : adapterViewProtocol.getDataInAdapterView( |
| adapterView)) { |
| |
| if (dataToLoadMatcher.matches(data.data)) { |
| matchedDataItems.add(data); |
| } |
| } |
| |
| if (matchedDataItems.size() == 0) { |
| StringDescription dataMatcherDescription = new StringDescription(); |
| dataToLoadMatcher.describeTo(dataMatcherDescription); |
| |
| if (matchedDataItems.isEmpty()) { |
| dataMatcherDescription.appendText(" contained values: "); |
| dataMatcherDescription.appendValue( |
| adapterViewProtocol.getDataInAdapterView(adapterView)); |
| throw new PerformException.Builder() |
| .withActionDescription(this.getDescription()) |
| .withViewDescription(HumanReadables.describe(view)) |
| .withCause(new RuntimeException("No data found matching: " + dataMatcherDescription)) |
| .build(); |
| } |
| } |
| |
| synchronized (dataLock) { |
| checkState(!performed, "perform called 2x!"); |
| performed = true; |
| if (atPosition.isPresent()) { |
| int matchedDataItemsSize = matchedDataItems.size() - 1; |
| if (atPosition.get() > matchedDataItemsSize) { |
| throw new PerformException.Builder() |
| .withActionDescription(this.getDescription()) |
| .withViewDescription(HumanReadables.describe(view)) |
| .withCause(new RuntimeException(String.format( |
| "There are only %d elements that matched but requested %d element.", |
| matchedDataItemsSize, atPosition.get()))) |
| .build(); |
| } else { |
| adaptedData = matchedDataItems.get(atPosition.get()); |
| } |
| } else { |
| if (matchedDataItems.size() != 1) { |
| StringDescription dataMatcherDescription = new StringDescription(); |
| dataToLoadMatcher.describeTo(dataMatcherDescription); |
| throw new PerformException.Builder() |
| .withActionDescription(this.getDescription()) |
| .withViewDescription(HumanReadables.describe(view)) |
| .withCause(new RuntimeException("Multiple data elements " + |
| "matched: " + dataMatcherDescription + ". Elements: " + matchedDataItems)) |
| .build(); |
| } else { |
| adaptedData = matchedDataItems.get(0); |
| } |
| } |
| } |
| |
| int requestCount = 0; |
| while (!adapterViewProtocol.isDataRenderedWithinAdapterView(adapterView, adaptedData)) { |
| if (requestCount > 1) { |
| if ((requestCount % 50) == 0) { |
| // sometimes an adapter view will receive an event that will block its attempts to scroll. |
| adapterView.invalidate(); |
| adapterViewProtocol.makeDataRenderedWithinAdapterView(adapterView, adaptedData); |
| } |
| } else { |
| adapterViewProtocol.makeDataRenderedWithinAdapterView(adapterView, adaptedData); |
| } |
| uiController.loopMainThreadForAtLeast(100); |
| requestCount++; |
| } |
| } |
| |
| @Override |
| public String getDescription() { |
| return "load adapter data"; |
| } |
| } |