blob: 9c90d244c7c971501ba4b5c10776a9cde70094a8 [file] [log] [blame]
Justin Klaassen4d01eea2018-04-03 23:21:57 -04001/*
2 * Copyright 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package androidx.recyclerview.widget;
18
19import static androidx.recyclerview.widget.ItemTouchHelper.END;
20import static androidx.recyclerview.widget.ItemTouchHelper.LEFT;
21import static androidx.recyclerview.widget.ItemTouchHelper.RIGHT;
22import static androidx.recyclerview.widget.ItemTouchHelper.START;
23import static androidx.recyclerview.widget.ItemTouchHelper.SimpleCallback;
24
25import static org.junit.Assert.assertEquals;
26import static org.junit.Assert.assertNotNull;
27import static org.junit.Assert.assertTrue;
28
29import android.os.Build;
30import android.support.test.filters.LargeTest;
31import android.support.test.filters.SdkSuppress;
32import android.support.test.filters.Suppress;
33import android.support.test.runner.AndroidJUnit4;
34import android.view.Gravity;
35import android.view.View;
36
37import androidx.annotation.NonNull;
38import androidx.core.util.Pair;
39import androidx.testutils.PollingCheck;
40
41import org.junit.Test;
42import org.junit.runner.RunWith;
43
44import java.util.ArrayList;
45import java.util.List;
46
47@LargeTest
48@RunWith(AndroidJUnit4.class)
49public class ItemTouchHelperTest extends BaseRecyclerViewInstrumentationTest {
50
51 private static class RecyclerViewState {
52 public TestAdapter mAdapter;
53 public TestLayoutManager mLayoutManager;
54 public WrappedRecyclerView mWrappedRecyclerView;
55 }
56
57 private LoggingCalback mCalback;
58
59 private LoggingItemTouchHelper mItemTouchHelper;
60
61 private Boolean mSetupRTL;
62
63 public ItemTouchHelperTest() {
64 super(false);
65 }
66
67 private RecyclerViewState setupRecyclerView() throws Throwable {
68 RecyclerViewState rvs = new RecyclerViewState();
69 rvs.mWrappedRecyclerView = inflateWrappedRV();
70 rvs.mAdapter = new TestAdapter(10);
71 rvs.mLayoutManager = new TestLayoutManager() {
72 @Override
73 public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
74 detachAndScrapAttachedViews(recycler);
75 layoutRange(recycler, 0, Math.min(5, state.getItemCount()));
76 layoutLatch.countDown();
77 }
78
79 @Override
80 public boolean canScrollHorizontally() {
81 return false;
82 }
83
84 @Override
85 public boolean supportsPredictiveItemAnimations() {
86 return false;
87 }
88 };
89 rvs.mWrappedRecyclerView.setFakeRTL(mSetupRTL);
90 rvs.mWrappedRecyclerView.setAdapter(rvs.mAdapter);
91 rvs.mWrappedRecyclerView.setLayoutManager(rvs.mLayoutManager);
92 return rvs;
93 }
94
95 private RecyclerViewState setupItemTouchHelper(final RecyclerViewState rvs, int dragDirs,
96 int swipeDirs) throws Throwable {
97 mCalback = new LoggingCalback(dragDirs, swipeDirs);
98 mItemTouchHelper = new LoggingItemTouchHelper(mCalback);
99 mActivityRule.runOnUiThread(new Runnable() {
100 @Override
101 public void run() {
102 mItemTouchHelper.attachToRecyclerView(rvs.mWrappedRecyclerView);
103 }
104 });
105
106 return rvs;
107 }
108
109 @Test
110 public void swipeLeft() throws Throwable {
111 basicSwipeTest(LEFT, LEFT | RIGHT, -getActivity().getWindow().getDecorView().getWidth());
112 }
113
114 @Test
115 public void swipeRight() throws Throwable {
116 basicSwipeTest(RIGHT, LEFT | RIGHT, getActivity().getWindow().getDecorView().getWidth());
117 }
118
119 @Test
120 public void swipeStart() throws Throwable {
121 basicSwipeTest(START, START | END, -getActivity().getWindow().getDecorView().getWidth());
122 }
123
124 @Test
125 public void swipeEnd() throws Throwable {
126 basicSwipeTest(END, START | END, getActivity().getWindow().getDecorView().getWidth());
127 }
128
129 // Test is disabled as it is flaky.
130 @Suppress
131 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.JELLY_BEAN_MR1)
132 @Test
133 public void swipeStartInRTL() throws Throwable {
134 mSetupRTL = true;
135 basicSwipeTest(START, START | END, getActivity().getWindow().getDecorView().getWidth());
136 }
137
138 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.JELLY_BEAN_MR1)
139 @Test
140 public void swipeEndInRTL() throws Throwable {
141 mSetupRTL = true;
142 basicSwipeTest(END, START | END, -getActivity().getWindow().getDecorView().getWidth());
143 }
144
145 @Test
146 public void attachToNullRecycleViewDuringLongPress() throws Throwable {
147 final RecyclerViewState rvs = setupItemTouchHelper(setupRecyclerView(), END, 0);
148 rvs.mLayoutManager.expectLayouts(1);
149 setRecyclerView(rvs.mWrappedRecyclerView);
150 rvs.mLayoutManager.waitForLayout(1);
151
152 final RecyclerView.ViewHolder target = mRecyclerView
153 .findViewHolderForAdapterPosition(1);
154 target.itemView.setOnLongClickListener(new View.OnLongClickListener() {
155 @Override
156 public boolean onLongClick(View v) {
157 mItemTouchHelper.attachToRecyclerView(null);
158 return false;
159 }
160 });
161 TouchUtils.longClickView(getInstrumentation(), target.itemView);
162 }
163
164 @Test
165 public void attachToAnotherRecycleViewDuringLongPress() throws Throwable {
166 final RecyclerViewState rvs2 = setupRecyclerView();
167 rvs2.mLayoutManager.expectLayouts(1);
168 mActivityRule.runOnUiThread(new Runnable() {
169 @Override
170 public void run() {
171 getActivity().getContainer().addView(rvs2.mWrappedRecyclerView);
172 }
173 });
174 rvs2.mLayoutManager.waitForLayout(1);
175
176 final RecyclerViewState rvs = setupItemTouchHelper(setupRecyclerView(), END, 0);
177 rvs.mLayoutManager.expectLayouts(1);
178 setRecyclerView(rvs.mWrappedRecyclerView);
179 rvs.mLayoutManager.waitForLayout(1);
180
181 final RecyclerView.ViewHolder target = mRecyclerView
182 .findViewHolderForAdapterPosition(1);
183 target.itemView.setOnLongClickListener(new View.OnLongClickListener() {
184 @Override
185 public boolean onLongClick(View v) {
186 mItemTouchHelper.attachToRecyclerView(rvs2.mWrappedRecyclerView);
187 return false;
188 }
189 });
190 TouchUtils.longClickView(getInstrumentation(), target.itemView);
191 assertEquals(0, mCalback.mHasDragFlag.size());
192 }
193
194 public void basicSwipeTest(int dir, int swipeDirs, int targetX) throws Throwable {
195 final RecyclerViewState rvs = setupItemTouchHelper(setupRecyclerView(), 0, swipeDirs);
196 rvs.mLayoutManager.expectLayouts(1);
197 setRecyclerView(rvs.mWrappedRecyclerView);
198 rvs.mLayoutManager.waitForLayout(1);
199
200 final RecyclerView.ViewHolder target = mRecyclerView
201 .findViewHolderForAdapterPosition(1);
202 TouchUtils.dragViewToX(getInstrumentation(), target.itemView, Gravity.CENTER, targetX);
203
204 PollingCheck.waitFor(1000, new PollingCheck.PollingCheckCondition() {
205 @Override
206 public boolean canProceed() {
207 return mCalback.getSwipe(target) != null;
208 }
209 });
210 final SwipeRecord swipe = mCalback.getSwipe(target);
211 assertNotNull(swipe);
212 assertEquals(dir, swipe.dir);
213 assertEquals(1, mItemTouchHelper.mRecoverAnimations.size());
214 assertEquals(1, mItemTouchHelper.mPendingCleanup.size());
215 // get rid of the view
216 rvs.mLayoutManager.expectLayouts(1);
217 rvs.mAdapter.deleteAndNotify(1, 1);
218 rvs.mLayoutManager.waitForLayout(1);
219 waitForAnimations();
220 assertEquals(0, mItemTouchHelper.mRecoverAnimations.size());
221 assertEquals(0, mItemTouchHelper.mPendingCleanup.size());
222 assertTrue(mCalback.isCleared(target));
223 }
224
225 private void waitForAnimations() throws InterruptedException {
226 while (mRecyclerView.getItemAnimator().isRunning()) {
227 Thread.sleep(100);
228 }
229 }
230
231 private static class LoggingCalback extends SimpleCallback {
232
233 private List<MoveRecord> mMoveRecordList = new ArrayList<MoveRecord>();
234
235 private List<SwipeRecord> mSwipeRecords = new ArrayList<SwipeRecord>();
236
237 private List<RecyclerView.ViewHolder> mCleared = new ArrayList<RecyclerView.ViewHolder>();
238
239 public List<Pair<RecyclerView, RecyclerView.ViewHolder>> mHasDragFlag = new ArrayList<>();
240
241 LoggingCalback(int dragDirs, int swipeDirs) {
242 super(dragDirs, swipeDirs);
243 }
244
245 @Override
246 public boolean onMove(@NonNull RecyclerView recyclerView,
247 @NonNull RecyclerView.ViewHolder viewHolder,
248 @NonNull RecyclerView.ViewHolder target) {
249 mMoveRecordList.add(new MoveRecord(viewHolder, target));
250 return true;
251 }
252
253 @Override
254 public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
255 mSwipeRecords.add(new SwipeRecord(viewHolder, direction));
256 }
257
258 public MoveRecord getMove(RecyclerView.ViewHolder vh) {
259 for (MoveRecord move : mMoveRecordList) {
260 if (move.from == vh) {
261 return move;
262 }
263 }
264 return null;
265 }
266
267 @Override
268 public void clearView(@NonNull RecyclerView recyclerView,
269 @NonNull RecyclerView.ViewHolder viewHolder) {
270 super.clearView(recyclerView, viewHolder);
271 mCleared.add(viewHolder);
272 }
273
274 @Override
275 boolean hasDragFlag(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
276 mHasDragFlag.add(new Pair<>(recyclerView, viewHolder));
277 return super.hasDragFlag(recyclerView, viewHolder);
278 }
279
280 public SwipeRecord getSwipe(RecyclerView.ViewHolder vh) {
281 for (SwipeRecord swipe : mSwipeRecords) {
282 if (swipe.viewHolder == vh) {
283 return swipe;
284 }
285 }
286 return null;
287 }
288
289 public boolean isCleared(RecyclerView.ViewHolder vh) {
290 return mCleared.contains(vh);
291 }
292 }
293
294 private static class LoggingItemTouchHelper extends ItemTouchHelper {
295
296 public LoggingItemTouchHelper(Callback callback) {
297 super(callback);
298 }
299 }
300
301 private static class SwipeRecord {
302
303 RecyclerView.ViewHolder viewHolder;
304
305 int dir;
306
307 public SwipeRecord(RecyclerView.ViewHolder viewHolder, int dir) {
308 this.viewHolder = viewHolder;
309 this.dir = dir;
310 }
311 }
312
313 private static class MoveRecord {
314
315 final int fromPos, toPos;
316
317 RecyclerView.ViewHolder from, to;
318
319 MoveRecord(RecyclerView.ViewHolder from, RecyclerView.ViewHolder to) {
320 this.from = from;
321 this.to = to;
322 fromPos = from.getAdapterPosition();
323 toPos = to.getAdapterPosition();
324 }
325 }
326}