DROID-2061 Widgets | Fix | Collection widget should not display dependent objects (#804)

This commit is contained in:
Evgenii Kozlov 2024-01-26 18:24:55 +01:00 committed by GitHub
parent c3d6882e5f
commit 8087338ce5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 333 additions and 6 deletions

View File

@ -99,25 +99,35 @@ interface StorelessSubscriptionContainer {
payload.forEach { event ->
when (event) {
is SubscriptionEvent.Add -> {
result = addEventProcessor.process(event, result)
if (event.subscription == subscription) {
result = addEventProcessor.process(event, result)
}
}
is SubscriptionEvent.Amend -> {
result = amendEventProcessor.process(event, result)
if (event.subscriptions.contains(subscription)) {
result = amendEventProcessor.process(event, result)
}
}
is SubscriptionEvent.Position -> {
result = positionEventProcessor.process(event, result)
}
is SubscriptionEvent.Remove -> {
result = removeEventProcessor.process(event, result)
if (event.subscription == subscription) {
result = removeEventProcessor.process(event, result)
}
}
is SubscriptionEvent.Set -> {
result = setEventProcessor.process(event, result)
if (event.subscriptions.contains(subscription)) {
result = setEventProcessor.process(event, result)
}
}
is SubscriptionEvent.Unset -> {
result = unsetEventProcessor.process(event, result)
if (event.subscriptions.contains(subscription)) {
result = unsetEventProcessor.process(event, result)
}
}
else -> {
// do nothing
logger.logWarning("Ignoring subscription event")
}
}
}

View File

@ -0,0 +1,307 @@
package com.anytypeio.anytype.domain.subscriptions
import app.cash.turbine.test
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_models.SearchResult
import com.anytypeio.anytype.core_models.StubObject
import com.anytypeio.anytype.core_models.StubObjectMinim
import com.anytypeio.anytype.core_models.SubscriptionEvent
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.debugging.Logger
import com.anytypeio.anytype.domain.library.StoreSearchParams
import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer
import com.anytypeio.anytype.domain.search.SubscriptionEventChannel
import com.anytypeio.anytype.test_utils.MockDataFactory
import kotlin.test.assertEquals
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.UnconfinedTestDispatcher
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 StorelessSubscriptionContainerTest {
@get:Rule
val coroutineTestRule = DefaultCoroutineTestRule()
@Mock
lateinit var repo: BlockRepository
@Mock
lateinit var eventChannel: SubscriptionEventChannel
private lateinit var container: StorelessSubscriptionContainer.Impl
private val appCoroutineTestDispatchers = AppCoroutineDispatchers(
io = UnconfinedTestDispatcher(),
main = UnconfinedTestDispatcher(),
computation = UnconfinedTestDispatcher()
)
private val defaultSearchParams = StoreSearchParams(
filters = emptyList(),
sorts = emptyList(),
subscription = MockDataFactory.randomUuid(),
keys = listOf(Relations.ID, Relations.NAME)
)
@Before
fun setup() {
MockitoAnnotations.openMocks(this)
container = StorelessSubscriptionContainer.Impl(
repo = repo,
channel = eventChannel,
logger = TestLogger,
dispatchers = appCoroutineTestDispatchers
)
}
@Test
fun `should emit two objects from initial results`() = runTest {
val obj1 = StubObjectMinim()
val obj2 = StubObjectMinim()
val givenResults = listOf(obj1, obj2)
stubSearchWithSubscription(
results = givenResults,
params = defaultSearchParams
)
stubSubscriptionEventChannel(
subscription = defaultSearchParams.subscription,
events = emptyFlow()
)
container.subscribe(defaultSearchParams).test {
val first = awaitItem()
assertEquals(
expected = givenResults,
actual = first
)
awaitComplete()
}
}
@Test
fun `should add one object to objects from initial results after consuming add-event and set-event`() = runTest {
val obj1 = StubObjectMinim(id = "1", name = "Walter Benjamin")
val obj2 = StubObjectMinim(id = "2", name = "Aby Warburg")
val addedObject = StubObjectMinim(id = "3", name = "Aloïs Riegl")
val initialResults = listOf(obj1, obj2)
val resultAfterEvents = listOf(obj1, obj2, addedObject)
val events = buildList {
add(
SubscriptionEvent.Add(
subscription = defaultSearchParams.subscription,
afterId = obj2.id,
target = addedObject.id
)
)
add(
SubscriptionEvent.Set(
subscriptions = listOf(defaultSearchParams.subscription),
target = addedObject.id,
data = addedObject.map
)
)
}
stubSearchWithSubscription(
results = initialResults,
params = defaultSearchParams
)
stubSubscriptionEventChannel(
subscription = defaultSearchParams.subscription,
events = flowOf(events)
)
container.subscribe(defaultSearchParams).test {
val first = awaitItem()
assertEquals(
expected = initialResults,
actual = first
)
val second = awaitItem()
assertEquals(
expected = resultAfterEvents,
actual = second
)
awaitComplete()
}
}
@Test
fun `should add one object to objects from initial results after consuming add-event, amend-event and then set-event`() = runTest {
val obj1 = StubObjectMinim(id = "1", name = "Walter Benjamin")
val obj2 = StubObjectMinim(id = "2", name = "Aby Warburg")
val addedObject = StubObjectMinim(id = "3", name = "")
val addedObjectAfterUpdate = StubObjectMinim(id = "3", name = "Erwin Panofsky")
val initialResults = listOf(obj1, obj2)
val resultAfterEvents = listOf(obj1, obj2, addedObjectAfterUpdate)
val events = buildList {
add(
SubscriptionEvent.Add(
subscription = defaultSearchParams.subscription,
afterId = obj2.id,
target = addedObject.id
)
)
add(
SubscriptionEvent.Set(
subscriptions = listOf(defaultSearchParams.subscription),
target = addedObject.id,
data = addedObject.map
)
)
add(
SubscriptionEvent.Amend(
subscriptions = listOf(defaultSearchParams.subscription),
target = addedObject.id,
diff = mapOf(
Relations.NAME to "Erwin Panofsky"
)
)
)
}
stubSearchWithSubscription(
results = initialResults,
params = defaultSearchParams
)
stubSubscriptionEventChannel(
subscription = defaultSearchParams.subscription,
events = flowOf(events)
)
container.subscribe(defaultSearchParams).test {
val first = awaitItem()
assertEquals(
expected = initialResults,
actual = first
)
val second = awaitItem()
assertEquals(
expected = resultAfterEvents,
actual = second
)
awaitComplete()
}
}
@Test
fun `should update one object from initial results after consuming amend event`() = runTest {
val obj1 = StubObjectMinim(id = "1", name = "Heinrich")
val obj2 = StubObjectMinim(id = "2", name = "Aby Warburg")
val updatedName = "Heinrich Wölfflin"
val obj2Updated = ObjectWrapper.Basic(
obj2.map.toMutableMap().apply {
set(Relations.NAME, updatedName)
}
)
val initialResults = listOf(obj1, obj2)
val resultsAfterEvents = listOf(obj1, obj2Updated)
val events = buildList {
add(
SubscriptionEvent.Amend(
subscriptions = listOf(defaultSearchParams.subscription),
target = obj2.id,
diff = mapOf(
Relations.NAME to updatedName
)
)
)
}
stubSearchWithSubscription(
results = initialResults,
params = defaultSearchParams
)
stubSubscriptionEventChannel(
subscription = defaultSearchParams.subscription,
events = flowOf(events)
)
container.subscribe(defaultSearchParams).test {
val first = awaitItem()
assertEquals(
expected = initialResults,
actual = first
)
val second = awaitItem()
assertEquals(
expected = resultsAfterEvents,
actual = second
)
awaitComplete()
}
}
private fun stubSearchWithSubscription(
results: List<ObjectWrapper.Basic>,
params: StoreSearchParams
) {
repo.stub {
onBlocking {
searchObjectsWithSubscription(
subscription = params.subscription,
sorts = params.sorts,
filters = params.filters,
limit = params.limit,
offset = params.offset,
keys = params.keys,
afterId = null,
beforeId = null,
noDepSubscription = true,
ignoreWorkspace = null,
collection = null,
source = emptyList()
)
} doReturn SearchResult(
results = results,
dependencies = emptyList()
)
}
}
private fun stubSubscriptionEventChannel(
subscription: Id = defaultSearchParams.subscription,
events: Flow<List<SubscriptionEvent>> = emptyFlow()
) {
eventChannel.stub {
on {
subscribe(listOf(subscription))
} doReturn events
}
}
}
object TestLogger : Logger {
override fun logWarning(msg: String) {
println("Warning: $msg")
}
override fun logException(e: Throwable) {
println("Error: ${e.message}")
}
}

View File

@ -38,6 +38,16 @@ fun StubObject(
)
)
fun StubObjectMinim(
id: String = MockDataFactory.randomUuid(),
name: String = MockDataFactory.randomString()
): ObjectWrapper.Basic = ObjectWrapper.Basic(
map = mapOf(
Relations.ID to id,
Relations.NAME to name
)
)
fun StubObjectView(
root: Id,
blocks: List<Block> = emptyList(),