1 /*
2  * Copyright 2020 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 com.google.common.truth.FailureMetadata
20 import com.google.common.truth.Subject
21 import com.google.common.truth.ThrowableSubject
22 import com.google.common.truth.Truth.assertAbout
23 import com.google.common.truth.Truth.assertThat
24 import com.google.common.truth.Truth.assertWithMessage
25 
26 /** Helper subject to write nicer looking ConcatAdapter tests. */
27 internal class ConcatAdapterSubject(metadata: FailureMetadata, private val adapter: ConcatAdapter) :
28     Subject(metadata, adapter) {
hasItemCountnull29     fun hasItemCount(itemCount: Int) {
30         assertThat(adapter.itemCount).isEqualTo(itemCount)
31     }
32 
hasStateRestorationPolicynull33     fun hasStateRestorationPolicy(policy: RecyclerView.Adapter.StateRestorationPolicy) {
34         assertThat(adapter.stateRestorationPolicy).isEqualTo(policy)
35     }
36 
bindViewnull37     fun bindView(recyclerView: RecyclerView, globalPosition: Int): BindingSubject {
38         if (recyclerView.adapter == null) {
39             recyclerView.adapter = adapter
40         } else {
41             check(recyclerView.adapter == adapter) { "recyclerview is bound to another adapter" }
42         }
43         // clear state
44         recyclerView.mState.apply {
45             mItemCount = adapter.itemCount
46             mLayoutStep = RecyclerView.State.STEP_LAYOUT
47         }
48         return assertAbout(BindingSubject.Factory(recyclerView = recyclerView)).that(globalPosition)
49     }
50 
canRestoreStatenull51     fun canRestoreState() {
52         assertThat(adapter.canRestoreState()).isTrue()
53     }
54 
cannotRestoreStatenull55     fun cannotRestoreState() {
56         assertThat(adapter.canRestoreState()).isFalse()
57     }
58 
throwsExceptionnull59     fun throwsException(block: (ConcatAdapter) -> Unit): ThrowableSubject {
60         val result = runCatching { block(adapter) }.exceptionOrNull()
61         assertWithMessage("expected an exception").that(result).isNotNull()
62         return assertThat(result)
63     }
64 
hasItemIdsnull65     fun hasItemIds(expectedIds: IntRange) = hasItemIds(expectedIds.toList())
66 
67     fun hasItemIds(expectedIds: Collection<Int>) {
68         val existingIds = (0 until adapter.itemCount).map { adapter.getItemId(it) }
69         assertThat(existingIds).containsExactlyElementsIn(expectedIds.map { it.toLong() }).inOrder()
70     }
71 
hasStableIdsnull72     fun hasStableIds() {
73         assertWithMessage("should have stable ids").that(adapter.hasStableIds()).isTrue()
74     }
75 
doesNotHaveStableIdsnull76     fun doesNotHaveStableIds() {
77         assertWithMessage("should not have stable ids").that(adapter.hasStableIds()).isFalse()
78     }
79 
80     object Factory : Subject.Factory<ConcatAdapterSubject, ConcatAdapter> {
createSubjectnull81         override fun createSubject(
82             metadata: FailureMetadata,
83             actual: ConcatAdapter
84         ): ConcatAdapterSubject {
85             return ConcatAdapterSubject(metadata = metadata, adapter = actual)
86         }
87     }
88 
89     companion object {
assertThatnull90         fun assertThat(concatAdapter: ConcatAdapter) = assertAbout(Factory).that(concatAdapter)
91     }
92 
93     class BindingSubject(
94         metadata: FailureMetadata,
95         recyclerView: RecyclerView,
96         globalPosition: Int
97     ) : Subject(metadata, globalPosition) {
98         private val viewHolder by lazy {
99             val view = recyclerView.mRecycler.getViewForPosition(globalPosition)
100             val layoutParams = view.layoutParams
101             check(layoutParams is RecyclerView.LayoutParams)
102             val viewHolder = layoutParams.mViewHolder
103             viewHolder as ConcatAdapterTest.ConcatAdapterViewHolder
104         }
105 
106         internal fun verifyBoundTo(
107             adapter: ConcatAdapterTest.NestedTestAdapter,
108             localPosition: Int
109         ) {
110             assertThat(viewHolder.boundItem()).isEqualTo(adapter.getItemAt(localPosition))
111             assertThat(viewHolder.boundLocalPosition()).isEqualTo(localPosition)
112             assertThat(viewHolder.boundAdapter()).isSameInstanceAs(adapter)
113         }
114 
115         class Factory(private val recyclerView: RecyclerView) :
116             Subject.Factory<BindingSubject, Int> {
117             override fun createSubject(
118                 metadata: FailureMetadata,
119                 globalPosition: Int
120             ): BindingSubject {
121                 return BindingSubject(
122                     metadata = metadata,
123                     recyclerView = recyclerView,
124                     globalPosition = globalPosition
125                 )
126             }
127         }
128     }
129 }
130