diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6e7405c4..d326c739 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,65 +25,20 @@ jobs: - name: Run Unit Tests run: ./gradlew testDebug - - name: Upload test results - if: always() - uses: actions/upload-artifact@v3 - with: - name: test-results - path: app/build/reports/ - - name: Assemble env: VERSION_CODE: ${{ github.run_number }} run: ./gradlew assembleDebug assembleDebugAndroidTest - - name: Decode Firebase Service Account JSON - run: echo ${{ secrets.FIREBASE_SERVICE_ACCOUNT_BASE_64 }} | base64 -d > service_account.json - - - name: 'Run Device Tests on Firebase Test Lab' - run: | - gcloud auth login --cred-file=service_account.json - gcloud config set project vocable-fcb07 - gcloud firebase test android run \ - --app app/build/outputs/apk/debug/app-debug.apk \ - --test app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk \ - --device version=34,orientation=portrait,model=MediumPhone.arm,locale=en_US \ - --test-runner-class com.willowtree.vocable.utility.VocableTestRunner - - - name: Decode Keystore - if: github.event_name != 'pull_request' - env: - ENCODED_RELEASE_KEYSTORE: ${{ secrets.ENCODED_RELEASE_KEYSTORE }} - run: echo $ENCODED_RELEASE_KEYSTORE | base64 -d > keystore - - - name: Build & Assemble Release APK - if: github.event_name != 'pull_request' - env: - VERSION_CODE: ${{ github.run_number }} - RELEASE_KEY_ALIAS: ${{ secrets.RELEASE_KEY_ALIAS }} - RELEASE_KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }} - run: | - ./gradlew assembleRelease - - - name: Install Firebase CLI - if: github.event_name != 'pull_request' - run: npm install -g firebase-tools - - - name: Decode Firebase Service Account JSON - if: github.event_name != 'pull_request' - run: echo ${{ secrets.FIREBASE_SERVICE_ACCOUNT_BASE_64 }} | base64 -d > service_account.json - - - name: Deploy to Firebase - if: github.event_name != 'pull_request' - env: - GOOGLE_APPLICATION_CREDENTIALS: service_account.json - run: firebase appdistribution:distribute app/build/outputs/apk/release/app-release.apk --app ${{ secrets.FIREBASE_APP_ID }} --groups "internal" - - - name: Upload to Play Store - if: github.event_name != 'pull_request' - uses: r0adkll/upload-google-play@v1 + - uses: actions/upload-artifact@v4 with: - serviceAccountJsonPlainText: ${{ secrets.PLAY_STORE_CREDENTIALS }} - packageName: com.willowtree.vocable - releaseFiles: app/build/outputs/apk/release/app-release.apk - track: alpha \ No newline at end of file + name: apk + path: | + app/build/outputs/apk/debug/app-debug.apk + app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk + + pre-release-upload: + needs: build + if: github.event_name != 'pull_request' + uses: ./.github/workflows/pre-release-upload.yml + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/device-tests.yml b/.github/workflows/device-tests.yml new file mode 100644 index 00000000..cf8ed55c --- /dev/null +++ b/.github/workflows/device-tests.yml @@ -0,0 +1,42 @@ +name: Run Device Tests +# Run device tests using Firebase Test Lab +# +# This action is separated so that it can run on PRs coming from forks, without +# giving those forks access to our repository secrets, which are necessary to run against Firebase. +# There is also additional configuration so this workflow can be reused in our main workflow and block +# deployments on UI test results. + +on: + workflow_run: + workflows: ["Build & Test Project"] + branches-ignore: [main] + types: + - completed + workflow_call: + +jobs: + device-tests: + runs-on: ubuntu-20.04 + if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name != 'workflow_run' }} + steps: + - uses: haya14busa/action-workflow_run-status@v1 + if: ${{ github.event_name == 'workflow_run' }} + - name: Set RUN_ID + run: echo "RUN_ID=${{ github.event_name == 'workflow_run' && github.event.workflow_run.id || github.run_id }}" >> $GITHUB_ENV + - uses: actions/download-artifact@v4 + with: + run-id: ${{ env.RUN_ID }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Decode Firebase Service Account JSON + run: echo ${{ secrets.FIREBASE_SERVICE_ACCOUNT_BASE_64 }} | base64 -d > service_account.json + + - name: 'Run Device Tests on Firebase Test Lab' + run: | + gcloud auth login --cred-file=service_account.json + gcloud config set project vocable-fcb07 + gcloud firebase test android run \ + --app apk/debug/app-debug.apk \ + --test apk/androidTest/debug/app-debug-androidTest.apk \ + --device version=34,orientation=portrait,model=MediumPhone.arm,locale=en_US \ + --test-runner-class com.willowtree.vocable.utility.VocableTestRunner \ No newline at end of file diff --git a/.github/workflows/pre-release-upload.yml b/.github/workflows/pre-release-upload.yml new file mode 100644 index 00000000..16a08d1b --- /dev/null +++ b/.github/workflows/pre-release-upload.yml @@ -0,0 +1,55 @@ +name: Pre-Release Upload + +on: + workflow_call: + +jobs: + device-tests: + uses: ./.github/workflows/device-tests.yml + secrets: inherit + + pre-release-upload: + needs: device-tests + runs-on: ubuntu-20.04 + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set Up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + cache: gradle + + - name: Decode Keystore + env: + ENCODED_RELEASE_KEYSTORE: ${{ secrets.ENCODED_RELEASE_KEYSTORE }} + run: echo $ENCODED_RELEASE_KEYSTORE | base64 -d > keystore + + - name: Build & Assemble Release APK + env: + VERSION_CODE: ${{ github.run_number }} + RELEASE_KEY_ALIAS: ${{ secrets.RELEASE_KEY_ALIAS }} + RELEASE_KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }} + run: | + ./gradlew assembleRelease + + - name: Install Firebase CLI + run: npm install -g firebase-tools + + - name: Decode Firebase Service Account JSON + run: echo ${{ secrets.FIREBASE_SERVICE_ACCOUNT_BASE_64 }} | base64 -d > service_account.json + + - name: Deploy to Firebase + env: + GOOGLE_APPLICATION_CREDENTIALS: service_account.json + run: firebase appdistribution:distribute app/build/outputs/apk/release/app-release.apk --app ${{ secrets.FIREBASE_APP_ID }} --groups "internal" + + - name: Upload to Play Store + uses: r0adkll/upload-google-play@v1 + with: + serviceAccountJsonPlainText: ${{ secrets.PLAY_STORE_CREDENTIALS }} + packageName: com.willowtree.vocable + releaseFiles: app/build/outputs/apk/release/app-release.apk + track: alpha \ No newline at end of file diff --git a/README.md b/README.md index c137adbc..83649de4 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Use a list of common phrases provided by speech language pathologists, or create Type with your head or your hands. ## Roadmap -For the current progress on features, please visit the [project board](https://github.com/willowtreeapps/vocable-android/projects/1). +For the current progress on features, please visit the [project board](https://github.com/orgs/willowtreeapps/projects/50/views/1). For a high-level roadmap, see the [Vocable Roadmap](./ROADMAP.md) diff --git a/app/build.gradle b/app/build.gradle index 97754b2a..d9645f85 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -113,6 +113,7 @@ dependencies { testImplementation 'junit:junit:4.13.2' testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version" testImplementation "androidx.arch.core:core-testing:2.2.0" + testImplementation(libs.turbine) androidTestImplementation "androidx.room:room-testing:2.2.5" androidTestImplementation 'androidx.test:runner:1.4.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' @@ -123,6 +124,7 @@ dependencies { androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version" androidTestImplementation 'androidx.test:rules:1.4.0' androidTestImplementation "androidx.arch.core:core-testing:2.2.0" + androidTestImplementation(libs.turbine) // Testing Navigation androidTestImplementation "androidx.navigation:navigation-testing:2.3.1" diff --git a/app/src/androidTest/java/com/willowtree/vocable/screens/MainScreen.kt b/app/src/androidTest/java/com/willowtree/vocable/screens/MainScreen.kt index 9a376fe8..fce2e51f 100644 --- a/app/src/androidTest/java/com/willowtree/vocable/screens/MainScreen.kt +++ b/app/src/androidTest/java/com/willowtree/vocable/screens/MainScreen.kt @@ -47,7 +47,6 @@ class MainScreen { onView(withText(phrase)).check(matches(isDisplayed())) } } - // Taps on the selected phrase fun tapPhrase(phraseText: String) { onView(withText(phraseText)).tap() diff --git a/app/src/androidTest/java/com/willowtree/vocable/tests/MainScreenTest.kt b/app/src/androidTest/java/com/willowtree/vocable/tests/MainScreenTest.kt index e79c3bc4..29eac0e5 100644 --- a/app/src/androidTest/java/com/willowtree/vocable/tests/MainScreenTest.kt +++ b/app/src/androidTest/java/com/willowtree/vocable/tests/MainScreenTest.kt @@ -1,11 +1,8 @@ package com.willowtree.vocable.tests import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.rule.GrantPermissionRule import com.willowtree.vocable.screens.MainScreen import com.willowtree.vocable.utility.assertTextMatches -import org.junit.Ignore -import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith diff --git a/app/src/main/java/com/willowtree/vocable/presets/PresetsViewModel.kt b/app/src/main/java/com/willowtree/vocable/presets/PresetsViewModel.kt index c3faf183..edbccd83 100644 --- a/app/src/main/java/com/willowtree/vocable/presets/PresetsViewModel.kt +++ b/app/src/main/java/com/willowtree/vocable/presets/PresetsViewModel.kt @@ -34,7 +34,20 @@ class PresetsViewModel( categoriesUseCase.categories(), liveSelectedCategoryId ) { categories, selectedId -> - categories.find { it.categoryId == selectedId } + val currentCategory = categories.find { it.categoryId == selectedId } + if (currentCategory?.hidden == true) { + val newSortOrder = (currentCategory.sortOrder + 1) + val newCategory = categories.find { it.sortOrder == newSortOrder} + if (newCategory != null) { + liveSelectedCategoryId.update { newCategory.categoryId } + categories.find { it.sortOrder == newSortOrder} + } else { + liveSelectedCategoryId.update { categories.first().categoryId } + categories.first() + } + } else { + currentCategory + } }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000L), null) val selectedCategoryLiveData: LiveData = selectedCategory.asLiveData() diff --git a/app/src/main/java/com/willowtree/vocable/settings/EditCategoriesFragment.kt b/app/src/main/java/com/willowtree/vocable/settings/EditCategoriesFragment.kt index bf5ba55d..c840c491 100644 --- a/app/src/main/java/com/willowtree/vocable/settings/EditCategoriesFragment.kt +++ b/app/src/main/java/com/willowtree/vocable/settings/EditCategoriesFragment.kt @@ -4,6 +4,9 @@ import android.os.Bundle import android.view.View import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.fragment.findNavController import androidx.viewpager2.widget.ViewPager2 import com.willowtree.vocable.BaseFragment @@ -13,6 +16,7 @@ import com.willowtree.vocable.R import com.willowtree.vocable.databinding.FragmentEditCategoriesBinding import com.willowtree.vocable.presets.Category import com.willowtree.vocable.utils.VocableFragmentStateAdapter +import kotlinx.coroutines.launch import org.koin.androidx.viewmodel.ViewModelOwner import org.koin.androidx.viewmodel.ext.android.viewModel import kotlin.math.min @@ -107,9 +111,13 @@ class EditCategoriesFragment : BaseFragment() { } private fun subscribeToViewModel() { - editCategoriesViewModel.categoryList.observe(viewLifecycleOwner) { - it?.let { categories -> - categoriesAdapter.setItems(categories) + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + editCategoriesViewModel.categoryList.collect { + it.let { categories -> + categoriesAdapter.setItems(categories) + } + } } } diff --git a/app/src/main/java/com/willowtree/vocable/settings/EditCategoriesListFragment.kt b/app/src/main/java/com/willowtree/vocable/settings/EditCategoriesListFragment.kt index 0ed08a0b..155df677 100644 --- a/app/src/main/java/com/willowtree/vocable/settings/EditCategoriesListFragment.kt +++ b/app/src/main/java/com/willowtree/vocable/settings/EditCategoriesListFragment.kt @@ -4,6 +4,9 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.fragment.findNavController import com.willowtree.vocable.BaseFragment import com.willowtree.vocable.BindingInflater @@ -12,6 +15,7 @@ import com.willowtree.vocable.databinding.CategoryEditButtonBinding import com.willowtree.vocable.databinding.FragmentEditCategoriesListBinding import com.willowtree.vocable.presets.Category import com.willowtree.vocable.utils.locale.LocalizedResourceUtility +import kotlinx.coroutines.launch import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ViewModelOwner import org.koin.androidx.viewmodel.ext.android.viewModel @@ -101,22 +105,26 @@ class EditCategoriesListFragment : BaseFragment - list?.let { overallList -> - val hiddenCategories = overallList.filter { it.hidden } - if (endPosition > overallList.size) { - endPosition = overallList.size - 1 - } - if (startPosition <= endPosition) { - overallList.subList(startPosition, endPosition) - .forEachIndexed { index, category -> - bindCategoryEditButton( - editButtonList[index], - category, - startPosition + index, - overallList.size - hiddenCategories.size - ) + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + editCategoriesViewModel.categoryList.collect { list -> + list.let { overallList -> + val hiddenCategories = overallList.filter { it.hidden } + if (endPosition > overallList.size) { + endPosition = overallList.size - 1 + } + if (startPosition <= endPosition) { + overallList.subList(startPosition, endPosition) + .forEachIndexed { index, category -> + bindCategoryEditButton( + editButtonList[index], + category, + startPosition + index, + overallList.size - hiddenCategories.size + ) + } } + } } } } diff --git a/app/src/main/java/com/willowtree/vocable/settings/EditCategoriesViewModel.kt b/app/src/main/java/com/willowtree/vocable/settings/EditCategoriesViewModel.kt index 7e0cde26..e8b7338d 100644 --- a/app/src/main/java/com/willowtree/vocable/settings/EditCategoriesViewModel.kt +++ b/app/src/main/java/com/willowtree/vocable/settings/EditCategoriesViewModel.kt @@ -3,26 +3,18 @@ package com.willowtree.vocable.settings import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import com.willowtree.vocable.ICategoriesUseCase import com.willowtree.vocable.presets.Category import com.willowtree.vocable.room.CategorySortOrder -import com.willowtree.vocable.settings.editcategories.EditCategoriesPage import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch class EditCategoriesViewModel( private val categoriesUseCase: ICategoriesUseCase ) : ViewModel() { - val categoryList: LiveData> = categoriesUseCase.categories().asLiveData() - - val categoryPages = categoriesUseCase.categories().map { categories -> - val pageSize = 8 - categories.chunked(pageSize).map { EditCategoriesPage(it) } - } + val categoryList = categoriesUseCase.categories() private val liveLastViewedIndex = MutableLiveData() val lastViewedIndex: LiveData = liveLastViewedIndex diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 4b660621..e2f8e9d2 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -238,7 +238,7 @@ 44dp 20dp 0dp - 10dp + 5dp 20dp diff --git a/app/src/test/java/com/willowtree/vocable/presets/PresetsViewModelTest.kt b/app/src/test/java/com/willowtree/vocable/presets/PresetsViewModelTest.kt index 46cf498d..c44a7832 100644 --- a/app/src/test/java/com/willowtree/vocable/presets/PresetsViewModelTest.kt +++ b/app/src/test/java/com/willowtree/vocable/presets/PresetsViewModelTest.kt @@ -114,6 +114,148 @@ class PresetsViewModelTest { ) } + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun `selected category is hidden and next immediate category is shown`() = runTest(UnconfinedTestDispatcher()) { + fakeCategoriesUseCase._categories.update { + listOf( + Category.StoredCategory( + categoryId = "1", + localizedName = LocalesWithText(mapOf("en_US" to "category")), + hidden = true, + sortOrder = 0 + ), + Category.StoredCategory( + categoryId = "2", + localizedName = LocalesWithText(mapOf("en_US" to "second category")), + hidden = false, + sortOrder = 1 + ) + ) + } + + val vm = createViewModel() + + vm.onCategorySelected("1") + + var category: Category? = null + val job = launch { + vm.selectedCategory.collect { + category = it + } + } + job.cancel() + + assertEquals( + Category.StoredCategory( + categoryId = "2", + localizedName = LocalesWithText(mapOf("en_US" to "second category")), + hidden = false, + sortOrder = 1 + ), + category + ) + + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun `selected category (last in list) is hidden and first category is shown`() = runTest(UnconfinedTestDispatcher()) { + fakeCategoriesUseCase._categories.update { + listOf( + Category.StoredCategory( + categoryId = "1", + localizedName = LocalesWithText(mapOf("en_US" to "category")), + hidden = false, + sortOrder = 0 + ), + Category.StoredCategory( + categoryId = "2", + localizedName = LocalesWithText(mapOf("en_US" to "second category")), + hidden = false, + sortOrder = 1 + ), + Category.StoredCategory( + categoryId = "3", + localizedName = LocalesWithText(mapOf("en_US" to "third category")), + hidden = true, + sortOrder = 2 + ) + ) + } + + val vm = createViewModel() + + vm.onCategorySelected("3") + + var category: Category? = null + val job = launch { + vm.selectedCategory.collect { + category = it + } + } + job.cancel() + + assertEquals( + Category.StoredCategory( + categoryId = "1", + localizedName = LocalesWithText(mapOf("en_US" to "category")), + hidden = false, + sortOrder = 0 + ), + category + ) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun `selected category is hidden and next non-hidden category is shown`() = runTest(UnconfinedTestDispatcher()) { + fakeCategoriesUseCase._categories.update { + listOf( + Category.StoredCategory( + categoryId = "1", + localizedName = LocalesWithText(mapOf("en_US" to "category")), + hidden = true, + sortOrder = 0 + ), + Category.StoredCategory( + categoryId = "2", + localizedName = LocalesWithText(mapOf("en_US" to "second category")), + hidden = false, + sortOrder = 1 + ), + Category.StoredCategory( + categoryId = "3", + localizedName = LocalesWithText(mapOf("en_US" to "third category")), + hidden = true, + sortOrder = 2 + ) + ) + } + + val vm = createViewModel() + + vm.onCategorySelected("3") + + var category: Category? = null + val job = launch { + vm.selectedCategory.collect { + category = it + } + } + job.cancel() + + assertEquals( + Category.StoredCategory( + categoryId = "2", + localizedName = LocalesWithText(mapOf("en_US" to "second category")), + hidden = false, + sortOrder = 1 + ), + category + ) + } + @Test fun `current phrases updated when category ID changed`() { fakePhrasesUseCase._allCategories.update { diff --git a/app/src/test/java/com/willowtree/vocable/settings/EditCategoriesViewModelTest.kt b/app/src/test/java/com/willowtree/vocable/settings/EditCategoriesViewModelTest.kt index 6d522a5e..85fecc1a 100644 --- a/app/src/test/java/com/willowtree/vocable/settings/EditCategoriesViewModelTest.kt +++ b/app/src/test/java/com/willowtree/vocable/settings/EditCategoriesViewModelTest.kt @@ -1,12 +1,9 @@ package com.willowtree.vocable.settings -import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import app.cash.turbine.test import com.willowtree.vocable.FakeCategoriesUseCase import com.willowtree.vocable.MainDispatcherRule -import com.willowtree.vocable.getOrAwaitValue import com.willowtree.vocable.presets.createStoredCategory -import com.willowtree.vocable.settings.editcategories.EditCategoriesPage -import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.update import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals @@ -18,9 +15,6 @@ class EditCategoriesViewModelTest { @get:Rule val mainDispatcherRule = MainDispatcherRule() - @get:Rule - val instantTaskExecutorRule = InstantTaskExecutorRule() - private val categoriesUseCase = FakeCategoriesUseCase() private fun createViewModel(): EditCategoriesViewModel { @@ -30,7 +24,7 @@ class EditCategoriesViewModelTest { } @Test - fun `categories are populated`() { + fun `categories are populated`() = runTest { categoriesUseCase._categories.update { listOf( createStoredCategory(categoryId = "1") @@ -39,16 +33,18 @@ class EditCategoriesViewModelTest { val vm = createViewModel() vm.refreshCategories() - assertEquals( - listOf( - createStoredCategory(categoryId = "1") - ), - vm.categoryList.getOrAwaitValue() - ) + vm.categoryList.test { + assertEquals( + listOf( + createStoredCategory(categoryId = "1") + ), + awaitItem() + ) + } } @Test - fun `move category up`() { + fun `move category up`() = runTest { categoriesUseCase._categories.update { listOf( createStoredCategory( @@ -65,23 +61,25 @@ class EditCategoriesViewModelTest { vm.refreshCategories() vm.moveCategoryUp("2") - assertEquals( - listOf( - createStoredCategory( - categoryId = "2", - sortOrder = 0 + vm.categoryList.test { + assertEquals( + listOf( + createStoredCategory( + categoryId = "2", + sortOrder = 0 + ), + createStoredCategory( + categoryId = "1", + sortOrder = 1 + ) ), - createStoredCategory( - categoryId = "1", - sortOrder = 1 - ) - ), - vm.categoryList.getOrAwaitValue() - ) + awaitItem() + ) + } } @Test - fun `move category down`() { + fun `move category down`() = runTest { categoriesUseCase._categories.update { listOf( createStoredCategory( @@ -98,61 +96,21 @@ class EditCategoriesViewModelTest { vm.refreshCategories() vm.moveCategoryDown("1") - assertEquals( - listOf( - createStoredCategory( - categoryId = "2", - sortOrder = 0 + vm.categoryList.test { + assertEquals( + listOf( + createStoredCategory( + categoryId = "2", + sortOrder = 0 + ), + createStoredCategory( + categoryId = "1", + sortOrder = 1 + ) ), - createStoredCategory( - categoryId = "1", - sortOrder = 1 - ) - ), - vm.categoryList.getOrAwaitValue() - ) - } - - @Test - fun `category pages are populated`() = runTest { - categoriesUseCase._categories.update { - listOf( - createStoredCategory(categoryId = "1"), - createStoredCategory(categoryId = "2"), - createStoredCategory(categoryId = "3"), - createStoredCategory(categoryId = "4"), - createStoredCategory(categoryId = "5"), - createStoredCategory(categoryId = "6"), - createStoredCategory(categoryId = "7"), - createStoredCategory(categoryId = "8"), - createStoredCategory(categoryId = "9"), + awaitItem() ) } - val vm = createViewModel() - vm.refreshCategories() - - assertEquals( - listOf( - EditCategoriesPage( - listOf( - createStoredCategory(categoryId = "1"), - createStoredCategory(categoryId = "2"), - createStoredCategory(categoryId = "3"), - createStoredCategory(categoryId = "4"), - createStoredCategory(categoryId = "5"), - createStoredCategory(categoryId = "6"), - createStoredCategory(categoryId = "7"), - createStoredCategory(categoryId = "8"), - ) - ), - EditCategoriesPage( - listOf( - createStoredCategory(categoryId = "9"), - ) - ) - ), - vm.categoryPages.first() - ) } } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 00000000..0421dfe6 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,5 @@ +[versions] +turbine = "1.1.0" + +[libraries] +turbine = { group = "app.cash.turbine", name = "turbine", version.ref = "turbine" } \ No newline at end of file