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