diff options
Diffstat (limited to 'tests/src/com/android/customization/model/grid')
4 files changed, 458 insertions, 0 deletions
diff --git a/tests/src/com/android/customization/model/grid/data/repository/FakeGridRepository.kt b/tests/src/com/android/customization/model/grid/data/repository/FakeGridRepository.kt new file mode 100644 index 00000000..59539379 --- /dev/null +++ b/tests/src/com/android/customization/model/grid/data/repository/FakeGridRepository.kt @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.customization.model.grid.data.repository + +import com.android.customization.model.grid.shared.model.GridOptionItemModel +import com.android.customization.model.grid.shared.model.GridOptionItemsModel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +class FakeGridRepository( + private val scope: CoroutineScope, + initialOptionCount: Int, + var available: Boolean = true +) : GridRepository { + private val _optionChanges = + MutableSharedFlow<Unit>( + replay = 1, + onBufferOverflow = BufferOverflow.DROP_OLDEST, + ) + + override suspend fun isAvailable(): Boolean = available + + override fun getOptionChanges(): Flow<Unit> = _optionChanges.asSharedFlow() + + private val selectedOptionIndex = MutableStateFlow(0) + private var options: GridOptionItemsModel = createOptions(count = initialOptionCount) + + override suspend fun getOptions(): GridOptionItemsModel { + return options + } + + fun setOptions( + count: Int, + selectedIndex: Int = 0, + ) { + options = createOptions(count, selectedIndex) + _optionChanges.tryEmit(Unit) + } + + private fun createOptions( + count: Int, + selectedIndex: Int = 0, + ): GridOptionItemsModel { + selectedOptionIndex.value = selectedIndex + return GridOptionItemsModel.Loaded( + options = + buildList { + repeat(times = count) { index -> + add( + GridOptionItemModel( + name = "option_$index", + cols = 4, + rows = index * 2, + isSelected = + selectedOptionIndex + .map { it == index } + .stateIn( + scope = scope, + started = SharingStarted.Eagerly, + initialValue = false, + ), + onSelected = { selectedOptionIndex.value = index }, + ) + ) + } + } + ) + } +} diff --git a/tests/src/com/android/customization/model/grid/domain/interactor/GridInteractorTest.kt b/tests/src/com/android/customization/model/grid/domain/interactor/GridInteractorTest.kt new file mode 100644 index 00000000..f73d5a38 --- /dev/null +++ b/tests/src/com/android/customization/model/grid/domain/interactor/GridInteractorTest.kt @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.customization.model.grid.domain.interactor + +import androidx.test.filters.SmallTest +import com.android.customization.model.grid.data.repository.FakeGridRepository +import com.android.customization.model.grid.shared.model.GridOptionItemsModel +import com.android.wallpaper.testing.FakeSnapshotStore +import com.android.wallpaper.testing.collectLastValue +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class GridInteractorTest { + + private lateinit var underTest: GridInteractor + private lateinit var testScope: TestScope + private lateinit var repository: FakeGridRepository + private lateinit var store: FakeSnapshotStore + + @Before + fun setUp() { + testScope = TestScope() + repository = + FakeGridRepository( + scope = testScope.backgroundScope, + initialOptionCount = 3, + ) + store = FakeSnapshotStore() + underTest = + GridInteractor( + applicationScope = testScope.backgroundScope, + repository = repository, + snapshotRestorer = { + GridSnapshotRestorer( + interactor = underTest, + ) + .apply { + runBlocking { + setUpSnapshotRestorer( + store = store, + ) + } + } + }, + ) + } + + @Test + fun selectingOptionThroughModel_updatesOptions() = + testScope.runTest { + val options = collectLastValue(underTest.options) + assertThat(options()).isInstanceOf(GridOptionItemsModel.Loaded::class.java) + (options() as? GridOptionItemsModel.Loaded)?.let { loaded -> + assertThat(loaded.options).hasSize(3) + assertThat(loaded.options[0].isSelected.value).isTrue() + assertThat(loaded.options[1].isSelected.value).isFalse() + assertThat(loaded.options[2].isSelected.value).isFalse() + } + + val storedSnapshot = store.retrieve() + (options() as? GridOptionItemsModel.Loaded)?.let { loaded -> + loaded.options[1].onSelected() + } + + assertThat(options()).isInstanceOf(GridOptionItemsModel.Loaded::class.java) + (options() as? GridOptionItemsModel.Loaded)?.let { loaded -> + assertThat(loaded.options).hasSize(3) + assertThat(loaded.options[0].isSelected.value).isFalse() + assertThat(loaded.options[1].isSelected.value).isTrue() + assertThat(loaded.options[2].isSelected.value).isFalse() + } + assertThat(store.retrieve()).isNotEqualTo(storedSnapshot) + } + + @Test + fun selectingOptionThroughSetter_returnsSelectedOptionFromGetter() = + testScope.runTest { + val options = collectLastValue(underTest.options) + assertThat(options()).isInstanceOf(GridOptionItemsModel.Loaded::class.java) + (options() as? GridOptionItemsModel.Loaded)?.let { loaded -> + assertThat(loaded.options).hasSize(3) + } + + val storedSnapshot = store.retrieve() + (options() as? GridOptionItemsModel.Loaded)?.let { loaded -> + underTest.setSelectedOption(loaded.options[1]) + runCurrent() + assertThat(underTest.getSelectedOption()?.name).isEqualTo(loaded.options[1].name) + assertThat(store.retrieve()).isNotEqualTo(storedSnapshot) + } + } + + @Test + fun externalUpdates_reloadInvoked() = + testScope.runTest { + val options = collectLastValue(underTest.options) + assertThat(options()).isInstanceOf(GridOptionItemsModel.Loaded::class.java) + (options() as? GridOptionItemsModel.Loaded)?.let { loaded -> + assertThat(loaded.options).hasSize(3) + } + + val storedSnapshot = store.retrieve() + repository.setOptions(4) + + assertThat(options()).isInstanceOf(GridOptionItemsModel.Loaded::class.java) + (options() as? GridOptionItemsModel.Loaded)?.let { loaded -> + assertThat(loaded.options).hasSize(4) + } + // External updates do not record a new snapshot with the undo system. + assertThat(store.retrieve()).isEqualTo(storedSnapshot) + } + + @Test + fun unavailableRepository_emptyOptions() = + testScope.runTest { + repository.available = false + val options = collectLastValue(underTest.options) + assertThat(options()).isNull() + } +} diff --git a/tests/src/com/android/customization/model/grid/domain/interactor/GridSnapshotRestorerTest.kt b/tests/src/com/android/customization/model/grid/domain/interactor/GridSnapshotRestorerTest.kt new file mode 100644 index 00000000..c2712b1d --- /dev/null +++ b/tests/src/com/android/customization/model/grid/domain/interactor/GridSnapshotRestorerTest.kt @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.customization.model.grid.domain.interactor + +import androidx.test.filters.SmallTest +import com.android.customization.model.grid.data.repository.FakeGridRepository +import com.android.customization.model.grid.shared.model.GridOptionItemsModel +import com.android.wallpaper.testing.FakeSnapshotStore +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class GridSnapshotRestorerTest { + + private lateinit var underTest: GridSnapshotRestorer + private lateinit var testScope: TestScope + private lateinit var repository: FakeGridRepository + private lateinit var store: FakeSnapshotStore + + @Before + fun setUp() { + testScope = TestScope() + repository = + FakeGridRepository( + scope = testScope.backgroundScope, + initialOptionCount = 4, + ) + underTest = + GridSnapshotRestorer( + interactor = + GridInteractor( + applicationScope = testScope.backgroundScope, + repository = repository, + snapshotRestorer = { underTest }, + ) + ) + store = FakeSnapshotStore() + } + + @Test + fun restoreToSnapshot_noCallsToStore_restoresToInitialSnapshot() = + testScope.runTest { + runCurrent() + val initialSnapshot = underTest.setUpSnapshotRestorer(store = store) + assertThat(initialSnapshot.args).isNotEmpty() + repository.setOptions( + count = 4, + selectedIndex = 2, + ) + runCurrent() + assertThat(getSelectedIndex()).isEqualTo(2) + + underTest.restoreToSnapshot(initialSnapshot) + runCurrent() + + assertThat(getSelectedIndex()).isEqualTo(0) + } + + @Test + fun restoreToSnapshot_withCallToStore_restoresToInitialSnapshot() = + testScope.runTest { + runCurrent() + val initialSnapshot = underTest.setUpSnapshotRestorer(store = store) + assertThat(initialSnapshot.args).isNotEmpty() + repository.setOptions( + count = 4, + selectedIndex = 2, + ) + runCurrent() + assertThat(getSelectedIndex()).isEqualTo(2) + underTest.store((repository.getOptions() as GridOptionItemsModel.Loaded).options[1]) + runCurrent() + + underTest.restoreToSnapshot(initialSnapshot) + runCurrent() + + assertThat(getSelectedIndex()).isEqualTo(0) + } + + private suspend fun getSelectedIndex(): Int { + return (repository.getOptions() as? GridOptionItemsModel.Loaded)?.options?.indexOfFirst { + optionItem -> + optionItem.isSelected.value + } + ?: -1 + } +} diff --git a/tests/src/com/android/customization/model/grid/ui/viewmodel/GridScreenViewModelTest.kt b/tests/src/com/android/customization/model/grid/ui/viewmodel/GridScreenViewModelTest.kt new file mode 100644 index 00000000..58c5d99f --- /dev/null +++ b/tests/src/com/android/customization/model/grid/ui/viewmodel/GridScreenViewModelTest.kt @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.customization.model.grid.ui.viewmodel + +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry +import com.android.customization.model.grid.data.repository.FakeGridRepository +import com.android.customization.model.grid.domain.interactor.GridInteractor +import com.android.customization.model.grid.domain.interactor.GridSnapshotRestorer +import com.android.wallpaper.picker.option.ui.viewmodel.OptionItemViewModel +import com.android.wallpaper.testing.FakeSnapshotStore +import com.android.wallpaper.testing.collectLastValue +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Ignore +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class GridScreenViewModelTest { + + private lateinit var underTest: GridScreenViewModel + private lateinit var testScope: TestScope + private lateinit var interactor: GridInteractor + private lateinit var store: FakeSnapshotStore + + @Before + fun setUp() { + testScope = TestScope() + store = FakeSnapshotStore() + interactor = + GridInteractor( + applicationScope = testScope.backgroundScope, + repository = + FakeGridRepository( + scope = testScope.backgroundScope, + initialOptionCount = 4, + ), + snapshotRestorer = { + GridSnapshotRestorer( + interactor = interactor, + ) + .apply { runBlocking { setUpSnapshotRestorer(store) } } + } + ) + + underTest = + GridScreenViewModel( + context = InstrumentationRegistry.getInstrumentation().targetContext, + interactor = interactor, + ) + } + + @Test + @Ignore("b/270371382") + fun clickOnItem_itGetsSelected() = + testScope.runTest { + val optionItemsValueProvider = collectLastValue(underTest.optionItems) + var optionItemsValue = checkNotNull(optionItemsValueProvider.invoke()) + assertThat(optionItemsValue).hasSize(4) + assertThat(getSelectedIndex(optionItemsValue)).isEqualTo(0) + assertThat(getOnClick(optionItemsValue[0])).isNull() + + val item1OnClickedValue = getOnClick(optionItemsValue[1]) + assertThat(item1OnClickedValue).isNotNull() + item1OnClickedValue?.invoke() + + optionItemsValue = checkNotNull(optionItemsValueProvider.invoke()) + assertThat(optionItemsValue).hasSize(4) + assertThat(getSelectedIndex(optionItemsValue)).isEqualTo(1) + assertThat(getOnClick(optionItemsValue[0])).isNotNull() + assertThat(getOnClick(optionItemsValue[1])).isNull() + } + + private fun TestScope.getSelectedIndex( + optionItems: List<OptionItemViewModel<GridIconViewModel>> + ): Int { + return optionItems.indexOfFirst { optionItem -> + collectLastValue(optionItem.isSelected).invoke() == true + } + } + + private fun TestScope.getOnClick( + optionItem: OptionItemViewModel<GridIconViewModel> + ): (() -> Unit)? { + return collectLastValue(optionItem.onClicked).invoke() + } +} |
