Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Verified Commit f1632493 authored by Fahim M. Choudhury's avatar Fahim M. Choudhury
Browse files

test: add tests for PlayStore search paging

Add unit tests for Play Store paging source/repository, SearchViewModelV2 paging behavior, and icon URL resolution.

Fix SearchResultsContent compose tests to align with the updated API and locale-aware rating display.
parent 7fdce1f5
Loading
Loading
Loading
Loading
Loading
+8 −2
Original line number Diff line number Diff line
@@ -49,6 +49,7 @@ import foundation.e.apps.data.enums.Source
import foundation.e.apps.data.enums.Status
import foundation.e.apps.ui.compose.theme.AppTheme
import foundation.e.apps.ui.search.v2.SearchTabType
import java.util.Locale

@RunWith(AndroidJUnit4::class)
class SearchResultsContentTest {
@@ -103,6 +104,7 @@ class SearchResultsContentTest {
                        selectedTab = selectedTab,
                        fossItems = fossItems,
                        pwaItems = pwaItems,
                        searchVersion = 0,
                        getScrollPosition = { null },
                        onScrollPositionChange = { _, _, _ -> },
                        onTabSelect = { tab ->
@@ -129,6 +131,8 @@ class SearchResultsContentTest {
    fun applicationMapping_setsAuthorRatingAndPrimaryAction() {
        val notAvailable = composeRule.activity.getString(R.string.not_available)
        val openLabel = composeRule.activity.getString(R.string.open)
        val expectedRating = String.format(Locale.getDefault(), "%.1f", 4.4)
        val unexpectedRating = String.format(Locale.getDefault(), "%.1f", 4.9)

        renderSearchResults(
            tabs = listOf(SearchTabType.OPEN_SOURCE),
@@ -164,10 +168,10 @@ class SearchResultsContentTest {
        )

        composeRule.onNodeWithText("com.example.rated").assertIsDisplayed()
        composeRule.onNodeWithText("4.4").assertIsDisplayed()
        composeRule.onNodeWithText(expectedRating).assertIsDisplayed()
        composeRule.onNodeWithText(openLabel).assertIsDisplayed()
        composeRule.onNodeWithText(notAvailable).assertIsDisplayed()
        composeRule.onAllNodesWithText("4.9").assertCountEquals(0)
        composeRule.onAllNodesWithText(unexpectedRating).assertCountEquals(0)
    }

    @Test
@@ -251,6 +255,7 @@ class SearchResultsContentTest {
        selectedTab: SearchTabType,
        fossPagingData: PagingData<Application>,
        pwaPagingData: PagingData<Application>? = null,
        searchVersion: Int = 0,
    ) {
        composeRule.setContent {
            val fossItems = remember { flowOf(fossPagingData) }.collectAsLazyPagingItems()
@@ -265,6 +270,7 @@ class SearchResultsContentTest {
                        selectedTab = selectedTab,
                        fossItems = fossItems,
                        pwaItems = pwaItems,
                        searchVersion = searchVersion,
                        getScrollPosition = { null },
                        onScrollPositionChange = { _, _, _ -> },
                        onTabSelect = {},
+24 −13
Original line number Diff line number Diff line
@@ -22,29 +22,40 @@ import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.aurora.gplayapi.data.models.App
import foundation.e.apps.data.playstore.utils.GPlayHttpClient
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class PlayStorePagingRepository @Inject constructor(
    private val gPlayHttpClient: GPlayHttpClient,
    private val playStoreWebSearch: PlayStoreWebSearch,
) {

    fun playStoreSearch(query: String, pageSize: Int): Flow<PagingData<App>> {
        return Pager(
            config = PagingConfig(
            config = buildPagingConfig(pageSize),
            pagingSourceFactory = buildPagingSourceFactory(query),
        ).flow
    }

    internal fun buildPagingConfig(pageSize: Int): PagingConfig {
        return PagingConfig(
            pageSize = pageSize,
            enablePlaceholders = false,
                prefetchDistance = 2
            ),
            pagingSourceFactory = {
            prefetchDistance = PREFETCH_DISTANCE
        )
    }

    internal fun buildPagingSourceFactory(query: String): () -> PlayStorePagingSource =
        { createPagingSource(query) }

    internal fun createPagingSource(query: String): PlayStorePagingSource =
        PlayStorePagingSource(
            query = query,
                    gPlayHttpClient = gPlayHttpClient,
            playStoreWebSearch = playStoreWebSearch,
        )
            }
        ).flow

    private companion object {
        private const val PREFETCH_DISTANCE = 2
    }
}
+4 −8
Original line number Diff line number Diff line
@@ -22,8 +22,6 @@ import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.gplayapi.helpers.web.WebSearchHelper
import foundation.e.apps.data.playstore.utils.GPlayHttpClient
import foundation.e.apps.data.playstore.utils.GplayHttpRequestException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -33,11 +31,9 @@ private const val INITIAL_PAGE = 1

class PlayStorePagingSource(
    private val query: String,
    private val gPlayHttpClient: GPlayHttpClient,
    private val playStoreWebSearch: PlayStoreWebSearch,
) : PagingSource<Int, App>() {

    private val webSearchHelper = WebSearchHelper().using(gPlayHttpClient)

    private var nextBundleUrl: String? = null
    private val nextStreamUrls = mutableSetOf<String>()

@@ -88,7 +84,7 @@ class PlayStorePagingSource(
    }

    private suspend fun loadFirstPage(): List<App> = withContext(Dispatchers.IO) {
        val bundle = webSearchHelper.searchResults(query)
        val bundle = playStoreWebSearch.searchResults(query)
        nextBundleUrl = bundle.streamNextPageUrl.takeIf { it.isNotBlank() }

        if (!bundle.hasCluster()) {
@@ -105,13 +101,13 @@ class PlayStorePagingSource(
                nextStreamUrls.clear()

                pendingStreamUrls.flatMap { streamUrl ->
                    val cluster = webSearchHelper.nextStreamCluster(query, streamUrl)
                    val cluster = playStoreWebSearch.nextStreamCluster(query, streamUrl)
                    listOf(cluster).collectApplications()
                }
            }

            !nextBundleUrl.isNullOrBlank() -> {
                val bundle = webSearchHelper.nextStreamBundle(query, nextBundleUrl!!)
                val bundle = playStoreWebSearch.nextStreamBundle(query, nextBundleUrl!!)
                nextBundleUrl = bundle.streamNextPageUrl.takeIf { it.isNotBlank() }

                bundle.streamClusters.values.collectApplications()
+30 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2026 e Foundation
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

package foundation.e.apps.data.search

import com.aurora.gplayapi.data.models.StreamBundle
import com.aurora.gplayapi.data.models.StreamCluster

interface PlayStoreWebSearch {
    suspend fun searchResults(query: String): StreamBundle

    suspend fun nextStreamBundle(query: String, nextUrl: String): StreamBundle

    suspend fun nextStreamCluster(query: String, nextUrl: String): StreamCluster
}
+42 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2026 e Foundation
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

package foundation.e.apps.data.search

import com.aurora.gplayapi.data.models.StreamBundle
import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.gplayapi.helpers.web.WebSearchHelper
import foundation.e.apps.data.playstore.utils.GPlayHttpClient
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class PlayStoreWebSearchImpl @Inject constructor(
    gPlayHttpClient: GPlayHttpClient,
) : PlayStoreWebSearch {
    private val webSearchHelper = WebSearchHelper().using(gPlayHttpClient)

    override suspend fun searchResults(query: String): StreamBundle =
        webSearchHelper.searchResults(query)

    override suspend fun nextStreamBundle(query: String, nextUrl: String): StreamBundle =
        webSearchHelper.nextStreamBundle(query, nextUrl)

    override suspend fun nextStreamCluster(query: String, nextUrl: String): StreamCluster =
        webSearchHelper.nextStreamCluster(query, nextUrl)
}
Loading