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

DROID-923 Collections | Empty states (#3013)

* DROID-923 no query fix

* DROID-923 set, no objects

* DROID-923 collection, no items

* DROID-923 delete legacy param

* DROID-923 check setOf for only valid objects

* DROID-923 tests

* DROID-923 update turbin library version
This commit is contained in:
Konstantin Ivanov 2023-03-15 11:40:02 +01:00 committed by GitHub
parent 3041605feb
commit 73d4a1658f
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 278 additions and 170 deletions

View file

@ -442,7 +442,7 @@ open class ObjectSetFragment :
addNewButton.isEnabled = true
customizeViewButton.isEnabled = true
viewerTitle.text = state.title
dataViewInfo.show(type = DataViewInfo.TYPE.SET_NO_ITEMS, extra = state.type)
dataViewInfo.show(type = DataViewInfo.TYPE.SET_NO_ITEMS)
setViewer(viewer = null)
}

View file

@ -23,7 +23,7 @@ class DataViewInfo @JvmOverloads constructor(
val binding = ViewDataviewInfoBinding.inflate(LayoutInflater.from(context), this, true)
private var type: TYPE = TYPE.INIT
fun show(type: TYPE, extra: String = "") {
fun show(type: TYPE) {
this.type = type
visible()
when (type) {
@ -46,7 +46,7 @@ class DataViewInfo @JvmOverloads constructor(
TYPE.SET_NO_ITEMS -> {
binding.title.text = resources.getString(R.string.set_no_items_title)
binding.description.text =
resources.getString(R.string.set_no_items_description, extra)
resources.getString(R.string.set_no_items_description)
binding.button.text = resources.getString(R.string.set_no_items_button)
}
}

View file

@ -611,13 +611,13 @@
<string name="content_description_customize_view_button">Customize-view button</string>
<string name="collection_no_items_title">No objects</string>
<string name="collection_no_items_description">Add objects or turn into set to aggregate objects with\n equal types or relations</string>
<string name="collection_no_items_description">Create first object to continue</string>
<string name="collection_no_items_button">Create object</string>
<string name="set_no_query_title">No query selected</string>
<string name="set_no_query_description">All objects satisfying query will be displayed in Set</string>
<string name="set_no_query_description">Add search query to aggregate objects with equal types and relations in a live mode</string>
<string name="set_no_query_button">Select query</string>
<string name="set_no_items_title">No objects</string>
<string name="set_no_items_description">No objects of type \"%1$s\" found in your account, please create the first object</string>
<string name="set_no_items_description">Create first object to continue</string>
<string name="set_no_items_button">Create object</string>
<string name="set_collection_view_not_present">View is not present in set or collection, please recreate object</string>
<string name="menu_turn_to_collection">Turn into collection</string>

View file

@ -47,7 +47,7 @@ katexVersion = '1.0.2'
robolectricLatestVersion = '4.7.3'
kluentVersion = '1.14'
timberJunitVersion = '1.0.1'
turbineVersion = '0.7.0'
turbineVersion = '0.12.1'
coroutineTestingVersion = '1.6.4'
liveDataTestingVersion = '1.2.0'
espressoVersion = '3.4.0'

View file

@ -13,7 +13,7 @@ sealed class DataViewViewState {
sealed class Set : DataViewViewState() {
object NoQuery : Set()
object NoView : Set()
data class NoItems(val title: String, val type: String) : Set()
data class NoItems(val title: String) : Set()
data class Default(val viewer: Viewer?) : Set()
}

View file

@ -18,7 +18,6 @@ import com.anytypeio.anytype.presentation.objects.getProperName
import com.anytypeio.anytype.presentation.relations.ObjectRelationView
import com.anytypeio.anytype.presentation.relations.ObjectSetConfig.ID_KEY
import com.anytypeio.anytype.presentation.relations.isSystemKey
import com.anytypeio.anytype.presentation.relations.objectTypeRelation
import com.anytypeio.anytype.presentation.relations.view
import com.anytypeio.anytype.presentation.sets.model.ObjectView
import com.anytypeio.anytype.presentation.sets.model.SimpleRelationView
@ -317,10 +316,20 @@ fun List<DVViewerRelation>.updateViewerRelations(updates: List<DVViewerRelationU
return relations
}
fun ObjectState.DataView.Set.getSetOf(ctx: Id): List<Id> {
fun ObjectState.DataView.Set.getSetOfValue(ctx: Id): List<Id> {
return ObjectWrapper.Basic(details[ctx]?.map.orEmpty()).setOf
}
fun ObjectState.DataView.filterOutDeletedAndMissingObjects(query: List<Id>): List<Id> {
return query.filter(::isValidObject)
}
private fun ObjectState.DataView.isValidObject(objectId: Id): Boolean {
val objectDetails = details[objectId] ?: return false
val objectWrapper = ObjectWrapper.Basic(objectDetails.map)
return objectWrapper.isDeleted != true
}
fun DVViewer.updateFields(fields: DVViewerFields?): DVViewer {
if (fields == null) return this
return copy(

View file

@ -426,7 +426,8 @@ class ObjectSetViewModel(
state.dataViewContent.relationLinks.mapNotNull {
storeOfRelations.getByKey(it.key)
}
val setOf = state.getSetOf(ctx = context)
val setOfValue = state.getSetOfValue(ctx = context)
val query = state.filterOutDeletedAndMissingObjects(query = setOfValue)
val render = state.viewerById(currentViewId)
?.render(
coverImageHashProvider = coverImageHashProvider,
@ -437,13 +438,11 @@ class ObjectSetViewModel(
store = objectStore
)
when {
setOf.isEmpty() -> DataViewViewState.Set.NoQuery
query.isEmpty() -> DataViewViewState.Set.NoQuery
setOfValue.isEmpty() -> DataViewViewState.Set.NoQuery
render == null -> DataViewViewState.Set.NoView
render.isEmpty() -> {
DataViewViewState.Set.NoItems(
title = render.title,
type = state.details[setOf[0]]?.name.orEmpty()
)
DataViewViewState.Set.NoItems(title = render.title)
}
else -> DataViewViewState.Set.Default(viewer = render)
}

View file

@ -6,7 +6,8 @@ import com.anytypeio.anytype.domain.search.DataViewSubscriptionContainer
import com.anytypeio.anytype.presentation.relations.ObjectSetConfig
import com.anytypeio.anytype.presentation.search.ObjectSearchConstants
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
import com.anytypeio.anytype.presentation.sets.getSetOf
import com.anytypeio.anytype.presentation.sets.filterOutDeletedAndMissingObjects
import com.anytypeio.anytype.presentation.sets.getSetOfValue
import com.anytypeio.anytype.presentation.sets.state.ObjectState
import com.anytypeio.anytype.presentation.sets.updateFormatForSubscription
import com.anytypeio.anytype.presentation.sets.viewerById
@ -96,11 +97,22 @@ class DefaultDataViewSubscription(private val dataViewSubscriptionContainer: Dat
Timber.w("Data view set subscription: active viewer is null")
return emptyFlow()
}
val source = state.getSetOf(ctx = context)
if (source.isEmpty()) {
Timber.w("Data view set subscription: source is empty")
val setOfValue = state.getSetOfValue(ctx = context)
if (setOfValue.isEmpty()) {
Timber.w("Data view set subscription: setOf value is empty, proceed without subscription")
return emptyFlow()
}
val query = state.filterOutDeletedAndMissingObjects(setOfValue)
if (query.isEmpty()) {
Timber.w(
"Data view set subscription: query has no valid types or relations, " +
"proceed without subscription"
)
return emptyFlow()
}
val filters =
activeViewer.filters.updateFormatForSubscription(storeOfRelations) + ObjectSearchConstants.defaultDataViewFilters(
workspaceId = workspaceId
@ -112,7 +124,7 @@ class DefaultDataViewSubscription(private val dataViewSubscriptionContainer: Dat
subscription = getSubscriptionId(context),
sorts = activeViewer.sorts,
filters = filters,
sources = source,
sources = query,
keys = keys,
limit = ObjectSetConfig.DEFAULT_LIMIT,
offset = offset

View file

@ -1,6 +1,6 @@
package com.anytypeio.anytype.presentation.collections
import app.cash.turbine.test
import app.cash.turbine.testIn
import com.anytypeio.anytype.presentation.sets.DataViewViewState
import com.anytypeio.anytype.presentation.sets.ObjectSetViewModel
import com.anytypeio.anytype.presentation.sets.main.ObjectSetViewModelTestSetup
@ -49,35 +49,21 @@ class ObjectStateCollectionViewTest : ObjectSetViewModelTestSetup() {
// TESTING
viewModel.onStart(ctx = root)
// ASSERT COLLECTION OBJECT STATE
stateReducer.state.test {
val first = awaitItem()
assertIs<ObjectState.Init>(first)
val headerFlow = viewModel.header.testIn(backgroundScope)
val viewerFlow = viewModel.currentViewer.testIn(backgroundScope)
val stateFlow = stateReducer.state.testIn(backgroundScope)
val second = awaitItem()
assertIs<ObjectState.DataView.Collection>(second)
assertNull(headerFlow.awaitItem())
assertIs<ObjectState.Init>(stateFlow.awaitItem())
assertIs<DataViewViewState.Init>(viewerFlow.awaitItem())
}
assertIs<ObjectState.DataView.Collection>(stateFlow.awaitItem())
// ASSERT HEADER STATE
viewModel.header.test {
val first = awaitItem()
assertNull(first)
val second = awaitItem()
assertEquals(
expected = mockObjectCollection.title.content.asText().text,
actual = second?.text
)
expectNoEvents()
}
// ASSERT DATA VIEW STATE
viewModel.currentViewer.test {
val first = awaitItem()
assertIs<DataViewViewState.Init>(first)
expectNoEvents()
}
assertEquals(
expected = mockObjectCollection.title.content.asText().text,
actual = headerFlow.awaitItem()?.text
)
viewerFlow.expectNoEvents()
// ASSERT NO SUBSCRIPTION TO COLLECTION RECORDS
advanceUntilIdle()
@ -102,27 +88,16 @@ class ObjectStateCollectionViewTest : ObjectSetViewModelTestSetup() {
// TESTING
viewModel.onStart(ctx = root)
// ASSERT COLLECTION OBJECT STATE
stateReducer.state.test {
val first = awaitItem()
assertIs<ObjectState.Init>(first)
val viewerFlow = viewModel.currentViewer.testIn(backgroundScope)
val stateFlow = stateReducer.state.testIn(backgroundScope)
val second = awaitItem()
assertIs<ObjectState.DataView.Collection>(second)
expectNoEvents()
}
assertIs<ObjectState.Init>(stateFlow.awaitItem())
assertIs<DataViewViewState.Init>(viewerFlow.awaitItem())
// ASSERT DATA VIEW STATE
viewModel.currentViewer.test {
val first = awaitItem()
assertIs<DataViewViewState.Init>(first)
assertIs<ObjectState.DataView.Collection>(stateFlow.awaitItem())
assertIs<DataViewViewState.Collection.NoView>(viewerFlow.awaitItem())
val second = awaitItem()
assertIs<DataViewViewState.Collection.NoView>(second)
expectNoEvents()
}
// ASSERT SUBSCRIPTION TO COLLECTION RECORDS
// ASSERT NO SUBSCRIPTION TO COLLECTION RECORDS
advanceUntilIdle()
verifyNoInteractions(repo)
}
@ -154,25 +129,15 @@ class ObjectStateCollectionViewTest : ObjectSetViewModelTestSetup() {
// TESTING
viewModel.onStart(ctx = root)
// ASSERT COLLECTION OBJECT STATE
stateReducer.state.test {
val first = awaitItem()
assertIs<ObjectState.Init>(first)
val viewerFlow = viewModel.currentViewer.testIn(backgroundScope)
val stateFlow = stateReducer.state.testIn(backgroundScope)
val second = awaitItem()
assertIs<ObjectState.DataView.Collection>(second)
expectNoEvents()
}
// ASSERT STATES
assertIs<ObjectState.Init>(stateFlow.awaitItem())
assertIs<DataViewViewState.Init>(viewerFlow.awaitItem())
// ASSERT DATA VIEW STATE
viewModel.currentViewer.test {
val first = awaitItem()
assertIs<DataViewViewState.Init>(first)
val second = awaitItem()
assertIs<DataViewViewState.Collection.NoItems>(second)
expectNoEvents()
}
assertIs<ObjectState.DataView.Collection>(stateFlow.awaitItem())
assertIs<DataViewViewState.Collection.NoItems>(viewerFlow.awaitItem())
}
@Test
@ -203,27 +168,15 @@ class ObjectStateCollectionViewTest : ObjectSetViewModelTestSetup() {
// TESTING
viewModel.onStart(ctx = root)
// ASSERT COLLECTION OBJECT STATE
stateReducer.state.test {
val first = awaitItem()
assertIs<ObjectState.Init>(first)
val viewerFlow = viewModel.currentViewer.testIn(backgroundScope)
val stateFlow = stateReducer.state.testIn(backgroundScope)
val second = awaitItem()
assertIs<ObjectState.DataView.Collection>(second)
expectNoEvents()
}
// ASSERT STATES
assertIs<ObjectState.Init>(stateFlow.awaitItem())
assertIs<DataViewViewState.Init>(viewerFlow.awaitItem())
// ASSERT DATA VIEW STATE
viewModel.currentViewer.test {
val first = awaitItem()
assertIs<DataViewViewState.Init>(first)
val second = awaitItem()
assertIs<DataViewViewState.Collection.NoItems>(second)
val third = awaitItem()
assertIs<DataViewViewState.Collection.Default>(third)
expectNoEvents()
}
assertIs<ObjectState.DataView.Collection>(stateFlow.awaitItem())
assertIs<DataViewViewState.Collection.NoItems>(viewerFlow.awaitItem())
assertIs<DataViewViewState.Collection.Default>(viewerFlow.awaitItem())
}
}

View file

@ -1,9 +1,14 @@
package com.anytypeio.anytype.presentation.collections
import app.cash.turbine.test
import app.cash.turbine.testIn
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.ObjectTypeIds
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.presentation.relations.ObjectSetConfig
import com.anytypeio.anytype.presentation.search.ObjectSearchConstants
import com.anytypeio.anytype.presentation.sets.DataViewViewState
import com.anytypeio.anytype.presentation.sets.ObjectSetViewModel
import com.anytypeio.anytype.presentation.sets.main.ObjectSetViewModelTestSetup
@ -11,11 +16,16 @@ import com.anytypeio.anytype.presentation.sets.state.ObjectState
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import net.bytebuddy.utility.RandomString
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.eq
import org.mockito.kotlin.times
import org.mockito.kotlin.verifyBlocking
import org.mockito.kotlin.verifyNoInteractions
@OptIn(ExperimentalCoroutinesApi::class)
@ -131,26 +141,19 @@ class ObjectStateSetViewTest : ObjectSetViewModelTestSetup() {
// TESTING
viewModel.onStart(ctx = root)
// ASSERT SET OBJECT STATE
stateReducer.state.test {
val first = awaitItem()
assertIs<ObjectState.Init>(first)
val viewerFlow = viewModel.currentViewer.testIn(backgroundScope)
val stateFlow = stateReducer.state.testIn(backgroundScope)
val second = awaitItem()
assertIs<ObjectState.DataView.Set>(second)
}
// ASSERT STATES
assertIs<ObjectState.Init>(stateFlow.awaitItem())
assertIs<DataViewViewState.Init>(viewerFlow.awaitItem())
assertIs<ObjectState.DataView.Set>(stateFlow.awaitItem())
assertIs<DataViewViewState.Set.NoQuery>(viewerFlow.awaitItem())
// ASSERT SUBSCRIPTION TO SET RECORDS
advanceUntilIdle()
verifyNoInteractions(repo)
// ASSERT DATA VIEW STATE
viewModel.currentViewer.test {
val first = awaitItem()
assertIs<DataViewViewState.Init>(first)
val second = awaitItem()
assertIs<DataViewViewState.Set.NoQuery>(second)
}
}
@Test
@ -167,28 +170,15 @@ class ObjectStateSetViewTest : ObjectSetViewModelTestSetup() {
// TESTING
viewModel.onStart(ctx = root)
// ASSERT SET OBJECT STATE
stateReducer.state.test {
val first = awaitItem()
assertIs<ObjectState.Init>(first)
val viewerFlow = viewModel.currentViewer.testIn(backgroundScope)
val stateFlow = stateReducer.state.testIn(backgroundScope)
val second = awaitItem()
assertIs<ObjectState.DataView.Set>(second)
}
// ASSERT STATES
assertIs<ObjectState.Init>(stateFlow.awaitItem())
assertIs<DataViewViewState.Init>(viewerFlow.awaitItem())
// ASSERT SUBSCRIPTION TO SET RECORDS
verifyNoInteractions(repo)
// ASSERT DATA VIEW STATE
viewModel.currentViewer.test {
val first = awaitItem()
assertIs<DataViewViewState.Init>(first)
val second = awaitItem()
assertIs<DataViewViewState.Set.NoView>(second)
expectNoEvents()
}
assertIs<ObjectState.DataView.Set>(stateFlow.awaitItem())
assertIs<DataViewViewState.Set.NoView>(viewerFlow.awaitItem())
}
@Test
@ -214,26 +204,16 @@ class ObjectStateSetViewTest : ObjectSetViewModelTestSetup() {
// TESTING
viewModel.onStart(ctx = root)
// ASSERT SET OBJECT STATE
stateReducer.state.test {
val first = awaitItem()
assertIs<ObjectState.Init>(first)
val viewerFlow = viewModel.currentViewer.testIn(backgroundScope)
val stateFlow = stateReducer.state.testIn(backgroundScope)
val second = awaitItem()
assertIs<ObjectState.DataView.Set>(second)
}
// ASSERT STATES
assertIs<ObjectState.Init>(stateFlow.awaitItem())
assertIs<DataViewViewState.Init>(viewerFlow.awaitItem())
// ASSERT DATA VIEW STATE
viewModel.currentViewer.test {
val first = awaitItem()
assertIs<DataViewViewState.Init>(first)
val second = awaitItem()
assertIs<DataViewViewState.Set.NoItems>(second)
val third = awaitItem()
assertIs<DataViewViewState.Set.Default>(third)
}
assertIs<ObjectState.DataView.Set>(stateFlow.awaitItem())
assertIs<DataViewViewState.Set.NoItems>(viewerFlow.awaitItem())
assertIs<DataViewViewState.Set.Default>(viewerFlow.awaitItem())
}
@Test
@ -257,22 +237,177 @@ class ObjectStateSetViewTest : ObjectSetViewModelTestSetup() {
// TESTING
viewModel.onStart(ctx = root)
// ASSERT SET OBJECT STATE
stateReducer.state.test {
val first = awaitItem()
assertIs<ObjectState.Init>(first)
// ASSERT STATES
val viewerFlow = viewModel.currentViewer.testIn(backgroundScope)
val stateFlow = stateReducer.state.testIn(backgroundScope)
val second = awaitItem()
assertIs<ObjectState.DataView.Set>(second)
}
// ASSERT STATES
assertIs<ObjectState.Init>(stateFlow.awaitItem())
assertIs<DataViewViewState.Init>(viewerFlow.awaitItem())
// ASSERT DATA VIEW STATE
viewModel.currentViewer.test {
val first = awaitItem()
assertIs<DataViewViewState.Init>(first)
assertIs<ObjectState.DataView.Set>(stateFlow.awaitItem())
assertIs<DataViewViewState.Set.NoItems>(viewerFlow.awaitItem())
}
val second = awaitItem()
assertIs<DataViewViewState.Set.NoItems>(second)
@Test
fun `Displaying Object Sets with Non-Deleted Types Only`() = runTest {
// SETUP
stubWorkspaceManager(mockObjectSet.workspaceId)
stubInterceptEvents()
stubInterceptThreadStatus()
val typeDeleted1 = ObjectWrapper.Type(
mapOf(
Relations.ID to RandomString.make(),
Relations.IS_DELETED to true
)
)
val type2 = ObjectWrapper.Type(
mapOf(
Relations.ID to RandomString.make(),
Relations.TYPE to ObjectTypeIds.OBJECT_TYPE
)
)
val typeDeleted3 = ObjectWrapper.Type(
mapOf(
Relations.ID to RandomString.make(),
)
)
val detailsDeletedSetOf = Block.Details(
details = mapOf(
root to Block.Fields(
mapOf(
Relations.ID to root,
Relations.LAYOUT to ObjectType.Layout.SET.code.toDouble(),
Relations.SET_OF to listOf(typeDeleted1.id, type2.id, typeDeleted3.id)
)
),
typeDeleted1.id to Block.Fields(
mapOf(
Relations.ID to typeDeleted1.id,
Relations.IS_DELETED to true
)
),
type2.id to Block.Fields(
mapOf(
Relations.ID to type2.id,
Relations.TYPE to ObjectTypeIds.OBJECT_TYPE
)
)
)
)
stubOpenObject(
doc = listOf(mockObjectSet.header, mockObjectSet.title, mockObjectSet.dataView),
details = detailsDeletedSetOf
)
stubSubscriptionResults(
subscription = mockObjectSet.subscriptionId,
workspace = mockObjectSet.workspaceId,
storeOfRelations = storeOfRelations,
keys = mockObjectSet.dvKeys,
sources = listOf(type2.id),
objects = listOf(mockObjectSet.obj1, mockObjectSet.obj2),
dvFilters = mockObjectSet.filters
)
// TESTING
viewModel.onStart(ctx = root)
// ASSERT STATES
val viewerFlow = viewModel.currentViewer.testIn(backgroundScope)
val stateFlow = stateReducer.state.testIn(backgroundScope)
assertIs<ObjectState.Init>(stateFlow.awaitItem())
assertIs<DataViewViewState.Init>(viewerFlow.awaitItem())
assertIs<ObjectState.DataView.Set>(stateFlow.awaitItem())
assertIs<DataViewViewState.Set.NoItems>(viewerFlow.awaitItem())
assertIs<DataViewViewState.Set.Default>(viewerFlow.awaitItem())
stateFlow.ensureAllEventsConsumed()
viewerFlow.ensureAllEventsConsumed()
advanceUntilIdle()
verifyBlocking(repo, times(1)) {
searchObjectsWithSubscription(
eq(mockObjectSet.subscriptionId),
eq(listOf()),
eq(
mockObjectSet.filters + ObjectSearchConstants.defaultDataViewFilters(
mockObjectSet.workspaceId
)
),
eq(ObjectSearchConstants.defaultDataViewKeys + mockObjectSet.dvKeys),
eq(listOf(type2.id)),
eq(0L),
eq(ObjectSetConfig.DEFAULT_LIMIT),
eq(null),
eq(null),
eq(null),
eq(null),
eq(null)
)
}
}
@Test
fun `Displaying Object Set with No Query State When All Types are Deleted`() = runTest {
// SETUP
stubWorkspaceManager(mockObjectSet.workspaceId)
stubInterceptEvents()
stubInterceptThreadStatus()
val typeDeleted1 = ObjectWrapper.Type(
mapOf(
Relations.ID to RandomString.make(),
Relations.IS_DELETED to true
)
)
val typeDeleted3 = ObjectWrapper.Type(
mapOf(
Relations.ID to RandomString.make(),
)
)
val detailsDeletedSetOf = Block.Details(
details = mapOf(
root to Block.Fields(
mapOf(
Relations.ID to root,
Relations.LAYOUT to ObjectType.Layout.SET.code.toDouble(),
Relations.SET_OF to listOf(typeDeleted1.id, typeDeleted3.id)
)
),
typeDeleted1.id to Block.Fields(
mapOf(
Relations.ID to typeDeleted1.id,
Relations.IS_DELETED to true
)
)
)
)
stubOpenObject(
doc = listOf(mockObjectSet.header, mockObjectSet.title, mockObjectSet.dataView),
details = detailsDeletedSetOf
)
// TESTING
viewModel.onStart(ctx = root)
// ASSERT STATES
val viewerFlow = viewModel.currentViewer.testIn(backgroundScope)
val stateFlow = stateReducer.state.testIn(backgroundScope)
assertIs<ObjectState.Init>(stateFlow.awaitItem())
assertIs<DataViewViewState.Init>(viewerFlow.awaitItem())
assertIs<ObjectState.DataView.Set>(stateFlow.awaitItem())
assertIs<DataViewViewState.Set.NoQuery>(viewerFlow.awaitItem())
stateFlow.ensureAllEventsConsumed()
viewerFlow.ensureAllEventsConsumed()
}
}