2023-03-13 10:22:33 +01:00
/ *
* Copyright 2018 Tusky Contributors
2018-03-27 19:47:00 +02:00
*
* This file is a part of Tusky .
*
* 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 .
*
* Tusky 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 Tusky ; if not ,
2023-03-13 10:22:33 +01:00
* see < http : //www.gnu.org/licenses>.
* /
2018-03-27 19:47:00 +02:00
2023-12-10 07:38:25 +01:00
package com.keylesspalace.tusky.components.compose
2018-03-09 22:02:32 +01:00
2020-10-28 18:43:11 +01:00
import android.content.Intent
2020-11-22 19:02:54 +01:00
import android.os.Looper.getMainLooper
2018-03-09 22:02:32 +01:00
import android.widget.EditText
2019-12-19 19:09:40 +01:00
import androidx.test.ext.junit.runners.AndroidJUnit4
2022-05-30 20:03:40 +02:00
import at.connyduck.calladapter.networkresult.NetworkResult
2023-11-13 10:05:28 +01:00
import com.google.gson.Gson
2023-03-13 10:22:33 +01:00
import com.keylesspalace.tusky.R
2022-04-21 18:46:21 +02:00
import com.keylesspalace.tusky.components.instanceinfo.InstanceInfoRepository
2021-06-28 21:13:24 +02:00
import com.keylesspalace.tusky.db.AccountEntity
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.db.AppDatabase
2022-04-21 18:46:21 +02:00
import com.keylesspalace.tusky.db.EmojisEntity
2021-06-28 21:13:24 +02:00
import com.keylesspalace.tusky.db.InstanceDao
2022-04-21 18:46:21 +02:00
import com.keylesspalace.tusky.db.InstanceInfoEntity
2019-12-19 19:09:40 +01:00
import com.keylesspalace.tusky.di.ViewModelFactory
2018-04-22 10:35:46 +02:00
import com.keylesspalace.tusky.entity.Instance
2022-03-01 19:43:36 +01:00
import com.keylesspalace.tusky.entity.InstanceConfiguration
2023-10-25 12:53:10 +02:00
import com.keylesspalace.tusky.entity.InstanceV1
2024-03-28 09:13:05 +01:00
import com.keylesspalace.tusky.entity.SearchResult
2022-03-01 19:43:36 +01:00
import com.keylesspalace.tusky.entity.StatusConfiguration
2018-04-13 22:37:21 +02:00
import com.keylesspalace.tusky.network.MastodonApi
2024-01-04 17:00:55 +01:00
import java.util.Locale
2024-03-09 16:12:18 +01:00
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
2023-10-25 12:53:10 +02:00
import okhttp3.ResponseBody
import okhttp3.ResponseBody.Companion.toResponseBody
2021-06-28 21:13:24 +02:00
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
2018-03-09 22:02:32 +01:00
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
2022-04-14 19:49:49 +02:00
import org.mockito.kotlin.any
2024-03-28 09:13:05 +01:00
import org.mockito.kotlin.anyOrNull
2022-04-14 19:49:49 +02:00
import org.mockito.kotlin.doReturn
2022-08-07 19:13:59 +02:00
import org.mockito.kotlin.eq
2022-04-14 19:49:49 +02:00
import org.mockito.kotlin.mock
2018-03-09 22:02:32 +01:00
import org.robolectric.Robolectric
2020-11-22 19:02:54 +01:00
import org.robolectric.Shadows.shadowOf
2018-03-09 22:02:32 +01:00
import org.robolectric.annotation.Config
import org.robolectric.fakes.RoboMenuItem
2023-10-25 12:53:10 +02:00
import retrofit2.HttpException
import retrofit2.Response
2019-03-07 21:33:29 +01:00
2018-03-09 22:02:32 +01:00
/ * *
* Created by charlag on 3 / 7 / 18.
* /
2020-02-25 19:49:15 +01:00
@Config ( sdk = [ 28 ] )
2019-03-30 15:18:16 +01:00
@RunWith ( AndroidJUnit4 :: class )
2018-03-09 22:02:32 +01:00
class ComposeActivityTest {
2019-03-30 15:18:16 +01:00
private lateinit var activity : ComposeActivity
private lateinit var accountManagerMock : AccountManager
private lateinit var apiMock : MastodonApi
2018-03-09 22:02:32 +01:00
2019-12-19 19:09:40 +01:00
private val instanceDomain = " example.domain "
2019-03-30 15:18:16 +01:00
private val account = AccountEntity (
2021-06-28 21:13:24 +02:00
id = 1 ,
domain = instanceDomain ,
accessToken = " token " ,
2022-06-20 16:45:54 +02:00
clientId = " id " ,
clientSecret = " secret " ,
2021-06-28 21:13:24 +02:00
isActive = true ,
accountId = " 1 " ,
username = " username " ,
displayName = " Display Name " ,
profilePictureUrl = " " ,
notificationsEnabled = true ,
notificationsMentioned = true ,
notificationsFollowed = true ,
notificationsFollowRequested = false ,
notificationsReblogged = true ,
notificationsFavorited = true ,
notificationSound = true ,
notificationVibration = true ,
notificationLight = true
2018-03-09 22:02:32 +01:00
)
2023-10-25 12:53:10 +02:00
private var instanceV1ResponseCallback : ( ( ) -> InstanceV1 ) ? = null
2021-06-28 21:13:24 +02:00
private var instanceResponseCallback : ( ( ) -> Instance ) ? = null
2020-11-22 19:02:54 +01:00
private var composeOptions : ComposeActivity . ComposeOptions ? = null
2023-11-13 10:05:28 +01:00
private val gson = Gson ( )
2018-03-09 22:02:32 +01:00
@Before
2018-04-29 10:08:25 +02:00
fun setupActivity ( ) {
2018-03-09 22:02:32 +01:00
val controller = Robolectric . buildActivity ( ComposeActivity :: class . java )
activity = controller . get ( )
2018-04-13 22:37:21 +02:00
2022-04-14 19:49:49 +02:00
accountManagerMock = mock {
on { activeAccount } doReturn account
}
2018-04-13 22:37:21 +02:00
2022-04-14 19:49:49 +02:00
apiMock = mock {
2022-05-30 20:03:40 +02:00
onBlocking { getCustomEmojis ( ) } doReturn NetworkResult . success ( emptyList ( ) )
2022-04-14 19:49:49 +02:00
onBlocking { getInstance ( ) } doReturn instanceResponseCallback ?. invoke ( ) . let { instance ->
2023-10-25 12:53:10 +02:00
if ( instance == null ) {
NetworkResult . failure ( HttpException ( Response . error < ResponseBody > ( 404 , " Not found " . toResponseBody ( ) ) ) )
} else {
NetworkResult . success ( instance )
}
}
onBlocking { getInstanceV1 ( ) } doReturn instanceV1ResponseCallback ?. invoke ( ) . let { instance ->
2019-08-04 20:25:07 +02:00
if ( instance == null ) {
2022-05-30 20:03:40 +02:00
NetworkResult . failure ( Throwable ( ) )
2019-08-04 20:25:07 +02:00
} else {
2022-05-30 20:03:40 +02:00
NetworkResult . success ( instance )
2019-08-04 20:25:07 +02:00
}
2018-04-29 10:08:25 +02:00
}
2024-03-28 09:13:05 +01:00
onBlocking { search ( anyOrNull ( ) , anyOrNull ( ) , anyOrNull ( ) , anyOrNull ( ) , anyOrNull ( ) , anyOrNull ( ) ) } doReturn NetworkResult . success (
SearchResult ( emptyList ( ) , emptyList ( ) , emptyList ( ) )
)
2022-04-14 19:49:49 +02:00
}
2018-04-13 22:37:21 +02:00
2022-04-14 19:49:49 +02:00
val instanceDaoMock : InstanceDao = mock {
2022-04-21 18:46:21 +02:00
onBlocking { getInstanceInfo ( any ( ) ) } doReturn
2024-03-09 16:12:18 +01:00
InstanceInfoEntity ( instanceDomain , null , null , null , null , null , null , null , null , null , null , null , null , null , null , null )
2022-04-21 18:46:21 +02:00
onBlocking { getEmojiInfo ( any ( ) ) } doReturn
EmojisEntity ( instanceDomain , emptyList ( ) )
2022-04-14 19:49:49 +02:00
}
2019-12-19 19:09:40 +01:00
2022-04-14 19:49:49 +02:00
val dbMock : AppDatabase = mock {
on { instanceDao ( ) } doReturn instanceDaoMock
}
2018-05-27 10:22:12 +02:00
2024-03-09 16:12:18 +01:00
val instanceInfoRepo = InstanceInfoRepository ( apiMock , dbMock , accountManagerMock , CoroutineScope ( SupervisorJob ( ) ) )
2022-04-21 18:46:21 +02:00
2019-12-19 19:09:40 +01:00
val viewModel = ComposeViewModel (
2021-06-28 21:13:24 +02:00
apiMock ,
accountManagerMock ,
2022-04-14 19:49:49 +02:00
mock ( ) ,
mock ( ) ,
mock ( ) ,
2022-04-21 18:46:21 +02:00
instanceInfoRepo
2019-12-19 19:09:40 +01:00
)
2020-10-28 18:43:11 +01:00
activity . intent = Intent ( activity , ComposeActivity :: class . java ) . apply {
putExtra ( ComposeActivity . COMPOSE _OPTIONS _EXTRA , composeOptions )
}
2018-04-13 22:37:21 +02:00
2022-04-14 19:49:49 +02:00
val viewModelFactoryMock : ViewModelFactory = mock {
2022-08-07 19:13:59 +02:00
on { create ( eq ( ComposeViewModel :: class . java ) , any ( ) ) } doReturn viewModel
2022-04-14 19:49:49 +02:00
}
2018-04-13 22:37:21 +02:00
2019-12-19 19:09:40 +01:00
activity . accountManager = accountManagerMock
activity . viewModelFactory = viewModelFactoryMock
2018-04-13 22:37:21 +02:00
2018-03-09 22:02:32 +01:00
controller . create ( ) . start ( )
2022-04-21 18:46:21 +02:00
shadowOf ( getMainLooper ( ) ) . idle ( )
2018-03-09 22:02:32 +01:00
}
@Test
fun whenCloseButtonPressedAndEmpty _finish ( ) {
clickUp ( )
assertTrue ( activity . isFinishing )
}
@Test
fun whenCloseButtonPressedNotEmpty _notFinish ( ) {
insertSomeTextInContent ( )
clickUp ( )
assertFalse ( activity . isFinishing )
// We would like to check for dialog but Robolectric doesn't work with AppCompat v7 yet
}
2020-10-28 18:43:11 +01:00
@Test
fun whenModifiedInitialState _andCloseButtonPressed _notFinish ( ) {
composeOptions = ComposeActivity . ComposeOptions ( modifiedInitialState = true )
setupActivity ( )
clickUp ( )
assertFalse ( activity . isFinishing )
}
2018-03-09 22:02:32 +01:00
@Test
fun whenBackButtonPressedAndEmpty _finish ( ) {
clickBack ( )
assertTrue ( activity . isFinishing )
}
@Test
fun whenBackButtonPressedNotEmpty _notFinish ( ) {
insertSomeTextInContent ( )
clickBack ( )
assertFalse ( activity . isFinishing )
// We would like to check for dialog but Robolectric doesn't work with AppCompat v7 yet
}
2020-10-28 18:43:11 +01:00
@Test
fun whenModifiedInitialState _andBackButtonPressed _notFinish ( ) {
composeOptions = ComposeActivity . ComposeOptions ( modifiedInitialState = true )
setupActivity ( )
clickBack ( )
assertFalse ( activity . isFinishing )
}
2018-04-29 10:08:25 +02:00
@Test
fun whenMaximumTootCharsIsNull _defaultLimitIsUsed ( ) {
2023-10-25 12:53:10 +02:00
instanceV1ResponseCallback = { getInstanceV1WithCustomConfiguration ( null ) }
2018-04-29 10:08:25 +02:00
setupActivity ( )
2022-04-21 18:46:21 +02:00
assertEquals ( InstanceInfoRepository . DEFAULT _CHARACTER _LIMIT , activity . maximumTootCharacters )
2018-04-29 10:08:25 +02:00
}
@Test
fun whenMaximumTootCharsIsPopulated _customLimitIsUsed ( ) {
val customMaximum = 1000
2023-10-25 12:53:10 +02:00
instanceResponseCallback = { getInstanceWithCustomConfiguration ( customMaximum ) }
2018-04-29 10:08:25 +02:00
setupActivity ( )
2020-11-22 19:02:54 +01:00
shadowOf ( getMainLooper ( ) ) . idle ( )
2018-04-29 10:08:25 +02:00
assertEquals ( customMaximum , activity . maximumTootCharacters )
}
2022-03-01 19:43:36 +01:00
@Test
fun whenOnlyLegacyMaximumTootCharsIsPopulated _customLimitIsUsed ( ) {
val customMaximum = 1000
2023-10-25 12:53:10 +02:00
instanceV1ResponseCallback = { getInstanceV1WithCustomConfiguration ( customMaximum ) }
2022-03-01 19:43:36 +01:00
setupActivity ( )
shadowOf ( getMainLooper ( ) ) . idle ( )
assertEquals ( customMaximum , activity . maximumTootCharacters )
}
@Test
fun whenOnlyConfigurationMaximumTootCharsIsPopulated _customLimitIsUsed ( ) {
val customMaximum = 1000
2023-10-25 12:53:10 +02:00
instanceV1ResponseCallback = { getInstanceV1WithCustomConfiguration ( null , getCustomInstanceConfiguration ( maximumStatusCharacters = customMaximum ) ) }
2022-03-01 19:43:36 +01:00
setupActivity ( )
shadowOf ( getMainLooper ( ) ) . idle ( )
assertEquals ( customMaximum , activity . maximumTootCharacters )
}
@Test
fun whenDifferentCharLimitsArePopulated _statusConfigurationLimitIsUsed ( ) {
val customMaximum = 1000
2023-10-25 12:53:10 +02:00
instanceV1ResponseCallback = { getInstanceV1WithCustomConfiguration ( customMaximum , getCustomInstanceConfiguration ( maximumStatusCharacters = customMaximum * 2 ) ) }
2022-03-01 19:43:36 +01:00
setupActivity ( )
shadowOf ( getMainLooper ( ) ) . idle ( )
assertEquals ( customMaximum * 2 , activity . maximumTootCharacters )
}
2018-05-16 19:14:26 +02:00
@Test
2018-07-10 19:39:52 +02:00
fun whenTextContainsNoUrl _everyCharacterIsCounted ( ) {
val content = " This is test content please ignore thx "
insertSomeTextInContent ( content )
2023-12-10 07:38:25 +01:00
assertEquals ( content . length , activity . calculateTextLength ( ) )
}
@Test
fun whenTextContainsEmoji _emojisAreCountedAsOneCharacter ( ) {
val content = " Test 😜 "
insertSomeTextInContent ( content )
assertEquals ( 6 , activity . calculateTextLength ( ) )
}
@Test
fun whenTextContainsUrlWithEmoji _ellipsizedUrlIsCountedCorrectly ( ) {
val content = " https://🤪.com "
insertSomeTextInContent ( content )
assertEquals ( InstanceInfoRepository . DEFAULT _CHARACTERS _RESERVED _PER _URL , activity . calculateTextLength ( ) )
2018-07-10 19:39:52 +02:00
}
@Test
fun whenTextContainsUrl _onlyEllipsizedURLIsCounted ( ) {
2018-05-16 19:14:26 +02:00
val url = " https://www.google.dk/search?biw=1920&bih=990&tbm=isch&sa=1&ei=bmDrWuOoKMv6kwWOkIaoDQ&q=indiana+jones+i+hate+snakes+animated&oq=indiana+jones+i+hate+snakes+animated&gs_l=psy-ab.3...54174.55443.0.55553.9.7.0.0.0.0.255.333.1j0j1.2.0....0...1c.1.64.psy-ab..7.0.0....0.40G-kcDkC6A#imgdii=PSp15hQjN1JqvM:&imgrc=H0hyE2JW5wrpBM: "
val additionalContent = " Check out this @image #search result: "
insertSomeTextInContent ( additionalContent + url )
2023-12-10 07:38:25 +01:00
assertEquals ( additionalContent . length + InstanceInfoRepository . DEFAULT _CHARACTERS _RESERVED _PER _URL , activity . calculateTextLength ( ) )
2018-07-10 19:39:52 +02:00
}
@Test
2022-03-24 19:52:18 +01:00
fun whenTextContainsShortUrls _allUrlsGetEllipsized ( ) {
2018-07-10 19:39:52 +02:00
val shortUrl = " https://tusky.app "
val url = " https://www.google.dk/search?biw=1920&bih=990&tbm=isch&sa=1&ei=bmDrWuOoKMv6kwWOkIaoDQ&q=indiana+jones+i+hate+snakes+animated&oq=indiana+jones+i+hate+snakes+animated&gs_l=psy-ab.3...54174.55443.0.55553.9.7.0.0.0.0.255.333.1j0j1.2.0....0...1c.1.64.psy-ab..7.0.0....0.40G-kcDkC6A#imgdii=PSp15hQjN1JqvM:&imgrc=H0hyE2JW5wrpBM: "
val additionalContent = " Check out this @image #search result: "
insertSomeTextInContent ( shortUrl + additionalContent + url )
2023-12-10 07:38:25 +01:00
assertEquals ( additionalContent . length + ( InstanceInfoRepository . DEFAULT _CHARACTERS _RESERVED _PER _URL * 2 ) , activity . calculateTextLength ( ) )
2018-05-16 19:14:26 +02:00
}
@Test
fun whenTextContainsMultipleURLs _allURLsGetEllipsized ( ) {
val url = " https://www.google.dk/search?biw=1920&bih=990&tbm=isch&sa=1&ei=bmDrWuOoKMv6kwWOkIaoDQ&q=indiana+jones+i+hate+snakes+animated&oq=indiana+jones+i+hate+snakes+animated&gs_l=psy-ab.3...54174.55443.0.55553.9.7.0.0.0.0.255.333.1j0j1.2.0....0...1c.1.64.psy-ab..7.0.0....0.40G-kcDkC6A#imgdii=PSp15hQjN1JqvM:&imgrc=H0hyE2JW5wrpBM: "
val additionalContent = " Check out this @image #search result: "
insertSomeTextInContent ( url + additionalContent + url )
2023-12-10 07:38:25 +01:00
assertEquals ( additionalContent . length + ( InstanceInfoRepository . DEFAULT _CHARACTERS _RESERVED _PER _URL * 2 ) , activity . calculateTextLength ( ) )
2022-03-01 19:43:36 +01:00
}
@Test
fun whenTextContainsUrl _onlyEllipsizedURLIsCounted _withCustomConfiguration ( ) {
val url = " https://www.google.dk/search?biw=1920&bih=990&tbm=isch&sa=1&ei=bmDrWuOoKMv6kwWOkIaoDQ&q=indiana+jones+i+hate+snakes+animated&oq=indiana+jones+i+hate+snakes+animated&gs_l=psy-ab.3...54174.55443.0.55553.9.7.0.0.0.0.255.333.1j0j1.2.0....0...1c.1.64.psy-ab..7.0.0....0.40G-kcDkC6A#imgdii=PSp15hQjN1JqvM:&imgrc=H0hyE2JW5wrpBM: "
val additionalContent = " Check out this @image #search result: "
val customUrlLength = 16
2023-10-25 12:53:10 +02:00
instanceResponseCallback = { getInstanceWithCustomConfiguration ( null , customUrlLength ) }
setupActivity ( )
shadowOf ( getMainLooper ( ) ) . idle ( )
insertSomeTextInContent ( additionalContent + url )
2023-12-10 07:38:25 +01:00
assertEquals ( additionalContent . length + customUrlLength , activity . calculateTextLength ( ) )
2023-10-25 12:53:10 +02:00
}
@Test
fun whenTextContainsUrl _onlyEllipsizedURLIsCounted _withCustomConfigurationV1 ( ) {
val url = " https://www.google.dk/search?biw=1920&bih=990&tbm=isch&sa=1&ei=bmDrWuOoKMv6kwWOkIaoDQ&q=indiana+jones+i+hate+snakes+animated&oq=indiana+jones+i+hate+snakes+animated&gs_l=psy-ab.3...54174.55443.0.55553.9.7.0.0.0.0.255.333.1j0j1.2.0....0...1c.1.64.psy-ab..7.0.0....0.40G-kcDkC6A#imgdii=PSp15hQjN1JqvM:&imgrc=H0hyE2JW5wrpBM: "
val additionalContent = " Check out this @image #search result: "
val customUrlLength = 16
instanceV1ResponseCallback = { getInstanceV1WithCustomConfiguration ( configuration = getCustomInstanceConfiguration ( charactersReservedPerUrl = customUrlLength ) ) }
2022-03-01 19:43:36 +01:00
setupActivity ( )
shadowOf ( getMainLooper ( ) ) . idle ( )
insertSomeTextInContent ( additionalContent + url )
2023-12-10 07:38:25 +01:00
assertEquals ( additionalContent . length + customUrlLength , activity . calculateTextLength ( ) )
2022-03-01 19:43:36 +01:00
}
@Test
2022-03-24 19:52:18 +01:00
fun whenTextContainsShortUrls _allUrlsGetEllipsized _withCustomConfiguration ( ) {
2022-03-01 19:43:36 +01:00
val shortUrl = " https://tusky.app "
val url = " https://www.google.dk/search?biw=1920&bih=990&tbm=isch&sa=1&ei=bmDrWuOoKMv6kwWOkIaoDQ&q=indiana+jones+i+hate+snakes+animated&oq=indiana+jones+i+hate+snakes+animated&gs_l=psy-ab.3...54174.55443.0.55553.9.7.0.0.0.0.255.333.1j0j1.2.0....0...1c.1.64.psy-ab..7.0.0....0.40G-kcDkC6A#imgdii=PSp15hQjN1JqvM:&imgrc=H0hyE2JW5wrpBM: "
val additionalContent = " Check out this @image #search result: "
val customUrlLength = 18 // The intention is that this is longer than shortUrl.length
2023-10-25 12:53:10 +02:00
instanceResponseCallback = { getInstanceWithCustomConfiguration ( null , customUrlLength ) }
setupActivity ( )
shadowOf ( getMainLooper ( ) ) . idle ( )
insertSomeTextInContent ( shortUrl + additionalContent + url )
2023-12-10 07:38:25 +01:00
assertEquals ( additionalContent . length + ( customUrlLength * 2 ) , activity . calculateTextLength ( ) )
2023-10-25 12:53:10 +02:00
}
@Test
fun whenTextContainsShortUrls _allUrlsGetEllipsized _withCustomConfigurationV1 ( ) {
val shortUrl = " https://tusky.app "
val url = " https://www.google.dk/search?biw=1920&bih=990&tbm=isch&sa=1&ei=bmDrWuOoKMv6kwWOkIaoDQ&q=indiana+jones+i+hate+snakes+animated&oq=indiana+jones+i+hate+snakes+animated&gs_l=psy-ab.3...54174.55443.0.55553.9.7.0.0.0.0.255.333.1j0j1.2.0....0...1c.1.64.psy-ab..7.0.0....0.40G-kcDkC6A#imgdii=PSp15hQjN1JqvM:&imgrc=H0hyE2JW5wrpBM: "
val additionalContent = " Check out this @image #search result: "
val customUrlLength = 18 // The intention is that this is longer than shortUrl.length
instanceV1ResponseCallback = { getInstanceV1WithCustomConfiguration ( configuration = getCustomInstanceConfiguration ( charactersReservedPerUrl = customUrlLength ) ) }
2022-03-01 19:43:36 +01:00
setupActivity ( )
shadowOf ( getMainLooper ( ) ) . idle ( )
insertSomeTextInContent ( shortUrl + additionalContent + url )
2023-12-10 07:38:25 +01:00
assertEquals ( additionalContent . length + ( customUrlLength * 2 ) , activity . calculateTextLength ( ) )
2022-03-01 19:43:36 +01:00
}
@Test
fun whenTextContainsMultipleURLs _allURLsGetEllipsized _withCustomConfiguration ( ) {
val url = " https://www.google.dk/search?biw=1920&bih=990&tbm=isch&sa=1&ei=bmDrWuOoKMv6kwWOkIaoDQ&q=indiana+jones+i+hate+snakes+animated&oq=indiana+jones+i+hate+snakes+animated&gs_l=psy-ab.3...54174.55443.0.55553.9.7.0.0.0.0.255.333.1j0j1.2.0....0...1c.1.64.psy-ab..7.0.0....0.40G-kcDkC6A#imgdii=PSp15hQjN1JqvM:&imgrc=H0hyE2JW5wrpBM: "
val additionalContent = " Check out this @image #search result: "
val customUrlLength = 16
2023-10-25 12:53:10 +02:00
instanceResponseCallback = { getInstanceWithCustomConfiguration ( null , customUrlLength ) }
setupActivity ( )
shadowOf ( getMainLooper ( ) ) . idle ( )
insertSomeTextInContent ( url + additionalContent + url )
2023-12-10 07:38:25 +01:00
assertEquals ( additionalContent . length + ( customUrlLength * 2 ) , activity . calculateTextLength ( ) )
2023-10-25 12:53:10 +02:00
}
@Test
fun whenTextContainsMultipleURLs _allURLsGetEllipsized _withCustomConfigurationV1 ( ) {
val url = " https://www.google.dk/search?biw=1920&bih=990&tbm=isch&sa=1&ei=bmDrWuOoKMv6kwWOkIaoDQ&q=indiana+jones+i+hate+snakes+animated&oq=indiana+jones+i+hate+snakes+animated&gs_l=psy-ab.3...54174.55443.0.55553.9.7.0.0.0.0.255.333.1j0j1.2.0....0...1c.1.64.psy-ab..7.0.0....0.40G-kcDkC6A#imgdii=PSp15hQjN1JqvM:&imgrc=H0hyE2JW5wrpBM: "
val additionalContent = " Check out this @image #search result: "
val customUrlLength = 16
instanceV1ResponseCallback = { getInstanceV1WithCustomConfiguration ( configuration = getCustomInstanceConfiguration ( charactersReservedPerUrl = customUrlLength ) ) }
2022-03-01 19:43:36 +01:00
setupActivity ( )
shadowOf ( getMainLooper ( ) ) . idle ( )
insertSomeTextInContent ( url + additionalContent + url )
2023-12-10 07:38:25 +01:00
assertEquals ( additionalContent . length + ( customUrlLength * 2 ) , activity . calculateTextLength ( ) )
2018-05-16 19:14:26 +02:00
}
2020-01-13 15:21:17 +01:00
@Test
fun whenSelectionIsEmpty _specialTextIsInsertedAtCaret ( ) {
val editor = activity . findViewById < EditText > ( R . id . composeEditField )
val insertText = " # "
editor . setText ( " Some text " )
for ( caretIndex in listOf ( 9 , 1 , 0 ) ) {
editor . setSelection ( caretIndex )
activity . prependSelectedWordsWith ( insertText )
// Text should be inserted at caret
2020-11-22 19:02:54 +01:00
assertEquals ( " Unexpected value at $caretIndex " , insertText , editor . text . substring ( caretIndex , caretIndex + insertText . length ) )
2020-01-13 15:21:17 +01:00
// Caret should be placed after inserted text
assertEquals ( caretIndex + insertText . length , editor . selectionStart )
assertEquals ( caretIndex + insertText . length , editor . selectionEnd )
}
}
@Test
fun whenSelectionDoesNotIncludeWordBreak _noSpecialTextIsInserted ( ) {
val editor = activity . findViewById < EditText > ( R . id . composeEditField )
val insertText = " # "
val originalText = " Some text "
val selectionStart = 1
val selectionEnd = 4
editor . setText ( originalText )
editor . setSelection ( selectionStart , selectionEnd ) // "ome"
activity . prependSelectedWordsWith ( insertText )
// Text and selection should be unmodified
assertEquals ( originalText , editor . text . toString ( ) )
assertEquals ( selectionStart , editor . selectionStart )
assertEquals ( selectionEnd , editor . selectionEnd )
}
@Test
fun whenSelectionIncludesWordBreaks _startsOfAllWordsArePrepended ( ) {
val editor = activity . findViewById < EditText > ( R . id . composeEditField )
val insertText = " # "
val originalText = " one two three four "
val selectionStart = 2
val originalSelectionEnd = 15
val modifiedSelectionEnd = 18
editor . setText ( originalText )
editor . setSelection ( selectionStart , originalSelectionEnd ) // "e two three f"
activity . prependSelectedWordsWith ( insertText )
// text should be inserted at word starts inside selection
assertEquals ( " one #two #three #four " , editor . text . toString ( ) )
// selection should be expanded accordingly
assertEquals ( selectionStart , editor . selectionStart )
assertEquals ( modifiedSelectionEnd , editor . selectionEnd )
}
@Test
fun whenSelectionIncludesEnd _textIsNotAppended ( ) {
val editor = activity . findViewById < EditText > ( R . id . composeEditField )
val insertText = " # "
val originalText = " Some text "
val selectionStart = 7
val selectionEnd = 9
editor . setText ( originalText )
editor . setSelection ( selectionStart , selectionEnd ) // "xt"
activity . prependSelectedWordsWith ( insertText )
// Text and selection should be unmodified
assertEquals ( originalText , editor . text . toString ( ) )
assertEquals ( selectionStart , editor . selectionStart )
assertEquals ( selectionEnd , editor . selectionEnd )
}
@Test
fun whenSelectionIncludesStartAndStartIsAWord _textIsPrepended ( ) {
val editor = activity . findViewById < EditText > ( R . id . composeEditField )
val insertText = " # "
val originalText = " Some text "
val selectionStart = 0
val selectionEnd = 3
editor . setText ( originalText )
editor . setSelection ( selectionStart , selectionEnd ) // "Som"
activity . prependSelectedWordsWith ( insertText )
// Text should be inserted at beginning
assert ( editor . text . startsWith ( insertText ) )
// selection should be expanded accordingly
assertEquals ( selectionStart , editor . selectionStart )
assertEquals ( selectionEnd + insertText . length , editor . selectionEnd )
}
@Test
fun whenSelectionIncludesStartAndStartIsNotAWord _textIsNotPrepended ( ) {
val editor = activity . findViewById < EditText > ( R . id . composeEditField )
val insertText = " # "
val originalText = " Some text "
val selectionStart = 0
val selectionEnd = 1
editor . setText ( originalText )
editor . setSelection ( selectionStart , selectionEnd ) // " "
activity . prependSelectedWordsWith ( insertText )
// Text and selection should be unmodified
assertEquals ( originalText , editor . text . toString ( ) )
assertEquals ( selectionStart , editor . selectionStart )
assertEquals ( selectionEnd , editor . selectionEnd )
}
@Test
fun whenSelectionBeginsAtWordStart _textIsPrepended ( ) {
val editor = activity . findViewById < EditText > ( R . id . composeEditField )
val insertText = " # "
val originalText = " Some text "
val selectionStart = 5
val selectionEnd = 9
editor . setText ( originalText )
editor . setSelection ( selectionStart , selectionEnd ) // "text"
activity . prependSelectedWordsWith ( insertText )
// Text is prepended
assertEquals ( " Some #text " , editor . text . toString ( ) )
// Selection is expanded accordingly
assertEquals ( selectionStart , editor . selectionStart )
assertEquals ( selectionEnd + insertText . length , editor . selectionEnd )
}
@Test
fun whenSelectionEndsAtWordStart _textIsAppended ( ) {
val editor = activity . findViewById < EditText > ( R . id . composeEditField )
val insertText = " # "
val originalText = " Some text "
val selectionStart = 1
val selectionEnd = 5
editor . setText ( originalText )
editor . setSelection ( selectionStart , selectionEnd ) // "ome "
activity . prependSelectedWordsWith ( insertText )
// Text is prepended
assertEquals ( " Some #text " , editor . text . toString ( ) )
// Selection is expanded accordingly
assertEquals ( selectionStart , editor . selectionStart )
assertEquals ( selectionEnd + insertText . length , editor . selectionEnd )
}
2022-08-31 18:53:57 +02:00
@Test
fun whenNoLanguageIsGiven _defaultLanguageIsSelected ( ) {
assertEquals ( Locale . getDefault ( ) . language , activity . selectedLanguage )
}
@Test
fun languageGivenInComposeOptionsIsRespected ( ) {
val language = " no "
composeOptions = ComposeActivity . ComposeOptions ( language = language )
setupActivity ( )
assertEquals ( language , activity . selectedLanguage )
}
2022-11-24 15:45:19 +01:00
@Test
fun modernLanguageCodeIsUsed ( ) {
// https://github.com/tuskyapp/Tusky/issues/2903
// "ji" was deprecated in favor of "yi"
composeOptions = ComposeActivity . ComposeOptions ( language = " ji " )
setupActivity ( )
assertEquals ( " yi " , activity . selectedLanguage )
}
@Test
fun unknownLanguageGivenInComposeOptionsIsRespected ( ) {
val language = " zzz "
composeOptions = ComposeActivity . ComposeOptions ( language = language )
setupActivity ( )
assertEquals ( language , activity . selectedLanguage )
}
2023-11-13 10:05:28 +01:00
@Test
fun sampleFriendicaInstanceResponseIsDeserializable ( ) {
// https://github.com/tuskyapp/Tusky/issues/4100
instanceResponseCallback = { getSampleFriendicaInstance ( ) }
setupActivity ( )
shadowOf ( getMainLooper ( ) ) . idle ( )
2024-01-04 17:00:55 +01:00
assertEquals ( FRIENDICA _MAXIMUM , activity . maximumTootCharacters )
2023-11-13 10:05:28 +01:00
}
2018-03-09 22:02:32 +01:00
private fun clickUp ( ) {
val menuItem = RoboMenuItem ( android . R . id . home )
activity . onOptionsItemSelected ( menuItem )
}
private fun clickBack ( ) {
2022-11-04 19:22:38 +01:00
activity . onBackPressedDispatcher . onBackPressed ( )
2018-03-09 22:02:32 +01:00
}
2018-05-16 19:14:26 +02:00
private fun insertSomeTextInContent ( text : String ? = null ) {
activity . findViewById < EditText > ( R . id . composeEditField ) . setText ( text ?: " Some text " )
2018-03-09 22:02:32 +01:00
}
2018-04-29 10:08:25 +02:00
2023-10-25 12:53:10 +02:00
private fun getInstanceWithCustomConfiguration ( maximumStatusCharacters : Int ? = null , charactersReservedPerUrl : Int ? = null ) : Instance {
2018-04-29 10:08:25 +02:00
return Instance (
2023-10-25 12:53:10 +02:00
domain = " https://example.token " ,
version = " 2.6.3 " ,
configuration = getConfiguration ( maximumStatusCharacters , charactersReservedPerUrl ) ,
pleroma = null ,
rules = emptyList ( )
)
}
private fun getConfiguration ( maximumStatusCharacters : Int ? , charactersReservedPerUrl : Int ? ) : Instance . Configuration {
return Instance . Configuration (
Instance . Configuration . Urls ( streamingApi = " " ) ,
Instance . Configuration . Accounts ( 1 ) ,
Instance . Configuration . Statuses (
maximumStatusCharacters ?: InstanceInfoRepository . DEFAULT _CHARACTER _LIMIT ,
InstanceInfoRepository . DEFAULT _MAX _MEDIA _ATTACHMENTS ,
charactersReservedPerUrl ?: InstanceInfoRepository . DEFAULT _CHARACTERS _RESERVED _PER _URL
) ,
2023-11-13 10:05:28 +01:00
Instance . Configuration . MediaAttachments ( 0 , 0 , 0 , 0 , 0 ) ,
2023-10-25 12:53:10 +02:00
Instance . Configuration . Polls ( 0 , 0 , 0 , 0 ) ,
2024-01-04 17:00:55 +01:00
Instance . Configuration . Translation ( false )
2023-10-25 12:53:10 +02:00
)
}
private fun getInstanceV1WithCustomConfiguration ( maximumLegacyTootCharacters : Int ? = null , configuration : InstanceConfiguration ? = null ) : InstanceV1 {
return InstanceV1 (
2022-07-26 20:24:50 +02:00
uri = " https://example.token " ,
version = " 2.6.3 " ,
maxTootChars = maximumLegacyTootCharacters ,
pollConfiguration = null ,
configuration = configuration ,
maxMediaAttachments = null ,
2022-08-07 19:14:42 +02:00
pleroma = null ,
2022-11-05 17:37:20 +01:00
uploadLimit = null ,
rules = emptyList ( )
2022-03-01 19:43:36 +01:00
)
}
2022-04-14 19:49:49 +02:00
private fun getCustomInstanceConfiguration ( maximumStatusCharacters : Int ? = null , charactersReservedPerUrl : Int ? = null ) : InstanceConfiguration {
2022-03-01 19:43:36 +01:00
return InstanceConfiguration (
statuses = StatusConfiguration (
maxCharacters = maximumStatusCharacters ,
maxMediaAttachments = null ,
charactersReservedPerUrl = charactersReservedPerUrl
) ,
mediaAttachments = null ,
polls = null
2018-04-29 10:08:25 +02:00
)
}
2023-11-13 10:05:28 +01:00
private fun getSampleFriendicaInstance ( ) : Instance {
return gson . fromJson ( sampleFriendicaResponse , Instance :: class . java )
}
companion object {
2024-01-04 17:00:55 +01:00
private const val FRIENDICA _MAXIMUM = 200000
2023-11-13 10:05:28 +01:00
// https://github.com/tuskyapp/Tusky/issues/4100
private val sampleFriendicaResponse = """ {
" domain " : " loma.ml " ,
" title " : " [ˈ loma] " ,
" version " : " 2.8.0 (compatible; Friendica 2023.09-rc) " ,
" source_url " : " https://git.friendi.ca/friendica/friendica " ,
" description " : " loma.ml ist eine Friendica Community im Fediverse auf der vorwiegend DE \uD83C \uDDE9 \uD83C \uDDEA gesprochen wird. \\ r \\ nServer in Germany/EU \uD83C \uDDE9 \uD83C \uDDEA \uD83C \uDDEA \uD83C \uDDFA . Open to all with fun in new. \\ r \\ nServer in Deutschland. Offen für alle mit Spaß an Neuen. " ,
" usage " : {
" users " : {
" active_month " : 125
}
} ,
" thumbnail " : {
" url " : " https://loma.ml/ad/friendica-banner.jpg "
} ,
" languages " : [
" de "
] ,
" configuration " : {
" statuses " : {
2024-01-04 17:00:55 +01:00
" max_characters " : $ FRIENDICA _MAXIMUM
2023-11-13 10:05:28 +01:00
} ,
" media_attachments " : {
" supported_mime_types " : {
" image/jpeg " : " jpg " ,
" image/jpg " : " jpg " ,
" image/png " : " png " ,
" image/gif " : " gif "
} ,
" image_size_limit " : 10485760
}
} ,
" registrations " : {
" enabled " : true ,
" approval_required " : false
} ,
" contact " : {
" email " : " anony@miz.ed " ,
" account " : {
" id " : " 9632 " ,
" username " : " webm " ,
" acct " : " webm " ,
" display_name " : " web m \uD83C \uDDEA \uD83C \uDDFA " ,
" locked " : false ,
" bot " : false ,
" discoverable " : true ,
" group " : false ,
" created_at " : " 2018-05-21T11:24:55.000Z " ,
" note " : " \uD83C \uDDE9 \uD83C \uDDEA Über diesen Account werden Änderungen oder geplante Beeinträchtigungen angekündigt. Wenn du einen Account auf Loma.ml besitzt, dann solltest du dich mit mir verbinden. \uD83C \uDDEA \uD83C \uDDFA Changes or planned impairments are announced via this account. If you have an account on Loma.ml, you should connect to me. \uD83C \uDD98 Fallbackaccount @webm@joinfriendica.de " ,
" url " : " https://loma.ml/profile/webm " ,
" avatar " : " https://loma.ml/photo/contact/320/373ebf56355ac895a09cb99264485383?ts=1686417730 " ,
" avatar_static " : " https://loma.ml/photo/contact/320/373ebf56355ac895a09cb99264485383?ts=1686417730&static=1 " ,
" header " : " https://loma.ml/photo/header/373ebf56355ac895a09cb99264485383?ts=1686417730 " ,
" header_static " : " https://loma.ml/photo/header/373ebf56355ac895a09cb99264485383?ts=1686417730&static=1 " ,
" followers_count " : 23 ,
" following_count " : 25 ,
" statuses_count " : 15 ,
" last_status_at " : " 2023-09-19T00:00:00.000Z " ,
" emojis " : [ ] ,
" fields " : [ ]
}
} ,
" rules " : [ ] ,
" friendica " : {
" version " : " 2023.09-rc " ,
" codename " : " Giant Rhubarb " ,
" db_version " : 1539
}
}
""" .trimIndent()
}
2019-12-19 19:09:40 +01:00
}