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