test(notifications): cover NotificationFeedFilter modeOverride wiring
Adds an instrumented test for the split-notifications feature
This commit is contained in:
+162
@@ -0,0 +1,162 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
|
import com.vitorpamplona.amethyst.model.AccountSettings
|
||||||
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
|
import com.vitorpamplona.amethyst.model.TopFilter
|
||||||
|
import com.vitorpamplona.amethyst.service.location.LocationState
|
||||||
|
import com.vitorpamplona.amethyst.service.okhttp.OkHttpWebSocket
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.nwc.NWCPaymentFilterAssembler
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.dal.NotificationFeedFilter
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
|
||||||
|
import com.vitorpamplona.quartz.nip03Timestamp.EmptyOtsResolverBuilder
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.Assert.assertNotEquals
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for [NotificationFeedFilter]'s `modeOverride` constructor parameter — the wiring
|
||||||
|
* that drives the split-notifications Following / Everyone tabs (Issue #197).
|
||||||
|
*
|
||||||
|
* Asserts the three contracts the feature relies on:
|
||||||
|
* 1. `feedKey` is mode-discriminated so each pinned tab caches independently.
|
||||||
|
* 2. `followList()` honors `modeOverride` when set; falls back to the spinner setting otherwise.
|
||||||
|
* 3. `buildFilterParams()` returns a GlobalTopNavFilter-backed FilterByListParams for
|
||||||
|
* `TopFilter.Global` (so `isGlobal()` is true, allowing non-follower notifications through),
|
||||||
|
* and a non-Global filter for `TopFilter.AllFollows` (forcing the follow-membership gate).
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class NotificationFeedFilterModeOverrideTest {
|
||||||
|
companion object {
|
||||||
|
private val keyPair = KeyPair()
|
||||||
|
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||||
|
|
||||||
|
private val client =
|
||||||
|
NostrClient(
|
||||||
|
OkHttpWebSocket.Builder {
|
||||||
|
OkHttpClient
|
||||||
|
.Builder()
|
||||||
|
.followRedirects(true)
|
||||||
|
.followSslRedirects(true)
|
||||||
|
.build()
|
||||||
|
},
|
||||||
|
scope,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val account =
|
||||||
|
Account(
|
||||||
|
settings = AccountSettings(keyPair = keyPair),
|
||||||
|
signer = NostrSignerInternal(keyPair),
|
||||||
|
geolocationFlow = { MutableStateFlow<LocationState.LocationResult>(LocationState.LocationResult.Loading) },
|
||||||
|
nwcFilterAssembler = { NWCPaymentFilterAssembler(client) },
|
||||||
|
otsResolverBuilder = { EmptyOtsResolverBuilder.build() },
|
||||||
|
cache = LocalCache,
|
||||||
|
client = client,
|
||||||
|
scope = scope,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun feedKeyDiffersByModeOverride() {
|
||||||
|
val spinner = NotificationFeedFilter(account)
|
||||||
|
val following = NotificationFeedFilter(account, TopFilter.AllFollows)
|
||||||
|
val everyone = NotificationFeedFilter(account, TopFilter.Global)
|
||||||
|
|
||||||
|
assertNotEquals(
|
||||||
|
"Following tab's feedKey must differ from Everyone tab's so each caches independently",
|
||||||
|
following.feedKey(),
|
||||||
|
everyone.feedKey(),
|
||||||
|
)
|
||||||
|
assertTrue(
|
||||||
|
"Everyone feedKey should encode the Global filter code",
|
||||||
|
everyone.feedKey().endsWith(TopFilter.Global.code),
|
||||||
|
)
|
||||||
|
assertTrue(
|
||||||
|
"Following feedKey should encode the AllFollows filter code",
|
||||||
|
following.feedKey().endsWith(TopFilter.AllFollows.code),
|
||||||
|
)
|
||||||
|
|
||||||
|
// When override is null, feedKey reflects the spinner-selected default.
|
||||||
|
account.settings.defaultNotificationFollowList.value = TopFilter.Global
|
||||||
|
assertEquals(everyone.feedKey(), spinner.feedKey())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun followListHonorsModeOverride() {
|
||||||
|
val following = NotificationFeedFilter(account, TopFilter.AllFollows)
|
||||||
|
val everyone = NotificationFeedFilter(account, TopFilter.Global)
|
||||||
|
|
||||||
|
assertEquals(TopFilter.AllFollows, following.followList())
|
||||||
|
assertEquals(TopFilter.Global, everyone.followList())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun followListFallsBackToSpinnerWhenOverrideNull() {
|
||||||
|
val spinner = NotificationFeedFilter(account)
|
||||||
|
|
||||||
|
account.settings.defaultNotificationFollowList.value = TopFilter.Global
|
||||||
|
assertEquals(TopFilter.Global, spinner.followList())
|
||||||
|
|
||||||
|
account.settings.defaultNotificationFollowList.value = TopFilter.AllFollows
|
||||||
|
assertEquals(TopFilter.AllFollows, spinner.followList())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun buildFilterParamsForGlobalOverrideReportsGlobal() {
|
||||||
|
val everyone = NotificationFeedFilter(account, TopFilter.Global)
|
||||||
|
|
||||||
|
val params = everyone.buildFilterParams(account)
|
||||||
|
|
||||||
|
// isGlobal() short-circuits the follow-membership gate in acceptableEvent,
|
||||||
|
// which is how the Everyone tab admits notifications from non-followed authors.
|
||||||
|
assertTrue(
|
||||||
|
"Everyone tab's FilterByListParams must report isGlobal so non-followers pass the gate",
|
||||||
|
params.isGlobal(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun buildFilterParamsForAllFollowsOverrideIsNotGlobal() {
|
||||||
|
val following = NotificationFeedFilter(account, TopFilter.AllFollows)
|
||||||
|
|
||||||
|
val params = following.buildFilterParams(account)
|
||||||
|
|
||||||
|
// The Following tab must NOT be Global so acceptableEvent falls through to
|
||||||
|
// isAuthorInFollows() — that's the gate that drops non-follower notifications.
|
||||||
|
assertFalse(
|
||||||
|
"Following tab's FilterByListParams must not be Global; it must apply the follows gate",
|
||||||
|
params.isGlobal(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user