Feature: Show where file is being used on Commons & Other wikis (#6006)

* add url to build config

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* add network call functions

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* return response asynchronously

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* inject page size in the request

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* rename from Commons..Response.kt to ..Response.kt

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* convert to .kt

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* ui setup working

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* fix merge conflict

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* cleanup

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* fix CI

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* use suspend function for network calls

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* doc

* doc

* doc

* doc

* doc

---------

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>
Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
This commit is contained in:
Parneet Singh 2024-12-09 19:37:44 +05:30 committed by GitHub
parent 04a07ed655
commit 85d9aef2f3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 2587 additions and 1499 deletions

View file

@ -52,13 +52,14 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
// Jetpack Compose // Jetpack Compose
def composeBom = platform('androidx.compose:compose-bom:2024.08.00') def composeBom = platform('androidx.compose:compose-bom:2024.11.00')
implementation "androidx.activity:activity-compose:1.9.1" implementation "androidx.activity:activity-compose:1.9.3"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.8.4" implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.8.4"
implementation (composeBom) implementation (composeBom)
implementation "androidx.compose.runtime:runtime" implementation "androidx.compose.runtime:runtime"
implementation "androidx.compose.ui:ui" implementation "androidx.compose.ui:ui"
implementation "androidx.compose.ui:ui-viewbinding"
implementation "androidx.compose.ui:ui-graphics" implementation "androidx.compose.ui:ui-graphics"
implementation "androidx.compose.ui:ui-tooling" implementation "androidx.compose.ui:ui-tooling"
implementation "androidx.compose.foundation:foundation" implementation "androidx.compose.foundation:foundation"
@ -313,6 +314,7 @@ android {
buildConfigField "String", "SIGNUP_SUCCESS_REDIRECTION_URL", "\"https://commons.m.wikimedia.org/w/index.php?title=Main_Page&welcome=yes\"" buildConfigField "String", "SIGNUP_SUCCESS_REDIRECTION_URL", "\"https://commons.m.wikimedia.org/w/index.php?title=Main_Page&welcome=yes\""
buildConfigField "String", "FORGOT_PASSWORD_URL", "\"https://commons.wikimedia.org/wiki/Special:PasswordReset\"" buildConfigField "String", "FORGOT_PASSWORD_URL", "\"https://commons.wikimedia.org/wiki/Special:PasswordReset\""
buildConfigField "String", "PRIVACY_POLICY_URL", "\"https://github.com/commons-app/commons-app-documentation/blob/master/android/Privacy-policy.md\"" buildConfigField "String", "PRIVACY_POLICY_URL", "\"https://github.com/commons-app/commons-app-documentation/blob/master/android/Privacy-policy.md\""
buildConfigField "String", "FILE_USAGES_BASE_URL", "\"https://commons.wikimedia.org/w/api.php?action=query&format=json&formatversion=2\""
buildConfigField "String", "ACCOUNT_TYPE", "\"fr.free.nrw.commons\"" buildConfigField "String", "ACCOUNT_TYPE", "\"fr.free.nrw.commons\""
buildConfigField "String", "CONTRIBUTION_AUTHORITY", "\"fr.free.nrw.commons.contributions.contentprovider\"" buildConfigField "String", "CONTRIBUTION_AUTHORITY", "\"fr.free.nrw.commons.contributions.contentprovider\""
buildConfigField "String", "MODIFICATION_AUTHORITY", "\"fr.free.nrw.commons.modifications.contentprovider\"" buildConfigField "String", "MODIFICATION_AUTHORITY", "\"fr.free.nrw.commons.modifications.contentprovider\""
@ -348,6 +350,7 @@ android {
buildConfigField "String", "SIGNUP_SUCCESS_REDIRECTION_URL", "\"https://commons.m.wikimedia.beta.wmflabs.org/w/index.php?title=Main_Page&welcome=yes\"" buildConfigField "String", "SIGNUP_SUCCESS_REDIRECTION_URL", "\"https://commons.m.wikimedia.beta.wmflabs.org/w/index.php?title=Main_Page&welcome=yes\""
buildConfigField "String", "FORGOT_PASSWORD_URL", "\"https://commons.wikimedia.beta.wmflabs.org/wiki/Special:PasswordReset\"" buildConfigField "String", "FORGOT_PASSWORD_URL", "\"https://commons.wikimedia.beta.wmflabs.org/wiki/Special:PasswordReset\""
buildConfigField "String", "PRIVACY_POLICY_URL", "\"https://github.com/commons-app/commons-app-documentation/blob/master/android/Privacy-policy.md\"" buildConfigField "String", "PRIVACY_POLICY_URL", "\"https://github.com/commons-app/commons-app-documentation/blob/master/android/Privacy-policy.md\""
buildConfigField "String", "FILE_USAGES_BASE_URL", "\"https://commons.wikimedia.org/w/api.php?action=query&format=json&formatversion=2\""
buildConfigField "String", "ACCOUNT_TYPE", "\"fr.free.nrw.commons.beta\"" buildConfigField "String", "ACCOUNT_TYPE", "\"fr.free.nrw.commons.beta\""
buildConfigField "String", "CONTRIBUTION_AUTHORITY", "\"fr.free.nrw.commons.beta.contributions.contentprovider\"" buildConfigField "String", "CONTRIBUTION_AUTHORITY", "\"fr.free.nrw.commons.beta.contributions.contentprovider\""
buildConfigField "String", "MODIFICATION_AUTHORITY", "\"fr.free.nrw.commons.beta.modifications.contentprovider\"" buildConfigField "String", "MODIFICATION_AUTHORITY", "\"fr.free.nrw.commons.beta.modifications.contentprovider\""

View file

@ -0,0 +1,35 @@
package fr.free.nrw.commons.fileusages
import com.google.gson.annotations.SerializedName
/**
* Show where file is being used on Commons and oher wikis.
*/
data class FileUsagesResponse(
@SerializedName("continue") val continueResponse: CommonsContinue?,
@SerializedName("batchcomplete") val batchComplete: Boolean,
@SerializedName("query") val query: Query,
)
data class CommonsContinue(
@SerializedName("fucontinue") val fuContinue: String,
@SerializedName("continue") val continueKey: String
)
data class Query(
@SerializedName("pages") val pages: List<Page>
)
data class Page(
@SerializedName("pageid") val pageId: Int,
@SerializedName("ns") val nameSpace: Int,
@SerializedName("title") val title: String,
@SerializedName("fileusage") val fileUsage: List<FileUsage>
)
data class FileUsage(
@SerializedName("pageid") val pageId: Int,
@SerializedName("ns") val nameSpace: Int,
@SerializedName("title") val title: String,
@SerializedName("redirect") val redirect: Boolean
)

View file

@ -0,0 +1,18 @@
package fr.free.nrw.commons.fileusages
/**
* Show where file is being used on Commons and oher wikis.
*/
data class FileUsagesUiModel(
val title: String,
val link: String?
)
fun FileUsage.toUiModel(): FileUsagesUiModel {
return FileUsagesUiModel(title = title, link = "https://commons.wikimedia.org/wiki/$title")
}
fun GlobalFileUsage.toUiModel(): FileUsagesUiModel {
// link is associated with sub items under wiki group (which is not used ATM)
return FileUsagesUiModel(title = wiki, link = null)
}

View file

@ -0,0 +1,34 @@
package fr.free.nrw.commons.fileusages
import com.google.gson.annotations.SerializedName
/**
* Show where file is being used on Commons and oher wikis.
*/
data class GlobalFileUsagesResponse(
@SerializedName("continue") val continueResponse: GlobalContinue?,
@SerializedName("batchcomplete") val batchComplete: Boolean,
@SerializedName("query") val query: GlobalQuery,
)
data class GlobalContinue(
@SerializedName("gucontinue") val guContinue: String,
@SerializedName("continue") val continueKey: String
)
data class GlobalQuery(
@SerializedName("pages") val pages: List<GlobalPage>
)
data class GlobalPage(
@SerializedName("pageid") val pageId: Int,
@SerializedName("ns") val nameSpace: Int,
@SerializedName("title") val title: String,
@SerializedName("globalusage") val fileUsage: List<GlobalFileUsage>
)
data class GlobalFileUsage(
@SerializedName("title") val title: String,
@SerializedName("wiki") val wiki: String,
@SerializedName("url") val url: String
)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,116 @@
package fr.free.nrw.commons.media
import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import fr.free.nrw.commons.R
import fr.free.nrw.commons.fileusages.FileUsagesUiModel
import fr.free.nrw.commons.fileusages.toUiModel
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
/**
* Show where file is being used on Commons and oher wikis.
*/
class MediaDetailViewModel(
private val applicationContext: Context,
private val okHttpJsonApiClient: OkHttpJsonApiClient
) :
ViewModel() {
private val _commonsContainerState =
MutableStateFlow<FileUsagesContainerState>(FileUsagesContainerState.Initial)
val commonsContainerState = _commonsContainerState.asStateFlow()
private val _globalContainerState =
MutableStateFlow<FileUsagesContainerState>(FileUsagesContainerState.Initial)
val globalContainerState = _globalContainerState.asStateFlow()
fun loadFileUsagesCommons(fileName: String) {
viewModelScope.launch {
_commonsContainerState.update { FileUsagesContainerState.Loading }
try {
val result =
okHttpJsonApiClient.getFileUsagesOnCommons(fileName, 10)
val data = result?.query?.pages?.first()?.fileUsage?.map { it.toUiModel() }
_commonsContainerState.update { FileUsagesContainerState.Success(data = data) }
} catch (e: Exception) {
_commonsContainerState.update {
FileUsagesContainerState.Error(
errorMessage = applicationContext.getString(
R.string.error_while_loading
)
)
}
Timber.e(e, javaClass.simpleName)
}
}
}
fun loadGlobalFileUsages(fileName: String) {
viewModelScope.launch {
_globalContainerState.update { FileUsagesContainerState.Loading }
try {
val result = okHttpJsonApiClient.getGlobalFileUsages(fileName, 10)
val data = result?.query?.pages?.first()?.fileUsage?.map { it.toUiModel() }
_globalContainerState.update { FileUsagesContainerState.Success(data = data) }
} catch (e: Exception) {
_globalContainerState.update {
FileUsagesContainerState.Error(
errorMessage = applicationContext.getString(
R.string.error_while_loading
)
)
}
Timber.e(e, javaClass.simpleName)
}
}
}
sealed class FileUsagesContainerState {
object Initial : FileUsagesContainerState()
object Loading : FileUsagesContainerState()
data class Success(val data: List<FileUsagesUiModel>?) : FileUsagesContainerState()
data class Error(val errorMessage: String) : FileUsagesContainerState()
}
class MediaDetailViewModelProviderFactory
@Inject constructor(
private val okHttpJsonApiClient: OkHttpJsonApiClient,
private val applicationContext: Context
) :
ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MediaDetailViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return MediaDetailViewModel(applicationContext, okHttpJsonApiClient) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
}

View file

@ -2,8 +2,11 @@ package fr.free.nrw.commons.mwapi
import android.text.TextUtils import android.text.TextUtils
import com.google.gson.Gson import com.google.gson.Gson
import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.campaigns.CampaignResponseDTO import fr.free.nrw.commons.campaigns.CampaignResponseDTO
import fr.free.nrw.commons.explore.depictions.DepictsClient import fr.free.nrw.commons.explore.depictions.DepictsClient
import fr.free.nrw.commons.fileusages.FileUsagesResponse
import fr.free.nrw.commons.fileusages.GlobalFileUsagesResponse
import fr.free.nrw.commons.location.LatLng import fr.free.nrw.commons.location.LatLng
import fr.free.nrw.commons.nearby.Place import fr.free.nrw.commons.nearby.Place
import fr.free.nrw.commons.nearby.model.ItemsClass import fr.free.nrw.commons.nearby.model.ItemsClass
@ -20,6 +23,8 @@ import fr.free.nrw.commons.utils.ConfigUtils.isBetaFlavour
import fr.free.nrw.commons.wikidata.model.GetWikidataEditCountResponse import fr.free.nrw.commons.wikidata.model.GetWikidataEditCountResponse
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.Single import io.reactivex.Single
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
@ -50,8 +55,10 @@ class OkHttpJsonApiClient @Inject constructor(
): Observable<LeaderboardResponse> { ): Observable<LeaderboardResponse> {
val fetchLeaderboardUrlTemplate = val fetchLeaderboardUrlTemplate =
wikiMediaToolforgeUrl.toString() + LeaderboardConstants.LEADERBOARD_END_POINT wikiMediaToolforgeUrl.toString() + LeaderboardConstants.LEADERBOARD_END_POINT
val url = String.format(Locale.ENGLISH, val url = String.format(
fetchLeaderboardUrlTemplate, userName, duration, category, limit, offset) Locale.ENGLISH,
fetchLeaderboardUrlTemplate, userName, duration, category, limit, offset
)
val urlBuilder: HttpUrl.Builder = url.toHttpUrlOrNull()!!.newBuilder() val urlBuilder: HttpUrl.Builder = url.toHttpUrlOrNull()!!.newBuilder()
.addQueryParameter("user", userName) .addQueryParameter("user", userName)
.addQueryParameter("duration", duration) .addQueryParameter("duration", duration)
@ -80,6 +87,80 @@ class OkHttpJsonApiClient @Inject constructor(
}) })
} }
/**
* Show where file is being used on Commons.
*/
suspend fun getFileUsagesOnCommons(
fileName: String?,
pageSize: Int
): FileUsagesResponse? {
return withContext(Dispatchers.IO) {
return@withContext try {
val urlBuilder = BuildConfig.FILE_USAGES_BASE_URL.toHttpUrlOrNull()!!.newBuilder()
urlBuilder.addQueryParameter("prop", "fileusage")
urlBuilder.addQueryParameter("titles", fileName)
urlBuilder.addQueryParameter("fulimit", pageSize.toString())
Timber.i("Url %s", urlBuilder.toString())
val request: Request = Request.Builder()
.url(urlBuilder.toString())
.build()
val response: Response = okHttpClient.newCall(request).execute()
if (response.body != null && response.isSuccessful) {
val json: String = response.body!!.string()
gson.fromJson<FileUsagesResponse>(
json,
FileUsagesResponse::class.java
)
} else null
} catch (e: Exception) {
Timber.e(e)
null
}
}
}
/**
* Show where file is being used on non-Commons wikis, typically the Wikipedias in various languages.
*/
suspend fun getGlobalFileUsages(
fileName: String?,
pageSize: Int
): GlobalFileUsagesResponse? {
return withContext(Dispatchers.IO) {
return@withContext try {
val urlBuilder = BuildConfig.FILE_USAGES_BASE_URL.toHttpUrlOrNull()!!.newBuilder()
urlBuilder.addQueryParameter("prop", "globalusage")
urlBuilder.addQueryParameter("titles", fileName)
urlBuilder.addQueryParameter("gulimit", pageSize.toString())
Timber.i("Url %s", urlBuilder.toString())
val request: Request = Request.Builder()
.url(urlBuilder.toString())
.build()
val response: Response = okHttpClient.newCall(request).execute()
if (response.body != null && response.isSuccessful) {
val json: String = response.body!!.string()
gson.fromJson<GlobalFileUsagesResponse>(
json,
GlobalFileUsagesResponse::class.java
)
} else null
} catch (e: Exception) {
Timber.e(e)
null
}
}
}
fun setAvatar(username: String?, avatar: String?): Single<UpdateAvatarResponse?> { fun setAvatar(username: String?, avatar: String?): Single<UpdateAvatarResponse?> {
val urlTemplate = wikiMediaToolforgeUrl val urlTemplate = wikiMediaToolforgeUrl
.toString() + LeaderboardConstants.UPDATE_AVATAR_END_POINT .toString() + LeaderboardConstants.UPDATE_AVATAR_END_POINT

View file

@ -456,6 +456,11 @@
android:layout_height="match_parent" /> android:layout_height="match_parent" />
</LinearLayout> </LinearLayout>
<androidx.compose.ui.platform.ComposeView
android:id="@+id/file_usages_compose_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button <Button
android:id="@+id/copyWikicode" android:id="@+id/copyWikicode"
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -849,4 +849,12 @@ Upload your first media by tapping on the add button.</string>
<string name="red_pin">This place has no picture yet, go take one!</string> <string name="red_pin">This place has no picture yet, go take one!</string>
<string name="green_pin">This place has a picture already.</string> <string name="green_pin">This place has a picture already.</string>
<string name="grey_pin">Now checking whether this place has a picture.</string> <string name="grey_pin">Now checking whether this place has a picture.</string>
<string name="error_while_loading">Error while loading</string>
<string name="no_usages_found">No usages found</string>
<string name="usages_on_commons_heading">Commons</string>
<string name="usages_on_other_wikis_heading">Other wikis</string>
<string name="bullet_point"></string>
<string name="file_usages_container_heading">File usages</string>
</resources> </resources>

View file

@ -17,7 +17,7 @@ import fr.free.nrw.commons.customselector.ui.selector.ImageLoader
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestCoroutineDispatcher import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain import kotlinx.coroutines.test.setMain
import org.junit.After import org.junit.After
@ -34,7 +34,6 @@ import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config import org.robolectric.annotation.Config
import java.lang.reflect.Field import java.lang.reflect.Field
import java.util.TreeMap import java.util.TreeMap
import kotlin.collections.ArrayList
/** /**
* Custom Selector image adapter test. * Custom Selector image adapter test.
@ -65,7 +64,7 @@ class ImageAdapterTest {
private lateinit var selectedImageField: Field private lateinit var selectedImageField: Field
private var uri: Uri = Mockito.mock(Uri::class.java) private var uri: Uri = Mockito.mock(Uri::class.java)
private lateinit var image: Image private lateinit var image: Image
private val testDispatcher = TestCoroutineDispatcher() private val testDispatcher = StandardTestDispatcher()
/** /**
* Set up variables. * Set up variables.
@ -91,7 +90,6 @@ class ImageAdapterTest {
@After @After
fun tearDown() { fun tearDown() {
Dispatchers.resetMain() Dispatchers.resetMain()
testDispatcher.cleanupTestCoroutines()
} }
/** /**

View file

@ -21,9 +21,10 @@ import fr.free.nrw.commons.upload.FileUtilsWrapper
import io.reactivex.Single import io.reactivex.Single
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineDispatcher import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runBlockingTest import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain import kotlinx.coroutines.test.setMain
import org.junit.After import org.junit.After
import org.junit.Assert import org.junit.Assert
@ -45,7 +46,6 @@ import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
import java.util.Calendar import java.util.Calendar
import java.util.Date import java.util.Date
import kotlin.collections.HashMap
/** /**
* Image Loader Test. * Image Loader Test.
@ -92,7 +92,7 @@ class ImageLoaderTest {
private lateinit var contentResolver: ContentResolver private lateinit var contentResolver: ContentResolver
@ExperimentalCoroutinesApi @ExperimentalCoroutinesApi
private val testDispacher = TestCoroutineDispatcher() private val testDispacher = StandardTestDispatcher()
private lateinit var imageLoader: ImageLoader private lateinit var imageLoader: ImageLoader
private var mapImageSHA1: HashMap<Uri, String> = HashMap() private var mapImageSHA1: HashMap<Uri, String> = HashMap()
@ -153,7 +153,6 @@ class ImageLoaderTest {
@ExperimentalCoroutinesApi @ExperimentalCoroutinesApi
fun tearDown() { fun tearDown() {
Dispatchers.resetMain() Dispatchers.resetMain()
testDispacher.cleanupTestCoroutines()
mockedPickedFiles.close() mockedPickedFiles.close()
} }
@ -162,7 +161,7 @@ class ImageLoaderTest {
*/ */
@Test @Test
fun testQueryAndSetViewUploadedStatusNull() = fun testQueryAndSetViewUploadedStatusNull() =
testDispacher.runBlockingTest { TestScope(testDispacher).runTest {
whenever(uploadedStatusDao.getUploadedFromImageSHA1(any())).thenReturn(null) whenever(uploadedStatusDao.getUploadedFromImageSHA1(any())).thenReturn(null)
whenever(notForUploadStatusDao.find(any())).thenReturn(0) whenever(notForUploadStatusDao.find(any())).thenReturn(0)
mapModifiedImageSHA1[image] = "testSha1" mapModifiedImageSHA1[image] = "testSha1"
@ -182,7 +181,7 @@ class ImageLoaderTest {
*/ */
@Test @Test
fun testQueryAndSetViewUploadedStatusNotNull() = fun testQueryAndSetViewUploadedStatusNotNull() =
testDispacher.runBlockingTest { TestScope(testDispacher).runTest {
whenever(uploadedStatusDao.getUploadedFromImageSHA1(any())).thenReturn(uploadedStatus) whenever(uploadedStatusDao.getUploadedFromImageSHA1(any())).thenReturn(uploadedStatus)
whenever(notForUploadStatusDao.find(any())).thenReturn(0) whenever(notForUploadStatusDao.find(any())).thenReturn(0)
whenever(context.getSharedPreferences("custom_selector", 0)) whenever(context.getSharedPreferences("custom_selector", 0))
@ -195,7 +194,7 @@ class ImageLoaderTest {
*/ */
@Test @Test
fun testNextActionableImage() = fun testNextActionableImage() =
testDispacher.runBlockingTest { TestScope(testDispacher).runTest {
whenever(notForUploadStatusDao.find(any())).thenReturn(0) whenever(notForUploadStatusDao.find(any())).thenReturn(0)
whenever(uploadedStatusDao.findByImageSHA1(any(), any())).thenReturn(0) whenever(uploadedStatusDao.findByImageSHA1(any(), any())).thenReturn(0)
whenever(uploadedStatusDao.findByModifiedImageSHA1(any(), any())).thenReturn(0) whenever(uploadedStatusDao.findByModifiedImageSHA1(any(), any())).thenReturn(0)
@ -208,16 +207,40 @@ class ImageLoaderTest {
whenever(PickedFiles.pickedExistingPicture(context, Uri.parse("test"))).thenReturn( whenever(PickedFiles.pickedExistingPicture(context, Uri.parse("test"))).thenReturn(
uploadableFile, uploadableFile,
) )
imageLoader.nextActionableImage(listOf(image), testDispacher, testDispacher, 0, emptyList()) imageLoader.nextActionableImage(
listOf(image),
testDispacher,
testDispacher,
0,
emptyList()
)
whenever(notForUploadStatusDao.find(any())).thenReturn(1) whenever(notForUploadStatusDao.find(any())).thenReturn(1)
imageLoader.nextActionableImage(listOf(image), testDispacher, testDispacher, 0, emptyList()) imageLoader.nextActionableImage(
listOf(image),
testDispacher,
testDispacher,
0,
emptyList()
)
whenever(uploadedStatusDao.findByImageSHA1(any(), any())).thenReturn(2) whenever(uploadedStatusDao.findByImageSHA1(any(), any())).thenReturn(2)
imageLoader.nextActionableImage(listOf(image), testDispacher, testDispacher, 0, emptyList()) imageLoader.nextActionableImage(
listOf(image),
testDispacher,
testDispacher,
0,
emptyList()
)
whenever(uploadedStatusDao.findByModifiedImageSHA1(any(), any())).thenReturn(2) whenever(uploadedStatusDao.findByModifiedImageSHA1(any(), any())).thenReturn(2)
imageLoader.nextActionableImage(listOf(image), testDispacher, testDispacher, 0, emptyList()) imageLoader.nextActionableImage(
listOf(image),
testDispacher,
testDispacher,
0,
emptyList()
)
} }
/** /**
@ -226,7 +249,7 @@ class ImageLoaderTest {
@Test @Test
@ExperimentalCoroutinesApi @ExperimentalCoroutinesApi
fun testGetSha1() = fun testGetSha1() =
testDispacher.runBlockingTest { TestScope(testDispacher).runTest {
BDDMockito BDDMockito
.given(PickedFiles.pickedExistingPicture(context, image.uri)) .given(PickedFiles.pickedExistingPicture(context, image.uri))
.willReturn(UploadableFile(uri, File("ABC"))) .willReturn(UploadableFile(uri, File("ABC")))

View file

@ -162,7 +162,7 @@ class MediaDetailFragmentUnitTests {
@Mock @Mock
private lateinit var mockSharedPreferencesEditor: SharedPreferences.Editor private lateinit var mockSharedPreferencesEditor: SharedPreferences.Editor
private lateinit var binding: FragmentMediaDetailBinding private lateinit var _binding: FragmentMediaDetailBinding
@Before @Before
fun setUp() { fun setUp() {
@ -185,12 +185,12 @@ class MediaDetailFragmentUnitTests {
layoutInflater = LayoutInflater.from(activity) layoutInflater = LayoutInflater.from(activity)
binding = FragmentMediaDetailBinding.inflate(layoutInflater) _binding = FragmentMediaDetailBinding.inflate(layoutInflater)
scrollView = binding.mediaDetailScrollView scrollView = _binding.mediaDetailScrollView
progressBarDeletion = binding.progressBarDeletion progressBarDeletion = _binding.progressBarDeletion
delete = binding.nominateDeletion delete = _binding.nominateDeletion
Whitebox.setInternalState(fragment, "media", media) Whitebox.setInternalState(fragment, "media", media)
Whitebox.setInternalState(fragment, "isDeleted", isDeleted) Whitebox.setInternalState(fragment, "isDeleted", isDeleted)
@ -198,13 +198,13 @@ class MediaDetailFragmentUnitTests {
Whitebox.setInternalState(fragment, "reasonListEnglishMappings", reasonListEnglishMappings) Whitebox.setInternalState(fragment, "reasonListEnglishMappings", reasonListEnglishMappings)
Whitebox.setInternalState(fragment, "reasonBuilder", reasonBuilder) Whitebox.setInternalState(fragment, "reasonBuilder", reasonBuilder)
Whitebox.setInternalState(fragment, "deleteHelper", deleteHelper) Whitebox.setInternalState(fragment, "deleteHelper", deleteHelper)
Whitebox.setInternalState(fragment, "binding", binding) Whitebox.setInternalState(fragment, "_binding", _binding)
Whitebox.setInternalState(fragment, "detailProvider", detailProvider) Whitebox.setInternalState(fragment, "detailProvider", detailProvider)
Whitebox.setInternalState(binding, "mediaDetailImageView", simpleDraweeView) Whitebox.setInternalState(_binding, "mediaDetailImageView", simpleDraweeView)
Whitebox.setInternalState(binding, "mediaDetailTitle", textView) Whitebox.setInternalState(_binding, "mediaDetailTitle", textView)
Whitebox.setInternalState(binding, "mediaDetailDepictionContainer", linearLayout) Whitebox.setInternalState(_binding, "mediaDetailDepictionContainer", linearLayout)
Whitebox.setInternalState(binding, "dummyCaptionDescriptionContainer", linearLayout) Whitebox.setInternalState(_binding, "dummyCaptionDescriptionContainer", linearLayout)
Whitebox.setInternalState(binding, "depictionsEditButton", button) Whitebox.setInternalState(_binding, "depictionsEditButton", button)
Whitebox.setInternalState(fragment, "locationManager", locationManager) Whitebox.setInternalState(fragment, "locationManager", locationManager)
`when`(simpleDraweeView.hierarchy).thenReturn(genericDraweeHierarchy) `when`(simpleDraweeView.hierarchy).thenReturn(genericDraweeHierarchy)
@ -241,7 +241,7 @@ class MediaDetailFragmentUnitTests {
@Throws(Exception::class) @Throws(Exception::class)
fun testLaunchZoomActivity() { fun testLaunchZoomActivity() {
`when`(media.imageUrl).thenReturn("") `when`(media.imageUrl).thenReturn("")
fragment.launchZoomActivity(binding.root) fragment.launchZoomActivity(_binding.root)
} }
@Test @Test