1 /* 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 17 package androidx.recyclerview.widget; 18 19 import static androidx.recyclerview.widget.ViewInfoStore.InfoRecord.FLAG_APPEAR; 20 import static androidx.recyclerview.widget.ViewInfoStore.InfoRecord.FLAG_DISAPPEARED; 21 import static androidx.recyclerview.widget.ViewInfoStore.InfoRecord.FLAG_POST; 22 import static androidx.recyclerview.widget.ViewInfoStore.InfoRecord.FLAG_PRE; 23 24 import static org.junit.Assert.assertEquals; 25 import static org.junit.Assert.assertFalse; 26 import static org.junit.Assert.assertNotNull; 27 import static org.junit.Assert.assertNull; 28 import static org.junit.Assert.assertSame; 29 import static org.junit.Assert.assertTrue; 30 31 import android.support.test.filters.SmallTest; 32 import android.view.View; 33 34 import androidx.annotation.NonNull; 35 import androidx.annotation.Nullable; 36 import androidx.core.util.Pair; 37 import androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo; 38 import androidx.recyclerview.widget.RecyclerView.ViewHolder; 39 40 import org.junit.Before; 41 import org.junit.Test; 42 import org.junit.runner.RunWith; 43 import org.junit.runners.JUnit4; 44 45 import java.util.ArrayList; 46 import java.util.HashMap; 47 import java.util.List; 48 import java.util.Map; 49 50 @SuppressWarnings("ConstantConditions") 51 @RunWith(JUnit4.class) 52 @SmallTest 53 public class ViewInfoStoreTest { 54 ViewInfoStore mStore; 55 LoggingProcessCallback mCallback; 56 @Before 57 public void prepare() { 58 mStore = new ViewInfoStore(); 59 mCallback = new LoggingProcessCallback(); 60 } 61 62 @Test 63 public void addOverridePre() { 64 RecyclerView.ViewHolder vh = new MockViewHolder(); 65 MockInfo info = new MockInfo(); 66 mStore.addToPreLayout(vh, info); 67 MockInfo info2 = new MockInfo(); 68 mStore.addToPreLayout(vh, info2); 69 assertSame(info2, find(vh, FLAG_PRE)); 70 } 71 72 @Test 73 public void addOverridePost() { 74 RecyclerView.ViewHolder vh = new MockViewHolder(); 75 MockInfo info = new MockInfo(); 76 mStore.addToPostLayout(vh, info); 77 MockInfo info2 = new MockInfo(); 78 mStore.addToPostLayout(vh, info2); 79 assertSame(info2, find(vh, FLAG_POST)); 80 } 81 82 @Test 83 public void addRemoveAndReAdd() { 84 RecyclerView.ViewHolder vh = new MockViewHolder(); 85 MockInfo pre = new MockInfo(); 86 mStore.addToPreLayout(vh, pre); 87 MockInfo post1 = new MockInfo(); 88 mStore.addToPostLayout(vh, post1); 89 mStore.onViewDetached(vh); 90 mStore.addToDisappearedInLayout(vh); 91 } 92 93 @Test 94 public void addToPreLayout() { 95 RecyclerView.ViewHolder vh = new MockViewHolder(); 96 MockInfo info = new MockInfo(); 97 mStore.addToPreLayout(vh, info); 98 assertSame(info, find(vh, FLAG_PRE)); 99 assertTrue(mStore.isInPreLayout(vh)); 100 mStore.removeViewHolder(vh); 101 assertFalse(mStore.isInPreLayout(vh)); 102 } 103 104 @Test 105 public void addToPostLayout() { 106 RecyclerView.ViewHolder vh = new MockViewHolder(); 107 MockInfo info = new MockInfo(); 108 mStore.addToPostLayout(vh, info); 109 assertSame(info, find(vh, FLAG_POST)); 110 mStore.removeViewHolder(vh); 111 assertNull(find(vh, FLAG_POST)); 112 } 113 114 @Test 115 public void popFromPreLayout() { 116 assertEquals(0, sizeOf(FLAG_PRE)); 117 RecyclerView.ViewHolder vh = new MockViewHolder(); 118 MockInfo info = new MockInfo(); 119 mStore.addToPreLayout(vh, info); 120 assertSame(info, mStore.popFromPreLayout(vh)); 121 assertNull(mStore.popFromPreLayout(vh)); 122 } 123 124 @Test 125 public void addToOldChangeHolders() { 126 RecyclerView.ViewHolder vh = new MockViewHolder(); 127 mStore.addToOldChangeHolders(1, vh); 128 assertSame(vh, mStore.getFromOldChangeHolders(1)); 129 mStore.removeViewHolder(vh); 130 assertNull(mStore.getFromOldChangeHolders(1)); 131 } 132 133 @Test 134 public void appearListTests() { 135 RecyclerView.ViewHolder vh = new MockViewHolder(); 136 RecyclerView.ItemAnimator.ItemHolderInfo info = new MockInfo(); 137 mStore.addToAppearedInPreLayoutHolders(vh, info); 138 assertEquals(1, sizeOf(FLAG_APPEAR)); 139 RecyclerView.ViewHolder vh2 = new MockViewHolder(); 140 mStore.addToAppearedInPreLayoutHolders(vh2, info); 141 assertEquals(2, sizeOf(FLAG_APPEAR)); 142 mStore.removeViewHolder(vh2); 143 assertEquals(1, sizeOf(FLAG_APPEAR)); 144 } 145 146 @Test 147 public void disappearListTest() { 148 RecyclerView.ViewHolder vh = new MockViewHolder(); 149 mStore.addToDisappearedInLayout(vh); 150 assertEquals(1, sizeOf(FLAG_DISAPPEARED)); 151 mStore.addToDisappearedInLayout(vh); 152 assertEquals(1, sizeOf(FLAG_DISAPPEARED)); 153 RecyclerView.ViewHolder vh2 = new MockViewHolder(); 154 mStore.addToDisappearedInLayout(vh2); 155 assertEquals(2, sizeOf(FLAG_DISAPPEARED)); 156 mStore.removeViewHolder(vh2); 157 assertEquals(1, sizeOf(FLAG_DISAPPEARED)); 158 mStore.removeFromDisappearedInLayout(vh); 159 assertEquals(0, sizeOf(FLAG_DISAPPEARED)); 160 } 161 162 @Test 163 public void processAppear() { 164 ViewHolder vh = new MockViewHolder(); 165 MockInfo info = new MockInfo(); 166 mStore.addToPostLayout(vh, info); 167 mStore.process(mCallback); 168 assertEquals(new Pair<>(null, info), mCallback.appeared.get(vh)); 169 assertTrue(mCallback.disappeared.isEmpty()); 170 assertTrue(mCallback.unused.isEmpty()); 171 assertTrue(mCallback.persistent.isEmpty()); 172 } 173 174 @Test 175 public void processDisappearNormal() { 176 ViewHolder vh = new MockViewHolder(); 177 MockInfo info = new MockInfo(); 178 mStore.addToPreLayout(vh, info); 179 mStore.process(mCallback); 180 assertEquals(new Pair<>(info, null), mCallback.disappeared.get(vh)); 181 assertTrue(mCallback.appeared.isEmpty()); 182 assertTrue(mCallback.unused.isEmpty()); 183 assertTrue(mCallback.persistent.isEmpty()); 184 } 185 186 @Test 187 public void processDisappearMissingLayout() { 188 ViewHolder vh = new MockViewHolder(); 189 MockInfo info = new MockInfo(); 190 mStore.addToPreLayout(vh, info); 191 mStore.addToDisappearedInLayout(vh); 192 mStore.process(mCallback); 193 assertEquals(new Pair<>(info, null), mCallback.disappeared.get(vh)); 194 assertTrue(mCallback.appeared.isEmpty()); 195 assertTrue(mCallback.unused.isEmpty()); 196 assertTrue(mCallback.persistent.isEmpty()); 197 } 198 199 @Test 200 public void processDisappearMoveOut() { 201 ViewHolder vh = new MockViewHolder(); 202 MockInfo pre = new MockInfo(); 203 MockInfo post = new MockInfo(); 204 mStore.addToPreLayout(vh, pre); 205 mStore.addToDisappearedInLayout(vh); 206 mStore.addToPostLayout(vh, post); 207 mStore.process(mCallback); 208 assertEquals(new Pair<>(pre, post), mCallback.disappeared.get(vh)); 209 assertTrue(mCallback.appeared.isEmpty()); 210 assertTrue(mCallback.unused.isEmpty()); 211 assertTrue(mCallback.persistent.isEmpty()); 212 } 213 214 @Test 215 public void processDisappearAppear() { 216 ViewHolder vh = new MockViewHolder(); 217 MockInfo pre = new MockInfo(); 218 MockInfo post = new MockInfo(); 219 mStore.addToPreLayout(vh, pre); 220 mStore.addToDisappearedInLayout(vh); 221 mStore.addToPostLayout(vh, post); 222 mStore.removeFromDisappearedInLayout(vh); 223 mStore.process(mCallback); 224 assertTrue(mCallback.disappeared.isEmpty()); 225 assertTrue(mCallback.appeared.isEmpty()); 226 assertTrue(mCallback.unused.isEmpty()); 227 assertEquals(mCallback.persistent.get(vh), new Pair<>(pre, post)); 228 } 229 230 @Test 231 public void processAppearAndDisappearInPostLayout() { 232 ViewHolder vh = new MockViewHolder(); 233 MockInfo info1 = new MockInfo(); 234 mStore.addToPostLayout(vh, info1); 235 mStore.addToDisappearedInLayout(vh); 236 mStore.process(mCallback); 237 assertTrue(mCallback.disappeared.isEmpty()); 238 assertTrue(mCallback.appeared.isEmpty()); 239 assertTrue(mCallback.persistent.isEmpty()); 240 assertSame(mCallback.unused.get(0), vh); 241 } 242 243 static class MockViewHolder extends RecyclerView.ViewHolder { 244 public MockViewHolder() { 245 super(new View(null)); 246 } 247 } 248 249 static class MockInfo extends RecyclerView.ItemAnimator.ItemHolderInfo { 250 251 } 252 253 private int sizeOf(int flags) { 254 int cnt = 0; 255 final int size = mStore.mLayoutHolderMap.size(); 256 for (int i = 0; i < size; i ++) { 257 ViewInfoStore.InfoRecord record = mStore.mLayoutHolderMap.valueAt(i); 258 if ((record.flags & flags) != 0) { 259 cnt ++; 260 } 261 } 262 return cnt; 263 } 264 265 private RecyclerView.ItemAnimator.ItemHolderInfo find(RecyclerView.ViewHolder viewHolder, 266 int flags) { 267 final int size = mStore.mLayoutHolderMap.size(); 268 for (int i = 0; i < size; i ++) { 269 ViewInfoStore.InfoRecord record = mStore.mLayoutHolderMap.valueAt(i); 270 RecyclerView.ViewHolder holder = mStore.mLayoutHolderMap.keyAt(i); 271 if ((record.flags & flags) != 0 && holder == viewHolder) { 272 if (flags == FLAG_PRE || flags == FLAG_APPEAR) { 273 return record.preInfo; 274 } else if (flags == FLAG_POST) { 275 return record.postInfo; 276 } 277 throw new UnsupportedOperationException("don't know this flag"); 278 } 279 } 280 return null; 281 } 282 283 private static class LoggingProcessCallback implements ViewInfoStore.ProcessCallback { 284 final Map<ViewHolder, Pair<ItemHolderInfo, ItemHolderInfo>> disappeared = new HashMap<>(); 285 final Map<ViewHolder, Pair<ItemHolderInfo, ItemHolderInfo>> appeared = new HashMap<>(); 286 final Map<ViewHolder, Pair<ItemHolderInfo, ItemHolderInfo>> persistent = new HashMap<>(); 287 final List<ViewHolder> unused = new ArrayList<>(); 288 @Override 289 public void processDisappeared(ViewHolder viewHolder, 290 ItemHolderInfo preInfo, 291 @Nullable ItemHolderInfo postInfo) { 292 assertNotNull(preInfo); 293 assertFalse(disappeared.containsKey(viewHolder)); 294 disappeared.put(viewHolder, new Pair<>(preInfo, postInfo)); 295 } 296 297 @Override 298 public void processAppeared(ViewHolder viewHolder, 299 @Nullable ItemHolderInfo preInfo, @NonNull ItemHolderInfo info) { 300 assertNotNull(info); 301 assertFalse(appeared.containsKey(viewHolder)); 302 appeared.put(viewHolder, new Pair<>(preInfo, info)); 303 } 304 305 @Override 306 public void processPersistent(ViewHolder viewHolder, 307 @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) { 308 assertFalse(persistent.containsKey(viewHolder)); 309 assertNotNull(preInfo); 310 assertNotNull(postInfo); 311 persistent.put(viewHolder, new Pair<>(preInfo, postInfo)); 312 } 313 314 @Override 315 public void unused(ViewHolder holder) { 316 unused.add(holder); 317 } 318 } 319 320 } 321