1
0
Fork 0
mirror of https://github.com/anyproto/anytype-kotlin.git synced 2025-06-08 05:47:05 +09:00

DROID-2789 App | Tech | Optimize unsubscribe for global subscriptions (#1534)

This commit is contained in:
Evgenii Kozlov 2024-09-05 13:57:07 +02:00 committed by GitHub
parent 51ef35c45c
commit 63a658d515
Signed by: github
GPG key ID: B5690EEEBB952194
11 changed files with 664 additions and 135 deletions

View file

@ -2,6 +2,7 @@ package com.anytypeio.anytype.domain.config
import com.anytypeio.anytype.core_models.Config
@Deprecated("Refactoring needed")
interface ConfigStorage {
@Deprecated("Unsafe method. Use getOrNull() instead")
@Throws(IllegalStateException::class)

View file

@ -5,9 +5,9 @@ import com.anytypeio.anytype.core_models.DVSort
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.SubscriptionEvent
import com.anytypeio.anytype.domain.`object`.move
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.`object`.move
import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emitAll
@ -39,6 +39,7 @@ class ObjectTypesSubscriptionContainer(
beforeId = null,
collection = null
)
store.clear()
store.merge(
types = initial.results.map { ObjectWrapper.Type(it.map) }
)
@ -130,6 +131,7 @@ class ObjectTypesSubscriptionContainer(
suspend fun unsubscribe() = withContext(dispatchers.io) {
runCatching {
store.clear()
repo.cancelObjectSearchSubscription(
listOf(SUBSCRIPTION_ID)
)
@ -157,7 +159,15 @@ class ObjectTypesSubscriptionContainer(
val objects: List<Id> = emptyList(),
val dependencies: List<Id> = emptyList(),
val lastModified: Long = 0L
)
) {
companion object {
fun empty() = Index(
objects = emptyList(),
dependencies = emptyList(),
lastModified = 0L
)
}
}
companion object {
const val SUBSCRIPTION_ID = "object-type-store-subscription"

View file

@ -5,86 +5,55 @@ import com.anytypeio.anytype.core_models.DVFilter
import com.anytypeio.anytype.core_models.DVFilterCondition
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.domain.subscriptions.GlobalSubscription
import com.anytypeio.anytype.domain.workspace.SpaceManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
class ObjectTypesSubscriptionManager (
private val scope: CoroutineScope = GlobalScope,
private val container: ObjectTypesSubscriptionContainer,
private val spaceManager: SpaceManager
) {
): GlobalSubscription {
private val pipeline = spaceManager.observe().flatMapLatest { config ->
val params = buildParams(config)
container.observe(params)
val pipeline get() = spaceManager.state().flatMapLatest { state ->
when(state) {
is SpaceManager.State.Space.Active -> {
val params = buildParams(state.config)
container.observe(params)
}
is SpaceManager.State.Space.Idle -> {
flow {
emit(ObjectTypesSubscriptionContainer.Index.empty())
}
}
is SpaceManager.State.NoSpace -> {
flow {
container.unsubscribe()
emit(ObjectTypesSubscriptionContainer.Index.empty())
}
}
is SpaceManager.State.Init -> {
emptyFlow()
}
}
}
private var job: Job? = null
fun onStart() {
job?.cancel()
job = scope.launch { pipeline.collect() }
job = scope.launch {
pipeline.collect()
}
}
private fun buildParams(config: Config) =
ObjectTypesSubscriptionContainer.Params(
subscription = ObjectTypesSubscriptionContainer.SUBSCRIPTION_ID,
filters = listOf(
DVFilter(
relation = Relations.LAYOUT,
condition = DVFilterCondition.EQUAL,
value = ObjectType.Layout.OBJECT_TYPE.code.toDouble()
),
DVFilter(
relation = Relations.SPACE_ID,
condition = DVFilterCondition.EQUAL,
value = config.space
),
DVFilter(
relation = Relations.IS_DELETED,
condition = DVFilterCondition.NOT_EQUAL,
value = true
),
DVFilter(
relation = Relations.IS_ARCHIVED,
condition = DVFilterCondition.NOT_EQUAL,
value = true
),
DVFilter(
relation = Relations.UNIQUE_KEY,
condition = DVFilterCondition.NOT_EMPTY
),
),
limit = 0,
offset = 0L,
sorts = emptyList(),
sources = emptyList(),
keys = listOf(
Relations.ID,
Relations.NAME,
Relations.IS_HIDDEN,
Relations.IS_DELETED,
Relations.IS_ARCHIVED,
Relations.SMARTBLOCKTYPES,
Relations.LAYOUT,
Relations.DESCRIPTION,
Relations.ICON_EMOJI,
Relations.SOURCE_OBJECT,
Relations.IS_READ_ONLY,
Relations.RECOMMENDED_LAYOUT,
Relations.DEFAULT_TEMPLATE_ID,
Relations.SPACE_ID,
Relations.UNIQUE_KEY,
Relations.RESTRICTIONS
),
ignoreWorkspace = true
)
fun onStop() {
scope.launch {
container.unsubscribe()
@ -92,4 +61,60 @@ class ObjectTypesSubscriptionManager (
job = null
}
}
companion object {
fun buildParams(config: Config) =
ObjectTypesSubscriptionContainer.Params(
subscription = ObjectTypesSubscriptionContainer.SUBSCRIPTION_ID,
filters = listOf(
DVFilter(
relation = Relations.LAYOUT,
condition = DVFilterCondition.EQUAL,
value = ObjectType.Layout.OBJECT_TYPE.code.toDouble()
),
DVFilter(
relation = Relations.SPACE_ID,
condition = DVFilterCondition.EQUAL,
value = config.space
),
DVFilter(
relation = Relations.IS_DELETED,
condition = DVFilterCondition.NOT_EQUAL,
value = true
),
DVFilter(
relation = Relations.IS_ARCHIVED,
condition = DVFilterCondition.NOT_EQUAL,
value = true
),
DVFilter(
relation = Relations.UNIQUE_KEY,
condition = DVFilterCondition.NOT_EMPTY
),
),
limit = 0,
offset = 0L,
sorts = emptyList(),
sources = emptyList(),
keys = listOf(
Relations.ID,
Relations.NAME,
Relations.IS_HIDDEN,
Relations.IS_DELETED,
Relations.IS_ARCHIVED,
Relations.SMARTBLOCKTYPES,
Relations.LAYOUT,
Relations.DESCRIPTION,
Relations.ICON_EMOJI,
Relations.SOURCE_OBJECT,
Relations.IS_READ_ONLY,
Relations.RECOMMENDED_LAYOUT,
Relations.DEFAULT_TEMPLATE_ID,
Relations.SPACE_ID,
Relations.UNIQUE_KEY,
Relations.RESTRICTIONS
),
ignoreWorkspace = true
)
}
}

View file

@ -5,9 +5,9 @@ import com.anytypeio.anytype.core_models.DVSort
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.SubscriptionEvent
import com.anytypeio.anytype.domain.`object`.move
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.`object`.move
import com.anytypeio.anytype.domain.objects.StoreOfRelations
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emitAll
@ -39,6 +39,7 @@ class RelationsSubscriptionContainer(
beforeId = null,
collection = null
)
store.clear()
store.merge(
relations = initial.results.map { ObjectWrapper.Relation(it.map) }
)
@ -132,6 +133,7 @@ class RelationsSubscriptionContainer(
suspend fun unsubscribe() = withContext(dispatchers.io) {
runCatching {
store.clear()
repo.cancelObjectSearchSubscription(
listOf(SUBSCRIPTION_ID)
)
@ -159,7 +161,15 @@ class RelationsSubscriptionContainer(
val objects: List<Id> = emptyList(),
val dependencies: List<Id> = emptyList(),
val lastModified: Long = 0L
)
) {
companion object {
fun empty() = Index(
objects = emptyList(),
dependencies = emptyList(),
lastModified = 0L
)
}
}
companion object {
const val SUBSCRIPTION_ID = "relation-store-subscription"

View file

@ -6,13 +6,16 @@ import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Marketplace
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.domain.subscriptions.GlobalSubscription
import com.anytypeio.anytype.domain.workspace.SpaceManager
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
@ -20,10 +23,34 @@ class RelationsSubscriptionManager @Inject constructor(
private val scope: CoroutineScope = GlobalScope,
private val container: RelationsSubscriptionContainer,
private val spaceManager: SpaceManager
) {
private val pipeline = spaceManager.observe().flatMapLatest { config ->
val params = buildParams(listOf(config.space, config.techSpace, Marketplace.MARKETPLACE_SPACE_ID))
container.observe(params)
): GlobalSubscription {
val pipeline get() = spaceManager.state().flatMapLatest { state ->
when(state) {
is SpaceManager.State.Space.Active -> {
val params = buildParams(
spaces = listOf(
state.config.space,
state.config.techSpace,
Marketplace.MARKETPLACE_SPACE_ID
)
)
container.observe(params)
}
is SpaceManager.State.Space.Idle -> {
flow {
emit(RelationsSubscriptionContainer.Index.empty())
}
}
is SpaceManager.State.NoSpace -> {
flow {
container.unsubscribe()
emit(RelationsSubscriptionContainer.Index.empty())
}
}
is SpaceManager.State.Init -> {
emptyFlow()
}
}
}
private var job: Job? = null
@ -33,58 +60,6 @@ class RelationsSubscriptionManager @Inject constructor(
job = scope.launch { pipeline.collect() }
}
private fun buildParams(spaces: List<Id>) = RelationsSubscriptionContainer.Params(
subscription = RelationsSubscriptionContainer.SUBSCRIPTION_ID,
filters = listOf(
DVFilter(
relation = Relations.LAYOUT,
condition = DVFilterCondition.EQUAL,
value = ObjectType.Layout.RELATION.code.toDouble()
),
DVFilter(
relation = Relations.IS_DELETED,
condition = DVFilterCondition.NOT_EQUAL,
value = true
),
DVFilter(
relation = Relations.IS_ARCHIVED,
condition = DVFilterCondition.NOT_EQUAL,
value = true
),
DVFilter(
relation = Relations.SPACE_ID,
condition = DVFilterCondition.IN,
value = spaces
)
),
limit = 0,
offset = 0L,
sorts = emptyList(),
sources = emptyList(),
keys = listOf(
Relations.ID,
Relations.SPACE_ID,
Relations.TYPE,
Relations.LAYOUT,
Relations.NAME,
Relations.RELATION_FORMAT,
Relations.RELATION_KEY,
Relations.SCOPE,
Relations.IS_READ_ONLY,
Relations.IS_HIDDEN,
Relations.IS_DELETED,
Relations.IS_ARCHIVED,
Relations.IS_FAVORITE,
Relations.RESTRICTIONS,
Relations.MAX_COUNT,
Relations.RELATION_READ_ONLY_VALUE,
Relations.RELATION_DEFAULT_VALUE,
Relations.RELATION_FORMAT_OBJECT_TYPES,
Relations.SOURCE_OBJECT
),
ignoreWorkspace = true
)
fun onStop() {
scope.launch {
container.unsubscribe()
@ -92,4 +67,58 @@ class RelationsSubscriptionManager @Inject constructor(
job = null
}
}
companion object {
fun buildParams(spaces: List<Id>) = RelationsSubscriptionContainer.Params(
subscription = RelationsSubscriptionContainer.SUBSCRIPTION_ID,
filters = listOf(
DVFilter(
relation = Relations.LAYOUT,
condition = DVFilterCondition.EQUAL,
value = ObjectType.Layout.RELATION.code.toDouble()
),
DVFilter(
relation = Relations.IS_DELETED,
condition = DVFilterCondition.NOT_EQUAL,
value = true
),
DVFilter(
relation = Relations.IS_ARCHIVED,
condition = DVFilterCondition.NOT_EQUAL,
value = true
),
DVFilter(
relation = Relations.SPACE_ID,
condition = DVFilterCondition.IN,
value = spaces
)
),
limit = 0,
offset = 0L,
sorts = emptyList(),
sources = emptyList(),
keys = listOf(
Relations.ID,
Relations.SPACE_ID,
Relations.TYPE,
Relations.LAYOUT,
Relations.NAME,
Relations.RELATION_FORMAT,
Relations.RELATION_KEY,
Relations.SCOPE,
Relations.IS_READ_ONLY,
Relations.IS_HIDDEN,
Relations.IS_DELETED,
Relations.IS_ARCHIVED,
Relations.IS_FAVORITE,
Relations.RESTRICTIONS,
Relations.MAX_COUNT,
Relations.RELATION_READ_ONLY_VALUE,
Relations.RELATION_DEFAULT_VALUE,
Relations.RELATION_FORMAT_OBJECT_TYPES,
Relations.SOURCE_OBJECT
),
ignoreWorkspace = true
)
}
}

View file

@ -0,0 +1,3 @@
package com.anytypeio.anytype.domain.subscriptions
interface GlobalSubscription

View file

@ -10,16 +10,23 @@ import com.anytypeio.anytype.domain.debugging.Logger
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.withContext
/**
* Maybe convert to AppStateManager, with different states.
*/
interface SpaceManager {
suspend fun get(): Id
suspend fun set(space: Id): Result<Config>
fun getConfig(): Config?
fun getConfig(space: SpaceId) : Config?
fun observe() : Flow<Config>
fun state(): Flow<State>
fun clear()
class Impl @Inject constructor(
@ -29,14 +36,15 @@ interface SpaceManager {
private val logger: Logger
) : SpaceManager {
private val currentSpace = MutableStateFlow(VAULT)
private val currentSpace = MutableStateFlow(NO_SPACE)
private val info = mutableMapOf<Id, Config>()
override suspend fun get(): Id {
val curr = currentSpace.value
return curr.ifEmpty {
configStorage.getOrNull()?.space.orEmpty()
if (curr.isEmpty()) {
logger.logWarning("Accessing space manager in no space state")
}
return curr
}
override fun getConfig(): Config? {
@ -44,7 +52,7 @@ interface SpaceManager {
return if (curr.isNotEmpty()) {
info[curr]
} else {
configStorage.getOrNull()
null
}
}
@ -67,17 +75,23 @@ interface SpaceManager {
override fun observe(): Flow<Config> {
return currentSpace.mapNotNull { space ->
if (space.isEmpty()) {
configStorage.getOrNull()
null
} else {
info[space]
}
}
}
override fun state(): Flow<State> {
return currentSpace.map { space ->
if (space == NO_SPACE) {
State.NoSpace
} else {
val config = info[space]
if (config != null)
config
else {
val default = configStorage.getOrNull()
if (default != null && default.space == space)
default
else
null
if (config != null) {
State.Space.Active(config)
} else {
State.Space.Idle(SpaceId(space))
}
}
}
@ -85,11 +99,20 @@ interface SpaceManager {
override fun clear() {
info.clear()
currentSpace.value = VAULT
currentSpace.value = NO_SPACE
}
companion object {
const val VAULT = ""
const val NO_SPACE = ""
}
}
sealed class State {
data object Init: State()
data object NoSpace: State()
sealed class Space: State() {
data class Idle(val space: SpaceId): Space()
data class Active(val config: Config): Space()
}
}
}

View file

@ -0,0 +1,209 @@
package com.anytypeio.anytype.domain.subscriptions
import app.cash.turbine.test
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.SearchResult
import com.anytypeio.anytype.core_models.StubConfig
import com.anytypeio.anytype.core_models.StubObjectType
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.common.DefaultCoroutineTestRule
import com.anytypeio.anytype.domain.objects.DefaultStoreOfObjectTypes
import com.anytypeio.anytype.domain.search.ObjectTypesSubscriptionContainer
import com.anytypeio.anytype.domain.search.ObjectTypesSubscriptionManager
import com.anytypeio.anytype.domain.search.SubscriptionEventChannel
import com.anytypeio.anytype.domain.workspace.SpaceManager
import kotlin.test.assertEquals
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.stub
class ObjectTypesSubscriptionContainerTest {
@get:Rule
val coroutineTestRule = DefaultCoroutineTestRule()
private val dispatchers = AppCoroutineDispatchers(
main = coroutineTestRule.dispatcher,
computation = coroutineTestRule.dispatcher,
io = coroutineTestRule.dispatcher
)
@Mock lateinit var spaceManager: SpaceManager
@Mock
lateinit var repo: BlockRepository
@Mock
lateinit var channel: SubscriptionEventChannel
@Before
fun setup() {
MockitoAnnotations.openMocks(this)
}
@Test
fun `should populate store with latest results and clear it when unsubscribe`() = runTest {
// SETUP
val delay = 300
val subscription = ObjectTypesSubscriptionContainer.SUBSCRIPTION_ID
val store = DefaultStoreOfObjectTypes()
val defaultSpaceConfig = StubConfig()
val alternativeSpaceConfig = StubConfig()
val container = ObjectTypesSubscriptionContainer(
repo = repo,
channel = channel,
store = store,
dispatchers = dispatchers
)
val manager = ObjectTypesSubscriptionManager(
scope = TestScope(),
spaceManager = spaceManager,
container = container
)
val defaultSpaceSearchParams = ObjectTypesSubscriptionManager.buildParams(
defaultSpaceConfig
)
val alternativeSpaceSearchParams = ObjectTypesSubscriptionManager.buildParams(
alternativeSpaceConfig
)
val defaultSpaceTypes = buildList {
add(
StubObjectType()
)
add(
StubObjectType()
)
}
val alternativeSpaceTypes = buildList {
add(
StubObjectType()
)
add(
StubObjectType()
)
}
// STUBBING
channel.stub {
on {
subscribe(
listOf(subscription)
)
} doReturn emptyFlow()
}
repo.stub {
onBlocking {
cancelObjectSearchSubscription(listOf(subscription))
} doReturn Unit
}
spaceManager.stub {
on {
state()
} doReturn flow {
emit(
SpaceManager.State.Space.Active(
defaultSpaceConfig
)
)
delay(300)
emit(
SpaceManager.State.Space.Active(
alternativeSpaceConfig
)
)
}
}
repo.stub {
onBlocking {
searchObjectsWithSubscription(
subscription = defaultSpaceSearchParams.subscription,
sorts = defaultSpaceSearchParams.sorts,
filters = defaultSpaceSearchParams.filters,
limit = defaultSpaceSearchParams.limit,
offset = defaultSpaceSearchParams.offset,
keys = defaultSpaceSearchParams.keys,
ignoreWorkspace = defaultSpaceSearchParams.ignoreWorkspace,
source = defaultSpaceSearchParams.sources,
afterId = null,
beforeId = null,
collection = null,
noDepSubscription = true
)
} doReturn SearchResult(
results = defaultSpaceTypes.map { ObjectWrapper.Basic(it.map) },
dependencies = emptyList()
)
}
repo.stub {
onBlocking {
searchObjectsWithSubscription(
subscription = alternativeSpaceSearchParams.subscription,
sorts = alternativeSpaceSearchParams.sorts,
filters = alternativeSpaceSearchParams.filters,
limit = alternativeSpaceSearchParams.limit,
offset = alternativeSpaceSearchParams.offset,
keys = alternativeSpaceSearchParams.keys,
ignoreWorkspace = alternativeSpaceSearchParams.ignoreWorkspace,
source = alternativeSpaceSearchParams.sources,
afterId = null,
beforeId = null,
collection = null,
noDepSubscription = true
)
} doReturn SearchResult(
results = alternativeSpaceTypes.map { ObjectWrapper.Basic(it.map) },
dependencies = emptyList()
)
}
// TESTING
manager.pipeline.test {
val first = awaitItem()
assertEquals(
expected = defaultSpaceTypes,
actual = store.getAll()
)
val second = awaitItem()
awaitComplete()
assertEquals(
expected = alternativeSpaceTypes,
actual = store.getAll()
)
container.unsubscribe()
assertEquals(
expected = emptyList(),
actual = store.getAll()
)
}
}
}

View file

@ -0,0 +1,218 @@
package com.anytypeio.anytype.domain.subscriptions
import app.cash.turbine.test
import com.anytypeio.anytype.core_models.Marketplace
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.SearchResult
import com.anytypeio.anytype.core_models.StubConfig
import com.anytypeio.anytype.core_models.StubRelationObject
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.common.DefaultCoroutineTestRule
import com.anytypeio.anytype.domain.objects.DefaultStoreOfRelations
import com.anytypeio.anytype.domain.search.RelationsSubscriptionContainer
import com.anytypeio.anytype.domain.search.RelationsSubscriptionManager
import com.anytypeio.anytype.domain.search.SubscriptionEventChannel
import com.anytypeio.anytype.domain.workspace.SpaceManager
import kotlin.test.assertEquals
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.stub
class RelationsSubscriptionContainerTest {
@get:Rule
val coroutineTestRule = DefaultCoroutineTestRule()
private val dispatchers = AppCoroutineDispatchers(
main = coroutineTestRule.dispatcher,
computation = coroutineTestRule.dispatcher,
io = coroutineTestRule.dispatcher
)
@Mock lateinit var spaceManager: SpaceManager
@Mock
lateinit var repo: BlockRepository
@Mock
lateinit var channel: SubscriptionEventChannel
@Before
fun setup() {
MockitoAnnotations.openMocks(this)
}
@Test
fun `should populate store with latest results and clear it when unsubscribe`() = runTest {
// SETUP
val delay = 300L
val subscription = RelationsSubscriptionContainer.SUBSCRIPTION_ID
val store = DefaultStoreOfRelations()
val defaultSpaceConfig = StubConfig()
val alternativeSpaceConfig = StubConfig()
val container = RelationsSubscriptionContainer(
repo = repo,
channel = channel,
store = store,
dispatchers = dispatchers
)
val manager = RelationsSubscriptionManager (
scope = TestScope(),
spaceManager = spaceManager,
container = container
)
val defaultSpaceSearchParams = RelationsSubscriptionManager.buildParams(
spaces = listOf(
defaultSpaceConfig.space,
defaultSpaceConfig.techSpace,
Marketplace.MARKETPLACE_SPACE_ID,
)
)
val alternativeSpaceSearchParams = RelationsSubscriptionManager.buildParams(
spaces = listOf(
alternativeSpaceConfig.space,
alternativeSpaceConfig.techSpace,
Marketplace.MARKETPLACE_SPACE_ID,
)
)
val defaultSpaceTypes = buildList {
add(
StubRelationObject()
)
add(
StubRelationObject()
)
}
val alternativeSpaceTypes = buildList {
add(
StubRelationObject()
)
add(
StubRelationObject()
)
}
// STUBBING
channel.stub {
on {
subscribe(
listOf(subscription)
)
} doReturn emptyFlow()
}
repo.stub {
onBlocking {
cancelObjectSearchSubscription(listOf(subscription))
} doReturn Unit
}
spaceManager.stub {
on {
state()
} doReturn flow {
emit(
SpaceManager.State.Space.Active(
defaultSpaceConfig
)
)
delay(delay)
emit(
SpaceManager.State.Space.Active(
alternativeSpaceConfig
)
)
}
}
repo.stub {
onBlocking {
searchObjectsWithSubscription(
subscription = defaultSpaceSearchParams.subscription,
sorts = defaultSpaceSearchParams.sorts,
filters = defaultSpaceSearchParams.filters,
limit = defaultSpaceSearchParams.limit,
offset = defaultSpaceSearchParams.offset,
keys = defaultSpaceSearchParams.keys,
ignoreWorkspace = defaultSpaceSearchParams.ignoreWorkspace,
source = defaultSpaceSearchParams.sources,
afterId = null,
beforeId = null,
collection = null,
noDepSubscription = true
)
} doReturn SearchResult(
results = defaultSpaceTypes.map { ObjectWrapper.Basic(it.map) },
dependencies = emptyList()
)
}
repo.stub {
onBlocking {
searchObjectsWithSubscription(
subscription = alternativeSpaceSearchParams.subscription,
sorts = alternativeSpaceSearchParams.sorts,
filters = alternativeSpaceSearchParams.filters,
limit = alternativeSpaceSearchParams.limit,
offset = alternativeSpaceSearchParams.offset,
keys = alternativeSpaceSearchParams.keys,
ignoreWorkspace = alternativeSpaceSearchParams.ignoreWorkspace,
source = alternativeSpaceSearchParams.sources,
afterId = null,
beforeId = null,
collection = null,
noDepSubscription = true
)
} doReturn SearchResult(
results = alternativeSpaceTypes.map { ObjectWrapper.Basic(it.map) },
dependencies = emptyList()
)
}
// TESTING
manager.pipeline.test {
val first = awaitItem()
assertEquals(
expected = defaultSpaceTypes,
actual = store.getAll()
)
val second = awaitItem()
awaitComplete()
assertEquals(
expected = alternativeSpaceTypes,
actual = store.getAll()
)
container.unsubscribe()
assertEquals(
expected = emptyList(),
actual = store.getAll()
)
}
}
}

View file

@ -1643,6 +1643,7 @@ class HomeScreenViewModel(
fun onVaultClicked() {
viewModelScope.launch {
spaceManager.clear()
clearLastOpenedSpace.async(Unit).fold(
onSuccess = {
Timber.d("Cleared last opened space before opening vault")

View file

@ -213,7 +213,7 @@ fun StubCallout(
fun StubRelation(
relationKey: String = MockDataFactory.randomString(),
format: RelationFormat
format: RelationFormat = Relation.Format.SHORT_TEXT
): Relation = Relation(
key = relationKey,
name = MockDataFactory.randomString(),