diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/home/HomescreenDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/home/HomescreenDI.kt index 600606486c..5a6320c59e 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/home/HomescreenDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/home/HomescreenDI.kt @@ -2,6 +2,7 @@ package com.anytypeio.anytype.di.feature.home import androidx.lifecycle.ViewModelProvider import com.anytypeio.anytype.analytics.base.Analytics +import com.anytypeio.anytype.core_models.Payload import com.anytypeio.anytype.core_utils.di.scope.PerScreen import com.anytypeio.anytype.di.common.ComponentDependencies import com.anytypeio.anytype.di.feature.widgets.SelectWidgetSourceSubcomponent @@ -9,6 +10,8 @@ import com.anytypeio.anytype.domain.auth.repo.AuthRepository import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers import com.anytypeio.anytype.domain.block.repo.BlockRepository import com.anytypeio.anytype.domain.config.ConfigStorage +import com.anytypeio.anytype.domain.event.interactor.EventChannel +import com.anytypeio.anytype.domain.event.interactor.InterceptEvents import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.`object`.OpenObject import com.anytypeio.anytype.domain.objects.ObjectStore @@ -102,6 +105,19 @@ object HomeScreenModule { @PerScreen fun widgetEventDispatcher() : Dispatcher = Dispatcher.Default() + @JvmStatic + @Provides + @PerScreen + fun objectPayloadDispatcher() : Dispatcher = Dispatcher.Default() + + @JvmStatic + @Provides + @PerScreen + fun interceptEvents(channel: EventChannel) : InterceptEvents = InterceptEvents( + context = Dispatchers.IO, + channel = channel + ) + @Module interface Declarations { @PerScreen @@ -119,4 +135,5 @@ interface HomeScreenDependencies : ComponentDependencies { fun subscriptionEventChannel(): SubscriptionEventChannel fun workspaceManager(): WorkspaceManager fun analytics(): Analytics + fun eventChannel() : EventChannel } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DashboardFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DashboardFragment.kt index e31b2f18f7..8d47e8f6f7 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DashboardFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DashboardFragment.kt @@ -15,6 +15,7 @@ import androidx.transition.ChangeBounds import androidx.transition.TransitionManager import androidx.transition.TransitionSet import androidx.viewpager2.widget.ViewPager2 +import com.anytypeio.anytype.BuildConfig import com.anytypeio.anytype.R import com.anytypeio.anytype.core_models.ObjectWrapper import com.anytypeio.anytype.core_ui.reactive.click @@ -35,9 +36,9 @@ import com.anytypeio.anytype.ui.base.NavigationFragment import com.anytypeio.anytype.ui.editor.EditorFragment import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator +import javax.inject.Inject import kotlinx.coroutines.launch import timber.log.Timber -import javax.inject.Inject class DashboardFragment : NavigationFragment(R.layout.fragment_dashboard) { @@ -317,6 +318,16 @@ class DashboardFragment : } } } + + if (BuildConfig.DEBUG) { + // For debugging and testing widgets + binding.widgets.visible() + binding.widgets.setOnClickListener { + findNavController().navigate( + R.id.homeScreen + ) + } + } } private fun animateSelectionHiding() { diff --git a/app/src/main/res/layout/fragment_dashboard.xml b/app/src/main/res/layout/fragment_dashboard.xml index c33576906c..6b1fc719b6 100644 --- a/app/src/main/res/layout/fragment_dashboard.xml +++ b/app/src/main/res/layout/fragment_dashboard.xml @@ -6,6 +6,21 @@ android:layout_height="match_parent" app:layoutDescription="@xml/fragment_dashboard_scene"> + + diff --git a/app/src/main/res/xml/fragment_dashboard_scene.xml b/app/src/main/res/xml/fragment_dashboard_scene.xml index ff507b3eef..895090bd98 100644 --- a/app/src/main/res/xml/fragment_dashboard_scene.xml +++ b/app/src/main/res/xml/fragment_dashboard_scene.xml @@ -69,6 +69,16 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:visibilityMode="ignore" /> + @@ -138,6 +148,16 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:visibilityMode="ignore" /> + , + @Deprecated("To be removed") + val relations: Set = emptySet(), ) : Content() { sealed interface Relation { object COVER : Relation diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt index 5d3d0cf0e9..6c3ec9e0cc 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt @@ -3,11 +3,16 @@ package com.anytypeio.anytype.presentation.home import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope +import com.anytypeio.anytype.core_models.Event import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.ObjectView +import com.anytypeio.anytype.core_models.Payload +import com.anytypeio.anytype.core_utils.ext.replace import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers import com.anytypeio.anytype.domain.base.Resultat import com.anytypeio.anytype.domain.config.ConfigStorage +import com.anytypeio.anytype.domain.event.interactor.InterceptEvents +import com.anytypeio.anytype.domain.misc.Reducer import com.anytypeio.anytype.domain.`object`.OpenObject import com.anytypeio.anytype.domain.search.ObjectSearchSubscriptionContainer import com.anytypeio.anytype.domain.widgets.CreateWidget @@ -21,12 +26,14 @@ import com.anytypeio.anytype.presentation.widgets.WidgetDispatchEvent import com.anytypeio.anytype.presentation.widgets.WidgetView import com.anytypeio.anytype.presentation.widgets.parseWidgets import javax.inject.Inject -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.scan import kotlinx.coroutines.launch import timber.log.Timber @@ -36,24 +43,61 @@ class HomeScreenViewModel( private val createWidget: CreateWidget, private val objectSearchSubscriptionContainer: ObjectSearchSubscriptionContainer, private val appCoroutineDispatchers: AppCoroutineDispatchers, - private val dispatcher: Dispatcher -) : BaseViewModel() { + private val widgetEventDispatcher: Dispatcher, + private val objectPayloadDispatcher: Dispatcher, + private val interceptEvents: InterceptEvents +) : BaseViewModel(), Reducer { - val obj = MutableSharedFlow() val views = MutableStateFlow>(emptyList()) + private val objectViewState = MutableStateFlow(ObjectViewState.Idle) private val widgets = MutableStateFlow>(emptyList()) private val containers = MutableStateFlow>(emptyList()) private val expanded = TreeWidgetBranchStateHolder() init { + val config = configStorage.get() + + val externalChannelEvents = interceptEvents.build(InterceptEvents.Params(config.widgets)).map { + Payload( + context = config.widgets, + events = it + ) + } + + val internalChannelEvents = objectPayloadDispatcher.flow() + + val payloads = merge(externalChannelEvents, internalChannelEvents) + viewModelScope.launch { - obj.map { o -> - o.blocks.parseWidgets( - root = o.root, - details = o.details - ) + objectViewState.flatMapLatest { state -> + when (state) { + is ObjectViewState.Idle -> flowOf(state) + is ObjectViewState.Failure -> flowOf(state) + is ObjectViewState.Loading -> flowOf(state) + is ObjectViewState.Success -> { + payloads.scan(state) { s, p -> s.copy(obj = reduce(s.obj, p)) } + } + } + }.map { state -> + when (state) { + is ObjectViewState.Failure -> { + emptyList() + } + is ObjectViewState.Idle -> { + emptyList() + } + is ObjectViewState.Loading -> { + emptyList() + } + is ObjectViewState.Success -> { + state.obj.blocks.parseWidgets( + root = state.obj.root, + details = state.obj.details + ) + } + } }.collect { widgets.value = it } @@ -87,32 +131,29 @@ class HomeScreenViewModel( } }.flowOn(appCoroutineDispatchers.io).collect { Timber.d("Views update: $it") - views.value = it + listOf( - WidgetView.Action.EditWidgets, - WidgetView.Action.CreateWidget, - WidgetView.Action.Refresh - ) + views.value = it + actions } } - proceedWithOpeningObject() + proceedWithOpeningWidgetObject(widgetObject = config.widgets) proceedWithDispatches() } - private fun proceedWithOpeningObject() { + private fun proceedWithOpeningWidgetObject(widgetObject: Id) { viewModelScope.launch { - val config = configStorage.get() - openObject(config.widgets).flowOn(appCoroutineDispatchers.io).collect { result -> + openObject(widgetObject).flowOn(appCoroutineDispatchers.io).collect { result -> when (result) { is Resultat.Failure -> { + objectViewState.value = ObjectViewState.Failure(result.exception) Timber.e(result.exception, "Error while opening object.") } is Resultat.Loading -> { - // Do nothing. + objectViewState.value = ObjectViewState.Loading } is Resultat.Success -> { - Timber.d("Object view on start:\n${result.value}") - obj.emit(result.value) + objectViewState.value = ObjectViewState.Success( + obj = result.value + ) } } } @@ -121,7 +162,7 @@ class HomeScreenViewModel( private fun proceedWithDispatches() { viewModelScope.launch { - dispatcher.flow().collect { dispatch -> + widgetEventDispatcher.flow().collect { dispatch -> when (dispatch) { is WidgetDispatchEvent.SourcePicked -> { proceedWithCreatingWidget(source = dispatch.source) @@ -139,15 +180,27 @@ class HomeScreenViewModel( ctx = config.widgets, source = source ) - ).collect { s -> - Timber.d("Status while creating widget: $s") + ).collect { status -> + Timber.d("Status while creating widget: $status") + when (status) { + is Resultat.Failure -> { + sendToast("Error while creating widget: ${status.exception}") + Timber.e(status.exception, "Error while creating widget") + } + is Resultat.Loading -> { + // Do nothing? + } + is Resultat.Success -> { + objectPayloadDispatcher.send(status.value) + } + } } } } @Deprecated("For debugging only") fun onRefresh() { - proceedWithOpeningObject() + proceedWithOpeningWidgetObject(widgetObject = configStorage.get().widgets) } fun onStart() { @@ -158,13 +211,43 @@ class HomeScreenViewModel( expanded.onExpand(linkPath = path) } + // TODO move to a separate reducer inject into this VM's constructor + override fun reduce(state: ObjectView, event: Payload): ObjectView { + var curr = state + event.events.forEach { e -> + when (e) { + is Event.Command.AddBlock -> { + curr = curr.copy( + blocks = curr.blocks + e.blocks + ) + } + is Event.Command.UpdateStructure -> { + curr = curr.copy( + blocks = curr.blocks.replace( + replacement = { target -> + target.copy(children = e.children) + }, + target = { block -> block.id == e.id } + ) + ) + } + else -> { + Timber.d("Skipping event: $e") + } + } + } + return curr + } + class Factory @Inject constructor( private val configStorage: ConfigStorage, private val openObject: OpenObject, private val createWidget: CreateWidget, private val objectSearchSubscriptionContainer: ObjectSearchSubscriptionContainer, private val appCoroutineDispatchers: AppCoroutineDispatchers, - private val dispatcher: Dispatcher + private val widgetEventDispatcher: Dispatcher, + private val objectPayloadDispatcher: Dispatcher, + private val interceptEvents: InterceptEvents ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T = HomeScreenViewModel( @@ -173,7 +256,27 @@ class HomeScreenViewModel( createWidget = createWidget, objectSearchSubscriptionContainer = objectSearchSubscriptionContainer, appCoroutineDispatchers = appCoroutineDispatchers, - dispatcher = dispatcher + widgetEventDispatcher = widgetEventDispatcher, + objectPayloadDispatcher = objectPayloadDispatcher, + interceptEvents = interceptEvents ) as T } + + companion object { + val actions = listOf( + WidgetView.Action.EditWidgets, + WidgetView.Action.CreateWidget, + WidgetView.Action.Refresh + ) + } +} + +/** + * State representing session while working with an object. + */ +sealed class ObjectViewState { + object Idle : ObjectViewState() + object Loading : ObjectViewState() + data class Success(val obj: ObjectView) : ObjectViewState() + data class Failure(val e: Throwable) : ObjectViewState() } \ No newline at end of file diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/home/HomeViewModelTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/home/HomeViewModelTest.kt index bcd941e770..824b887f7d 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/home/HomeViewModelTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/home/HomeViewModelTest.kt @@ -1,5 +1,256 @@ package com.anytypeio.anytype.presentation.home +import app.cash.turbine.test +import com.anytypeio.anytype.core_models.Block +import com.anytypeio.anytype.core_models.Event +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.Key +import com.anytypeio.anytype.core_models.ObjectView +import com.anytypeio.anytype.core_models.Payload +import com.anytypeio.anytype.core_models.SmartBlockType +import com.anytypeio.anytype.core_models.StubConfig +import com.anytypeio.anytype.core_models.StubLinkToObjectBlock +import com.anytypeio.anytype.core_models.StubObject +import com.anytypeio.anytype.core_models.StubObjectView +import com.anytypeio.anytype.core_models.StubSmartBlock +import com.anytypeio.anytype.core_models.StubWidgetBlock +import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers +import com.anytypeio.anytype.domain.base.Resultat +import com.anytypeio.anytype.domain.config.ConfigStorage +import com.anytypeio.anytype.domain.event.interactor.InterceptEvents +import com.anytypeio.anytype.domain.`object`.OpenObject +import com.anytypeio.anytype.domain.search.ObjectSearchSubscriptionContainer +import com.anytypeio.anytype.domain.widgets.CreateWidget +import com.anytypeio.anytype.presentation.util.DefaultCoroutineTestRule +import com.anytypeio.anytype.presentation.util.Dispatcher +import com.anytypeio.anytype.presentation.widgets.TreeWidgetContainer +import com.anytypeio.anytype.presentation.widgets.WidgetDispatchEvent +import com.anytypeio.anytype.presentation.widgets.WidgetView +import com.anytypeio.anytype.test_utils.MockDataFactory +import kotlin.test.assertEquals +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flowOf +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 +import org.mockito.kotlin.times +import org.mockito.kotlin.verify + class HomeViewModelTest { - // TODO + + @get:Rule + val coroutineTestRule = DefaultCoroutineTestRule() + + @Mock + lateinit var configStorage: ConfigStorage + + @Mock + lateinit var createWidget: CreateWidget + + @Mock + lateinit var interceptEvents: InterceptEvents + + @Mock + lateinit var openObject: OpenObject + + @Mock + lateinit var objectSearchSubscriptionContainer: ObjectSearchSubscriptionContainer + + private val objectPayloadDispatcher = Dispatcher.Default() + private val widgetEventDispatcher = Dispatcher.Default() + + lateinit var vm: HomeScreenViewModel + + private val config = StubConfig( + widgets = WIDGET_OBJECT_ID + ) + + private val appCoroutineDispatchers = AppCoroutineDispatchers( + io = coroutineTestRule.dispatcher, + main = coroutineTestRule.dispatcher, + computation = coroutineTestRule.dispatcher + ) + + @Before + fun setup() { + MockitoAnnotations.openMocks(this) + } + + @Test + fun `should emit empty list if there is no block`() = runTest { + + // SETUP + + val smartBlock = StubSmartBlock( + id = WIDGET_OBJECT_ID, + children = emptyList(), + type = SmartBlockType.WIDGET + ) + + val givenObjectView = StubObjectView( + root = WIDGET_OBJECT_ID, + type = SmartBlockType.WIDGET, + blocks = listOf(smartBlock) + ) + + val events : Flow> = emptyFlow() + + stubConfig() + stubInterceptEvents(events) + stubOpenObject(givenObjectView) + + val vm = buildViewModel() + + // TESTING + + vm.views.test { + val firstTimeState = awaitItem() + assertEquals( + actual = firstTimeState, + expected = emptyList() + ) + } + + delay(1) + + verify(openObject, times(1)).invoke(WIDGET_OBJECT_ID) + } + + @Test + fun `should emit tree-widget with empty elements when source has no links`() = runTest { + + // SETUP + + val sourceObject = StubObject( + id = "SOURCE OBJECT", + links = emptyList() + ) + + val sourceLink = StubLinkToObjectBlock( + id = "SOURCE LINK", + target = sourceObject.id + ) + + val widgetBlock = StubWidgetBlock( + id = "WIDGET BLOCK", + layout = Block.Content.Widget.Layout.TREE, + children = listOf(sourceLink.id) + ) + + val smartBlock = StubSmartBlock( + id = WIDGET_OBJECT_ID, + children = listOf(widgetBlock.id), + type = SmartBlockType.WIDGET + ) + + val givenObjectView = StubObjectView( + root = WIDGET_OBJECT_ID, + type = SmartBlockType.WIDGET, + blocks = listOf( + smartBlock, + widgetBlock, + sourceLink + ), + details = mapOf( + sourceObject.id to sourceObject.map + ) + ) + + stubConfig() + stubInterceptEvents(events = emptyFlow()) + stubOpenObject(givenObjectView) + stubObjectSearchContainer( + subscription = widgetBlock.id, + targets = emptyList() + ) + + val vm = buildViewModel() + + // TESTING + + vm.views.test { + val firstTimeState = awaitItem() + assertEquals( + actual = firstTimeState, + expected = emptyList() + ) + val secondTimeItem = awaitItem() + assertEquals( + expected = buildList { + add( + WidgetView.Tree( + id = widgetBlock.id, + obj = sourceObject, + elements = emptyList() + ) + ) + addAll(HomeScreenViewModel.actions) + }, + actual = secondTimeItem + ) + verify(openObject, times(1)).invoke(WIDGET_OBJECT_ID) + } + } + + private fun stubInterceptEvents(events: Flow>) { + interceptEvents.stub { + on { build(InterceptEvents.Params(WIDGET_OBJECT_ID)) } doReturn events + } + } + + private fun stubConfig() { + configStorage.stub { + on { get() } doReturn config + } + } + + private fun stubOpenObject(givenObjectView: ObjectView) { + openObject.stub { + on { + invoke(WIDGET_OBJECT_ID) + } doReturn flowOf( + Resultat.Success( + value = givenObjectView + ) + ) + } + } + + private fun stubObjectSearchContainer( + subscription: Id, + targets: List, + keys: List = TreeWidgetContainer.keys + ) { + objectSearchSubscriptionContainer.stub { + onBlocking { + get( + subscription = subscription, + keys = keys, + targets = targets + ) + } doReturn emptyList() + } + } + + private fun buildViewModel() = HomeScreenViewModel( + configStorage = configStorage, + interceptEvents = interceptEvents, + createWidget = createWidget, + objectPayloadDispatcher = objectPayloadDispatcher, + widgetEventDispatcher = widgetEventDispatcher, + openObject = openObject, + objectSearchSubscriptionContainer = objectSearchSubscriptionContainer, + appCoroutineDispatchers = appCoroutineDispatchers + ) + + companion object { + val WIDGET_OBJECT_ID: Id = MockDataFactory.randomUuid() + } } \ No newline at end of file diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/util/DefaultCoroutineTestRule.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/util/DefaultCoroutineTestRule.kt index 361bb47366..159b020240 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/util/DefaultCoroutineTestRule.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/util/DefaultCoroutineTestRule.kt @@ -11,7 +11,7 @@ import org.junit.runner.Description @ExperimentalCoroutinesApi class DefaultCoroutineTestRule() : TestWatcher() { - private val dispatcher = StandardTestDispatcher(name = "Default test dispatcher") + val dispatcher = StandardTestDispatcher(name = "Default test dispatcher") override fun starting(description: Description) { super.starting(description) diff --git a/test/core-models-stub/src/main/java/com/anytypeio/anytype/core_models/Block.kt b/test/core-models-stub/src/main/java/com/anytypeio/anytype/core_models/Block.kt index dfaf3e2be0..4e5c7854b7 100644 --- a/test/core-models-stub/src/main/java/com/anytypeio/anytype/core_models/Block.kt +++ b/test/core-models-stub/src/main/java/com/anytypeio/anytype/core_models/Block.kt @@ -234,12 +234,13 @@ fun StubBookmark( fun StubSmartBlock( id: Id = MockDataFactory.randomString(), - children: List = emptyList() + children: List = emptyList(), + type: SmartBlockType = SmartBlockType.PAGE ): Block = Block( id = id, children = children, fields = Block.Fields.empty(), - content = Block.Content.Smart() + content = Block.Content.Smart(type = type) ) fun StubTable( @@ -337,4 +338,41 @@ fun StubDataViewBlock( children = children, fields = fields, backgroundColor = backgroundColor +) + +fun StubLinkToObjectBlock( + id: Id = MockDataFactory.randomUuid(), + target: Id, + children: List = emptyList(), + fields: Block.Fields = Block.Fields.empty(), + backgroundColor: String? = null, +) : Block = Block( + id = id, + content = Block.Content.Link( + target = target, + description = Block.Content.Link.Description.values().random(), + cardStyle = Block.Content.Link.CardStyle.values().random(), + iconSize = Block.Content.Link.IconSize.values().random(), + relations = emptySet(), + type = Block.Content.Link.Type.PAGE + ), + children = children, + fields = fields, + backgroundColor = backgroundColor +) + +fun StubWidgetBlock( + id: Id = MockDataFactory.randomUuid(), + layout: Block.Content.Widget.Layout, + children: List = emptyList(), + fields: Block.Fields = Block.Fields.empty(), + backgroundColor: String? = null, +) : Block = Block( + id = id, + children = children, + fields = fields, + backgroundColor = backgroundColor, + content = Block.Content.Widget( + layout = layout + ) ) \ No newline at end of file diff --git a/test/core-models-stub/src/main/java/com/anytypeio/anytype/core_models/Object.kt b/test/core-models-stub/src/main/java/com/anytypeio/anytype/core_models/Object.kt index c4f358eb56..059103c494 100644 --- a/test/core-models-stub/src/main/java/com/anytypeio/anytype/core_models/Object.kt +++ b/test/core-models-stub/src/main/java/com/anytypeio/anytype/core_models/Object.kt @@ -1,5 +1,7 @@ package com.anytypeio.anytype.core_models +import com.anytypeio.anytype.core_models.restrictions.DataViewRestrictions +import com.anytypeio.anytype.core_models.restrictions.ObjectRestriction import com.anytypeio.anytype.test_utils.MockDataFactory fun StubObject( @@ -32,6 +34,24 @@ fun StubObject( ) ) +fun StubObjectView( + root: Id, + type: SmartBlockType, + blocks: List = emptyList(), + details: Map = emptyMap(), + relations: List = emptyList(), + objectRestrictions: List = emptyList(), + dataViewRestrictions: List = emptyList() +): ObjectView = ObjectView( + root = root, + blocks = blocks, + details = details, + type = type, + relations = relations, + objectRestrictions = objectRestrictions, + dataViewRestrictions = dataViewRestrictions +) + fun StubObjectType( id: String = MockDataFactory.randomUuid(), name: String = MockDataFactory.randomString(),