Remove unneeded code
This commit is contained in:
parent
add62129f8
commit
4af160853d
16 changed files with 18 additions and 1937 deletions
|
|
@ -1,143 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 Tusky Contributors
|
||||
*
|
||||
* 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,
|
||||
* see <http://www.gnu.org/licenses>.
|
||||
*/
|
||||
|
||||
package com.keylesspalace.tusky.components.notifications
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.google.gson.Gson
|
||||
import com.keylesspalace.tusky.entity.Notification
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.anyOrNull
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.mock
|
||||
import org.robolectric.annotation.Config
|
||||
import retrofit2.Response
|
||||
|
||||
@Config(sdk = [28])
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class NotificationsPagingSourceTest {
|
||||
@Test
|
||||
fun `load() returns error message on HTTP error`() = runTest {
|
||||
// Given
|
||||
val jsonError = "{error: 'This is an error'}".toResponseBody()
|
||||
val mockApi: MastodonApi = mock {
|
||||
onBlocking { notifications(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()) } doReturn Response.error(429, jsonError)
|
||||
onBlocking { notification(any()) } doReturn Response.error(429, jsonError)
|
||||
}
|
||||
|
||||
val filter = emptySet<Notification.Type>()
|
||||
val gson = Gson()
|
||||
val pagingSource = NotificationsPagingSource(mockApi, gson, filter)
|
||||
val loadingParams = PagingSource.LoadParams.Refresh("0", 5, false)
|
||||
|
||||
// When
|
||||
val loadResult = pagingSource.load(loadingParams)
|
||||
|
||||
// Then
|
||||
assertTrue(loadResult is PagingSource.LoadResult.Error)
|
||||
assertEquals(
|
||||
"HTTP 429: This is an error",
|
||||
(loadResult as PagingSource.LoadResult.Error).throwable.message
|
||||
)
|
||||
}
|
||||
|
||||
// As previous, but with `error_description` field as well.
|
||||
@Test
|
||||
fun `load() returns extended error message on HTTP error`() = runTest {
|
||||
// Given
|
||||
val jsonError = "{error: 'This is an error', error_description: 'Description of the error'}".toResponseBody()
|
||||
val mockApi: MastodonApi = mock {
|
||||
onBlocking { notifications(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()) } doReturn Response.error(429, jsonError)
|
||||
onBlocking { notification(any()) } doReturn Response.error(429, jsonError)
|
||||
}
|
||||
|
||||
val filter = emptySet<Notification.Type>()
|
||||
val gson = Gson()
|
||||
val pagingSource = NotificationsPagingSource(mockApi, gson, filter)
|
||||
val loadingParams = PagingSource.LoadParams.Refresh("0", 5, false)
|
||||
|
||||
// When
|
||||
val loadResult = pagingSource.load(loadingParams)
|
||||
|
||||
// Then
|
||||
assertTrue(loadResult is PagingSource.LoadResult.Error)
|
||||
assertEquals(
|
||||
"HTTP 429: This is an error: Description of the error",
|
||||
(loadResult as PagingSource.LoadResult.Error).throwable.message
|
||||
)
|
||||
}
|
||||
|
||||
// As previous, but no error JSON, so expect default response
|
||||
@Test
|
||||
fun `load() returns default error message on empty HTTP error`() = runTest {
|
||||
// Given
|
||||
val jsonError = "{}".toResponseBody()
|
||||
val mockApi: MastodonApi = mock {
|
||||
onBlocking { notifications(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()) } doReturn Response.error(429, jsonError)
|
||||
onBlocking { notification(any()) } doReturn Response.error(429, jsonError)
|
||||
}
|
||||
|
||||
val filter = emptySet<Notification.Type>()
|
||||
val gson = Gson()
|
||||
val pagingSource = NotificationsPagingSource(mockApi, gson, filter)
|
||||
val loadingParams = PagingSource.LoadParams.Refresh("0", 5, false)
|
||||
|
||||
// When
|
||||
val loadResult = pagingSource.load(loadingParams)
|
||||
|
||||
// Then
|
||||
assertTrue(loadResult is PagingSource.LoadResult.Error)
|
||||
assertEquals(
|
||||
"HTTP 429: no reason given",
|
||||
(loadResult as PagingSource.LoadResult.Error).throwable.message
|
||||
)
|
||||
}
|
||||
|
||||
// As previous, but malformed JSON, so expect response with enough information to troubleshoot
|
||||
@Test
|
||||
fun `load() returns useful error message on malformed HTTP error`() = runTest {
|
||||
// Given
|
||||
val jsonError = "{'malformedjson}".toResponseBody()
|
||||
val mockApi: MastodonApi = mock {
|
||||
onBlocking { notifications(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()) } doReturn Response.error(429, jsonError)
|
||||
onBlocking { notification(any()) } doReturn Response.error(429, jsonError)
|
||||
}
|
||||
|
||||
val filter = emptySet<Notification.Type>()
|
||||
val gson = Gson()
|
||||
val pagingSource = NotificationsPagingSource(mockApi, gson, filter)
|
||||
val loadingParams = PagingSource.LoadParams.Refresh("0", 5, false)
|
||||
|
||||
// When
|
||||
val loadResult = pagingSource.load(loadingParams)
|
||||
|
||||
// Then
|
||||
assertTrue(loadResult is PagingSource.LoadResult.Error)
|
||||
assertEquals(
|
||||
"HTTP 429: {'malformedjson} (com.google.gson.JsonSyntaxException: com.google.gson.stream.MalformedJsonException: Unterminated string at line 1 column 17 path \$.)",
|
||||
(loadResult as PagingSource.LoadResult.Error).throwable.message
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,135 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 Tusky Contributors
|
||||
*
|
||||
* 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,
|
||||
* see <http://www.gnu.org/licenses>.
|
||||
*/
|
||||
|
||||
package com.keylesspalace.tusky.components.notifications
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Looper
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.keylesspalace.tusky.appstore.EventHub
|
||||
import com.keylesspalace.tusky.db.AccountEntity
|
||||
import com.keylesspalace.tusky.db.AccountManager
|
||||
import com.keylesspalace.tusky.settings.PrefKeys
|
||||
import com.keylesspalace.tusky.usecase.TimelineCases
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.TestDispatcher
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import kotlinx.coroutines.test.resetMain
|
||||
import kotlinx.coroutines.test.setMain
|
||||
import okhttp3.ResponseBody
|
||||
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.rules.TestWatcher
|
||||
import org.junit.runner.Description
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.doAnswer
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.mock
|
||||
import org.robolectric.Shadows.shadowOf
|
||||
import org.robolectric.annotation.Config
|
||||
import retrofit2.HttpException
|
||||
import retrofit2.Response
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class MainCoroutineRule(private val dispatcher: TestDispatcher = UnconfinedTestDispatcher()) : TestWatcher() {
|
||||
override fun starting(description: Description) {
|
||||
super.starting(description)
|
||||
Dispatchers.setMain(dispatcher)
|
||||
}
|
||||
|
||||
override fun finished(description: Description) {
|
||||
super.finished(description)
|
||||
Dispatchers.resetMain()
|
||||
}
|
||||
}
|
||||
|
||||
@Config(sdk = [28])
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
abstract class NotificationsViewModelTestBase {
|
||||
protected lateinit var notificationsRepository: NotificationsRepository
|
||||
protected lateinit var sharedPreferencesMap: MutableMap<String, Boolean>
|
||||
protected lateinit var sharedPreferences: SharedPreferences
|
||||
protected lateinit var accountManager: AccountManager
|
||||
protected lateinit var timelineCases: TimelineCases
|
||||
protected lateinit var eventHub: EventHub
|
||||
protected lateinit var viewModel: NotificationsViewModel
|
||||
|
||||
/** Empty success response, for API calls that return one */
|
||||
protected var emptySuccess: Response<ResponseBody> = Response.success("".toResponseBody())
|
||||
|
||||
/** Empty error response, for API calls that return one */
|
||||
protected var emptyError: Response<ResponseBody> = Response.error(404, "".toResponseBody())
|
||||
|
||||
/** Exception to throw when testing errors */
|
||||
protected val httpException = HttpException(emptyError)
|
||||
|
||||
@get:Rule
|
||||
val mainCoroutineRule = MainCoroutineRule()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
shadowOf(Looper.getMainLooper()).idle()
|
||||
|
||||
notificationsRepository = mock()
|
||||
|
||||
// Backing store for sharedPreferences, to allow mutation in tests
|
||||
sharedPreferencesMap = mutableMapOf(
|
||||
PrefKeys.ANIMATE_GIF_AVATARS to false,
|
||||
PrefKeys.ANIMATE_CUSTOM_EMOJIS to false,
|
||||
PrefKeys.ABSOLUTE_TIME_VIEW to false,
|
||||
PrefKeys.SHOW_BOT_OVERLAY to true,
|
||||
PrefKeys.USE_BLURHASH to true,
|
||||
PrefKeys.CONFIRM_REBLOGS to true,
|
||||
PrefKeys.CONFIRM_FAVOURITES to false,
|
||||
PrefKeys.WELLBEING_HIDE_STATS_POSTS to false,
|
||||
PrefKeys.FAB_HIDE to false
|
||||
)
|
||||
|
||||
// Any getBoolean() call looks for the result in sharedPreferencesMap
|
||||
sharedPreferences = mock {
|
||||
on { getBoolean(any(), any()) } doAnswer { sharedPreferencesMap[it.arguments[0]] }
|
||||
}
|
||||
|
||||
accountManager = mock {
|
||||
on { activeAccount } doReturn AccountEntity(
|
||||
id = 1,
|
||||
domain = "mastodon.test",
|
||||
accessToken = "fakeToken",
|
||||
clientId = "fakeId",
|
||||
clientSecret = "fakeSecret",
|
||||
isActive = true,
|
||||
notificationsFilter = "['follow']",
|
||||
mediaPreviewEnabled = true,
|
||||
alwaysShowSensitiveMedia = true,
|
||||
alwaysOpenSpoiler = true
|
||||
)
|
||||
}
|
||||
eventHub = EventHub()
|
||||
timelineCases = mock()
|
||||
|
||||
viewModel = NotificationsViewModel(
|
||||
notificationsRepository,
|
||||
sharedPreferences,
|
||||
accountManager,
|
||||
timelineCases,
|
||||
eventHub
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 Tusky Contributors
|
||||
*
|
||||
* 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,
|
||||
* see <http://www.gnu.org/licenses>.
|
||||
*/
|
||||
|
||||
package com.keylesspalace.tusky.components.notifications
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.stub
|
||||
import org.mockito.kotlin.verify
|
||||
|
||||
/**
|
||||
* Verify that [ClearNotifications] is handled correctly on receipt:
|
||||
*
|
||||
* - Is the correct [UiSuccess] or [UiError] value emitted?
|
||||
* - Are the correct [NotificationsRepository] functions called, with the correct arguments?
|
||||
* This is only tested in the success case; if it passed there it must also
|
||||
* have passed in the error case.
|
||||
*/
|
||||
class NotificationsViewModelTestClearNotifications : NotificationsViewModelTestBase() {
|
||||
@Test
|
||||
fun `clearing notifications succeeds && invalidate the repository`() = runTest {
|
||||
// Given
|
||||
notificationsRepository.stub { onBlocking { clearNotifications() } doReturn emptySuccess }
|
||||
|
||||
// When
|
||||
viewModel.accept(FallibleUiAction.ClearNotifications)
|
||||
|
||||
// Then
|
||||
verify(notificationsRepository).clearNotifications()
|
||||
verify(notificationsRepository).invalidate()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clearing notifications fails && emits UiError`() = runTest {
|
||||
// Given
|
||||
notificationsRepository.stub { onBlocking { clearNotifications() } doReturn emptyError }
|
||||
|
||||
viewModel.uiError.test {
|
||||
// When
|
||||
viewModel.accept(FallibleUiAction.ClearNotifications)
|
||||
|
||||
// Then
|
||||
assertThat(awaitItem()).isInstanceOf(UiError::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 Tusky Contributors
|
||||
*
|
||||
* 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,
|
||||
* see <http://www.gnu.org/licenses>.
|
||||
*/
|
||||
|
||||
package com.keylesspalace.tusky.components.notifications
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.keylesspalace.tusky.db.AccountEntity
|
||||
import com.keylesspalace.tusky.entity.Notification
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import org.mockito.kotlin.argumentCaptor
|
||||
import org.mockito.kotlin.verify
|
||||
|
||||
/**
|
||||
* Verify that [ApplyFilter] is handled correctly on receipt:
|
||||
*
|
||||
* - Is the [UiState] updated correctly?
|
||||
* - Are the correct [AccountManager] functions called, with the correct arguments?
|
||||
*/
|
||||
class NotificationsViewModelTestFilter : NotificationsViewModelTestBase() {
|
||||
|
||||
@Test
|
||||
fun `should load initial filter from active account`() = runTest {
|
||||
viewModel.uiState.test {
|
||||
assertThat(awaitItem().activeFilter)
|
||||
.containsExactlyElementsIn(setOf(Notification.Type.FOLLOW))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should save filter to active account && update state`() = runTest {
|
||||
viewModel.uiState.test {
|
||||
// When
|
||||
viewModel.accept(InfallibleUiAction.ApplyFilter(setOf(Notification.Type.REBLOG)))
|
||||
|
||||
// Then
|
||||
// - filter saved to active account
|
||||
argumentCaptor<AccountEntity>().apply {
|
||||
verify(accountManager).saveAccount(capture())
|
||||
assertThat(this.lastValue.notificationsFilter)
|
||||
.isEqualTo("[\"reblog\"]")
|
||||
}
|
||||
|
||||
// - filter updated in uiState
|
||||
assertThat(expectMostRecentItem().activeFilter)
|
||||
.containsExactlyElementsIn(setOf(Notification.Type.REBLOG))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,142 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 Tusky Contributors
|
||||
*
|
||||
* 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,
|
||||
* see <http://www.gnu.org/licenses>.
|
||||
*/
|
||||
|
||||
package com.keylesspalace.tusky.components.notifications
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.keylesspalace.tusky.entity.Relationship
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.argumentCaptor
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.doThrow
|
||||
import org.mockito.kotlin.stub
|
||||
import org.mockito.kotlin.verify
|
||||
|
||||
/**
|
||||
* Verify that [NotificationAction] are handled correctly on receipt:
|
||||
*
|
||||
* - Is the correct [UiSuccess] or [UiError] value emitted?
|
||||
* - Is the correct [TimelineCases] function called, with the correct arguments?
|
||||
* This is only tested in the success case; if it passed there it must also
|
||||
* have passed in the error case.
|
||||
*/
|
||||
class NotificationsViewModelTestNotificationAction : NotificationsViewModelTestBase() {
|
||||
/** Dummy relationship */
|
||||
private val relationship = Relationship(
|
||||
// Nothing special about these values, it's just to have something to return
|
||||
"1234",
|
||||
following = true,
|
||||
followedBy = true,
|
||||
blocking = false,
|
||||
muting = false,
|
||||
mutingNotifications = false,
|
||||
requested = false,
|
||||
showingReblogs = false,
|
||||
subscribing = null,
|
||||
blockingDomain = false,
|
||||
note = null,
|
||||
notifying = null
|
||||
)
|
||||
|
||||
/** Action to accept a follow request */
|
||||
private val acceptAction = NotificationAction.AcceptFollowRequest("1234")
|
||||
|
||||
/** Action to reject a follow request */
|
||||
private val rejectAction = NotificationAction.RejectFollowRequest("1234")
|
||||
|
||||
@Test
|
||||
fun `accepting follow request succeeds && emits UiSuccess`() = runTest {
|
||||
// Given
|
||||
timelineCases.stub {
|
||||
onBlocking { acceptFollowRequest(any()) } doReturn Single.just(relationship)
|
||||
}
|
||||
|
||||
viewModel.uiSuccess.test {
|
||||
// When
|
||||
viewModel.accept(acceptAction)
|
||||
|
||||
// Then
|
||||
val item = awaitItem()
|
||||
assertThat(item).isInstanceOf(NotificationActionSuccess::class.java)
|
||||
assertThat((item as NotificationActionSuccess).action).isEqualTo(acceptAction)
|
||||
}
|
||||
|
||||
// Then
|
||||
argumentCaptor<String>().apply {
|
||||
verify(timelineCases).acceptFollowRequest(capture())
|
||||
assertThat(this.lastValue).isEqualTo("1234")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `accepting follow request fails && emits UiError`() = runTest {
|
||||
// Given
|
||||
timelineCases.stub { onBlocking { acceptFollowRequest(any()) } doThrow httpException }
|
||||
|
||||
viewModel.uiError.test {
|
||||
// When
|
||||
viewModel.accept(acceptAction)
|
||||
|
||||
// Then
|
||||
val item = awaitItem()
|
||||
assertThat(item).isInstanceOf(UiError.AcceptFollowRequest::class.java)
|
||||
assertThat(item.action).isEqualTo(acceptAction)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `rejecting follow request succeeds && emits UiSuccess`() = runTest {
|
||||
// Given
|
||||
timelineCases.stub { onBlocking { rejectFollowRequest(any()) } doReturn Single.just(relationship) }
|
||||
|
||||
viewModel.uiSuccess.test {
|
||||
// When
|
||||
viewModel.accept(rejectAction)
|
||||
|
||||
// Then
|
||||
val item = awaitItem()
|
||||
assertThat(item).isInstanceOf(NotificationActionSuccess::class.java)
|
||||
assertThat((item as NotificationActionSuccess).action).isEqualTo(rejectAction)
|
||||
}
|
||||
|
||||
// Then
|
||||
argumentCaptor<String>().apply {
|
||||
verify(timelineCases).rejectFollowRequest(capture())
|
||||
assertThat(this.lastValue).isEqualTo("1234")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `rejecting follow request fails && emits UiError`() = runTest {
|
||||
// Given
|
||||
timelineCases.stub { onBlocking { rejectFollowRequest(any()) } doThrow httpException }
|
||||
|
||||
viewModel.uiError.test {
|
||||
// When
|
||||
viewModel.accept(rejectAction)
|
||||
|
||||
// Then
|
||||
val item = awaitItem()
|
||||
assertThat(item).isInstanceOf(UiError.RejectFollowRequest::class.java)
|
||||
assertThat(item.action).isEqualTo(rejectAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,225 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 Tusky Contributors
|
||||
*
|
||||
* 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,
|
||||
* see <http://www.gnu.org/licenses>.
|
||||
*/
|
||||
|
||||
package com.keylesspalace.tusky.components.notifications
|
||||
|
||||
import app.cash.turbine.test
|
||||
import at.connyduck.calladapter.networkresult.NetworkResult
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.keylesspalace.tusky.FilterV1Test.Companion.mockStatus
|
||||
import com.keylesspalace.tusky.viewdata.StatusViewData
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.argumentCaptor
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.doThrow
|
||||
import org.mockito.kotlin.stub
|
||||
import org.mockito.kotlin.verify
|
||||
|
||||
/**
|
||||
* Verify that [StatusAction] are handled correctly on receipt:
|
||||
*
|
||||
* - Is the correct [UiSuccess] or [UiError] value emitted?
|
||||
* - Is the correct [TimelineCases] function called, with the correct arguments?
|
||||
* This is only tested in the success case; if it passed there it must also
|
||||
* have passed in the error case.
|
||||
*/
|
||||
class NotificationsViewModelTestStatusAction : NotificationsViewModelTestBase() {
|
||||
private val status = mockStatus(pollOptions = listOf("Choice 1", "Choice 2", "Choice 3"))
|
||||
private val statusViewData = StatusViewData.Concrete(
|
||||
status = status,
|
||||
isExpanded = true,
|
||||
isShowingContent = false,
|
||||
isCollapsed = false
|
||||
)
|
||||
|
||||
/** Action to bookmark a status */
|
||||
private val bookmarkAction = StatusAction.Bookmark(true, statusViewData)
|
||||
|
||||
/** Action to favourite a status */
|
||||
private val favouriteAction = StatusAction.Favourite(true, statusViewData)
|
||||
|
||||
/** Action to reblog a status */
|
||||
private val reblogAction = StatusAction.Reblog(true, statusViewData)
|
||||
|
||||
/** Action to vote in a poll */
|
||||
private val voteInPollAction = StatusAction.VoteInPoll(
|
||||
poll = status.poll!!,
|
||||
choices = listOf(1, 0, 0),
|
||||
statusViewData
|
||||
)
|
||||
|
||||
/** Captors for status ID and state arguments */
|
||||
private val id = argumentCaptor<String>()
|
||||
private val state = argumentCaptor<Boolean>()
|
||||
|
||||
@Test
|
||||
fun `bookmark succeeds && emits UiSuccess`() = runTest {
|
||||
// Given
|
||||
timelineCases.stub { onBlocking { bookmark(any(), any()) } doReturn NetworkResult.success(status) }
|
||||
|
||||
viewModel.uiSuccess.test {
|
||||
// When
|
||||
viewModel.accept(bookmarkAction)
|
||||
|
||||
// Then
|
||||
val item = awaitItem()
|
||||
assertThat(item).isInstanceOf(StatusActionSuccess.Bookmark::class.java)
|
||||
assertThat((item as StatusActionSuccess).action).isEqualTo(bookmarkAction)
|
||||
}
|
||||
|
||||
// Then
|
||||
verify(timelineCases).bookmark(id.capture(), state.capture())
|
||||
assertThat(id.firstValue).isEqualTo(statusViewData.status.id)
|
||||
assertThat(state.firstValue).isEqualTo(true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `bookmark fails && emits UiError`() = runTest {
|
||||
// Given
|
||||
timelineCases.stub { onBlocking { bookmark(any(), any()) } doThrow httpException }
|
||||
|
||||
viewModel.uiError.test {
|
||||
// When
|
||||
viewModel.accept(bookmarkAction)
|
||||
|
||||
// Then
|
||||
val item = awaitItem()
|
||||
assertThat(item).isInstanceOf(UiError.Bookmark::class.java)
|
||||
assertThat(item.action).isEqualTo(bookmarkAction)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `favourite succeeds && emits UiSuccess`() = runTest {
|
||||
// Given
|
||||
timelineCases.stub {
|
||||
onBlocking { favourite(any(), any()) } doReturn NetworkResult.success(status)
|
||||
}
|
||||
|
||||
viewModel.uiSuccess.test {
|
||||
// When
|
||||
viewModel.accept(favouriteAction)
|
||||
|
||||
// Then
|
||||
val item = awaitItem()
|
||||
assertThat(item).isInstanceOf(StatusActionSuccess.Favourite::class.java)
|
||||
assertThat((item as StatusActionSuccess).action).isEqualTo(favouriteAction)
|
||||
}
|
||||
|
||||
// Then
|
||||
verify(timelineCases).favourite(id.capture(), state.capture())
|
||||
assertThat(id.firstValue).isEqualTo(statusViewData.status.id)
|
||||
assertThat(state.firstValue).isEqualTo(true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `favourite fails && emits UiError`() = runTest {
|
||||
// Given
|
||||
timelineCases.stub { onBlocking { favourite(any(), any()) } doThrow httpException }
|
||||
|
||||
viewModel.uiError.test {
|
||||
// When
|
||||
viewModel.accept(favouriteAction)
|
||||
|
||||
// Then
|
||||
val item = awaitItem()
|
||||
assertThat(item).isInstanceOf(UiError.Favourite::class.java)
|
||||
assertThat(item.action).isEqualTo(favouriteAction)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `reblog succeeds && emits UiSuccess`() = runTest {
|
||||
// Given
|
||||
timelineCases.stub { onBlocking { reblog(any(), any()) } doReturn NetworkResult.success(status) }
|
||||
|
||||
viewModel.uiSuccess.test {
|
||||
// When
|
||||
viewModel.accept(reblogAction)
|
||||
|
||||
// Then
|
||||
val item = awaitItem()
|
||||
assertThat(item).isInstanceOf(StatusActionSuccess.Reblog::class.java)
|
||||
assertThat((item as StatusActionSuccess).action).isEqualTo(reblogAction)
|
||||
}
|
||||
|
||||
// Then
|
||||
verify(timelineCases).reblog(id.capture(), state.capture())
|
||||
assertThat(id.firstValue).isEqualTo(statusViewData.status.id)
|
||||
assertThat(state.firstValue).isEqualTo(true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `reblog fails && emits UiError`() = runTest {
|
||||
// Given
|
||||
timelineCases.stub { onBlocking { reblog(any(), any()) } doThrow httpException }
|
||||
|
||||
viewModel.uiError.test {
|
||||
// When
|
||||
viewModel.accept(reblogAction)
|
||||
|
||||
// Then
|
||||
val item = awaitItem()
|
||||
assertThat(item).isInstanceOf(UiError.Reblog::class.java)
|
||||
assertThat(item.action).isEqualTo(reblogAction)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `voteinpoll succeeds && emits UiSuccess`() = runTest {
|
||||
// Given
|
||||
timelineCases.stub {
|
||||
onBlocking { voteInPoll(any(), any(), any()) } doReturn NetworkResult.success(status.poll!!)
|
||||
}
|
||||
|
||||
viewModel.uiSuccess.test {
|
||||
// When
|
||||
viewModel.accept(voteInPollAction)
|
||||
|
||||
// Then
|
||||
val item = awaitItem()
|
||||
assertThat(item).isInstanceOf(StatusActionSuccess.VoteInPoll::class.java)
|
||||
assertThat((item as StatusActionSuccess).action).isEqualTo(voteInPollAction)
|
||||
}
|
||||
|
||||
// Then
|
||||
val pollId = argumentCaptor<String>()
|
||||
val choices = argumentCaptor<List<Int>>()
|
||||
verify(timelineCases).voteInPoll(id.capture(), pollId.capture(), choices.capture())
|
||||
assertThat(id.firstValue).isEqualTo(statusViewData.status.id)
|
||||
assertThat(pollId.firstValue).isEqualTo(status.poll!!.id)
|
||||
assertThat(choices.firstValue).isEqualTo(voteInPollAction.choices)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `voteinpoll fails && emits UiError`() = runTest {
|
||||
// Given
|
||||
timelineCases.stub { onBlocking { voteInPoll(any(), any(), any()) } doThrow httpException }
|
||||
|
||||
viewModel.uiError.test {
|
||||
// When
|
||||
viewModel.accept(voteInPollAction)
|
||||
|
||||
// Then
|
||||
val item = awaitItem()
|
||||
assertThat(item).isInstanceOf(UiError.VoteInPoll::class.java)
|
||||
assertThat(item.action).isEqualTo(voteInPollAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 Tusky Contributors
|
||||
*
|
||||
* 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,
|
||||
* see <http://www.gnu.org/licenses>.
|
||||
*/
|
||||
|
||||
package com.keylesspalace.tusky.components.notifications
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.keylesspalace.tusky.appstore.PreferenceChangedEvent
|
||||
import com.keylesspalace.tusky.settings.PrefKeys
|
||||
import com.keylesspalace.tusky.util.CardViewMode
|
||||
import com.keylesspalace.tusky.util.StatusDisplayOptions
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* Verify that [StatusDisplayOptions] are handled correctly.
|
||||
*
|
||||
* - Is the initial value taken from values in sharedPreferences and account?
|
||||
* - Does the make() function correctly use an updated preference?
|
||||
* - Is the correct update emitted when a relevant preference changes?
|
||||
*/
|
||||
class NotificationsViewModelTestStatusDisplayOptions : NotificationsViewModelTestBase() {
|
||||
|
||||
private val defaultStatusDisplayOptions = StatusDisplayOptions(
|
||||
animateAvatars = false,
|
||||
mediaPreviewEnabled = true, // setting in NotificationsViewModelTestBase
|
||||
useAbsoluteTime = false,
|
||||
showBotOverlay = true,
|
||||
useBlurhash = true,
|
||||
cardViewMode = CardViewMode.NONE,
|
||||
confirmReblogs = true,
|
||||
confirmFavourites = false,
|
||||
hideStats = false,
|
||||
animateEmojis = false,
|
||||
showStatsInline = false,
|
||||
showSensitiveMedia = true, // setting in NotificationsViewModelTestBase
|
||||
openSpoiler = true // setting in NotificationsViewModelTestBase
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `initial settings are from sharedPreferences and activeAccount`() = runTest {
|
||||
viewModel.statusDisplayOptions.test {
|
||||
val item = awaitItem()
|
||||
assertThat(item).isEqualTo(defaultStatusDisplayOptions)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `make() uses updated preference`() = runTest {
|
||||
// Prior, should be false
|
||||
assertThat(defaultStatusDisplayOptions.animateAvatars).isFalse()
|
||||
|
||||
// Given; just a change to one preferences
|
||||
sharedPreferencesMap[PrefKeys.ANIMATE_GIF_AVATARS] = true
|
||||
|
||||
// When
|
||||
val updatedOptions = defaultStatusDisplayOptions.make(
|
||||
sharedPreferences,
|
||||
PrefKeys.ANIMATE_GIF_AVATARS,
|
||||
accountManager.activeAccount!!
|
||||
)
|
||||
|
||||
// Then, should be true
|
||||
assertThat(updatedOptions.animateAvatars).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `PreferenceChangedEvent emits new StatusDisplayOptions`() = runTest {
|
||||
// Prior, should be false
|
||||
viewModel.statusDisplayOptions.test {
|
||||
val item = expectMostRecentItem()
|
||||
assertThat(item.animateAvatars).isFalse()
|
||||
}
|
||||
|
||||
// Given
|
||||
sharedPreferencesMap[PrefKeys.ANIMATE_GIF_AVATARS] = true
|
||||
|
||||
// When
|
||||
eventHub.dispatch(PreferenceChangedEvent(PrefKeys.ANIMATE_GIF_AVATARS))
|
||||
|
||||
// Then, should be true
|
||||
viewModel.statusDisplayOptions.test {
|
||||
val item = expectMostRecentItem()
|
||||
assertThat(item.animateAvatars).isTrue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 Tusky Contributors
|
||||
*
|
||||
* 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,
|
||||
* see <http://www.gnu.org/licenses>.
|
||||
*/
|
||||
|
||||
package com.keylesspalace.tusky.components.notifications
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.keylesspalace.tusky.appstore.PreferenceChangedEvent
|
||||
import com.keylesspalace.tusky.entity.Notification
|
||||
import com.keylesspalace.tusky.settings.PrefKeys
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* Verify that [UiState] is handled correctly.
|
||||
*
|
||||
* - Is the initial value taken from values in sharedPreferences and account?
|
||||
* - Is the correct update emitted when a relevant preference changes?
|
||||
*/
|
||||
class NotificationsViewModelTestUiState : NotificationsViewModelTestBase() {
|
||||
|
||||
private val initialUiState = UiState(
|
||||
activeFilter = setOf(Notification.Type.FOLLOW),
|
||||
showFabWhileScrolling = true
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `should load initial filter from active account`() = runTest {
|
||||
viewModel.uiState.test {
|
||||
assertThat(expectMostRecentItem()).isEqualTo(initialUiState)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `showFabWhileScrolling depends on FAB_HIDE preference`() = runTest {
|
||||
// Prior
|
||||
viewModel.uiState.test {
|
||||
assertThat(expectMostRecentItem().showFabWhileScrolling).isTrue()
|
||||
}
|
||||
|
||||
// Given
|
||||
sharedPreferencesMap[PrefKeys.FAB_HIDE] = true
|
||||
|
||||
// When
|
||||
eventHub.dispatch(PreferenceChangedEvent(PrefKeys.FAB_HIDE))
|
||||
|
||||
// Then
|
||||
viewModel.uiState.test {
|
||||
assertThat(expectMostRecentItem().showFabWhileScrolling).isFalse()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 Tusky Contributors
|
||||
*
|
||||
* 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,
|
||||
* see <http://www.gnu.org/licenses>.
|
||||
*/
|
||||
|
||||
package com.keylesspalace.tusky.components.notifications
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.keylesspalace.tusky.db.AccountEntity
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import org.mockito.kotlin.argumentCaptor
|
||||
import org.mockito.kotlin.verify
|
||||
|
||||
class NotificationsViewModelTestVisibleId : NotificationsViewModelTestBase() {
|
||||
|
||||
@Test
|
||||
fun `should save notification ID to active account`() = runTest {
|
||||
argumentCaptor<AccountEntity>().apply {
|
||||
// When
|
||||
viewModel.accept(InfallibleUiAction.SaveVisibleId("1234"))
|
||||
|
||||
// Then
|
||||
verify(accountManager).saveAccount(capture())
|
||||
assertThat(this.lastValue.lastNotificationId)
|
||||
.isEqualTo("1234")
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue