diff --git a/app/build.gradle b/app/build.gradle index 446ba09e0..3ddec6a2b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -93,7 +93,9 @@ android { } } sourceSets { - androidTest.assets.srcDirs += files("$projectDir/schemas".toString()) + // workaround to have migrations available in unit tests + // https://github.com/robolectric/robolectric/issues/3928#issuecomment-395309991 + debug.assets.srcDirs += files("$projectDir/schemas".toString()) } // Exclude unneeded files added by libraries @@ -183,12 +185,8 @@ dependencies { testImplementation libs.bundles.mockito testImplementation libs.mockwebserver testImplementation libs.androidx.core.testing + testImplementation libs.androidx.room.testing testImplementation libs.kotlinx.coroutines.test testImplementation libs.androidx.work.testing - testImplementation libs.truth testImplementation libs.turbine - - androidTestImplementation libs.espresso.core - androidTestImplementation libs.androidx.room.testing - androidTestImplementation libs.androidx.test.junit } diff --git a/app/src/androidTest/java/com/keylesspalace/tusky/MigrationsTest.kt b/app/src/androidTest/java/com/keylesspalace/tusky/MigrationsTest.kt deleted file mode 100644 index 313ab8e20..000000000 --- a/app/src/androidTest/java/com/keylesspalace/tusky/MigrationsTest.kt +++ /dev/null @@ -1,66 +0,0 @@ -package com.keylesspalace.tusky - -import androidx.room.testing.MigrationTestHelper -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import com.keylesspalace.tusky.db.AppDatabase -import org.junit.Assert.assertEquals -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -const val TEST_DB = "migration_test" - -@RunWith(AndroidJUnit4::class) -class MigrationsTest { - - @JvmField - @Rule - var helper: MigrationTestHelper = MigrationTestHelper( - InstrumentationRegistry.getInstrumentation(), - AppDatabase::class.java - ) - - @Test - fun migrateTo11() { - val db = helper.createDatabase(TEST_DB, 10) - - val id = 1 - val domain = "domain.site" - val token = "token" - val active = true - val accountId = "accountId" - val username = "username" - val values = arrayOf( - id, domain, token, active, accountId, username, "Display Name", - "https://picture.url", true, true, true, true, true, true, true, - true, "1000", "[]", "[{\"shortcode\": \"emoji\", \"url\": \"yes\"}]", 0, false, - false, true - ) - - db.execSQL( - "INSERT OR REPLACE INTO `AccountEntity`(`id`,`domain`,`accessToken`,`isActive`," + - "`accountId`,`username`,`displayName`,`profilePictureUrl`,`notificationsEnabled`," + - "`notificationsMentioned`,`notificationsFollowed`,`notificationsReblogged`," + - "`notificationsFavorited`,`notificationSound`,`notificationVibration`," + - "`notificationLight`,`lastNotificationId`,`activeNotifications`,`emojis`," + - "`defaultPostPrivacy`,`defaultMediaSensitivity`,`alwaysShowSensitiveMedia`," + - "`mediaPreviewEnabled`) " + - "VALUES (nullif(?, 0),?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", - values - ) - - db.close() - - val newDb = helper.runMigrationsAndValidate(TEST_DB, 11, true, AppDatabase.MIGRATION_10_11) - - val cursor = newDb.query("SELECT * FROM AccountEntity") - cursor.moveToFirst() - assertEquals(id, cursor.getInt(0)) - assertEquals(domain, cursor.getString(1)) - assertEquals(token, cursor.getString(2)) - assertEquals(active, cursor.getInt(3) != 0) - assertEquals(accountId, cursor.getString(4)) - assertEquals(username, cursor.getString(5)) - } -} diff --git a/app/src/test/java/android/text/SpannableString.kt b/app/src/test/java/android/text/SpannableString.kt deleted file mode 100644 index dc8cd831f..000000000 --- a/app/src/test/java/android/text/SpannableString.kt +++ /dev/null @@ -1,49 +0,0 @@ -package android.text - -// Used for stubbing Android implementation without slow & buggy Robolectric things -@Suppress("unused") -class SpannableString(private val text: CharSequence) : Spannable { - - override fun setSpan(what: Any?, start: Int, end: Int, flags: Int) { - throw NotImplementedError() - } - - override fun getSpans(start: Int, end: Int, type: Class?): Array { - throw NotImplementedError() - } - - override fun removeSpan(what: Any?) { - throw NotImplementedError() - } - - override fun toString(): String { - return "FakeSpannableString[text=$text]" - } - - override val length: Int - get() = text.length - - override fun nextSpanTransition(start: Int, limit: Int, type: Class<*>?): Int { - throw NotImplementedError() - } - - override fun getSpanEnd(tag: Any?): Int { - throw NotImplementedError() - } - - override fun getSpanFlags(tag: Any?): Int { - throw NotImplementedError() - } - - override fun get(index: Int): Char { - throw NotImplementedError() - } - - override fun subSequence(startIndex: Int, endIndex: Int): CharSequence { - throw NotImplementedError() - } - - override fun getSpanStart(tag: Any?): Int { - throw NotImplementedError() - } -} diff --git a/app/src/test/java/com/keylesspalace/tusky/FilterV1Test.kt b/app/src/test/java/com/keylesspalace/tusky/FilterV1Test.kt index fd3912cb7..c7e9222c8 100644 --- a/app/src/test/java/com/keylesspalace/tusky/FilterV1Test.kt +++ b/app/src/test/java/com/keylesspalace/tusky/FilterV1Test.kt @@ -42,7 +42,7 @@ import org.robolectric.annotation.Config import retrofit2.HttpException import retrofit2.Response -@Config(sdk = [28]) +@Config(sdk = [34]) @RunWith(AndroidJUnit4::class) class FilterV1Test { diff --git a/app/src/test/java/com/keylesspalace/tusky/StatusComparisonTest.kt b/app/src/test/java/com/keylesspalace/tusky/StatusComparisonTest.kt index 6fd1c6a51..92b40d155 100644 --- a/app/src/test/java/com/keylesspalace/tusky/StatusComparisonTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/StatusComparisonTest.kt @@ -11,7 +11,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.robolectric.annotation.Config -@Config(sdk = [28]) +@Config(sdk = [34]) @RunWith(AndroidJUnit4::class) class StatusComparisonTest { diff --git a/app/src/test/java/com/keylesspalace/tusky/components/compose/ComposeActivityTest.kt b/app/src/test/java/com/keylesspalace/tusky/components/compose/ComposeActivityTest.kt index c1a4c8ac8..0f1fdce8c 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/compose/ComposeActivityTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/compose/ComposeActivityTest.kt @@ -67,7 +67,7 @@ import retrofit2.Response * Created by charlag on 3/7/18. */ -@Config(sdk = [28]) +@Config(sdk = [34]) @RunWith(AndroidJUnit4::class) class ComposeActivityTest { private lateinit var activity: ComposeActivity diff --git a/app/src/test/java/com/keylesspalace/tusky/components/compose/ComposeTokenizerTest.kt b/app/src/test/java/com/keylesspalace/tusky/components/compose/ComposeTokenizerTest.kt index 3c801dcbb..c7988c181 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/compose/ComposeTokenizerTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/compose/ComposeTokenizerTest.kt @@ -15,7 +15,7 @@ package com.keylesspalace.tusky.components.compose -import org.junit.Assert +import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized @@ -89,7 +89,7 @@ class ComposeTokenizerTest( @Test fun tokenIndices_matchExpectations() { - Assert.assertEquals(expectedStartIndex, tokenizer.findTokenStart(text, text.length)) - Assert.assertEquals(expectedEndIndex, tokenizer.findTokenEnd(text, text.length)) + assertEquals(expectedStartIndex, tokenizer.findTokenStart(text, text.length)) + assertEquals(expectedEndIndex, tokenizer.findTokenEnd(text, text.length)) } } diff --git a/app/src/test/java/com/keylesspalace/tusky/components/compose/ComposeViewModelTest.kt b/app/src/test/java/com/keylesspalace/tusky/components/compose/ComposeViewModelTest.kt index 0afcbb76b..fcb283311 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/compose/ComposeViewModelTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/compose/ComposeViewModelTest.kt @@ -14,7 +14,7 @@ import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock import org.robolectric.annotation.Config -@Config(sdk = [28]) +@Config(sdk = [34]) @RunWith(AndroidJUnit4::class) class ComposeViewModelTest { diff --git a/app/src/test/java/com/keylesspalace/tusky/components/compose/StatusLengthTest.kt b/app/src/test/java/com/keylesspalace/tusky/components/compose/StatusLengthTest.kt index 8b2b0113a..b833be6ff 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/compose/StatusLengthTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/compose/StatusLengthTest.kt @@ -27,7 +27,7 @@ import org.robolectric.ParameterizedRobolectricTestRunner import org.robolectric.annotation.Config @RunWith(ParameterizedRobolectricTestRunner::class) -@Config(sdk = [33]) +@Config(sdk = [34]) class StatusLengthTest( private val text: String, private val expectedLength: Int diff --git a/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsRemoteMediatorTest.kt b/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsRemoteMediatorTest.kt index 34ae432bb..4d89f45ba 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsRemoteMediatorTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/notifications/NotificationsRemoteMediatorTest.kt @@ -38,7 +38,7 @@ import org.robolectric.annotation.Config import retrofit2.HttpException import retrofit2.Response -@Config(sdk = [28]) +@Config(sdk = [34]) @RunWith(AndroidJUnit4::class) class NotificationsRemoteMediatorTest { diff --git a/app/src/test/java/com/keylesspalace/tusky/components/timeline/CachedTimelineRemoteMediatorTest.kt b/app/src/test/java/com/keylesspalace/tusky/components/timeline/CachedTimelineRemoteMediatorTest.kt index f003255e6..10d704fb5 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/timeline/CachedTimelineRemoteMediatorTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/timeline/CachedTimelineRemoteMediatorTest.kt @@ -37,7 +37,7 @@ import org.robolectric.annotation.Config import retrofit2.HttpException import retrofit2.Response -@Config(sdk = [28]) +@Config(sdk = [34]) @RunWith(AndroidJUnit4::class) class CachedTimelineRemoteMediatorTest { diff --git a/app/src/test/java/com/keylesspalace/tusky/components/timeline/NetworkTimelinePagingSourceTest.kt b/app/src/test/java/com/keylesspalace/tusky/components/timeline/NetworkTimelinePagingSourceTest.kt index 553587237..ff3c9ebaa 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/timeline/NetworkTimelinePagingSourceTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/timeline/NetworkTimelinePagingSourceTest.kt @@ -4,7 +4,7 @@ import androidx.paging.PagingSource import androidx.test.ext.junit.runners.AndroidJUnit4 import com.keylesspalace.tusky.components.timeline.viewmodel.NetworkTimelinePagingSource import com.keylesspalace.tusky.components.timeline.viewmodel.NetworkTimelineViewModel -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith @@ -12,7 +12,7 @@ import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock import org.robolectric.annotation.Config -@Config(sdk = [28]) +@Config(sdk = [34]) @RunWith(AndroidJUnit4::class) class NetworkTimelinePagingSourceTest { @@ -23,42 +23,36 @@ class NetworkTimelinePagingSourceTest { } @Test - fun `should return empty list when params are Append`() { + fun `should return empty list when params are Append`() = runTest { val pagingSource = NetworkTimelinePagingSource(timelineViewModel) val params = PagingSource.LoadParams.Append("132", 20, false) val expectedResult = PagingSource.LoadResult.Page(emptyList(), null, null) - runBlocking { - assertEquals(expectedResult, pagingSource.load(params)) - } + assertEquals(expectedResult, pagingSource.load(params)) } @Test - fun `should return empty list when params are Prepend`() { + fun `should return empty list when params are Prepend`() = runTest { val pagingSource = NetworkTimelinePagingSource(timelineViewModel) val params = PagingSource.LoadParams.Prepend("132", 20, false) val expectedResult = PagingSource.LoadResult.Page(emptyList(), null, null) - runBlocking { - assertEquals(expectedResult, pagingSource.load(params)) - } + assertEquals(expectedResult, pagingSource.load(params)) } @Test - fun `should return full list when params are Refresh`() { + fun `should return full list when params are Refresh`() = runTest { val pagingSource = NetworkTimelinePagingSource(timelineViewModel) val params = PagingSource.LoadParams.Refresh(null, 20, false) val expectedResult = PagingSource.LoadResult.Page(listOf(status), null, null) - runBlocking { - val result = pagingSource.load(params) - assertEquals(expectedResult, result) - } + val result = pagingSource.load(params) + assertEquals(expectedResult, result) } } diff --git a/app/src/test/java/com/keylesspalace/tusky/components/timeline/NetworkTimelineRemoteMediatorTest.kt b/app/src/test/java/com/keylesspalace/tusky/components/timeline/NetworkTimelineRemoteMediatorTest.kt index 14621fac3..84529d62f 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/timeline/NetworkTimelineRemoteMediatorTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/timeline/NetworkTimelineRemoteMediatorTest.kt @@ -14,7 +14,7 @@ import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.db.entity.AccountEntity import com.keylesspalace.tusky.viewdata.StatusViewData import java.io.IOException -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import okhttp3.Headers import okhttp3.ResponseBody.Companion.toResponseBody import org.junit.Assert.assertEquals @@ -30,7 +30,7 @@ import org.robolectric.annotation.Config import retrofit2.HttpException import retrofit2.Response -@Config(sdk = [29]) +@Config(sdk = [34]) @RunWith(AndroidJUnit4::class) class NetworkTimelineRemoteMediatorTest { @@ -47,7 +47,7 @@ class NetworkTimelineRemoteMediatorTest { @Test @ExperimentalPagingApi - fun `should return error when network call returns error code`() { + fun `should return error when network call returns error code`() = runTest { val timelineViewModel: NetworkTimelineViewModel = mock { on { statusData } doReturn mutableListOf() onBlocking { fetchStatusesForKind(anyOrNull(), anyOrNull(), anyOrNull()) } doReturn Response.error(500, "".toResponseBody()) @@ -55,7 +55,7 @@ class NetworkTimelineRemoteMediatorTest { val remoteMediator = NetworkTimelineRemoteMediator(accountManager, timelineViewModel) - val result = runBlocking { remoteMediator.load(LoadType.REFRESH, state()) } + val result = remoteMediator.load(LoadType.REFRESH, state()) assertTrue(result is RemoteMediator.MediatorResult.Error) assertTrue((result as RemoteMediator.MediatorResult.Error).throwable is HttpException) @@ -64,7 +64,7 @@ class NetworkTimelineRemoteMediatorTest { @Test @ExperimentalPagingApi - fun `should return error when network call fails`() { + fun `should return error when network call fails`() = runTest { val timelineViewModel: NetworkTimelineViewModel = mock { on { statusData } doReturn mutableListOf() onBlocking { fetchStatusesForKind(anyOrNull(), anyOrNull(), anyOrNull()) } doThrow IOException() @@ -72,7 +72,7 @@ class NetworkTimelineRemoteMediatorTest { val remoteMediator = NetworkTimelineRemoteMediator(accountManager, timelineViewModel) - val result = runBlocking { remoteMediator.load(LoadType.REFRESH, state()) } + val result = remoteMediator.load(LoadType.REFRESH, state()) assertTrue(result is RemoteMediator.MediatorResult.Error) assertTrue((result as RemoteMediator.MediatorResult.Error).throwable is IOException) @@ -80,7 +80,7 @@ class NetworkTimelineRemoteMediatorTest { @Test @ExperimentalPagingApi - fun `should do initial loading`() { + fun `should do initial loading`() = runTest { val statuses: MutableList = mutableListOf() val timelineViewModel: NetworkTimelineViewModel = mock { @@ -111,7 +111,7 @@ class NetworkTimelineRemoteMediatorTest { ) ) - val result = runBlocking { remoteMediator.load(LoadType.REFRESH, state) } + val result = remoteMediator.load(LoadType.REFRESH, state) val newStatusData = mutableListOf( fakeStatusViewData("7"), @@ -127,7 +127,7 @@ class NetworkTimelineRemoteMediatorTest { @Test @ExperimentalPagingApi - fun `should not prepend statuses`() { + fun `should not prepend statuses`() = runTest { val statuses: MutableList = mutableListOf( fakeStatusViewData("3"), fakeStatusViewData("2"), @@ -162,7 +162,7 @@ class NetworkTimelineRemoteMediatorTest { ) ) - val result = runBlocking { remoteMediator.load(LoadType.REFRESH, state) } + val result = remoteMediator.load(LoadType.REFRESH, state) val newStatusData = mutableListOf( fakeStatusViewData("5"), @@ -179,7 +179,7 @@ class NetworkTimelineRemoteMediatorTest { @Test @ExperimentalPagingApi - fun `should refresh and insert placeholder`() { + fun `should refresh and insert placeholder`() = runTest { val statuses: MutableList = mutableListOf( fakeStatusViewData("3"), fakeStatusViewData("2"), @@ -214,7 +214,7 @@ class NetworkTimelineRemoteMediatorTest { ) ) - val result = runBlocking { remoteMediator.load(LoadType.REFRESH, state) } + val result = remoteMediator.load(LoadType.REFRESH, state) val newStatusData = mutableListOf( fakeStatusViewData("10"), @@ -232,7 +232,7 @@ class NetworkTimelineRemoteMediatorTest { @Test @ExperimentalPagingApi - fun `should refresh and not insert placeholders`() { + fun `should refresh and not insert placeholders`() = runTest { val statuses: MutableList = mutableListOf( fakeStatusViewData("8"), fakeStatusViewData("7"), @@ -267,7 +267,7 @@ class NetworkTimelineRemoteMediatorTest { ) ) - val result = runBlocking { remoteMediator.load(LoadType.APPEND, state) } + val result = remoteMediator.load(LoadType.APPEND, state) val newStatusData = mutableListOf( fakeStatusViewData("8"), @@ -285,7 +285,7 @@ class NetworkTimelineRemoteMediatorTest { @Test @ExperimentalPagingApi - fun `should append statuses`() { + fun `should append statuses`() = runTest { val statuses: MutableList = mutableListOf( fakeStatusViewData("8"), fakeStatusViewData("7"), @@ -324,7 +324,7 @@ class NetworkTimelineRemoteMediatorTest { ) ) - val result = runBlocking { remoteMediator.load(LoadType.APPEND, state) } + val result = remoteMediator.load(LoadType.APPEND, state) val newStatusData = mutableListOf( fakeStatusViewData("8"), @@ -342,7 +342,7 @@ class NetworkTimelineRemoteMediatorTest { @Test @ExperimentalPagingApi - fun `should not append statuses when pagination end has been reached`() { + fun `should not append statuses when pagination end has been reached`() = runTest { val statuses: MutableList = mutableListOf( fakeStatusViewData("8"), fakeStatusViewData("7"), @@ -370,7 +370,7 @@ class NetworkTimelineRemoteMediatorTest { ) ) - val result = runBlocking { remoteMediator.load(LoadType.APPEND, state) } + val result = remoteMediator.load(LoadType.APPEND, state) val newStatusData = mutableListOf( fakeStatusViewData("8"), @@ -385,7 +385,7 @@ class NetworkTimelineRemoteMediatorTest { @Test @ExperimentalPagingApi - fun `should not append duplicates for trending statuses`() { + fun `should not append duplicates for trending statuses`() = runTest { val statuses: MutableList = mutableListOf( fakeStatusViewData("5"), fakeStatusViewData("4"), @@ -421,7 +421,7 @@ class NetworkTimelineRemoteMediatorTest { ) ) - val result = runBlocking { remoteMediator.load(LoadType.APPEND, state) } + val result = remoteMediator.load(LoadType.APPEND, state) val newStatusData = mutableListOf( fakeStatusViewData("5"), diff --git a/app/src/test/java/com/keylesspalace/tusky/components/viewthread/ViewThreadViewModelTest.kt b/app/src/test/java/com/keylesspalace/tusky/components/viewthread/ViewThreadViewModelTest.kt index 64266fc77..14b872a40 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/viewthread/ViewThreadViewModelTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/viewthread/ViewThreadViewModelTest.kt @@ -22,7 +22,7 @@ import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.usecase.TimelineCases import java.io.IOException import kotlinx.coroutines.flow.first -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import org.junit.After import org.junit.Assert.assertEquals import org.junit.Before @@ -35,7 +35,7 @@ import org.mockito.kotlin.stub import org.robolectric.Shadows.shadowOf import org.robolectric.annotation.Config -@Config(sdk = [28]) +@Config(sdk = [34]) @RunWith(AndroidJUnit4::class) class ViewThreadViewModelTest { @@ -113,29 +113,27 @@ class ViewThreadViewModelTest { } @Test - fun `should emit status and context when both load`() { + fun `should emit status and context when both load`() = runTest { mockSuccessResponses() viewModel.loadThread(threadId) - runBlocking { - assertEquals( - ThreadUiState.Success( - statusViewData = listOf( - fakeStatusViewData(id = "1", spoilerText = "Test"), - fakeStatusViewData(id = "2", inReplyToId = "1", inReplyToAccountId = "1", isDetailed = true, spoilerText = "Test"), - fakeStatusViewData(id = "3", inReplyToId = "2", inReplyToAccountId = "1", spoilerText = "Test") - ), - detailedStatusPosition = 1, - revealButton = RevealButtonState.REVEAL + assertEquals( + ThreadUiState.Success( + statusViewData = listOf( + fakeStatusViewData(id = "1", spoilerText = "Test"), + fakeStatusViewData(id = "2", inReplyToId = "1", inReplyToAccountId = "1", isDetailed = true, spoilerText = "Test"), + fakeStatusViewData(id = "3", inReplyToId = "2", inReplyToAccountId = "1", spoilerText = "Test") ), - viewModel.uiState.first() - ) - } + detailedStatusPosition = 1, + revealButton = RevealButtonState.REVEAL + ), + viewModel.uiState.first() + ) } @Test - fun `should emit status even if context fails to load`() { + fun `should emit status even if context fails to load`() = runTest { api.stub { onBlocking { status(threadId) } doReturn NetworkResult.success(fakeStatus(id = "2", inReplyToId = "1", inReplyToAccountId = "1")) onBlocking { statusContext(threadId) } doReturn NetworkResult.failure(IOException()) @@ -143,22 +141,20 @@ class ViewThreadViewModelTest { viewModel.loadThread(threadId) - runBlocking { - assertEquals( - ThreadUiState.Success( - statusViewData = listOf( - fakeStatusViewData(id = "2", inReplyToId = "1", inReplyToAccountId = "1", isDetailed = true) - ), - detailedStatusPosition = 0, - revealButton = RevealButtonState.NO_BUTTON + assertEquals( + ThreadUiState.Success( + statusViewData = listOf( + fakeStatusViewData(id = "2", inReplyToId = "1", inReplyToAccountId = "1", isDetailed = true) ), - viewModel.uiState.first() - ) - } + detailedStatusPosition = 0, + revealButton = RevealButtonState.NO_BUTTON + ), + viewModel.uiState.first() + ) } @Test - fun `should emit error when status and context fail to load`() { + fun `should emit error when status and context fail to load`() = runTest { api.stub { onBlocking { status(threadId) } doReturn NetworkResult.failure(IOException()) onBlocking { statusContext(threadId) } doReturn NetworkResult.failure(IOException()) @@ -166,16 +162,14 @@ class ViewThreadViewModelTest { viewModel.loadThread(threadId) - runBlocking { - assertEquals( - ThreadUiState.Error::class.java, - viewModel.uiState.first().javaClass - ) - } + assertEquals( + ThreadUiState.Error::class.java, + viewModel.uiState.first().javaClass + ) } @Test - fun `should emit error when status fails to load`() { + fun `should emit error when status fails to load`() = runTest { api.stub { onBlocking { status(threadId) } doReturn NetworkResult.failure(IOException()) onBlocking { statusContext(threadId) } doReturn NetworkResult.success( @@ -188,86 +182,78 @@ class ViewThreadViewModelTest { viewModel.loadThread(threadId) - runBlocking { - assertEquals( - ThreadUiState.Error::class.java, - viewModel.uiState.first().javaClass - ) - } + assertEquals( + ThreadUiState.Error::class.java, + viewModel.uiState.first().javaClass + ) } @Test - fun `should update state when reveal button is toggled`() { + fun `should update state when reveal button is toggled`() = runTest { mockSuccessResponses() viewModel.loadThread(threadId) viewModel.toggleRevealButton() - runBlocking { - assertEquals( - ThreadUiState.Success( - statusViewData = listOf( - fakeStatusViewData(id = "1", spoilerText = "Test", isExpanded = true), - fakeStatusViewData(id = "2", inReplyToId = "1", inReplyToAccountId = "1", isDetailed = true, spoilerText = "Test", isExpanded = true), - fakeStatusViewData(id = "3", inReplyToId = "2", inReplyToAccountId = "1", spoilerText = "Test", isExpanded = true) - ), - detailedStatusPosition = 1, - revealButton = RevealButtonState.HIDE + assertEquals( + ThreadUiState.Success( + statusViewData = listOf( + fakeStatusViewData(id = "1", spoilerText = "Test", isExpanded = true), + fakeStatusViewData(id = "2", inReplyToId = "1", inReplyToAccountId = "1", isDetailed = true, spoilerText = "Test", isExpanded = true), + fakeStatusViewData(id = "3", inReplyToId = "2", inReplyToAccountId = "1", spoilerText = "Test", isExpanded = true) ), - viewModel.uiState.first() - ) - } + detailedStatusPosition = 1, + revealButton = RevealButtonState.HIDE + ), + viewModel.uiState.first() + ) } @Test - fun `should handle status changed event`() { + fun `should handle status changed event`() = runTest { mockSuccessResponses() viewModel.loadThread(threadId) - runBlocking { - eventHub.dispatch(StatusChangedEvent(fakeStatus(id = "1", spoilerText = "Test", favourited = false))) + eventHub.dispatch(StatusChangedEvent(fakeStatus(id = "1", spoilerText = "Test", favourited = false))) - assertEquals( - ThreadUiState.Success( - statusViewData = listOf( - fakeStatusViewData(id = "1", spoilerText = "Test", favourited = false), - fakeStatusViewData(id = "2", inReplyToId = "1", inReplyToAccountId = "1", isDetailed = true, spoilerText = "Test"), - fakeStatusViewData(id = "3", inReplyToId = "2", inReplyToAccountId = "1", spoilerText = "Test") - ), - detailedStatusPosition = 1, - revealButton = RevealButtonState.REVEAL + assertEquals( + ThreadUiState.Success( + statusViewData = listOf( + fakeStatusViewData(id = "1", spoilerText = "Test", favourited = false), + fakeStatusViewData(id = "2", inReplyToId = "1", inReplyToAccountId = "1", isDetailed = true, spoilerText = "Test"), + fakeStatusViewData(id = "3", inReplyToId = "2", inReplyToAccountId = "1", spoilerText = "Test") ), - viewModel.uiState.first() - ) - } + detailedStatusPosition = 1, + revealButton = RevealButtonState.REVEAL + ), + viewModel.uiState.first() + ) } @Test - fun `should remove status`() { + fun `should remove status`() = runTest { mockSuccessResponses() viewModel.loadThread(threadId) viewModel.removeStatus(fakeStatusViewData(id = "3", inReplyToId = "2", inReplyToAccountId = "1", spoilerText = "Test")) - runBlocking { - assertEquals( - ThreadUiState.Success( - statusViewData = listOf( - fakeStatusViewData(id = "1", spoilerText = "Test"), - fakeStatusViewData(id = "2", inReplyToId = "1", inReplyToAccountId = "1", isDetailed = true, spoilerText = "Test") - ), - detailedStatusPosition = 1, - revealButton = RevealButtonState.REVEAL + assertEquals( + ThreadUiState.Success( + statusViewData = listOf( + fakeStatusViewData(id = "1", spoilerText = "Test"), + fakeStatusViewData(id = "2", inReplyToId = "1", inReplyToAccountId = "1", isDetailed = true, spoilerText = "Test") ), - viewModel.uiState.first() - ) - } + detailedStatusPosition = 1, + revealButton = RevealButtonState.REVEAL + ), + viewModel.uiState.first() + ) } @Test - fun `should change status expanded state`() { + fun `should change status expanded state`() = runTest { mockSuccessResponses() viewModel.loadThread(threadId) @@ -277,24 +263,22 @@ class ViewThreadViewModelTest { fakeStatusViewData(id = "2", inReplyToId = "1", inReplyToAccountId = "1", isDetailed = true, spoilerText = "Test") ) - runBlocking { - assertEquals( - ThreadUiState.Success( - statusViewData = listOf( - fakeStatusViewData(id = "1", spoilerText = "Test"), - fakeStatusViewData(id = "2", inReplyToId = "1", inReplyToAccountId = "1", isDetailed = true, spoilerText = "Test", isExpanded = true), - fakeStatusViewData(id = "3", inReplyToId = "2", inReplyToAccountId = "1", spoilerText = "Test") - ), - detailedStatusPosition = 1, - revealButton = RevealButtonState.REVEAL + assertEquals( + ThreadUiState.Success( + statusViewData = listOf( + fakeStatusViewData(id = "1", spoilerText = "Test"), + fakeStatusViewData(id = "2", inReplyToId = "1", inReplyToAccountId = "1", isDetailed = true, spoilerText = "Test", isExpanded = true), + fakeStatusViewData(id = "3", inReplyToId = "2", inReplyToAccountId = "1", spoilerText = "Test") ), - viewModel.uiState.first() - ) - } + detailedStatusPosition = 1, + revealButton = RevealButtonState.REVEAL + ), + viewModel.uiState.first() + ) } @Test - fun `should change content collapsed state`() { + fun `should change content collapsed state`() = runTest { mockSuccessResponses() viewModel.loadThread(threadId) @@ -304,24 +288,22 @@ class ViewThreadViewModelTest { fakeStatusViewData(id = "2", inReplyToId = "1", inReplyToAccountId = "1", isDetailed = true, spoilerText = "Test") ) - runBlocking { - assertEquals( - ThreadUiState.Success( - statusViewData = listOf( - fakeStatusViewData(id = "1", spoilerText = "Test"), - fakeStatusViewData(id = "2", inReplyToId = "1", inReplyToAccountId = "1", isDetailed = true, spoilerText = "Test", isCollapsed = true), - fakeStatusViewData(id = "3", inReplyToId = "2", inReplyToAccountId = "1", spoilerText = "Test") - ), - detailedStatusPosition = 1, - revealButton = RevealButtonState.REVEAL + assertEquals( + ThreadUiState.Success( + statusViewData = listOf( + fakeStatusViewData(id = "1", spoilerText = "Test"), + fakeStatusViewData(id = "2", inReplyToId = "1", inReplyToAccountId = "1", isDetailed = true, spoilerText = "Test", isCollapsed = true), + fakeStatusViewData(id = "3", inReplyToId = "2", inReplyToAccountId = "1", spoilerText = "Test") ), - viewModel.uiState.first() - ) - } + detailedStatusPosition = 1, + revealButton = RevealButtonState.REVEAL + ), + viewModel.uiState.first() + ) } @Test - fun `should change content showing state`() { + fun `should change content showing state`() = runTest { mockSuccessResponses() viewModel.loadThread(threadId) @@ -331,20 +313,18 @@ class ViewThreadViewModelTest { fakeStatusViewData(id = "2", inReplyToId = "1", inReplyToAccountId = "1", isDetailed = true, spoilerText = "Test") ) - runBlocking { - assertEquals( - ThreadUiState.Success( - statusViewData = listOf( - fakeStatusViewData(id = "1", spoilerText = "Test"), - fakeStatusViewData(id = "2", inReplyToId = "1", inReplyToAccountId = "1", isDetailed = true, spoilerText = "Test", isShowingContent = true), - fakeStatusViewData(id = "3", inReplyToId = "2", inReplyToAccountId = "1", spoilerText = "Test") - ), - detailedStatusPosition = 1, - revealButton = RevealButtonState.REVEAL + assertEquals( + ThreadUiState.Success( + statusViewData = listOf( + fakeStatusViewData(id = "1", spoilerText = "Test"), + fakeStatusViewData(id = "2", inReplyToId = "1", inReplyToAccountId = "1", isDetailed = true, spoilerText = "Test", isShowingContent = true), + fakeStatusViewData(id = "3", inReplyToId = "2", inReplyToAccountId = "1", spoilerText = "Test") ), - viewModel.uiState.first() - ) - } + detailedStatusPosition = 1, + revealButton = RevealButtonState.REVEAL + ), + viewModel.uiState.first() + ) } private fun mockSuccessResponses() { diff --git a/app/src/test/java/com/keylesspalace/tusky/db/MigrationsTest.kt b/app/src/test/java/com/keylesspalace/tusky/db/MigrationsTest.kt new file mode 100644 index 000000000..4db567e7d --- /dev/null +++ b/app/src/test/java/com/keylesspalace/tusky/db/MigrationsTest.kt @@ -0,0 +1,87 @@ +package com.keylesspalace.tusky.db + +import androidx.room.testing.MigrationTestHelper +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.keylesspalace.tusky.di.StorageModule +import com.keylesspalace.tusky.entity.Emoji +import com.squareup.moshi.Moshi +import com.squareup.moshi.Types +import org.junit.Assert.assertEquals +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.annotation.Config + +@Config(sdk = [34]) +@RunWith(AndroidJUnit4::class) +class MigrationsTest { + + @get:Rule + val migrationHelper = MigrationTestHelper( + InstrumentationRegistry.getInstrumentation(), + AppDatabase::class.java + ) + + @Test + fun testMigrations() { + /** the db name must match the one in [StorageModule.providesDatabase] */ + val db = migrationHelper.createDatabase("tuskyDB", 10) + val moshi = Moshi.Builder().build() + + val id = 1L + val domain = "domain.site" + val token = "token" + val active = true + val accountId = "accountId" + val username = "username" + val emoji = moshi.adapter>(Types.newParameterizedType(List::class.java, Emoji::class.java), emptySet()).toJson( + listOf( + Emoji( + shortcode = "testemoji", + url = "https://some.url", + staticUrl = "https://some.url", + visibleInPicker = true, + category = null + ) + ) + ) + val values = arrayOf( + id, domain, token, active, accountId, username, "Display Name", + "https://picture.url", true, true, true, true, true, true, true, + true, "1000", "[]", emoji, 0, false, + false, true + ) + + db.execSQL( + "INSERT OR REPLACE INTO `AccountEntity`(`id`,`domain`,`accessToken`,`isActive`," + + "`accountId`,`username`,`displayName`,`profilePictureUrl`,`notificationsEnabled`," + + "`notificationsMentioned`,`notificationsFollowed`,`notificationsReblogged`," + + "`notificationsFavorited`,`notificationSound`,`notificationVibration`," + + "`notificationLight`,`lastNotificationId`,`activeNotifications`,`emojis`," + + "`defaultPostPrivacy`,`defaultMediaSensitivity`,`alwaysShowSensitiveMedia`," + + "`mediaPreviewEnabled`) " + + "VALUES (nullif(?, 0),?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", + values + ) + + db.close() + + // Room will run all migrations and validate the scheme afterwards + val roomDb = StorageModule.providesDatabase( + InstrumentationRegistry.getInstrumentation().context, + Converters(moshi) + ) + + val account = roomDb.accountDao().loadAll().first() + + roomDb.close() + + assertEquals(id, account.id) + assertEquals(domain, account.domain) + assertEquals(token, account.accessToken) + assertEquals(active, account.isActive) + assertEquals(accountId, account.accountId) + assertEquals(username, account.username) + } +} diff --git a/app/src/test/java/com/keylesspalace/tusky/db/dao/DatabaseCleanerTest.kt b/app/src/test/java/com/keylesspalace/tusky/db/dao/DatabaseCleanerTest.kt index 8321e7e52..d9607a5e9 100644 --- a/app/src/test/java/com/keylesspalace/tusky/db/dao/DatabaseCleanerTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/db/dao/DatabaseCleanerTest.kt @@ -28,7 +28,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.robolectric.annotation.Config -@Config(sdk = [28]) +@Config(sdk = [34]) @RunWith(AndroidJUnit4::class) class DatabaseCleanerTest { private lateinit var timelineDao: TimelineDao diff --git a/app/src/test/java/com/keylesspalace/tusky/db/dao/NotificationsDaoTest.kt b/app/src/test/java/com/keylesspalace/tusky/db/dao/NotificationsDaoTest.kt index 16e6034bd..4a19aa0e0 100644 --- a/app/src/test/java/com/keylesspalace/tusky/db/dao/NotificationsDaoTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/db/dao/NotificationsDaoTest.kt @@ -25,7 +25,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.robolectric.annotation.Config -@Config(sdk = [28]) +@Config(sdk = [34]) @RunWith(AndroidJUnit4::class) class NotificationsDaoTest { private lateinit var notificationsDao: NotificationsDao diff --git a/app/src/test/java/com/keylesspalace/tusky/db/dao/TimelineDaoTest.kt b/app/src/test/java/com/keylesspalace/tusky/db/dao/TimelineDaoTest.kt index d015c8079..9a261fb67 100644 --- a/app/src/test/java/com/keylesspalace/tusky/db/dao/TimelineDaoTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/db/dao/TimelineDaoTest.kt @@ -19,7 +19,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.robolectric.annotation.Config -@Config(sdk = [28]) +@Config(sdk = [34]) @RunWith(AndroidJUnit4::class) class TimelineDaoTest { private lateinit var timelineDao: TimelineDao diff --git a/app/src/test/java/com/keylesspalace/tusky/entity/ProxyConfigurationTest.kt b/app/src/test/java/com/keylesspalace/tusky/entity/ProxyConfigurationTest.kt index 1f7bbed55..3d90c9d7c 100644 --- a/app/src/test/java/com/keylesspalace/tusky/entity/ProxyConfigurationTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/entity/ProxyConfigurationTest.kt @@ -1,46 +1,48 @@ package com.keylesspalace.tusky.entity import com.keylesspalace.tusky.settings.ProxyConfiguration -import org.junit.Assert +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue import org.junit.Test class ProxyConfigurationTest { @Test fun `serialized non-int is not valid proxy port`() { - Assert.assertFalse(ProxyConfiguration.isValidProxyPort("should fail")) - Assert.assertFalse(ProxyConfiguration.isValidProxyPort("1.5")) + assertFalse(ProxyConfiguration.isValidProxyPort("should fail")) + assertFalse(ProxyConfiguration.isValidProxyPort("1.5")) } @Test fun `number outside port range is not valid`() { - Assert.assertFalse(ProxyConfiguration.isValidProxyPort("${ProxyConfiguration.MIN_PROXY_PORT - 1}")) - Assert.assertFalse(ProxyConfiguration.isValidProxyPort("${ProxyConfiguration.MAX_PROXY_PORT + 1}")) + assertFalse(ProxyConfiguration.isValidProxyPort("${ProxyConfiguration.MIN_PROXY_PORT - 1}")) + assertFalse(ProxyConfiguration.isValidProxyPort("${ProxyConfiguration.MAX_PROXY_PORT + 1}")) } @Test fun `number in port range, inclusive of min and max, is valid`() { - Assert.assertTrue(ProxyConfiguration.isValidProxyPort(ProxyConfiguration.MIN_PROXY_PORT)) - Assert.assertTrue(ProxyConfiguration.isValidProxyPort(ProxyConfiguration.MAX_PROXY_PORT)) - Assert.assertTrue(ProxyConfiguration.isValidProxyPort((ProxyConfiguration.MIN_PROXY_PORT + ProxyConfiguration.MAX_PROXY_PORT) / 2)) + assertTrue(ProxyConfiguration.isValidProxyPort(ProxyConfiguration.MIN_PROXY_PORT)) + assertTrue(ProxyConfiguration.isValidProxyPort(ProxyConfiguration.MAX_PROXY_PORT)) + assertTrue(ProxyConfiguration.isValidProxyPort((ProxyConfiguration.MIN_PROXY_PORT + ProxyConfiguration.MAX_PROXY_PORT) / 2)) } @Test fun `create with invalid port yields null`() { - Assert.assertNull(ProxyConfiguration.create("hostname", ProxyConfiguration.MIN_PROXY_PORT - 1)) + assertNull(ProxyConfiguration.create("hostname", ProxyConfiguration.MIN_PROXY_PORT - 1)) } @Test fun `create with invalid hostname yields null`() { - Assert.assertNull(ProxyConfiguration.create(".", ProxyConfiguration.MIN_PROXY_PORT)) + assertNull(ProxyConfiguration.create(".", ProxyConfiguration.MIN_PROXY_PORT)) } @Test fun `create with valid hostname and port yields the config object`() { - Assert.assertTrue(ProxyConfiguration.create("hostname", ProxyConfiguration.MIN_PROXY_PORT) is ProxyConfiguration) + assertTrue(ProxyConfiguration.create("hostname", ProxyConfiguration.MIN_PROXY_PORT) is ProxyConfiguration) } @Test fun `unicode hostname allowed`() { - Assert.assertTrue(ProxyConfiguration.create("federação.social", ProxyConfiguration.MIN_PROXY_PORT) is ProxyConfiguration) + assertTrue(ProxyConfiguration.create("federação.social", ProxyConfiguration.MIN_PROXY_PORT) is ProxyConfiguration) } } diff --git a/app/src/test/java/com/keylesspalace/tusky/usecase/TimelineCasesTest.kt b/app/src/test/java/com/keylesspalace/tusky/usecase/TimelineCasesTest.kt index 7ae23d215..e0e50f3e0 100644 --- a/app/src/test/java/com/keylesspalace/tusky/usecase/TimelineCasesTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/usecase/TimelineCasesTest.kt @@ -8,7 +8,7 @@ import com.keylesspalace.tusky.appstore.StatusChangedEvent import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.network.MastodonApi import java.util.Date -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import okhttp3.ResponseBody.Companion.toResponseBody import org.junit.Assert.assertEquals import org.junit.Before @@ -21,7 +21,7 @@ import org.robolectric.annotation.Config import retrofit2.HttpException import retrofit2.Response -@Config(sdk = [28]) +@Config(sdk = [34]) @RunWith(AndroidJUnit4::class) class TimelineCasesTest { @@ -39,23 +39,21 @@ class TimelineCasesTest { } @Test - fun `pin success emits StatusChangedEvent`() { + fun `pin success emits StatusChangedEvent`() = runTest { val pinnedStatus = mockStatus(pinned = true) api.stub { onBlocking { pinStatus(statusId) } doReturn NetworkResult.success(pinnedStatus) } - runBlocking { - eventHub.events.test { - timelineCases.pin(statusId, true) - assertEquals(StatusChangedEvent(pinnedStatus), awaitItem()) - } + eventHub.events.test { + timelineCases.pin(statusId, true) + assertEquals(StatusChangedEvent(pinnedStatus), awaitItem()) } } @Test - fun `pin failure with server error throws TimelineError with server message`() { + fun `pin failure with server error throws TimelineError with server message`() = runTest { api.stub { onBlocking { pinStatus(statusId) } doReturn NetworkResult.failure( HttpException( @@ -66,12 +64,10 @@ class TimelineCasesTest { ) ) } - runBlocking { - assertEquals( - "Validation Failed: You have already pinned the maximum number of toots", - timelineCases.pin(statusId, true).exceptionOrNull()?.message - ) - } + assertEquals( + "Validation Failed: You have already pinned the maximum number of toots", + timelineCases.pin(statusId, true).exceptionOrNull()?.message + ) } private fun mockStatus(pinned: Boolean = false): Status { diff --git a/app/src/test/java/com/keylesspalace/tusky/util/HttpHeaderLinkTest.kt b/app/src/test/java/com/keylesspalace/tusky/util/HttpHeaderLinkTest.kt index ac253db03..19021180a 100644 --- a/app/src/test/java/com/keylesspalace/tusky/util/HttpHeaderLinkTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/util/HttpHeaderLinkTest.kt @@ -1,13 +1,8 @@ package com.keylesspalace.tusky.util -import androidx.test.ext.junit.runners.AndroidJUnit4 import org.junit.Assert.assertEquals import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.annotation.Config -@Config(sdk = [28]) -@RunWith(AndroidJUnit4::class) class HttpHeaderLinkTest { data class TestData(val name: String, val input: String, val want: List) diff --git a/app/src/test/java/com/keylesspalace/tusky/util/LinkHelperTest.kt b/app/src/test/java/com/keylesspalace/tusky/util/LinkHelperTest.kt index de5b7b768..38ce2abb3 100644 --- a/app/src/test/java/com/keylesspalace/tusky/util/LinkHelperTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/util/LinkHelperTest.kt @@ -19,7 +19,7 @@ import org.junit.runner.RunWith import org.junit.runners.Parameterized import org.robolectric.annotation.Config -@Config(sdk = [28]) +@Config(sdk = [34]) @RunWith(AndroidJUnit4::class) class LinkHelperTest { private val listener = object : LinkListener { diff --git a/app/src/test/java/com/keylesspalace/tusky/util/LocaleUtilsTest.kt b/app/src/test/java/com/keylesspalace/tusky/util/LocaleUtilsTest.kt index 259980f26..d93901279 100644 --- a/app/src/test/java/com/keylesspalace/tusky/util/LocaleUtilsTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/util/LocaleUtilsTest.kt @@ -4,45 +4,47 @@ import androidx.appcompat.app.AppCompatDelegate import androidx.core.os.LocaleListCompat import androidx.test.ext.junit.runners.AndroidJUnit4 import com.keylesspalace.tusky.db.entity.AccountEntity -import org.junit.Assert +import org.junit.Assert.assertArrayEquals +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito import org.robolectric.annotation.Config -@Config(sdk = [28]) +@Config(sdk = [34]) @RunWith(AndroidJUnit4::class) class LocaleUtilsTest { @Test fun initialLanguagesContainReplySelectedAppAndSystem() { val expectedLanguages = arrayOf("yi", "tok", "da", "fr", "sv", "kab") val languages = getMockedInitialLanguages(expectedLanguages) - Assert.assertArrayEquals(expectedLanguages, languages.subList(0, expectedLanguages.size).toTypedArray()) + assertArrayEquals(expectedLanguages, languages.subList(0, expectedLanguages.size).toTypedArray()) } @Test fun whenReplyLanguageIsNull_DefaultLanguageIsFirst() { val defaultLanguage = "tok" val languages = getMockedInitialLanguages(arrayOf(null, defaultLanguage, "da", "fr", "sv", "kab")) - Assert.assertEquals(defaultLanguage, languages[0]) + assertEquals(defaultLanguage, languages[0]) } @Test fun initialLanguagesAreDistinct() { val defaultLanguage = "da" val languages = getMockedInitialLanguages(arrayOf(defaultLanguage, defaultLanguage, "fr", defaultLanguage, "kab", defaultLanguage)) - Assert.assertEquals(1, languages.count { it == defaultLanguage }) + assertEquals(1, languages.count { it == defaultLanguage }) } @Test fun initialLanguageDeduplicationDoesNotReorder() { val defaultLanguage = "da" - Assert.assertEquals( + assertEquals( defaultLanguage, getMockedInitialLanguages(arrayOf(defaultLanguage, defaultLanguage, "fr", defaultLanguage, "kab", defaultLanguage))[0] ) - Assert.assertEquals( + assertEquals( defaultLanguage, getMockedInitialLanguages(arrayOf(null, defaultLanguage, "fr", defaultLanguage, "kab", defaultLanguage))[0] ) @@ -51,7 +53,7 @@ class LocaleUtilsTest { @Test fun emptyInitialLanguagesAreDropped() { val languages = getMockedInitialLanguages(arrayOf("", "", "fr", "", "kab", "")) - Assert.assertFalse(languages.any { it.isEmpty() }) + assertFalse(languages.any { it.isEmpty() }) } private fun getMockedInitialLanguages(configuredLanguages: Array): List { diff --git a/app/src/test/java/com/keylesspalace/tusky/util/NumberUtilsTest.kt b/app/src/test/java/com/keylesspalace/tusky/util/NumberUtilsTest.kt index 6b821b1c6..08b5dd5a7 100644 --- a/app/src/test/java/com/keylesspalace/tusky/util/NumberUtilsTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/util/NumberUtilsTest.kt @@ -3,7 +3,7 @@ package com.keylesspalace.tusky.util import java.util.Locale import kotlin.math.pow import org.junit.AfterClass -import org.junit.Assert +import org.junit.Assert.assertEquals import org.junit.BeforeClass import org.junit.Test import org.junit.runner.RunWith @@ -65,6 +65,6 @@ class NumberUtilsTest(private val input: Long, private val want: String) { @Test fun test() { - Assert.assertEquals(want, formatNumber(input, 1000)) + assertEquals(want, formatNumber(input, 1000)) } } diff --git a/app/src/test/java/com/keylesspalace/tusky/util/RickRollTest.kt b/app/src/test/java/com/keylesspalace/tusky/util/RickRollTest.kt index 9a5e8f819..7a90efc12 100644 --- a/app/src/test/java/com/keylesspalace/tusky/util/RickRollTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/util/RickRollTest.kt @@ -10,7 +10,7 @@ import org.junit.runner.RunWith import org.robolectric.Robolectric import org.robolectric.annotation.Config -@Config(sdk = [28]) +@Config(sdk = [34]) @RunWith(AndroidJUnit4::class) class RickRollTest { private lateinit var activity: Activity diff --git a/app/src/test/java/com/keylesspalace/tusky/util/SmartLengthInputFilterTest.kt b/app/src/test/java/com/keylesspalace/tusky/util/SmartLengthInputFilterTest.kt index 5b6f417b7..94d00be92 100644 --- a/app/src/test/java/com/keylesspalace/tusky/util/SmartLengthInputFilterTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/util/SmartLengthInputFilterTest.kt @@ -8,7 +8,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.robolectric.annotation.Config -@Config(sdk = [28]) +@Config(sdk = [34]) @RunWith(AndroidJUnit4::class) class SmartLengthInputFilterTest { diff --git a/app/src/test/java/com/keylesspalace/tusky/util/TimestampUtilsTest.kt b/app/src/test/java/com/keylesspalace/tusky/util/TimestampUtilsTest.kt index 1375b203e..6f7ff288e 100644 --- a/app/src/test/java/com/keylesspalace/tusky/util/TimestampUtilsTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/util/TimestampUtilsTest.kt @@ -1,29 +1,78 @@ package com.keylesspalace.tusky.util -import android.content.Context import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.keylesspalace.tusky.R +import androidx.test.platform.app.InstrumentationRegistry +import java.util.Locale +import kotlin.time.Duration.Companion.days +import kotlin.time.Duration.Companion.hours +import kotlin.time.Duration.Companion.minutes +import kotlin.time.Duration.Companion.seconds +import org.junit.AfterClass import org.junit.Assert.assertEquals +import org.junit.BeforeClass import org.junit.Test import org.junit.runner.RunWith -import org.mockito.kotlin.doReturn -import org.mockito.kotlin.mock import org.robolectric.annotation.Config -private const val STATUS_CREATED_AT_NOW = "test" - -@Config(sdk = [28]) +@Config(sdk = [34]) @RunWith(AndroidJUnit4::class) class TimestampUtilsTest { - private val ctx: Context = mock { - on { getString(R.string.status_created_at_now) } doReturn STATUS_CREATED_AT_NOW + + companion object { + private lateinit var locale: Locale + + @BeforeClass + @JvmStatic + fun beforeClass() { + locale = Locale.getDefault() + Locale.setDefault(Locale.ENGLISH) + } + + @AfterClass + @JvmStatic + fun afterClass() { + Locale.setDefault(locale) + } + } + + val context = InstrumentationRegistry.getInstrumentation().targetContext + + @Test + fun `should return 'now' for small timespans`() { + assertEquals("now", getRelativeTimeSpanString(context, 0, 300)) + assertEquals("now", getRelativeTimeSpanString(context, 300, 0)) + assertEquals("now", getRelativeTimeSpanString(context, 501, 0)) + assertEquals("now", getRelativeTimeSpanString(context, 0, 999)) } @Test - fun shouldShowNowForSmallTimeSpans() { - assertEquals(STATUS_CREATED_AT_NOW, getRelativeTimeSpanString(ctx, 0, 300)) - assertEquals(STATUS_CREATED_AT_NOW, getRelativeTimeSpanString(ctx, 300, 0)) - assertEquals(STATUS_CREATED_AT_NOW, getRelativeTimeSpanString(ctx, 501, 0)) - assertEquals(STATUS_CREATED_AT_NOW, getRelativeTimeSpanString(ctx, 0, 999)) + fun `should return 'in --' when then is after now`() { + assertEquals("in 49s", getRelativeTimeSpanString(context, 49.seconds.inWholeMilliseconds, 0)) + assertEquals("in 34m", getRelativeTimeSpanString(context, 37.minutes.inWholeMilliseconds, 3.minutes.inWholeMilliseconds)) + assertEquals("in 7h", getRelativeTimeSpanString(context, 10.hours.inWholeMilliseconds, 3.hours.inWholeMilliseconds)) + assertEquals("in 10d", getRelativeTimeSpanString(context, 10.days.inWholeMilliseconds, 0)) + assertEquals("in 4y", getRelativeTimeSpanString(context, 800.days.inWholeMilliseconds + (4 * 365).days.inWholeMilliseconds, 800.days.inWholeMilliseconds)) + } + + @Test + fun `should return correct timespans`() { + assertEquals("49s", getRelativeTimeSpanString(context, 0, 49.seconds.inWholeMilliseconds)) + assertEquals("34m", getRelativeTimeSpanString(context, 3.minutes.inWholeMilliseconds, 37.minutes.inWholeMilliseconds)) + assertEquals("7h", getRelativeTimeSpanString(context, 3.hours.inWholeMilliseconds, 10.hours.inWholeMilliseconds)) + assertEquals("10d", getRelativeTimeSpanString(context, 0, 10.days.inWholeMilliseconds)) + assertEquals("4y", getRelativeTimeSpanString(context, 800.days.inWholeMilliseconds, 800.days.inWholeMilliseconds + (4 * 365).days.inWholeMilliseconds)) + } + + @Test + fun `should return correct poll duration`() { + assertEquals("1 second left", formatPollDuration(context, 1.seconds.inWholeMilliseconds, 0)) + assertEquals("49 seconds left", formatPollDuration(context, 49.seconds.inWholeMilliseconds, 0)) + assertEquals("1 minute left", formatPollDuration(context, 37.minutes.inWholeMilliseconds, 36.minutes.inWholeMilliseconds)) + assertEquals("34 minutes left", formatPollDuration(context, 37.minutes.inWholeMilliseconds, 3.minutes.inWholeMilliseconds)) + assertEquals("1 hour left", formatPollDuration(context, 10.hours.inWholeMilliseconds, 9.hours.inWholeMilliseconds)) + assertEquals("7 hours left", formatPollDuration(context, 10.hours.inWholeMilliseconds, 3.hours.inWholeMilliseconds)) + assertEquals("1 day left", formatPollDuration(context, 1.days.inWholeMilliseconds, 0)) + assertEquals("10 days left", formatPollDuration(context, 10.days.inWholeMilliseconds, 0)) + assertEquals("1460 days left", formatPollDuration(context, 800.days.inWholeMilliseconds + (4 * 365).days.inWholeMilliseconds, 800.days.inWholeMilliseconds)) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 858a66aad..883322da3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -28,7 +28,6 @@ conscrypt = "2.5.3" coroutines = "1.9.0" diffx = "1.1.1" emoji2 = "1.4.0" -espresso = "3.6.1" filemoji-compat = "3.2.7" glide = "4.16.0" # Deliberate downgrade, https://github.com/tuskyapp/Tusky/issues/3631 @@ -46,10 +45,9 @@ networkresult-calladapter = "1.2.0" okhttp = "4.12.0" okio = "3.9.1" retrofit = "2.11.0" -robolectric = "4.13" +robolectric = "4.14.1" sparkbutton = "4.2.0" touchimageview = "3.6" -truth = "1.4.4" turbine = "1.2.0" unified-push = "2.4.0" xmlwriter = "1.0.4" @@ -102,7 +100,6 @@ conscrypt-android = { module = "org.conscrypt:conscrypt-android", version.ref = hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" } diffx = { module = "org.pageseeder.diffx:pso-diffx", version.ref = "diffx" } -espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso" } filemojicompat-core = { module = "de.c1710:filemojicompat", version.ref = "filemoji-compat" } filemojicompat-defaults = { module = "de.c1710:filemojicompat-defaults", version.ref = "filemoji-compat" } filemojicompat-ui = { module = "de.c1710:filemojicompat-ui", version.ref = "filemoji-compat" } @@ -131,7 +128,6 @@ retrofit-core = { module = "com.squareup.retrofit2:retrofit", version.ref = "ret robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" } sparkbutton = { module = "at.connyduck.sparkbutton:sparkbutton", version.ref = "sparkbutton" } touchimageview = { module = "com.github.MikeOrtiz:TouchImageView", version.ref = "touchimageview" } -truth = { module = "com.google.truth:truth", version.ref = "truth" } turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" } unified-push = { module = "com.github.UnifiedPush:android-connector", version.ref = "unified-push" } xmlwriter = { module = "org.pageseeder.xmlwriter:pso-xmlwriter", version.ref = "xmlwriter" } diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 2665b4d95..2eba62a14 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -2761,6 +2761,14 @@ + + + + + + + + @@ -8967,6 +8975,14 @@ + + + + + + + + @@ -9060,6 +9076,11 @@ + + + + + @@ -9159,6 +9180,17 @@ + + + + + + + + + + + @@ -9194,6 +9226,11 @@ + + + + + @@ -9321,6 +9358,19 @@ + + + + + + + + + + + + + @@ -10623,6 +10673,14 @@ + + + + + + + + @@ -12548,6 +12606,14 @@ + + + + + + + + @@ -12564,6 +12630,14 @@ + + + + + + + + @@ -12580,6 +12654,14 @@ + + + + + + + + @@ -12596,6 +12678,14 @@ + + + + + + + + @@ -12612,6 +12702,14 @@ + + + + + + + + @@ -12644,6 +12742,14 @@ + + + + + + + + @@ -12660,6 +12766,14 @@ + + + + + + + + @@ -12676,6 +12790,14 @@ + + + + + + + + @@ -12692,6 +12814,14 @@ + + + + + + + + @@ -12708,6 +12838,14 @@ + + + + + + + + @@ -12724,6 +12862,14 @@ + + + + + + + + @@ -12740,6 +12886,14 @@ + + + + + + + + @@ -12756,6 +12910,14 @@ + + + + + + + + @@ -12772,6 +12934,14 @@ + + + + + + + + @@ -12788,6 +12958,14 @@ + + + + + + + + @@ -12804,6 +12982,14 @@ + + + + + + + + @@ -12828,6 +13014,14 @@ + + + + + + + + @@ -12844,6 +13038,14 @@ + + + + + + + + @@ -12901,5 +13103,13 @@ + + + + + + + +