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

Epic with sets and relations + new app flavors (stable, experimental) (#1048)

This commit is contained in:
Evgenii Kozlov 2021-03-31 14:37:03 +03:00 committed by GitHub
parent 4f540aa356
commit 31d820c4dd
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
911 changed files with 51701 additions and 3827 deletions

View file

@ -63,6 +63,7 @@ object EventsDictionary {
const val ACCOUNT_STOP = "AccountStop"
const val PAGE_CREATE = "BlockCreatePage"
const val OBJECT_CREATE = "BlockCreateObject"
const val PAGE_MENTION_CREATE = "PageCreate"
const val BLOCK_CREATE = "BlockCreate"

View file

@ -49,10 +49,40 @@ android {
}
debug {
applicationIdSuffix ".debug"
debuggable true
}
}
flavorDimensions 'default'
productFlavors {
stable {
dimension 'default'
}
experimental{
dimension 'default'
}
}
sourceSets {
stable {
java {
srcDirs 'src/stable/java'
}
res {
srcDirs 'src/stable/res'
}
}
experimental {
java {
srcDirs 'src/experimental/java'
}
res {
srcDirs 'src/experimental/res'
}
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
@ -74,6 +104,7 @@ ext {
dependencies {
implementation project(':domain')
implementation project(':core-models')
implementation project(':data')
implementation project(':device')
implementation project(':persistence')
@ -135,6 +166,7 @@ dependencies {
implementation applicationDependencies.exoPlayer
implementation analyticsDependencies.amplitude
implementation analyticsDependencies.okhttp
implementation applicationDependencies.blurry
implementation applicationDependencies.shimmerLayout
@ -158,7 +190,10 @@ dependencies {
androidTestImplementation unitTestDependencies.kotlinTest
androidTestImplementation acceptanceTesting.testRules
androidTestImplementation acceptanceTesting.disableAnimation
androidTestImplementation unitTestDependencies.coroutineTesting
androidTestImplementation(unitTestDependencies.coroutineTesting) {
exclude group: "org.jetbrains.kotlinx", module: "kotlinx-coroutines-debug"
}
debugImplementation acceptanceTesting.fragmentTesting

View file

@ -1,3 +1,3 @@
version.versionMajor=0
version.versionMinor=1
version.versionPatch=7
version.versionPatch=6

View file

@ -11,6 +11,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.anytypeio.anytype.R
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.domain.auth.interactor.StartAccount
import com.anytypeio.anytype.domain.auth.repo.AuthRepository
import com.anytypeio.anytype.domain.device.PathProvider
@ -50,6 +51,9 @@ class SetupSelectedAccountTest {
@Mock
lateinit var authRepository: AuthRepository
@Mock
lateinit var analytics: Analytics
@Mock
lateinit var pathProvider: PathProvider
@ -62,7 +66,8 @@ class SetupSelectedAccountTest {
TestSetupSelectedAccountFragment.testViewModelFactory =
SetupSelectedAccountViewModelFactory(
startAccount = startAccount,
pathProvider = pathProvider
pathProvider = pathProvider,
analytics = analytics
)
}

View file

@ -14,16 +14,13 @@ import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.*
import com.anytypeio.anytype.core_ui.widgets.text.TextInputWidget
import com.anytypeio.anytype.data.auth.model.ClipEntity
import com.anytypeio.anytype.domain.block.model.Block
import com.anytypeio.anytype.domain.block.model.Command
import com.anytypeio.anytype.domain.clipboard.Paste
import com.anytypeio.anytype.domain.event.model.Event
import com.anytypeio.anytype.domain.event.model.Payload
import com.anytypeio.anytype.features.editor.base.EditorTestSetup
import com.anytypeio.anytype.features.editor.base.TestPageFragment
import com.anytypeio.anytype.mocking.MockDataFactory
import com.anytypeio.anytype.presentation.page.PageViewModel
import com.anytypeio.anytype.ui.page.PageFragment
import com.anytypeio.anytype.utils.CoroutinesTestRule
import com.anytypeio.anytype.utils.TestUtils.withRecyclerView
@ -190,7 +187,7 @@ class ClipboardTesting : EditorTestSetup() {
}
repo.stub {
onBlocking { paste(any()) } doReturn Paste.Response(
onBlocking { paste(any()) } doReturn Response.Clipboard.Paste(
cursor = 6,
payload = Payload(
context = root,
@ -202,6 +199,7 @@ class ClipboardTesting : EditorTestSetup() {
}
stubInterceptEvents()
stubInterceptThreadStatus( )
stubOpenDocument(document)
stubUpdateText()
@ -209,7 +207,7 @@ class ClipboardTesting : EditorTestSetup() {
// TESTING
val target = onView(withRecyclerView(R.id.recycler).atPositionOnView(1, view))
val target = onView(withRecyclerView(R.id.recycler).atPositionOnView(0, view))
// Click to open action mode
@ -229,7 +227,7 @@ class ClipboardTesting : EditorTestSetup() {
}
scenario.onFragment { fragment ->
val item = fragment.recycler.getChildAt(1)
val item = fragment.recycler.getChildAt(0)
item.findViewById<TextInputWidget>(view).apply {
assertEquals(expected = result.length, actual = selectionStart)
assertEquals(expected = result.length, actual = selectionEnd)
@ -255,6 +253,8 @@ class ClipboardTesting : EditorTestSetup() {
}
)
}
advance(PageViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
}
//endregion
@ -464,7 +464,7 @@ class ClipboardTesting : EditorTestSetup() {
}
repo.stub {
onBlocking { paste(any()) } doReturn Paste.Response(
onBlocking { paste(any()) } doReturn Response.Clipboard.Paste(
cursor = -1,
payload = Payload(
context = root,
@ -476,6 +476,7 @@ class ClipboardTesting : EditorTestSetup() {
}
stubInterceptEvents()
stubInterceptThreadStatus()
stubOpenDocument(document)
stubUpdateText()
@ -483,7 +484,7 @@ class ClipboardTesting : EditorTestSetup() {
// TESTING
val target = onView(withRecyclerView(R.id.recycler).atPositionOnView(1, targetBlockView))
val target = onView(withRecyclerView(R.id.recycler).atPositionOnView(0, targetBlockView))
// Click to open action mode
@ -517,21 +518,21 @@ class ClipboardTesting : EditorTestSetup() {
)
}
onView(withRecyclerView(R.id.recycler).atPositionOnView(1, targetBlockView)).apply {
onView(withRecyclerView(R.id.recycler).atPositionOnView(0, targetBlockView)).apply {
check(matches(withText(text)))
}
onView(withRecyclerView(R.id.recycler).atPositionOnView(2, firstPastedBlockView)).apply {
onView(withRecyclerView(R.id.recycler).atPositionOnView(1, firstPastedBlockView)).apply {
check(matches(withText(pasted.first)))
}
onView(withRecyclerView(R.id.recycler).atPositionOnView(3, secondPastedBlockView)).apply {
onView(withRecyclerView(R.id.recycler).atPositionOnView(2, secondPastedBlockView)).apply {
check(matches(withText(pasted.second)))
check(matches(hasFocus()))
}
scenario.onFragment { fragment ->
val item = fragment.recycler.getChildAt(3)
val item = fragment.recycler.getChildAt(2)
item.findViewById<TextInputWidget>(secondPastedBlockView).apply {
assertEquals(expected = pasted.second.length, actual = selectionStart)
assertEquals(expected = pasted.second.length, actual = selectionEnd)

View file

@ -11,13 +11,13 @@ import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.Event
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_models.Position
import com.anytypeio.anytype.core_ui.widgets.text.TextInputWidget
import com.anytypeio.anytype.domain.base.Either
import com.anytypeio.anytype.domain.block.interactor.CreateBlock
import com.anytypeio.anytype.domain.block.model.Block
import com.anytypeio.anytype.domain.block.model.Position
import com.anytypeio.anytype.domain.event.model.Event
import com.anytypeio.anytype.domain.event.model.Payload
import com.anytypeio.anytype.features.editor.base.EditorTestSetup
import com.anytypeio.anytype.features.editor.base.TestPageFragment
import com.anytypeio.anytype.mocking.MockDataFactory
@ -171,6 +171,7 @@ class CreateBlockTesting : EditorTestSetup() {
)
stubInterceptEvents()
stubInterceptThreadStatus()
stubOpenDocument(document)
stubUpdateText()
stubCreateBlocks(params, new, events)
@ -180,7 +181,7 @@ class CreateBlockTesting : EditorTestSetup() {
// TESTING
val target = Espresso.onView(
TestUtils.withRecyclerView(R.id.recycler).atPositionOnView(1, targetViewId)
TestUtils.withRecyclerView(R.id.recycler).atPositionOnView(0, targetViewId)
)
target.apply {
@ -196,13 +197,13 @@ class CreateBlockTesting : EditorTestSetup() {
verifyBlocking(createBlock, times(1)) { invoke(params) }
Espresso.onView(
TestUtils.withRecyclerView(R.id.recycler).atPositionOnView(1, targetViewId)
TestUtils.withRecyclerView(R.id.recycler).atPositionOnView(0, targetViewId)
).apply {
check(ViewAssertions.matches(ViewMatchers.withText("")))
}
Espresso.onView(
TestUtils.withRecyclerView(R.id.recycler).atPositionOnView(2, R.id.textContent)
TestUtils.withRecyclerView(R.id.recycler).atPositionOnView(1, R.id.textContent)
).apply {
check(ViewAssertions.matches(ViewMatchers.withText("")))
check(ViewAssertions.matches(ViewMatchers.hasFocus()))
@ -211,7 +212,7 @@ class CreateBlockTesting : EditorTestSetup() {
// Check cursor position at block B
scenario.onFragment { fragment ->
val item = fragment.recycler.getChildAt(2)
val item = fragment.recycler.getChildAt(1)
item.findViewById<TextInputWidget>(R.id.textContent).apply {
assertEquals(
expected = 0,

View file

@ -0,0 +1,349 @@
package com.anytypeio.anytype.features.editor
import android.os.Bundle
import androidx.core.os.bundleOf
import androidx.fragment.app.testing.FragmentScenario
import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.test.espresso.action.ViewActions.click
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.Command
import com.anytypeio.anytype.core_models.Event
import com.anytypeio.anytype.core_models.Position
import com.anytypeio.anytype.core_models.ext.content
import com.anytypeio.anytype.domain.block.interactor.CreateBlock
import com.anytypeio.anytype.emojifier.data.DefaultDocumentEmojiIconProvider
import com.anytypeio.anytype.features.editor.base.EditorTestSetup
import com.anytypeio.anytype.features.editor.base.TestPageFragment
import com.anytypeio.anytype.mocking.MockDataFactory
import com.anytypeio.anytype.presentation.page.editor.model.UiBlock
import com.anytypeio.anytype.ui.page.PageFragment
import com.anytypeio.anytype.utils.*
import com.bartoszlipinski.disableanimationsrule.DisableAnimationsRule
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@LargeTest
class CreateRelationBlockTesting : EditorTestSetup() {
@get:Rule
val animationsRule = DisableAnimationsRule()
@get:Rule
val coroutineTestRule = CoroutinesTestRule()
private val args = bundleOf(PageFragment.ID_KEY to root)
private val defaultDetails = Block.Details(
mapOf(
root to Block.Fields(
mapOf(
"iconEmoji" to DefaultDocumentEmojiIconProvider.DOCUMENT_SET.random()
)
)
)
)
private val title = Block(
id = MockDataFactory.randomUuid(),
content = Block.Content.Text(
style = Block.Content.Text.Style.TITLE,
text = "CreateRelationBlockTesting",
marks = emptyList()
),
children = emptyList(),
fields = Block.Fields.empty()
)
private val header = Block(
id = MockDataFactory.randomUuid(),
content = Block.Content.Layout(
type = Block.Content.Layout.Type.HEADER
),
fields = Block.Fields.empty(),
children = listOf(title.id)
)
@Before
override fun setup() {
super.setup()
}
@Test
fun shouldAddNewRelationBlockPlaceholderWithOnCreateAfterOnAddRelationCommand() {
val new = Block(
id = MockDataFactory.randomUuid(),
content = Block.Content.RelationBlock(
key = null
),
children = emptyList(),
fields = Block.Fields.empty()
)
val paragraph = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields.empty(),
children = emptyList(),
content = Block.Content.Text(
text = "Foo",
marks = emptyList(),
style = Block.Content.Text.Style.P
)
)
val page = Block(
id = root,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(
type = Block.Content.Smart.Type.PAGE
),
children = listOf(header.id, paragraph.id)
)
val document = listOf(page, header, title, paragraph)
stubInterceptEvents()
stubInterceptThreadStatus()
stubOpenDocument(document, defaultDetails)
stubCreateBlock(
params = CreateBlock.Params(
context = root,
target = paragraph.id,
position = Position.BOTTOM,
prototype = Block.Prototype.Relation("")
),
events = listOf(
Event.Command.UpdateStructure(
context = root,
id = root,
children = page.children + listOf(new.id)
),
Event.Command.AddBlock(
context = root,
blocks = listOf(new)
)
)
)
// TESTING
val fragment = launchFragment(args)
with(R.id.recycler.rVMatcher()) {
checkIsRecyclerSize(2)
onItemView(0, R.id.title).checkHasText(title.content<Block.Content.Text>().text)
onItemView(
1,
R.id.textContent
).checkHasText(paragraph.content<Block.Content.Text>().text)
}
with(R.id.recycler.rVMatcher()) {
onItemView(1, R.id.textContent).perform(click())
}
Thread.sleep(200)
fragment.onFragment { fr ->
fr.onAddBlockClicked(UiBlock.RELATION)
}
Thread.sleep(200)
with(R.id.recycler.rVMatcher()) {
checkIsRecyclerSize(3)
onItemView(0, R.id.title).checkHasText(title.content<Block.Content.Text>().text)
onItemView(
1,
R.id.textContent
).checkHasText(paragraph.content<Block.Content.Text>().text)
onItemView(2, R.id.tvPlaceholder).checkHasText(R.string.set_new_relation)
}
}
@Test
fun shouldAddNewRelationBlockPlaceholderWithOnCreateAfterOnAddRelationCommandAfterTitle() {
val new = Block(
id = MockDataFactory.randomUuid(),
content = Block.Content.RelationBlock(
key = null
),
children = emptyList(),
fields = Block.Fields.empty()
)
val page = Block(
id = root,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(
type = Block.Content.Smart.Type.PAGE
),
children = listOf(header.id)
)
val document = listOf(page, header, title)
stubInterceptEvents()
stubInterceptThreadStatus()
stubOpenDocument(document, defaultDetails)
stubCreateBlock(
params = CreateBlock.Params(
context = root,
target = title.id,
position = Position.BOTTOM,
prototype = Block.Prototype.Relation("")
),
events = listOf(
Event.Command.UpdateStructure(
context = root,
id = root,
children = page.children + listOf(new.id)
),
Event.Command.AddBlock(
context = root,
blocks = listOf(new)
)
)
)
// TESTING
val fragment = launchFragment(args)
with(R.id.recycler.rVMatcher()) {
checkIsRecyclerSize(1)
onItemView(0, R.id.title).checkHasText(title.content<Block.Content.Text>().text)
}
with(R.id.recycler.rVMatcher()) {
onItemView(0, R.id.title).perform(click())
}
Thread.sleep(200)
fragment.onFragment { fr ->
fr.onAddBlockClicked(UiBlock.RELATION)
}
Thread.sleep(100)
with(R.id.recycler.rVMatcher()) {
checkIsRecyclerSize(2)
onItemView(0, R.id.title).checkHasText(title.content<Block.Content.Text>().text)
onItemView(1, R.id.tvPlaceholder).checkHasText(R.string.set_new_relation)
}
}
@Test
fun shouldAddNewRelationBlockPlaceholderWithOnReplaceAfterOnAddRelationCommand() {
val new = Block(
id = MockDataFactory.randomUuid(),
content = Block.Content.RelationBlock(
key = null
),
children = emptyList(),
fields = Block.Fields.empty()
)
val paragraph = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields.empty(),
children = emptyList(),
content = Block.Content.Text(
text = "",
marks = emptyList(),
style = Block.Content.Text.Style.P
)
)
val page = Block(
id = root,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(
type = Block.Content.Smart.Type.PAGE
),
children = listOf(header.id, paragraph.id)
)
val document = listOf(page, header, title, paragraph)
stubInterceptEvents()
stubInterceptThreadStatus()
stubOpenDocument(document, defaultDetails)
stubReplaceBlock(
command = Command.Replace(
context = root,
target = paragraph.id,
prototype = Block.Prototype.Relation("")
),
events = listOf(
Event.Command.UpdateStructure(
context = root,
id = root,
children = listOf(page.id, header.id, new.id)
),
Event.Command.AddBlock(
context = root,
blocks = listOf(new)
),
Event.Command.DeleteBlock(
context = root,
targets = listOf(paragraph.id)
)
)
)
// TESTING
val fragment = launchFragment(args)
with(R.id.recycler.rVMatcher()) {
checkIsRecyclerSize(2)
onItemView(0, R.id.title).checkHasText(title.content<Block.Content.Text>().text)
onItemView(1, R.id.textContent).checkHasText("")
}
with(R.id.recycler.rVMatcher()) {
onItemView(1, R.id.textContent).perform(click())
}
Thread.sleep(200)
fragment.onFragment { fr ->
fr.onAddBlockClicked(UiBlock.RELATION)
}
Thread.sleep(100)
with(R.id.recycler.rVMatcher()) {
checkIsRecyclerSize(2)
onItemView(0, R.id.title).checkHasText(title.content<Block.Content.Text>().text)
onItemView(1, R.id.tvPlaceholder).checkHasText(R.string.set_new_relation)
}
}
// STUBBING & SETUP
private fun launchFragment(args: Bundle): FragmentScenario<TestPageFragment> {
return launchFragmentInContainer<TestPageFragment>(
fragmentArgs = args,
themeResId = R.style.AppTheme
)
}
/**
* Moves coroutines clock time.
*/
private fun advance(millis: Long) {
coroutineTestRule.advanceTime(millis)
}
}

View file

@ -12,12 +12,12 @@ import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.Event
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_ui.widgets.text.TextInputWidget
import com.anytypeio.anytype.domain.base.Either
import com.anytypeio.anytype.domain.block.interactor.UnlinkBlocks
import com.anytypeio.anytype.domain.block.model.Block
import com.anytypeio.anytype.domain.event.model.Event
import com.anytypeio.anytype.domain.event.model.Payload
import com.anytypeio.anytype.features.editor.base.EditorTestSetup
import com.anytypeio.anytype.features.editor.base.TestPageFragment
import com.anytypeio.anytype.mocking.MockDataFactory
@ -322,6 +322,7 @@ class DeleteBlockTesting : EditorTestSetup() {
stubInterceptEvents()
stubOpenDocument(document)
stubInterceptThreadStatus()
stubUpdateText()
stubUnlinkBlocks(params, events)
@ -331,7 +332,7 @@ class DeleteBlockTesting : EditorTestSetup() {
// TESTING
val target = Espresso.onView(
withRecyclerView(R.id.recycler).atPositionOnView(2, targetViewId)
withRecyclerView(R.id.recycler).atPositionOnView(1, targetViewId)
)
target.apply {
@ -347,7 +348,7 @@ class DeleteBlockTesting : EditorTestSetup() {
verifyBlocking(unlinkBlocks, times(1)) { invoke(params) }
Espresso.onView(
withRecyclerView(R.id.recycler).atPositionOnView(1, firstViewId)
withRecyclerView(R.id.recycler).atPositionOnView(0, firstViewId)
).apply {
check(ViewAssertions.matches(ViewMatchers.withText("Foo")))
check(ViewAssertions.matches(ViewMatchers.hasFocus()))
@ -356,7 +357,7 @@ class DeleteBlockTesting : EditorTestSetup() {
// Check cursor position
scenario.onFragment { fragment ->
val item = fragment.recycler.getChildAt(1)
val item = fragment.recycler.getChildAt(0)
val view = item.findViewById<TextInputWidget>(firstViewId)
assertEquals(
expected = 3,
@ -567,8 +568,6 @@ class DeleteBlockTesting : EditorTestSetup() {
Thread.sleep(100)
val target = Espresso.onView(
withRecyclerView(R.id.recycler).atPositionOnView(1, firstViewId)
)

View file

@ -11,12 +11,15 @@ import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_ui.features.page.BlockViewHolder
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.BlockSplitMode
import com.anytypeio.anytype.core_models.Command
import com.anytypeio.anytype.core_models.Event
import com.anytypeio.anytype.core_ui.features.editor.holders.text.Checkbox
import com.anytypeio.anytype.core_ui.features.editor.holders.text.Numbered
import com.anytypeio.anytype.core_ui.features.editor.holders.text.Toggle
import com.anytypeio.anytype.core_ui.widgets.text.TextInputWidget
import com.anytypeio.anytype.domain.base.Either
import com.anytypeio.anytype.domain.block.model.Block
import com.anytypeio.anytype.domain.block.model.Command
import com.anytypeio.anytype.domain.event.model.Event
import com.anytypeio.anytype.features.editor.base.EditorTestSetup
import com.anytypeio.anytype.features.editor.base.TestPageFragment
import com.anytypeio.anytype.mocking.MockDataFactory
@ -122,7 +125,7 @@ class EditorIntegrationTesting : EditorTestSetup() {
onView(withRecyclerView(R.id.recycler).atPositionOnView(6, R.id.bulletedListContent))
.check(matches(withText(BLOCK_BULLET.content.asText().text)))
R.id.recycler.scrollTo<BlockViewHolder.Numbered>(7)
R.id.recycler.scrollTo<Numbered>(7)
onView(withRecyclerView(R.id.recycler).atPositionOnView(7, R.id.numberedListContent))
.check(matches(withText(BLOCK_NUMBERED_1.content.asText().text)))
@ -130,12 +133,12 @@ class EditorIntegrationTesting : EditorTestSetup() {
onView(withRecyclerView(R.id.recycler).atPositionOnView(7, R.id.number))
.check(matches(withText("1.")))
R.id.recycler.scrollTo<BlockViewHolder.Toggle>(8)
R.id.recycler.scrollTo<Toggle>(8)
onView(withRecyclerView(R.id.recycler).atPositionOnView(8, R.id.toggleContent))
.check(matches(withText(BLOCK_TOGGLE.content.asText().text)))
R.id.recycler.scrollTo<BlockViewHolder.Checkbox>(9)
R.id.recycler.scrollTo<Checkbox>(9)
onView(withRecyclerView(R.id.recycler).atPositionOnView(9, R.id.checkboxContent))
.check(matches(withText(BLOCK_CHECKBOX.content.asText().text)))
@ -300,8 +303,9 @@ class EditorIntegrationTesting : EditorTestSetup() {
val command = Command.Split(
context = root,
target = paragraph.id,
index = 3,
style = Block.Content.Text.Style.P
style = Block.Content.Text.Style.P,
mode = BlockSplitMode.BOTTOM,
range = 3..3
)
stubSplitBlocks(

View file

@ -9,15 +9,15 @@ import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.matcher.ViewMatchers
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.Event
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_models.Position
import com.anytypeio.anytype.core_models.ext.content
import com.anytypeio.anytype.core_ui.widgets.text.TextInputWidget
import com.anytypeio.anytype.domain.base.Either
import com.anytypeio.anytype.domain.block.interactor.CreateBlock
import com.anytypeio.anytype.domain.block.interactor.UpdateTextStyle
import com.anytypeio.anytype.domain.block.model.Block
import com.anytypeio.anytype.domain.block.model.Position
import com.anytypeio.anytype.domain.event.model.Event
import com.anytypeio.anytype.domain.event.model.Payload
import com.anytypeio.anytype.domain.ext.content
import com.anytypeio.anytype.features.editor.base.EditorTestSetup
import com.anytypeio.anytype.features.editor.base.TestPageFragment
import com.anytypeio.anytype.mocking.MockDataFactory
@ -146,6 +146,7 @@ class ListBlockTesting : EditorTestSetup() {
)
stubInterceptEvents()
stubInterceptThreadStatus()
stubOpenDocument(document)
stubUpdateText()
stubCreateBlocks(params, new, events)
@ -155,7 +156,7 @@ class ListBlockTesting : EditorTestSetup() {
// TESTING
val target = Espresso.onView(
TestUtils.withRecyclerView(R.id.recycler).atPositionOnView(1, view)
TestUtils.withRecyclerView(R.id.recycler).atPositionOnView(0, view)
)
target.apply {
@ -177,13 +178,13 @@ class ListBlockTesting : EditorTestSetup() {
verifyBlocking(createBlock, times(1)) { invoke(params) }
Espresso.onView(
TestUtils.withRecyclerView(R.id.recycler).atPositionOnView(1, view)
TestUtils.withRecyclerView(R.id.recycler).atPositionOnView(0, view)
).apply {
check(ViewAssertions.matches(ViewMatchers.withText(a.content<Block.Content.Text>().text)))
}
Espresso.onView(
TestUtils.withRecyclerView(R.id.recycler).atPositionOnView(2, view)
TestUtils.withRecyclerView(R.id.recycler).atPositionOnView(1, view)
).apply {
check(ViewAssertions.matches(ViewMatchers.withText("")))
check(ViewAssertions.matches(ViewMatchers.hasFocus()))
@ -192,7 +193,7 @@ class ListBlockTesting : EditorTestSetup() {
// Check cursor position at block B
scenario.onFragment { fragment ->
val item = fragment.recycler.getChildAt(2)
val item = fragment.recycler.getChildAt(1)
item.findViewById<TextInputWidget>(view).apply {
assertEquals(
expected = 0,

View file

@ -12,12 +12,12 @@ import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.Event
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_ui.widgets.text.TextInputWidget
import com.anytypeio.anytype.domain.base.Either
import com.anytypeio.anytype.domain.block.interactor.MergeBlocks
import com.anytypeio.anytype.domain.block.model.Block
import com.anytypeio.anytype.domain.event.model.Event
import com.anytypeio.anytype.domain.event.model.Payload
import com.anytypeio.anytype.features.editor.base.EditorTestSetup
import com.anytypeio.anytype.features.editor.base.TestPageFragment
import com.anytypeio.anytype.mocking.MockDataFactory
@ -241,6 +241,7 @@ class MergeBlockTesting : EditorTestSetup() {
)
stubInterceptEvents()
stubInterceptThreadStatus()
stubOpenDocument(document)
stubUpdateText()
stubMergelocks(
@ -253,7 +254,7 @@ class MergeBlockTesting : EditorTestSetup() {
// TESTING
val target = Espresso.onView(
withRecyclerView(R.id.recycler).atPositionOnView(2, targetViewId)
withRecyclerView(R.id.recycler).atPositionOnView(1, targetViewId)
)
target.apply {
@ -263,7 +264,7 @@ class MergeBlockTesting : EditorTestSetup() {
// Set cursor at the beginning of B
scenario.onFragment { fragment ->
val item = fragment.recycler.getChildAt(2)
val item = fragment.recycler.getChildAt(1)
val view = item.findViewById<TextInputWidget>(targetViewId)
view.setSelection(0)
}
@ -282,7 +283,7 @@ class MergeBlockTesting : EditorTestSetup() {
verifyBlocking(mergeBlocks, times(1)) { invoke(params) }
Espresso.onView(
withRecyclerView(R.id.recycler).atPositionOnView(1, targetViewId)
withRecyclerView(R.id.recycler).atPositionOnView(0, targetViewId)
).apply {
check(ViewAssertions.matches(ViewMatchers.withText("FooBar")))
check(ViewAssertions.matches(ViewMatchers.hasFocus()))
@ -291,7 +292,7 @@ class MergeBlockTesting : EditorTestSetup() {
// Check cursor position
scenario.onFragment { fragment ->
val item = fragment.recycler.getChildAt(1)
val item = fragment.recycler.getChildAt(0)
val view = item.findViewById<TextInputWidget>(targetViewId)
assertEquals(
expected = 3,

View file

@ -10,7 +10,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.anytypeio.anytype.R
import com.anytypeio.anytype.domain.block.model.Block
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.features.editor.base.EditorTestSetup
import com.anytypeio.anytype.features.editor.base.TestPageFragment
import com.anytypeio.anytype.mocking.MockDataFactory

View file

@ -11,10 +11,11 @@ import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.BlockSplitMode
import com.anytypeio.anytype.core_models.Command
import com.anytypeio.anytype.core_models.Event
import com.anytypeio.anytype.core_ui.widgets.text.TextInputWidget
import com.anytypeio.anytype.domain.block.model.Block
import com.anytypeio.anytype.domain.block.model.Command
import com.anytypeio.anytype.domain.event.model.Event
import com.anytypeio.anytype.features.editor.base.EditorTestSetup
import com.anytypeio.anytype.features.editor.base.TestPageFragment
import com.anytypeio.anytype.mocking.MockDataFactory
@ -26,7 +27,6 @@ import com.bartoszlipinski.disableanimationsrule.DisableAnimationsRule
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.times
import com.nhaarman.mockitokotlin2.verifyBlocking
import com.nhaarman.mockitokotlin2.verifyZeroInteractions
import kotlinx.android.synthetic.main.fragment_page.*
import org.junit.Before
import org.junit.Rule
@ -49,86 +49,6 @@ class SplitBlockTesting : EditorTestSetup() {
super.setup()
}
@Test
fun shouldNotSplitTitle() {
// SETUP
val args = bundleOf(PageFragment.ID_KEY to root)
val title = "Indivisible title"
val page = Block(
id = root,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(
type = Block.Content.Smart.Type.PAGE
),
children = emptyList()
)
val document = listOf(page)
stubInterceptEvents()
stubOpenDocument(
document = document,
details = Block.Details(
mapOf(
root to Block.Fields(
mapOf("name" to title)
)
)
)
)
val scenario = launchFragment(args)
// TESTING
val target = onView(
withRecyclerView(R.id.recycler).atPositionOnView(0, R.id.title)
)
// Set cursor programmatically
scenario.onFragment { fragment ->
fragment.recycler.findViewById<TextInputWidget>(R.id.title).setSelection(3)
}
// Press ENTER
target.perform(ViewActions.pressImeActionButton())
// Check results
verifyZeroInteractions(updateText)
verifyZeroInteractions(repo)
target.apply {
check(ViewAssertions.matches(ViewMatchers.withText(title)))
check(ViewAssertions.matches(ViewMatchers.hasFocus()))
}
// Check cursor position
scenario.onFragment { fragment ->
val item = fragment.recycler.getChildAt(0)
val view = item.findViewById<TextInputWidget>(R.id.title)
assertEquals(
expected = 3,
actual = view.selectionStart
)
assertEquals(
expected = 3,
actual = view.selectionEnd
)
}
// Release pending coroutines
advance(PageViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
}
@Test
fun shouldSplitParagraph() {
@ -191,13 +111,15 @@ class SplitBlockTesting : EditorTestSetup() {
)
stubInterceptEvents()
stubInterceptThreadStatus()
stubOpenDocument(document)
stubUpdateText()
val command = Command.Split(
context = root,
target = block.id,
index = 3,
mode = BlockSplitMode.BOTTOM,
range = 3..3,
style = style
)
@ -214,7 +136,7 @@ class SplitBlockTesting : EditorTestSetup() {
// TESTING
val target = onView(
withRecyclerView(R.id.recycler).atPositionOnView(1, targetViewId)
withRecyclerView(R.id.recycler).atPositionOnView(0, targetViewId)
)
target.apply {
@ -244,13 +166,13 @@ class SplitBlockTesting : EditorTestSetup() {
verifyBlocking(repo, times(1)) { split(command) }
onView(
withRecyclerView(R.id.recycler).atPositionOnView(1, targetViewId)
withRecyclerView(R.id.recycler).atPositionOnView(0, targetViewId)
).apply {
check(ViewAssertions.matches(ViewMatchers.withText("Foo")))
}
onView(
withRecyclerView(R.id.recycler).atPositionOnView(2, targetViewId)
withRecyclerView(R.id.recycler).atPositionOnView(1, targetViewId)
).apply {
check(ViewAssertions.matches(ViewMatchers.withText("Bar")))
check(ViewAssertions.matches(ViewMatchers.hasFocus()))
@ -259,7 +181,7 @@ class SplitBlockTesting : EditorTestSetup() {
// Check cursor position
scenario.onFragment { fragment ->
val item = fragment.recycler.getChildAt(2)
val item = fragment.recycler.getChildAt(1)
val view = item.findViewById<TextInputWidget>(targetViewId)
assertEquals(
expected = 0,
@ -338,13 +260,15 @@ class SplitBlockTesting : EditorTestSetup() {
)
stubInterceptEvents()
stubInterceptThreadStatus()
stubOpenDocument(document)
stubUpdateText()
val command = Command.Split(
context = root,
target = block.id,
index = 3,
range = 3..3,
mode = BlockSplitMode.BOTTOM,
style = style
)
@ -361,7 +285,7 @@ class SplitBlockTesting : EditorTestSetup() {
// TESTING
val target = onView(
withRecyclerView(R.id.recycler).atPositionOnView(1, targetViewId)
withRecyclerView(R.id.recycler).atPositionOnView(0, targetViewId)
)
target.apply {
@ -391,13 +315,13 @@ class SplitBlockTesting : EditorTestSetup() {
verifyBlocking(repo, times(1)) { split(command) }
onView(
withRecyclerView(R.id.recycler).atPositionOnView(1, targetViewId)
withRecyclerView(R.id.recycler).atPositionOnView(0, targetViewId)
).apply {
check(ViewAssertions.matches(ViewMatchers.withText("Foo")))
}
onView(
withRecyclerView(R.id.recycler).atPositionOnView(2, targetViewId)
withRecyclerView(R.id.recycler).atPositionOnView(1, targetViewId)
).apply {
check(ViewAssertions.matches(ViewMatchers.withText("Bar")))
check(ViewAssertions.matches(ViewMatchers.hasFocus()))
@ -406,7 +330,7 @@ class SplitBlockTesting : EditorTestSetup() {
// Check cursor position
scenario.onFragment { fragment ->
val item = fragment.recycler.getChildAt(2)
val item = fragment.recycler.getChildAt(1)
val view = item.findViewById<TextInputWidget>(targetViewId)
assertEquals(
expected = 0,
@ -485,13 +409,15 @@ class SplitBlockTesting : EditorTestSetup() {
)
stubInterceptEvents()
stubInterceptThreadStatus()
stubOpenDocument(document)
stubUpdateText()
val command = Command.Split(
context = root,
target = block.id,
index = 3,
range = 3..3,
mode = BlockSplitMode.BOTTOM,
style = style
)
@ -508,7 +434,7 @@ class SplitBlockTesting : EditorTestSetup() {
// TESTING
val target = onView(
withRecyclerView(R.id.recycler).atPositionOnView(1, targetViewId)
withRecyclerView(R.id.recycler).atPositionOnView(0, targetViewId)
)
target.apply {
@ -538,13 +464,13 @@ class SplitBlockTesting : EditorTestSetup() {
verifyBlocking(repo, times(1)) { split(command) }
onView(
withRecyclerView(R.id.recycler).atPositionOnView(1, targetViewId)
withRecyclerView(R.id.recycler).atPositionOnView(0, targetViewId)
).apply {
check(ViewAssertions.matches(ViewMatchers.withText("Foo")))
}
onView(
withRecyclerView(R.id.recycler).atPositionOnView(2, targetViewId)
withRecyclerView(R.id.recycler).atPositionOnView(1, targetViewId)
).apply {
check(ViewAssertions.matches(ViewMatchers.withText("Bar")))
check(ViewAssertions.matches(ViewMatchers.hasFocus()))
@ -553,7 +479,7 @@ class SplitBlockTesting : EditorTestSetup() {
// Check cursor position
scenario.onFragment { fragment ->
val item = fragment.recycler.getChildAt(2)
val item = fragment.recycler.getChildAt(1)
val view = item.findViewById<TextInputWidget>(targetViewId)
assertEquals(
expected = 0,
@ -632,13 +558,15 @@ class SplitBlockTesting : EditorTestSetup() {
)
stubInterceptEvents()
stubInterceptThreadStatus()
stubOpenDocument(document)
stubUpdateText()
val command = Command.Split(
context = root,
target = block.id,
index = 3,
range = 3..3,
mode = BlockSplitMode.BOTTOM,
style = style
)
@ -655,7 +583,7 @@ class SplitBlockTesting : EditorTestSetup() {
// TESTING
val target = onView(
withRecyclerView(R.id.recycler).atPositionOnView(1, targetViewId)
withRecyclerView(R.id.recycler).atPositionOnView(0, targetViewId)
)
target.apply {
@ -683,13 +611,13 @@ class SplitBlockTesting : EditorTestSetup() {
verifyBlocking(repo, times(1)) { split(command) }
onView(
withRecyclerView(R.id.recycler).atPositionOnView(1, targetViewId)
withRecyclerView(R.id.recycler).atPositionOnView(0, targetViewId)
).apply {
check(ViewAssertions.matches(ViewMatchers.withText("Foo")))
}
onView(
withRecyclerView(R.id.recycler).atPositionOnView(2, targetViewId)
withRecyclerView(R.id.recycler).atPositionOnView(1, targetViewId)
).apply {
check(ViewAssertions.matches(ViewMatchers.withText("Bar")))
check(ViewAssertions.matches(ViewMatchers.hasFocus()))
@ -698,7 +626,7 @@ class SplitBlockTesting : EditorTestSetup() {
// Check cursor position
scenario.onFragment { fragment ->
val item = fragment.recycler.getChildAt(2)
val item = fragment.recycler.getChildAt(1)
val view = item.findViewById<TextInputWidget>(targetViewId)
assertEquals(
expected = 0,
@ -777,13 +705,15 @@ class SplitBlockTesting : EditorTestSetup() {
)
stubInterceptEvents()
stubInterceptThreadStatus()
stubOpenDocument(document)
stubUpdateText()
val command = Command.Split(
context = root,
target = block.id,
index = 3,
range = 3..3,
mode = BlockSplitMode.BOTTOM,
style = style
)
@ -800,7 +730,7 @@ class SplitBlockTesting : EditorTestSetup() {
// TESTING
val target = onView(
withRecyclerView(R.id.recycler).atPositionOnView(1, targetViewId)
withRecyclerView(R.id.recycler).atPositionOnView(0, targetViewId)
)
target.apply {
@ -828,13 +758,13 @@ class SplitBlockTesting : EditorTestSetup() {
verifyBlocking(repo, times(1)) { split(command) }
onView(
withRecyclerView(R.id.recycler).atPositionOnView(1, targetViewId)
withRecyclerView(R.id.recycler).atPositionOnView(0, targetViewId)
).apply {
check(ViewAssertions.matches(ViewMatchers.withText("Foo")))
}
onView(
withRecyclerView(R.id.recycler).atPositionOnView(2, targetViewId)
withRecyclerView(R.id.recycler).atPositionOnView(1, targetViewId)
).apply {
check(ViewAssertions.matches(ViewMatchers.withText("Bar")))
check(ViewAssertions.matches(ViewMatchers.hasFocus()))
@ -843,7 +773,7 @@ class SplitBlockTesting : EditorTestSetup() {
// Check cursor position
scenario.onFragment { fragment ->
val item = fragment.recycler.getChildAt(2)
val item = fragment.recycler.getChildAt(1)
val view = item.findViewById<TextInputWidget>(targetViewId)
assertEquals(
expected = 0,
@ -922,13 +852,15 @@ class SplitBlockTesting : EditorTestSetup() {
)
stubInterceptEvents()
stubInterceptThreadStatus()
stubOpenDocument(document)
stubUpdateText()
val command = Command.Split(
context = root,
target = block.id,
index = 3,
range = 3..3,
mode = BlockSplitMode.BOTTOM,
style = style
)
@ -973,13 +905,13 @@ class SplitBlockTesting : EditorTestSetup() {
verifyBlocking(repo, times(1)) { split(command) }
onView(
withRecyclerView(R.id.recycler).atPositionOnView(1, targetViewId)
withRecyclerView(R.id.recycler).atPositionOnView(0, targetViewId)
).apply {
check(ViewAssertions.matches(ViewMatchers.withText("Foo")))
}
onView(
withRecyclerView(R.id.recycler).atPositionOnView(2, targetViewId)
withRecyclerView(R.id.recycler).atPositionOnView(1, targetViewId)
).apply {
check(ViewAssertions.matches(ViewMatchers.withText("Bar")))
check(ViewAssertions.matches(ViewMatchers.hasFocus()))
@ -988,7 +920,7 @@ class SplitBlockTesting : EditorTestSetup() {
// Check cursor position
scenario.onFragment { fragment ->
val item = fragment.recycler.getChildAt(2)
val item = fragment.recycler.getChildAt(1)
val view = item.findViewById<TextInputWidget>(targetViewId)
assertEquals(
expected = 0,
@ -1067,13 +999,15 @@ class SplitBlockTesting : EditorTestSetup() {
)
stubInterceptEvents()
stubInterceptThreadStatus()
stubOpenDocument(document)
stubUpdateText()
val command = Command.Split(
context = root,
target = block.id,
index = 3,
range = 3..3,
mode = BlockSplitMode.BOTTOM,
style = style
)
@ -1090,7 +1024,7 @@ class SplitBlockTesting : EditorTestSetup() {
// TESTING
val target = onView(
withRecyclerView(R.id.recycler).atPositionOnView(1, targetViewId)
withRecyclerView(R.id.recycler).atPositionOnView(0, targetViewId)
)
target.apply {
@ -1116,13 +1050,13 @@ class SplitBlockTesting : EditorTestSetup() {
verifyBlocking(repo, times(1)) { split(command) }
onView(
withRecyclerView(R.id.recycler).atPositionOnView(1, targetViewId)
withRecyclerView(R.id.recycler).atPositionOnView(0, targetViewId)
).apply {
check(ViewAssertions.matches(ViewMatchers.withText("Foo")))
}
onView(
withRecyclerView(R.id.recycler).atPositionOnView(2, targetViewId)
withRecyclerView(R.id.recycler).atPositionOnView(1, targetViewId)
).apply {
check(ViewAssertions.matches(ViewMatchers.withText("Bar")))
check(ViewAssertions.matches(ViewMatchers.hasFocus()))
@ -1131,7 +1065,7 @@ class SplitBlockTesting : EditorTestSetup() {
// Check cursor position
scenario.onFragment { fragment ->
val item = fragment.recycler.getChildAt(2)
val item = fragment.recycler.getChildAt(1)
val view = item.findViewById<TextInputWidget>(targetViewId)
assertEquals(
expected = 0,
@ -1210,13 +1144,15 @@ class SplitBlockTesting : EditorTestSetup() {
)
stubInterceptEvents()
stubInterceptThreadStatus()
stubOpenDocument(document)
stubUpdateText()
val command = Command.Split(
context = root,
target = block.id,
index = 3,
range = 3..3,
mode = BlockSplitMode.BOTTOM,
style = style
)
@ -1233,7 +1169,7 @@ class SplitBlockTesting : EditorTestSetup() {
// TESTING
val target = onView(
withRecyclerView(R.id.recycler).atPositionOnView(1, targetViewId)
withRecyclerView(R.id.recycler).atPositionOnView(0, targetViewId)
)
target.apply {
@ -1259,13 +1195,13 @@ class SplitBlockTesting : EditorTestSetup() {
verifyBlocking(repo, times(1)) { split(command) }
onView(
withRecyclerView(R.id.recycler).atPositionOnView(1, targetViewId)
withRecyclerView(R.id.recycler).atPositionOnView(0, targetViewId)
).apply {
check(ViewAssertions.matches(ViewMatchers.withText("Foo")))
}
onView(
withRecyclerView(R.id.recycler).atPositionOnView(2, targetViewId)
withRecyclerView(R.id.recycler).atPositionOnView(1, targetViewId)
).apply {
check(ViewAssertions.matches(ViewMatchers.withText("Bar")))
check(ViewAssertions.matches(ViewMatchers.hasFocus()))
@ -1274,7 +1210,7 @@ class SplitBlockTesting : EditorTestSetup() {
// Check cursor position
scenario.onFragment { fragment ->
val item = fragment.recycler.getChildAt(2)
val item = fragment.recycler.getChildAt(1)
val view = item.findViewById<TextInputWidget>(targetViewId)
assertEquals(
expected = 0,
@ -1353,13 +1289,15 @@ class SplitBlockTesting : EditorTestSetup() {
)
stubInterceptEvents()
stubInterceptThreadStatus()
stubOpenDocument(document)
stubUpdateText()
val command = Command.Split(
context = root,
target = block.id,
index = 3,
range = 3..3,
mode = BlockSplitMode.BOTTOM,
style = style
)
@ -1376,7 +1314,7 @@ class SplitBlockTesting : EditorTestSetup() {
// TESTING
val target = onView(
withRecyclerView(R.id.recycler).atPositionOnView(1, targetViewId)
withRecyclerView(R.id.recycler).atPositionOnView(0, targetViewId)
)
target.apply {
@ -1402,13 +1340,13 @@ class SplitBlockTesting : EditorTestSetup() {
verifyBlocking(repo, times(1)) { split(command) }
onView(
withRecyclerView(R.id.recycler).atPositionOnView(1, targetViewId)
withRecyclerView(R.id.recycler).atPositionOnView(0, targetViewId)
).apply {
check(ViewAssertions.matches(ViewMatchers.withText("Foo")))
}
onView(
withRecyclerView(R.id.recycler).atPositionOnView(2, targetViewId)
withRecyclerView(R.id.recycler).atPositionOnView(1, targetViewId)
).apply {
check(ViewAssertions.matches(ViewMatchers.withText("Bar")))
check(ViewAssertions.matches(ViewMatchers.hasFocus()))
@ -1417,7 +1355,7 @@ class SplitBlockTesting : EditorTestSetup() {
// Check cursor position
scenario.onFragment { fragment ->
val item = fragment.recycler.getChildAt(2)
val item = fragment.recycler.getChildAt(1)
val view = item.findViewById<TextInputWidget>(targetViewId)
assertEquals(
expected = 0,

View file

@ -1,36 +1,44 @@
package com.anytypeio.anytype.features.editor.base
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.core_models.*
import com.anytypeio.anytype.core_utils.tools.Counter
import com.anytypeio.anytype.domain.`object`.UpdateDetail
import com.anytypeio.anytype.domain.base.Either
import com.anytypeio.anytype.domain.base.Result
import com.anytypeio.anytype.domain.block.UpdateDivider
import com.anytypeio.anytype.domain.block.interactor.*
import com.anytypeio.anytype.domain.block.model.Block
import com.anytypeio.anytype.domain.block.model.Command
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.clipboard.Clipboard
import com.anytypeio.anytype.domain.clipboard.Copy
import com.anytypeio.anytype.domain.clipboard.Paste
import com.anytypeio.anytype.domain.common.Id
import com.anytypeio.anytype.domain.config.Config
import com.anytypeio.anytype.domain.config.Gateway
import com.anytypeio.anytype.domain.cover.RemoveDocCover
import com.anytypeio.anytype.domain.cover.SetDocCoverImage
import com.anytypeio.anytype.domain.dataview.interactor.SetRelationKey
import com.anytypeio.anytype.domain.download.DownloadFile
import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
import com.anytypeio.anytype.domain.event.model.Event
import com.anytypeio.anytype.domain.event.model.Payload
import com.anytypeio.anytype.domain.icon.DocumentEmojiIconProvider
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.page.*
import com.anytypeio.anytype.domain.page.bookmark.SetupBookmark
import com.anytypeio.anytype.domain.page.navigation.GetListPages
import com.anytypeio.anytype.domain.status.InterceptThreadStatus
import com.anytypeio.anytype.domain.status.ThreadStatusChannel
import com.anytypeio.anytype.mocking.MockDataFactory
import com.anytypeio.anytype.presentation.page.DocumentExternalEventReducer
import com.anytypeio.anytype.presentation.page.Editor
import com.anytypeio.anytype.presentation.page.PageViewModelFactory
import com.anytypeio.anytype.presentation.page.cover.CoverImageHashProvider
import com.anytypeio.anytype.presentation.page.editor.Interactor
import com.anytypeio.anytype.presentation.page.editor.InternalDetailModificationManager
import com.anytypeio.anytype.presentation.page.editor.Orchestrator
import com.anytypeio.anytype.presentation.page.editor.Proxy
import com.anytypeio.anytype.presentation.page.editor.pattern.DefaultPatternMatcher
import com.anytypeio.anytype.presentation.page.render.DefaultBlockViewRenderer
import com.anytypeio.anytype.presentation.page.selection.SelectionStateHolder
import com.anytypeio.anytype.presentation.page.toggle.ToggleStateHolder
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.mock
@ -42,6 +50,7 @@ import org.mockito.MockitoAnnotations
open class EditorTestSetup {
lateinit var createObject: CreateObject
lateinit var archiveDocument: ArchiveDocument
lateinit var createDocument: CreateDocument
lateinit var downloadFile: DownloadFile
@ -58,11 +67,14 @@ open class EditorTestSetup {
lateinit var createPage: CreatePage
lateinit var updateBackgroundColor: UpdateBackgroundColor
lateinit var move: Move
lateinit var setRelationKey: SetRelationKey
lateinit var updateDetail: UpdateDetail
@Mock
lateinit var openPage: OpenPage
@Mock
lateinit var closePage: ClosePage
lateinit var closePage: CloseBlock
@Mock
lateinit var updateText: UpdateText
@Mock
@ -77,25 +89,56 @@ open class EditorTestSetup {
lateinit var getListPages: GetListPages
@Mock
lateinit var duplicateBlock: DuplicateBlock
@Mock
lateinit var updateTextStyle: UpdateTextStyle
@Mock
lateinit var updateTextColor: UpdateTextColor
@Mock
lateinit var updateLinkMarks: UpdateLinkMarks
@Mock
lateinit var removeLinkMark: RemoveLinkMark
@Mock
lateinit var mergeBlocks: MergeBlocks
lateinit var createNewDocument: CreateNewDocument
lateinit var interceptThreadStatus: InterceptThreadStatus
lateinit var setDocCoverImage: SetDocCoverImage
lateinit var removeDocCover: RemoveDocCover
lateinit var updateFields: UpdateFields
lateinit var turnIntoDocument: TurnIntoDocument
lateinit var turnIntoStyle: TurnIntoStyle
@Mock
lateinit var updateDivider: UpdateDivider
@Mock
lateinit var uriMatcher: Clipboard.UriMatcher
@Mock
lateinit var repo: BlockRepository
@Mock
lateinit var coverImageHashProvider: CoverImageHashProvider
@Mock
lateinit var clipboard: Clipboard
@Mock
lateinit var gateway: Gateway
@Mock
lateinit var analytics: Analytics
@Mock
lateinit var threadStatusChannel: ThreadStatusChannel
@Mock
lateinit var documentEmojiIconProvider: DocumentEmojiIconProvider
@ -107,9 +150,11 @@ open class EditorTestSetup {
profile = MockDataFactory.randomUuid()
)
private val urlBuilder = UrlBuilder(
config = config
)
private val urlBuilder by lazy {
UrlBuilder(
gateway = gateway
)
}
private val intents = Proxy.Intents()
@ -133,6 +178,12 @@ open class EditorTestSetup {
updateAlignment = UpdateAlignment(repo)
updateTitle = UpdateTitle(repo)
uploadBlock = UploadBlock(repo)
createObject = CreateObject(repo, documentEmojiIconProvider)
setRelationKey = SetRelationKey(repo)
turnIntoDocument = TurnIntoDocument(repo)
updateFields = UpdateFields(repo)
createNewDocument = CreateNewDocument(repo, documentEmojiIconProvider)
interceptThreadStatus = InterceptThreadStatus(channel = threadStatusChannel)
downloadFile = DownloadFile(
downloader = mock(),
context = Dispatchers.Main
@ -151,6 +202,11 @@ open class EditorTestSetup {
updateBackgroundColor = UpdateBackgroundColor(repo)
setDocCoverImage = SetDocCoverImage(repo)
removeDocCover = RemoveDocCover(repo)
turnIntoStyle = TurnIntoStyle(repo)
updateDetail = UpdateDetail(repo)
TestPageFragment.testViewModelFactory = PageViewModelFactory(
openPage = openPage,
closePage = closePage,
@ -158,6 +214,7 @@ open class EditorTestSetup {
updateLinkMarks = updateLinkMarks,
removeLinkMark = removeLinkMark,
createPage = createPage,
createObject = createObject,
documentEventReducer = DocumentExternalEventReducer(),
archiveDocument = archiveDocument,
createDocument = createDocument,
@ -165,10 +222,11 @@ open class EditorTestSetup {
renderer = DefaultBlockViewRenderer(
urlBuilder = urlBuilder,
counter = Counter.Default(),
toggleStateHolder = ToggleStateHolder.Default()
toggleStateHolder = ToggleStateHolder.Default(),
coverImageHashProvider = coverImageHashProvider
),
getListPages = getListPages,
interactor = Orchestrator(
orchestrator = Orchestrator(
createBlock = createBlock,
splitBlock = splitBlock,
unlinkBlocks = unlinkBlocks,
@ -188,6 +246,7 @@ open class EditorTestSetup {
updateTextColor = updateTextColor,
replaceBlock = replaceBlock,
setupBookmark = setupBookmark,
setRelationKey = setRelationKey,
memory = Editor.Memory(
selections = SelectionStateHolder.Default()
),
@ -199,8 +258,21 @@ open class EditorTestSetup {
matcher = DefaultPatternMatcher()
),
uploadBlock = uploadBlock,
move = move
)
move = move,
analytics = analytics,
updateDivider = updateDivider,
updateFields = updateFields,
turnIntoDocument = turnIntoDocument,
turnIntoStyle = turnIntoStyle
),
createNewDocument = createNewDocument,
interceptThreadStatus = interceptThreadStatus,
analytics = analytics,
dispatcher = Dispatcher.Default(),
setDocCoverImage = setDocCoverImage,
removeDocCover = removeDocCover,
detailModificationManager = InternalDetailModificationManager(stores.details),
updateDetail = updateDetail
)
}
@ -210,24 +282,36 @@ open class EditorTestSetup {
fun stubInterceptEvents() {
interceptEvents.stub {
onBlocking { build() } doReturn emptyFlow()
onBlocking { build(any()) } doReturn emptyFlow()
}
}
fun stubInterceptThreadStatus(
params: InterceptThreadStatus.Params = InterceptThreadStatus.Params(ctx = root)
) {
interceptThreadStatus.stub {
onBlocking { build(params) } doReturn emptyFlow()
}
}
fun stubOpenDocument(
document: List<Block>,
details: Block.Details = Block.Details()
details: Block.Details = Block.Details(),
relations: List<Relation> = emptyList()
) {
openPage.stub {
onBlocking { invoke(any()) } doReturn Either.Right(
Payload(
context = root,
events = listOf(
Event.Command.ShowBlock(
context = root,
root = root,
details = details,
blocks = document
Result.Success(
Payload(
context = root,
events = listOf(
Event.Command.ShowBlock(
context = root,
root = root,
details = details,
blocks = document,
relations = relations
)
)
)
)
@ -235,6 +319,31 @@ open class EditorTestSetup {
}
}
fun stubCreateBlock(
params: CreateBlock.Params,
events: List<Event.Command>
) {
createBlock.stub {
onBlocking { invoke(params) } doReturn Either.Right(
Pair(
MockDataFactory.randomUuid(),
Payload(context = root, events = events)
)
)
}
}
fun stubReplaceBlock(
command: Command.Replace,
events: List<Event.Command>
) {
repo.stub {
onBlocking {
replace(command = command)
} doReturn Pair(command.context, Payload(command.context, events))
}
}
fun stubSplitBlocks(
command: Command.Split,
new: Id,

View file

@ -19,7 +19,9 @@ import com.anytypeio.anytype.emojifier.data.EmojiProvider
import com.anytypeio.anytype.emojifier.suggest.EmojiSuggester
import com.anytypeio.anytype.emojifier.suggest.model.EmojiModel
import com.anytypeio.anytype.mocking.MockDataFactory
import com.anytypeio.anytype.presentation.page.editor.DetailModificationManager
import com.anytypeio.anytype.presentation.page.picker.DocumentEmojiIconPickerViewModelFactory
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.utils.TestUtils.withRecyclerView
import com.nhaarman.mockitokotlin2.*
import kotlinx.android.synthetic.main.fragment_page_icon_picker.*
@ -35,6 +37,9 @@ import kotlin.test.assertEquals
@LargeTest
class DocumentEmojiPickerFragmentTest {
@Mock
lateinit var detailModificationManager: DetailModificationManager
@Mock
lateinit var suggester: EmojiSuggester
@ -54,7 +59,9 @@ class DocumentEmojiPickerFragmentTest {
DocumentEmojiIconPickerViewModelFactory(
emojiProvider = provider,
emojiSuggester = suggester,
setEmojiIcon = setEmojiIcon
setEmojiIcon = setEmojiIcon,
dispatcher = Dispatcher.Default(),
details = detailModificationManager
)
}

View file

@ -0,0 +1,606 @@
package com.anytypeio.anytype.features.relations
import android.os.Bundle
import androidx.core.os.bundleOf
import androidx.fragment.app.testing.FragmentScenario
import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.*
import com.anytypeio.anytype.domain.`object`.UpdateDetail
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.config.Gateway
import com.anytypeio.anytype.domain.dataview.interactor.AddDataViewRelationOption
import com.anytypeio.anytype.domain.dataview.interactor.AddStatusToDataViewRecord
import com.anytypeio.anytype.domain.dataview.interactor.AddTagToDataViewRecord
import com.anytypeio.anytype.domain.dataview.interactor.RemoveTagFromDataViewRecord
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.relations.AddObjectRelationOption
import com.anytypeio.anytype.mocking.MockDataFactory
import com.anytypeio.anytype.presentation.page.editor.ThemeColor
import com.anytypeio.anytype.presentation.relations.AddObjectSetObjectRelationValueViewModel
import com.anytypeio.anytype.presentation.relations.ObjectSetConfig
import com.anytypeio.anytype.presentation.relations.providers.DataViewObjectRelationProvider
import com.anytypeio.anytype.presentation.relations.providers.DataViewObjectValueProvider
import com.anytypeio.anytype.presentation.relations.providers.ObjectDetailProvider
import com.anytypeio.anytype.presentation.relations.providers.ObjectTypeProvider
import com.anytypeio.anytype.presentation.sets.ObjectSet
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.relations.AddObjectRelationValueFragment
import com.anytypeio.anytype.utils.*
import com.bartoszlipinski.disableanimationsrule.DisableAnimationsRule
import com.nhaarman.mockitokotlin2.*
import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
@LargeTest
class AddRelationStatusValueTest {
@Mock
lateinit var repo: BlockRepository
@Mock
lateinit var gateway: Gateway
@Mock
lateinit var dispatcher: Dispatcher<Payload>
private lateinit var addRelationOption: AddDataViewRelationOption
private lateinit var addObjectRelationOption: AddObjectRelationOption
private lateinit var removeTagFromDataViewRecord: RemoveTagFromDataViewRecord
private lateinit var addTagToDataViewRecord: AddTagToDataViewRecord
private lateinit var addStatusToDataViewRecord: AddStatusToDataViewRecord
private lateinit var updateDetail: UpdateDetail
private lateinit var urlBuilder: UrlBuilder
@get:Rule
val animationsRule = DisableAnimationsRule()
@get:Rule
val coroutineTestRule = CoroutinesTestRule()
private val ctx = MockDataFactory.randomUuid()
private val state = MutableStateFlow(ObjectSet.init())
private val session = ObjectSetSession()
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
addRelationOption = AddDataViewRelationOption(repo)
addTagToDataViewRecord = AddTagToDataViewRecord(repo)
addObjectRelationOption = AddObjectRelationOption(repo)
addStatusToDataViewRecord = AddStatusToDataViewRecord(repo)
removeTagFromDataViewRecord = RemoveTagFromDataViewRecord(repo)
updateDetail = UpdateDetail(repo)
urlBuilder = UrlBuilder(gateway)
TestAddObjectSetObjectRelationValueFragment.testVmFactory = AddObjectSetObjectRelationValueViewModel.Factory(
relations = DataViewObjectRelationProvider(state),
values = DataViewObjectValueProvider(state, session),
details = object : ObjectDetailProvider {
override fun provide(): Map<Id, Block.Fields> = state.value.details
},
types = object : ObjectTypeProvider {
override fun provide(): List<ObjectType> = state.value.objectTypes
},
addDataViewRelationOption = addRelationOption,
addTagToDataViewRecord = addTagToDataViewRecord,
addStatusToDataViewRecord = addStatusToDataViewRecord,
urlBuilder = urlBuilder,
dispatcher = dispatcher,
)
}
@Test
fun shouldStartCreatingDataViewOptionWhenTypeAndButtonClicked() {
// SETUP
val relation = Relation(
key = MockDataFactory.randomUuid(),
defaultValue = null,
isHidden = false,
isReadOnly = false,
isMulti = true,
name = MockDataFactory.randomString(),
source = Relation.Source.values().random(),
format = Relation.Format.STATUS,
selections = emptyList()
)
val obj = MockDataFactory.randomUuid()
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to obj,
relation.key to emptyList<Id>()
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
val dv = Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(relation),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
state.value = ObjectSet(
blocks = listOf(dv),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
repo.stub {
onBlocking {
addDataViewRelationOption(
ctx = any(),
dataview = any(),
relation = any(),
color = any(),
name = any(),
record = any()
)
} doReturn Pair(
Payload(
context = ctx,
events = emptyList()
),
MockDataFactory.randomUuid()
)
}
// TESTING
launchFragment(
bundleOf(
AddObjectRelationValueFragment.CTX_KEY to ctx,
AddObjectRelationValueFragment.RELATION_KEY to relation.key,
AddObjectRelationValueFragment.DATAVIEW_KEY to dv.id,
AddObjectRelationValueFragment.VIEWER_KEY to viewer.id,
AddObjectRelationValueFragment.TARGET_KEY to obj
)
)
// Creating name for a new option
R.id.filterInput.type("In progress")
val btn = R.id.recycler.rVMatcher().onItemView(0, R.id.tvCreateOptionValue)
btn.checkHasText("Create option \"In progress\"")
// Pressing button, in order to trigger request.
btn.performClick()
// Verifying that the request is made.
verifyBlocking(repo, times(1)) {
addDataViewRelationOption(
ctx = any(),
dataview = any(),
relation = any(),
color = any(),
name = any(),
record = any()
)
}
}
@Test
fun addButtonShouldNotBeVisible() {
// SETUP
val relation = Relation(
key = MockDataFactory.randomUuid(),
defaultValue = null,
isHidden = false,
isReadOnly = false,
isMulti = true,
name = MockDataFactory.randomString(),
source = Relation.Source.values().random(),
format = Relation.Format.STATUS,
selections = emptyList()
)
val obj = MockDataFactory.randomUuid()
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to obj,
relation.key to emptyList<Id>()
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
val dv = Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(relation),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
state.value = ObjectSet(
blocks = listOf(dv),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
repo.stub {
onBlocking {
addDataViewRelationOption(
ctx = any(),
dataview = any(),
relation = any(),
color = any(),
name = any(),
record = any()
)
} doReturn Pair(
Payload(
context = ctx,
events = emptyList()
),
MockDataFactory.randomUuid()
)
}
// TESTING
launchFragment(
bundleOf(
AddObjectRelationValueFragment.CTX_KEY to ctx,
AddObjectRelationValueFragment.RELATION_KEY to relation.key,
AddObjectRelationValueFragment.DATAVIEW_KEY to dv.id,
AddObjectRelationValueFragment.VIEWER_KEY to viewer.id,
AddObjectRelationValueFragment.TARGET_KEY to obj
)
)
R.id.btnAdd.matchView().checkIsNotDisplayed()
}
@Test
fun shouldRenderOnlyStatusesWhichRelationValueDoesNotContain() {
// SETUP
val option1Color = ThemeColor.values().random()
val option2Color = ThemeColor.values().random()
val option1 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "In progress",
color = option1Color.title
)
val option2 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Done",
color = option2Color.title
)
val option3 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Development",
color = ""
)
val relationKey = MockDataFactory.randomUuid()
val target = MockDataFactory.randomUuid()
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target,
relationKey to listOf(option2.id, option3.id)
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
val relation = Relation(
key = relationKey,
defaultValue = null,
isHidden = false,
isReadOnly = false,
isMulti = true,
name = "Roles",
source = Relation.Source.values().random(),
format = Relation.Format.STATUS,
selections = listOf(option1, option2, option3)
)
val dv = Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(relation),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
state.value = ObjectSet(
blocks = listOf(dv),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
// TESTING
launchFragment(
bundleOf(
AddObjectRelationValueFragment.CTX_KEY to ctx,
AddObjectRelationValueFragment.RELATION_KEY to relation.key,
AddObjectRelationValueFragment.DATAVIEW_KEY to dv.id,
AddObjectRelationValueFragment.VIEWER_KEY to viewer.id,
AddObjectRelationValueFragment.TARGET_KEY to target
)
)
R.id.recycler.rVMatcher().apply {
onItemView(0, R.id.tvStatusName).checkHasText(option1.text)
onItemView(0, R.id.tvStatusName).checkHasTextColor(option1Color.text)
checkIsRecyclerSize(1)
}
}
@Test
fun statusesShouldBeInTheListWhenTypingToCreateNewOption() {
// SETUP
val option1 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "In Testing",
color = ThemeColor.values().random().title
)
val option2 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Done",
color = ThemeColor.values().random().title
)
val option3 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Development",
color = ThemeColor.values().random().title
)
val relationKey = MockDataFactory.randomUuid()
val target = MockDataFactory.randomUuid()
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target,
relationKey to emptyList<String>()
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
val relation = Relation(
key = relationKey,
defaultValue = null,
isHidden = false,
isReadOnly = false,
isMulti = true,
name = "Roles",
source = Relation.Source.values().random(),
format = Relation.Format.STATUS,
selections = listOf(option1, option2, option3)
)
val dv = Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(relation),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
state.value = ObjectSet(
blocks = listOf(dv),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
// TESTING
launchFragment(
bundleOf(
AddObjectRelationValueFragment.CTX_KEY to ctx,
AddObjectRelationValueFragment.RELATION_KEY to relation.key,
AddObjectRelationValueFragment.DATAVIEW_KEY to dv.id,
AddObjectRelationValueFragment.VIEWER_KEY to viewer.id,
AddObjectRelationValueFragment.TARGET_KEY to target
)
)
// Typing name for a new option
R.id.filterInput.type("Backlog")
// Checking that not only create-option view button, but also tags are visible
R.id.recycler.rVMatcher().apply {
onItemView(0, R.id.tvCreateOptionValue).checkHasText("Create option \"Backlog\"")
onItemView(1, R.id.tvStatusName).checkHasText(option1.text)
onItemView(2, R.id.tvStatusName).checkHasText(option2.text)
onItemView(3, R.id.tvStatusName).checkHasText(option3.text)
checkIsRecyclerSize(4)
}
}
@Test
fun shouldRequestAddingStatusToDataViewRecordWhenStatusClicked() {
// SETUP
val option1 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "In progress",
color = ThemeColor.values().random().title
)
val option2 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Testing",
color = ThemeColor.values().random().title
)
val option3 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Development",
color = ThemeColor.values().random().title
)
val relationKey = MockDataFactory.randomUuid()
val target = MockDataFactory.randomUuid()
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target,
relationKey to emptyList<String>()
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
val relation = Relation(
key = relationKey,
defaultValue = null,
isHidden = false,
isReadOnly = false,
isMulti = true,
name = "Roles",
source = Relation.Source.values().random(),
format = Relation.Format.STATUS,
selections = listOf(option1, option2, option3)
)
val dv = Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(relation),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
state.value = ObjectSet(
blocks = listOf(dv),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
// TESTING
launchFragment(
bundleOf(
AddObjectRelationValueFragment.CTX_KEY to ctx,
AddObjectRelationValueFragment.RELATION_KEY to relation.key,
AddObjectRelationValueFragment.DATAVIEW_KEY to dv.id,
AddObjectRelationValueFragment.VIEWER_KEY to viewer.id,
AddObjectRelationValueFragment.TARGET_KEY to target
)
)
// Selecting the first two tags
R.id.recycler.rVMatcher().onItemView(1, R.id.tvStatusName).performClick()
// Veryfying UI
verifyBlocking(repo, times(1)) {
updateDataViewRecord(
context = ctx,
target = dv.id,
record = target,
values = mapOf(
ObjectSetConfig.ID_KEY to target,
relationKey to listOf(option2.id)
)
)
}
}
private fun launchFragment(args: Bundle): FragmentScenario<TestAddObjectSetObjectRelationValueFragment> {
return launchFragmentInContainer<TestAddObjectSetObjectRelationValueFragment>(
fragmentArgs = args,
themeResId = R.style.AppTheme
)
}
}

View file

@ -0,0 +1,532 @@
package com.anytypeio.anytype.features.relations
import android.os.Bundle
import androidx.core.os.bundleOf
import androidx.fragment.app.testing.FragmentScenario
import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.*
import com.anytypeio.anytype.domain.`object`.UpdateDetail
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.config.Gateway
import com.anytypeio.anytype.domain.dataview.interactor.AddDataViewRelationOption
import com.anytypeio.anytype.domain.dataview.interactor.AddStatusToDataViewRecord
import com.anytypeio.anytype.domain.dataview.interactor.AddTagToDataViewRecord
import com.anytypeio.anytype.domain.dataview.interactor.RemoveTagFromDataViewRecord
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.relations.AddObjectRelationOption
import com.anytypeio.anytype.mocking.MockDataFactory
import com.anytypeio.anytype.presentation.page.editor.ThemeColor
import com.anytypeio.anytype.presentation.relations.AddObjectSetObjectRelationValueViewModel
import com.anytypeio.anytype.presentation.relations.ObjectSetConfig
import com.anytypeio.anytype.presentation.relations.providers.DataViewObjectRelationProvider
import com.anytypeio.anytype.presentation.relations.providers.DataViewObjectValueProvider
import com.anytypeio.anytype.presentation.relations.providers.ObjectDetailProvider
import com.anytypeio.anytype.presentation.relations.providers.ObjectTypeProvider
import com.anytypeio.anytype.presentation.sets.ObjectSet
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.relations.AddObjectRelationValueFragment
import com.anytypeio.anytype.utils.*
import com.bartoszlipinski.disableanimationsrule.DisableAnimationsRule
import com.nhaarman.mockitokotlin2.*
import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
@LargeTest
class AddRelationTagValueTest {
@Mock
lateinit var repo: BlockRepository
@Mock
lateinit var gateway: Gateway
@Mock
lateinit var dispatcher: Dispatcher<Payload>
private lateinit var addRelationOption: AddDataViewRelationOption
private lateinit var addObjectRelationOption: AddObjectRelationOption
private lateinit var removeTagFromDataViewRecord: RemoveTagFromDataViewRecord
private lateinit var addTagToDataViewRecord: AddTagToDataViewRecord
private lateinit var addStatusToDataViewRecord: AddStatusToDataViewRecord
private lateinit var updateDetail: UpdateDetail
private lateinit var urlBuilder: UrlBuilder
@get:Rule
val animationsRule = DisableAnimationsRule()
@get:Rule
val coroutineTestRule = CoroutinesTestRule()
private val ctx = MockDataFactory.randomUuid()
private val state = MutableStateFlow(ObjectSet.init())
private val session = ObjectSetSession()
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
addRelationOption = AddDataViewRelationOption(repo)
addTagToDataViewRecord = AddTagToDataViewRecord(repo)
addObjectRelationOption = AddObjectRelationOption(repo)
addStatusToDataViewRecord = AddStatusToDataViewRecord(repo)
removeTagFromDataViewRecord = RemoveTagFromDataViewRecord(repo)
updateDetail = UpdateDetail(repo)
urlBuilder = UrlBuilder(gateway)
TestAddObjectSetObjectRelationValueFragment.testVmFactory = AddObjectSetObjectRelationValueViewModel.Factory(
relations = DataViewObjectRelationProvider(state),
values = DataViewObjectValueProvider(state, session),
details = object : ObjectDetailProvider {
override fun provide(): Map<Id, Block.Fields> = state.value.details
},
types = object : ObjectTypeProvider {
override fun provide(): List<ObjectType> = state.value.objectTypes
},
addDataViewRelationOption = addRelationOption,
addTagToDataViewRecord = addTagToDataViewRecord,
addStatusToDataViewRecord = addStatusToDataViewRecord,
urlBuilder = urlBuilder,
dispatcher = dispatcher
)
}
@Test
fun shouldStartCreatingDataViewOptionWhenTypeAndButtonClicked() {
// SETUP
val relation = Relation(
key = MockDataFactory.randomUuid(),
defaultValue = null,
isHidden = false,
isReadOnly = false,
isMulti = true,
name = MockDataFactory.randomString(),
source = Relation.Source.values().random(),
format = Relation.Format.TAG,
selections = emptyList()
)
val obj = MockDataFactory.randomUuid()
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to obj,
relation.key to emptyList<Id>()
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
val dv = Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(relation),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
state.value = ObjectSet(
blocks = listOf(dv),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
repo.stub {
onBlocking {
addDataViewRelationOption(
ctx = any(),
dataview = any(),
relation = any(),
color = any(),
name = any(),
record = any()
)
} doReturn Pair(
Payload(
context = ctx,
events = emptyList()
),
MockDataFactory.randomUuid()
)
}
// TESTING
launchFragment(
bundleOf(
AddObjectRelationValueFragment.CTX_KEY to ctx,
AddObjectRelationValueFragment.RELATION_KEY to relation.key,
AddObjectRelationValueFragment.DATAVIEW_KEY to dv.id,
AddObjectRelationValueFragment.VIEWER_KEY to viewer.id,
AddObjectRelationValueFragment.TARGET_KEY to obj
)
)
// Creating name for a new option
R.id.filterInput.type("Writer")
val btn = R.id.recycler.rVMatcher().onItemView(0, R.id.tvCreateOptionValue)
btn.checkHasText("Create option \"Writer\"")
// Pressing button, in order to trigger request.
btn.performClick()
// Verifying that the request is made.
verifyBlocking(repo, times(1)) {
addDataViewRelationOption(
ctx = any(),
dataview = any(),
relation = any(),
color = any(),
name = any(),
record = any()
)
}
}
@Test
fun shouldRenderOnlyTagsWhichRelationValueDoesNotContain() {
// SETUP
val option1Color = ThemeColor.values().random()
val option2Color = ThemeColor.values().random()
val option1 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Architect",
color = option1Color.title
)
val option2 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Manager",
color = option2Color.title
)
val option3 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Developer",
color = ""
)
val relationKey = MockDataFactory.randomUuid()
val target = MockDataFactory.randomUuid()
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target,
relationKey to listOf(option2.id, option3.id)
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
val relation = Relation(
key = relationKey,
defaultValue = null,
isHidden = false,
isReadOnly = false,
isMulti = true,
name = "Roles",
source = Relation.Source.values().random(),
format = Relation.Format.TAG,
selections = listOf(option1, option2, option3)
)
val dv = Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(relation),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
state.value = ObjectSet(
blocks = listOf(dv),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
// TESTING
launchFragment(
bundleOf(
AddObjectRelationValueFragment.CTX_KEY to ctx,
AddObjectRelationValueFragment.RELATION_KEY to relation.key,
AddObjectRelationValueFragment.DATAVIEW_KEY to dv.id,
AddObjectRelationValueFragment.VIEWER_KEY to viewer.id,
AddObjectRelationValueFragment.TARGET_KEY to target
)
)
R.id.recycler.rVMatcher().apply {
onItemView(0, R.id.tvTagName).checkHasText(option1.text)
onItemView(0, R.id.tvTagName).checkHasTextColor(option1Color.text)
checkIsRecyclerSize(1)
}
}
@Test
fun tagsShouldBeInTheListWhenTypingToCreateNewOption() {
// SETUP
val option1 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Architect",
color = ThemeColor.values().random().title
)
val option2 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Manager",
color = ThemeColor.values().random().title
)
val option3 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Developer",
color = ThemeColor.values().random().title
)
val relationKey = MockDataFactory.randomUuid()
val target = MockDataFactory.randomUuid()
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target,
relationKey to emptyList<String>()
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
val relation = Relation(
key = relationKey,
defaultValue = null,
isHidden = false,
isReadOnly = false,
isMulti = true,
name = "Roles",
source = Relation.Source.values().random(),
format = Relation.Format.TAG,
selections = listOf(option1, option2, option3)
)
val dv = Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(relation),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
state.value = ObjectSet(
blocks = listOf(dv),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
// TESTING
launchFragment(
bundleOf(
AddObjectRelationValueFragment.CTX_KEY to ctx,
AddObjectRelationValueFragment.RELATION_KEY to relation.key,
AddObjectRelationValueFragment.DATAVIEW_KEY to dv.id,
AddObjectRelationValueFragment.VIEWER_KEY to viewer.id,
AddObjectRelationValueFragment.TARGET_KEY to target
)
)
// Typing name for a new option
R.id.filterInput.type("Writer")
// Checking that not only create-option view button, but also tags are visible
R.id.recycler.rVMatcher().apply {
onItemView(0, R.id.tvCreateOptionValue).checkHasText("Create option \"Writer\"")
onItemView(1, R.id.tvTagName).checkHasText(option1.text)
onItemView(2, R.id.tvTagName).checkHasText(option2.text)
onItemView(3, R.id.tvTagName).checkHasText(option3.text)
checkIsRecyclerSize(4)
}
}
@Test
fun shouldSelectFirstTwoTagsUpdateCounterAndRequestAddingThisTagToDataViewRecord() {
// SETUP
val option1 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Architect",
color = ThemeColor.values().random().title
)
val option2 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Manager",
color = ThemeColor.values().random().title
)
val option3 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Developer",
color = ThemeColor.values().random().title
)
val relationKey = MockDataFactory.randomUuid()
val target = MockDataFactory.randomUuid()
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target,
relationKey to emptyList<String>()
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
val relation = Relation(
key = relationKey,
defaultValue = null,
isHidden = false,
isReadOnly = false,
isMulti = true,
name = "Roles",
source = Relation.Source.values().random(),
format = Relation.Format.TAG,
selections = listOf(option1, option2, option3)
)
val dv = Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(relation),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
state.value = ObjectSet(
blocks = listOf(dv),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
// TESTING
launchFragment(
bundleOf(
AddObjectRelationValueFragment.CTX_KEY to ctx,
AddObjectRelationValueFragment.RELATION_KEY to relation.key,
AddObjectRelationValueFragment.DATAVIEW_KEY to dv.id,
AddObjectRelationValueFragment.VIEWER_KEY to viewer.id,
AddObjectRelationValueFragment.TARGET_KEY to target
)
)
// Selecting the first two tags
R.id.recycler.rVMatcher().apply {
onItemView(0, R.id.tvTagName).performClick()
onItemView(1, R.id.tvTagName).performClick()
}
// Clicking twice on the last tag, in order to check select / unselect logic.
R.id.recycler.rVMatcher().apply {
onItemView(2, R.id.tvTagName).performClick()
onItemView(2, R.id.tvTagName).performClick()
}
// Veryfying UI
R.id.tvSelectionCounter.matchView().checkHasText("2")
R.id.btnAdd.performClick()
verifyBlocking(repo, times(1)) {
updateDataViewRecord(
context = ctx,
target = dv.id,
record = target,
values = mapOf(
ObjectSetConfig.ID_KEY to target,
relationKey to listOf(option1.id, option2.id)
)
)
}
}
private fun launchFragment(args: Bundle): FragmentScenario<TestAddObjectSetObjectRelationValueFragment> {
return launchFragmentInContainer<TestAddObjectSetObjectRelationValueFragment>(
fragmentArgs = args,
themeResId = R.style.AppTheme
)
}
}

View file

@ -0,0 +1,447 @@
package com.anytypeio.anytype.features.relations
import android.os.Bundle
import androidx.core.os.bundleOf
import androidx.fragment.app.testing.FragmentScenario
import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.Relation
import com.anytypeio.anytype.mocking.MockDataFactory
import com.anytypeio.anytype.presentation.relations.ObjectSetConfig
import com.anytypeio.anytype.presentation.relations.providers.DataViewObjectRelationProvider
import com.anytypeio.anytype.presentation.relations.providers.DataViewObjectValueProvider
import com.anytypeio.anytype.presentation.sets.ObjectRelationTextValueViewModel
import com.anytypeio.anytype.presentation.sets.ObjectSet
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
import com.anytypeio.anytype.ui.relations.ObjectRelationTextValueFragment
import com.anytypeio.anytype.utils.CoroutinesTestRule
import com.bartoszlipinski.disableanimationsrule.DisableAnimationsRule
import kotlinx.coroutines.flow.MutableStateFlow
import org.hamcrest.CoreMatchers.not
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
@LargeTest
class DisplayObjectRelationTextValueTest {
@get:Rule
val animationsRule = DisableAnimationsRule()
@get:Rule
val coroutineTestRule = CoroutinesTestRule()
val root = MockDataFactory.randomUuid()
private val state = MutableStateFlow(ObjectSet.init())
private val session = ObjectSetSession()
@Before
fun before() {
MockitoAnnotations.initMocks(this)
TestObjectRelationTextValueFragment.testVmFactory = ObjectRelationTextValueViewModel.Factory(
relations = DataViewObjectRelationProvider(state),
values = DataViewObjectValueProvider(state, session)
)
}
@Test
fun shouldSetDescriptionTextAndNotDisplayActionButton() {
// SETUP
val relationText = "Architect"
val valueText = "Filippo Brunelleschi"
val target = MockDataFactory.randomUuid()
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
val relation = Relation(
key = MockDataFactory.randomUuid(),
name = relationText,
format = Relation.Format.SHORT_TEXT,
source = Relation.Source.values().random()
)
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target,
relation.key to valueText
)
state.value = ObjectSet(
blocks = listOf(
Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(relation),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
launchFragment(
bundleOf(
ObjectRelationTextValueFragment.CONTEXT_ID to root,
ObjectRelationTextValueFragment.RELATION_ID to relation.key,
ObjectRelationTextValueFragment.OBJECT_ID to target,
ObjectRelationTextValueFragment.FLOW_KEY to ObjectRelationTextValueFragment.FLOW_DATAVIEW
)
)
// TESTING
onView(withId(R.id.textInputField)).apply {
check(matches(withText(valueText)))
}
onView(withId(R.id.tvRelationHeader)).apply {
check(matches(withText(relationText)))
}
onView(withId(R.id.btnAction)).apply {
check(matches(not(isDisplayed())))
}
}
@Test
fun shouldSetNumberTextAndNotDisplayActionButton() {
// SETUP
val relationText = "Year"
val valueText = "1446"
val target = MockDataFactory.randomUuid()
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
val relation = Relation(
key = MockDataFactory.randomUuid(),
name = relationText,
format = Relation.Format.NUMBER,
source = Relation.Source.values().random()
)
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target,
relation.key to valueText
)
state.value = ObjectSet(
blocks = listOf(
Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(relation),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
// TESTING
launchFragment(
bundleOf(
ObjectRelationTextValueFragment.CONTEXT_ID to root,
ObjectRelationTextValueFragment.RELATION_ID to relation.key,
ObjectRelationTextValueFragment.OBJECT_ID to target,
ObjectRelationTextValueFragment.FLOW_KEY to ObjectRelationTextValueFragment.FLOW_DATAVIEW
)
)
// TESTING
onView(withId(R.id.textInputField)).apply {
check(matches(withText(valueText)))
}
onView(withId(R.id.tvRelationHeader)).apply {
check(matches(withText(relationText)))
}
onView(withId(R.id.btnAction)).apply {
check(matches(not(isDisplayed())))
}
}
@Test
fun shouldSetPhoneTextAndDisplayActionButton() {
// SETUP
val relationText = "Phone number"
val valueText = "+ 124242423423"
val target = MockDataFactory.randomUuid()
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
val relation = Relation(
key = MockDataFactory.randomUuid(),
name = relationText,
format = Relation.Format.PHONE,
source = Relation.Source.values().random()
)
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target,
relation.key to valueText
)
state.value = ObjectSet(
blocks = listOf(
Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(relation),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
// TESTING
launchFragment(
bundleOf(
ObjectRelationTextValueFragment.CONTEXT_ID to root,
ObjectRelationTextValueFragment.RELATION_ID to relation.key,
ObjectRelationTextValueFragment.OBJECT_ID to target,
ObjectRelationTextValueFragment.FLOW_KEY to ObjectRelationTextValueFragment.FLOW_DATAVIEW
)
)
// TESTING
onView(withId(R.id.textInputField)).apply {
check(matches(withText(valueText)))
}
onView(withId(R.id.tvRelationHeader)).apply {
check(matches(withText(relationText)))
}
onView(withId(R.id.btnAction)).apply {
check(matches(isDisplayed()))
}
}
@Test
fun shouldSetEmailTextAndDisplayActionButton() {
// SETUP
val relationText = "Email"
val valueText = "foo.bar@foobar.com"
val target = MockDataFactory.randomUuid()
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
val relation = Relation(
key = MockDataFactory.randomUuid(),
name = relationText,
format = Relation.Format.EMAIL,
source = Relation.Source.values().random()
)
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target,
relation.key to valueText
)
state.value = ObjectSet(
blocks = listOf(
Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(relation),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
launchFragment(
bundleOf(
ObjectRelationTextValueFragment.CONTEXT_ID to root,
ObjectRelationTextValueFragment.RELATION_ID to relation.key,
ObjectRelationTextValueFragment.OBJECT_ID to target,
ObjectRelationTextValueFragment.FLOW_KEY to ObjectRelationTextValueFragment.FLOW_DATAVIEW
)
)
// TESTING
onView(withId(R.id.textInputField)).apply {
check(matches(withText(valueText)))
}
onView(withId(R.id.tvRelationHeader)).apply {
check(matches(withText(relationText)))
}
onView(withId(R.id.btnAction)).apply {
check(matches(isDisplayed()))
}
}
@Test
fun shouldSetUrlTextAndDisplayActionButton() {
// SETUP
val relationText = "Url"
val valueText = "https://anytype.io"
val target = MockDataFactory.randomUuid()
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
val relation = Relation(
key = MockDataFactory.randomUuid(),
name = relationText,
format = Relation.Format.URL,
source = Relation.Source.values().random()
)
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target,
relation.key to valueText
)
state.value = ObjectSet(
blocks = listOf(
Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(relation),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
launchFragment(
bundleOf(
ObjectRelationTextValueFragment.CONTEXT_ID to root,
ObjectRelationTextValueFragment.RELATION_ID to relation.key,
ObjectRelationTextValueFragment.OBJECT_ID to target,
ObjectRelationTextValueFragment.FLOW_KEY to ObjectRelationTextValueFragment.FLOW_DATAVIEW
)
)
// TESTING
onView(withId(R.id.textInputField)).apply {
check(matches(withText(valueText)))
}
onView(withId(R.id.tvRelationHeader)).apply {
check(matches(withText(relationText)))
}
onView(withId(R.id.btnAction)).apply {
check(matches(isDisplayed()))
}
}
private fun launchFragment(args: Bundle): FragmentScenario<TestObjectRelationTextValueFragment> {
return launchFragmentInContainer<TestObjectRelationTextValueFragment>(
fragmentArgs = args,
themeResId = R.style.AppTheme
)
}
}

View file

@ -0,0 +1,640 @@
package com.anytypeio.anytype.features.relations
import android.os.Bundle
import androidx.core.os.bundleOf
import androidx.fragment.app.testing.FragmentScenario
import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.*
import com.anytypeio.anytype.domain.`object`.UpdateDetail
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.config.Gateway
import com.anytypeio.anytype.domain.dataview.interactor.AddDataViewRelationOption
import com.anytypeio.anytype.domain.dataview.interactor.AddTagToDataViewRecord
import com.anytypeio.anytype.domain.dataview.interactor.RemoveTagFromDataViewRecord
import com.anytypeio.anytype.domain.dataview.interactor.UpdateDataViewRecord
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.mocking.MockDataFactory
import com.anytypeio.anytype.presentation.relations.ObjectSetConfig
import com.anytypeio.anytype.presentation.relations.providers.DataViewObjectRelationProvider
import com.anytypeio.anytype.presentation.relations.providers.DataViewObjectValueProvider
import com.anytypeio.anytype.presentation.relations.providers.ObjectDetailProvider
import com.anytypeio.anytype.presentation.relations.providers.ObjectTypeProvider
import com.anytypeio.anytype.presentation.sets.ObjectSet
import com.anytypeio.anytype.presentation.sets.ObjectSetObjectRelationValueViewModel
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.database.modals.ObjectRelationValueFragment
import com.anytypeio.anytype.utils.*
import com.anytypeio.anytype.utils.TestUtils.withRecyclerView
import com.bartoszlipinski.disableanimationsrule.DisableAnimationsRule
import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
@LargeTest
class DisplayRelationObjectValueTest {
@Mock
lateinit var repo: BlockRepository
@Mock
lateinit var dispatcher: Dispatcher<Payload>
@Mock
lateinit var gateway: Gateway
private lateinit var addRelationOption: AddDataViewRelationOption
private lateinit var removeTagFromDataViewRecord: RemoveTagFromDataViewRecord
private lateinit var addTagToDataViewRecord: AddTagToDataViewRecord
private lateinit var updateDataViewRecord: UpdateDataViewRecord
private lateinit var updateDetail: UpdateDetail
private lateinit var urlBuilder: UrlBuilder
@get:Rule
val animationsRule = DisableAnimationsRule()
@get:Rule
val coroutineTestRule = CoroutinesTestRule()
private val root = MockDataFactory.randomUuid()
private val state = MutableStateFlow(ObjectSet.init())
private val session = ObjectSetSession()
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
addRelationOption = AddDataViewRelationOption(repo)
addTagToDataViewRecord = AddTagToDataViewRecord(repo)
removeTagFromDataViewRecord = RemoveTagFromDataViewRecord(repo)
updateDataViewRecord = UpdateDataViewRecord(repo)
updateDetail = UpdateDetail(repo)
urlBuilder = UrlBuilder(gateway)
TestObjectSetObjectRelationValueFragment.testVmFactory = ObjectSetObjectRelationValueViewModel.Factory(
relations = DataViewObjectRelationProvider(state),
values = DataViewObjectValueProvider(state, session),
details = object: ObjectDetailProvider {
override fun provide(): Map<Id, Block.Fields> = state.value.details
},
types = object: ObjectTypeProvider {
override fun provide(): List<ObjectType> = state.value.objectTypes
},
removeTagFromRecord = removeTagFromDataViewRecord,
urlBuilder = urlBuilder,
dispatcher = dispatcher,
updateDataViewRecord = updateDataViewRecord
)
}
@Test
fun shouldDisplayEditButtonAndPlusButton() {
// SETUP
val relation = MockDataFactory.randomUuid()
val target = MockDataFactory.randomUuid()
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target,
relation to emptyList<Id>()
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
state.value = ObjectSet(
blocks = listOf(
Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(
Relation(
key = relation,
isMulti = true,
name = MockDataFactory.randomString(),
format = Relation.Format.OBJECT,
source = Relation.Source.values().random()
)
),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
// TESTING
launchFragment(
bundleOf(
ObjectRelationValueFragment.CTX_KEY to root,
ObjectRelationValueFragment.RELATION_KEY to relation,
ObjectRelationValueFragment.TARGET_KEY to target
)
)
// Checking that the buttons are invisible
onView(withId(R.id.btnEditOrDone)).apply {
check(matches(isDisplayed()))
}
onView(withId(R.id.btnAddValue)).apply {
check(matches(isDisplayed()))
}
}
@Test
fun shouldSetRelationName() {
// SETUP
val name = "Object"
val relation = MockDataFactory.randomUuid()
val target = MockDataFactory.randomUuid()
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target,
relation to emptyList<Id>()
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
state.value = ObjectSet(
blocks = listOf(
Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(
Relation(
key = relation,
isMulti = true,
name = name,
format = Relation.Format.OBJECT,
source = Relation.Source.values().random()
)
),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
// TESTING
launchFragment(
bundleOf(
ObjectRelationValueFragment.CTX_KEY to root,
ObjectRelationValueFragment.RELATION_KEY to relation,
ObjectRelationValueFragment.TARGET_KEY to target
)
)
// Checking that the name is set
onView(withId(R.id.tvTagOrStatusRelationHeader)).apply {
check(matches(withText(name)))
}
}
@Test
fun shouldRenderEmptyState() {
// SETUP
val name = "Object"
val relationId = MockDataFactory.randomUuid()
val targetId = MockDataFactory.randomUuid()
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to targetId,
relationId to emptyList<Id>()
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
state.value = ObjectSet(
blocks = listOf(
Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(
Relation(
key = relationId,
name = name,
format = Relation.Format.OBJECT,
source = Relation.Source.values().random()
)
),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
// TESTING
launchFragment(
bundleOf(
ObjectRelationValueFragment.CTX_KEY to root,
ObjectRelationValueFragment.RELATION_KEY to relationId,
ObjectRelationValueFragment.TARGET_KEY to targetId
)
)
val rvMatcher = withRecyclerView(R.id.recycler)
onView(rvMatcher.atPositionOnView(0, R.id.tvEmptyMessage)).apply {
check(matches(isDisplayed()))
}
}
@Test
fun shouldRenderTwoObjectsWithNames() {
// SETUP
val relationName = "Cast"
val object1Name = "Charlie Chaplin"
val object1Id = MockDataFactory.randomUuid()
val object2Name = "Jean-Pierre Léaud"
val object2Id = MockDataFactory.randomUuid()
val objectType1 = ObjectType(
url = MockDataFactory.randomUuid(),
name = "Director",
relations = emptyList(),
emoji = "",
layout = ObjectType.Layout.values().random(),
description = "",
isHidden = false
)
val objectType2 = ObjectType(
url = MockDataFactory.randomUuid(),
name = "Actor",
relations = emptyList(),
emoji = "",
layout = ObjectType.Layout.values().random(),
description = "",
isHidden = false
)
val relationId = MockDataFactory.randomUuid()
val recordId = MockDataFactory.randomUuid()
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to recordId,
relationId to listOf(object1Id, object2Id)
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
state.value = ObjectSet(
blocks = listOf(
Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(
Relation(
key = relationId,
name = relationName,
format = Relation.Format.OBJECT,
source = Relation.Source.values().random()
)
),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
),
details = mapOf(
object1Id to Block.Fields(
mapOf(
Block.Fields.NAME_KEY to object1Name,
Block.Fields.TYPE_KEY to objectType1.url,
"iconEmoji" to "👤"
)
),
object2Id to Block.Fields(
mapOf(
Block.Fields.NAME_KEY to object2Name,
Block.Fields.TYPE_KEY to objectType2.url,
"iconEmoji" to "👤"
)
)
),
objectTypes = listOf(objectType1, objectType2)
)
// TESTING
launchFragment(
bundleOf(
ObjectRelationValueFragment.CTX_KEY to root,
ObjectRelationValueFragment.RELATION_KEY to relationId,
ObjectRelationValueFragment.TARGET_KEY to recordId
)
)
val rvMatcher = withRecyclerView(R.id.recycler)
onView(rvMatcher.atPositionOnView(0, R.id.tvTitle)).apply {
check(matches(withText(object1Name)))
}
onView(rvMatcher.atPositionOnView(0, R.id.tvSubtitle)).apply {
check(matches(withText(objectType1.name)))
}
onView(rvMatcher.atPositionOnView(1, R.id.tvTitle)).apply {
check(matches(withText(object2Name)))
}
onView(rvMatcher.atPositionOnView(1, R.id.tvSubtitle)).apply {
check(matches(withText(objectType2.name)))
}
}
@Test
fun shouldRenderTwoObjectsWithoutObjectTypes() {
// SETUP
val relationName = "Cast"
val object1Name = "Charlie Chaplin"
val object1Id = MockDataFactory.randomUuid()
val object2Name = "Jean-Pierre Léaud"
val object2Id = MockDataFactory.randomUuid()
val relationId = MockDataFactory.randomUuid()
val recordId = MockDataFactory.randomUuid()
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to recordId,
relationId to listOf(object1Id, object2Id)
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
state.value = ObjectSet(
blocks = listOf(
Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(
Relation(
key = relationId,
name = relationName,
format = Relation.Format.OBJECT,
source = Relation.Source.values().random()
)
),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
),
details = mapOf(
object1Id to Block.Fields(
mapOf(
Block.Fields.NAME_KEY to object1Name,
"iconEmoji" to "👤"
)
),
object2Id to Block.Fields(
mapOf(
Block.Fields.NAME_KEY to object2Name,
"iconEmoji" to "👤"
)
)
),
objectTypes = listOf()
)
// TESTING
launchFragment(
bundleOf(
ObjectRelationValueFragment.CTX_KEY to root,
ObjectRelationValueFragment.RELATION_KEY to relationId,
ObjectRelationValueFragment.TARGET_KEY to recordId
)
)
with(R.id.recycler.rVMatcher()) {
onItemView(0, R.id.tvTitle).checkHasText(object1Name)
onItemView(0, R.id.tvSubtitle).checkHasText(R.string.unknown_object_type)
onItemView(1, R.id.tvTitle).checkHasText(object2Name)
onItemView(1, R.id.tvSubtitle).checkHasText(R.string.unknown_object_type)
checkIsRecyclerSize(2)
}
}
@Test
fun shouldRenderProfileObjectWithNameAndInitial() {
// SETUP
val relationName = "Writers"
val object1Name = "Virginia Woolf"
val object1Id = MockDataFactory.randomUuid()
val object2Name = "Réné-Auguste Chateaubriand"
val object2Id = MockDataFactory.randomUuid()
val objectType1 = ObjectType(
url = MockDataFactory.randomUuid(),
name = "Writer",
relations = emptyList(),
emoji = "",
layout = ObjectType.Layout.PROFILE,
description = "",
isHidden = false
)
val objectType2 = ObjectType(
url = MockDataFactory.randomUuid(),
name = "Writer",
relations = emptyList(),
emoji = "",
layout = ObjectType.Layout.PROFILE,
description = "",
isHidden = false
)
val relationId = MockDataFactory.randomUuid()
val recordId = MockDataFactory.randomUuid()
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to recordId,
relationId to listOf(object1Id, object2Id)
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
state.value = ObjectSet(
blocks = listOf(
Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(
Relation(
key = relationId,
name = relationName,
format = Relation.Format.OBJECT,
source = Relation.Source.values().random()
)
),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
),
details = mapOf(
object1Id to Block.Fields(
mapOf(
Block.Fields.NAME_KEY to object1Name,
Block.Fields.TYPE_KEY to objectType1.url
)
),
object2Id to Block.Fields(
mapOf(
Block.Fields.NAME_KEY to object2Name,
Block.Fields.TYPE_KEY to objectType2.url
)
)
),
objectTypes = listOf(objectType1, objectType2)
)
// TESTING
launchFragment(
bundleOf(
ObjectRelationValueFragment.CTX_KEY to root,
ObjectRelationValueFragment.RELATION_KEY to relationId,
ObjectRelationValueFragment.TARGET_KEY to recordId
)
)
val rvMatcher = withRecyclerView(R.id.recycler)
onView(rvMatcher.atPositionOnView(0, R.id.initial)).apply {
check(matches(withText(object1Name.first().toString())))
}
onView(rvMatcher.atPositionOnView(1, R.id.initial)).apply {
check(matches(withText(object2Name.first().toString())))
}
}
private fun launchFragment(args: Bundle): FragmentScenario<TestObjectSetObjectRelationValueFragment> {
return launchFragmentInContainer<TestObjectSetObjectRelationValueFragment>(
fragmentArgs = args,
themeResId = R.style.AppTheme
)
}
}

View file

@ -0,0 +1,508 @@
package com.anytypeio.anytype.features.relations
import android.os.Bundle
import androidx.core.os.bundleOf
import androidx.fragment.app.testing.FragmentScenario
import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.*
import com.anytypeio.anytype.domain.`object`.UpdateDetail
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.config.Gateway
import com.anytypeio.anytype.domain.dataview.interactor.AddDataViewRelationOption
import com.anytypeio.anytype.domain.dataview.interactor.AddTagToDataViewRecord
import com.anytypeio.anytype.domain.dataview.interactor.RemoveTagFromDataViewRecord
import com.anytypeio.anytype.domain.dataview.interactor.UpdateDataViewRecord
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.mocking.MockDataFactory
import com.anytypeio.anytype.presentation.page.editor.ThemeColor
import com.anytypeio.anytype.presentation.relations.ObjectSetConfig
import com.anytypeio.anytype.presentation.relations.providers.DataViewObjectRelationProvider
import com.anytypeio.anytype.presentation.relations.providers.DataViewObjectValueProvider
import com.anytypeio.anytype.presentation.relations.providers.ObjectDetailProvider
import com.anytypeio.anytype.presentation.relations.providers.ObjectTypeProvider
import com.anytypeio.anytype.presentation.sets.ObjectSet
import com.anytypeio.anytype.presentation.sets.ObjectSetObjectRelationValueViewModel
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.database.modals.ObjectRelationValueFragment
import com.anytypeio.anytype.utils.CoroutinesTestRule
import com.anytypeio.anytype.utils.TestUtils.withRecyclerView
import com.anytypeio.anytype.utils.WithTextColor
import com.anytypeio.anytype.utils.WithTextColorRes
import com.bartoszlipinski.disableanimationsrule.DisableAnimationsRule
import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
@LargeTest
class DisplayRelationStatusValueTest {
@Mock
lateinit var repo: BlockRepository
@Mock
lateinit var dispatcher: Dispatcher<Payload>
@Mock
lateinit var gateway: Gateway
private lateinit var addRelationOption: AddDataViewRelationOption
private lateinit var removeTagFromDataViewRecord: RemoveTagFromDataViewRecord
private lateinit var addTagToDataViewRecord: AddTagToDataViewRecord
private lateinit var updateDataViewRecord: UpdateDataViewRecord
private lateinit var updateDetail: UpdateDetail
private lateinit var urlBuilder: UrlBuilder
@get:Rule
val animationsRule = DisableAnimationsRule()
@get:Rule
val coroutineTestRule = CoroutinesTestRule()
private val root = MockDataFactory.randomUuid()
private val state = MutableStateFlow(ObjectSet.init())
private val session = ObjectSetSession()
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
addRelationOption = AddDataViewRelationOption(repo)
addTagToDataViewRecord = AddTagToDataViewRecord(repo)
updateDataViewRecord = UpdateDataViewRecord(repo)
removeTagFromDataViewRecord = RemoveTagFromDataViewRecord(repo)
updateDetail = UpdateDetail(repo)
urlBuilder = UrlBuilder(gateway)
TestObjectSetObjectRelationValueFragment.testVmFactory = ObjectSetObjectRelationValueViewModel.Factory(
relations = DataViewObjectRelationProvider(state),
values = DataViewObjectValueProvider(state, session),
details = object: ObjectDetailProvider {
override fun provide(): Map<Id, Block.Fields> = state.value.details
},
types = object: ObjectTypeProvider {
override fun provide(): List<ObjectType> = state.value.objectTypes
},
removeTagFromRecord = removeTagFromDataViewRecord,
urlBuilder = urlBuilder,
dispatcher = dispatcher,
updateDataViewRecord = updateDataViewRecord
)
}
@Test
fun shouldDisplayEditButtonAndPlusButton() {
// SETUP
val relation = MockDataFactory.randomUuid()
val target = MockDataFactory.randomUuid()
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
state.value = ObjectSet(
blocks = listOf(
Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(
Relation(
key = relation,
isMulti = true,
name = MockDataFactory.randomString(),
format = Relation.Format.STATUS,
source = Relation.Source.values().random()
)
),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
// TESTING
launchFragment(
bundleOf(
ObjectRelationValueFragment.CTX_KEY to root,
ObjectRelationValueFragment.RELATION_KEY to relation,
ObjectRelationValueFragment.TARGET_KEY to target
)
)
// Checking that the buttons are invisible
onView(withId(R.id.btnEditOrDone)).apply {
check(matches(isDisplayed()))
}
onView(withId(R.id.btnAddValue)).apply {
check(matches(isDisplayed()))
}
}
@Test
fun shouldSetRelationName() {
// SETUP
val name = "Status"
val relation = MockDataFactory.randomUuid()
val target = MockDataFactory.randomUuid()
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
state.value = ObjectSet(
blocks = listOf(
Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(
Relation(
key = relation,
isMulti = true,
name = name,
format = Relation.Format.STATUS,
source = Relation.Source.values().random()
)
),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
// TESTING
launchFragment(
bundleOf(
ObjectRelationValueFragment.CTX_KEY to root,
ObjectRelationValueFragment.RELATION_KEY to relation,
ObjectRelationValueFragment.TARGET_KEY to target
)
)
// Checking that the name is set
onView(withId(R.id.tvTagOrStatusRelationHeader)).apply {
check(matches(withText(name)))
}
}
@Test
fun shouldRenderStatusFromDV() {
// SETUP
val option1Color = ThemeColor.values().random()
val option2Color = ThemeColor.values().random()
val option1 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "In progress",
color = option1Color.title
)
val option2 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Done",
color = option2Color.title
)
val option3 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Todo",
color = ""
)
val relationKey = MockDataFactory.randomUuid()
val target = MockDataFactory.randomUuid()
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target,
relationKey to listOf(option2.id)
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
state.value = ObjectSet(
blocks = listOf(
Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(
Relation(
key = relationKey,
defaultValue = null,
isHidden = false,
isReadOnly = false,
isMulti = true,
name = MockDataFactory.randomString(),
source = Relation.Source.values().random(),
format = Relation.Format.STATUS,
selections = listOf(option1, option2, option3)
)
),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
// TESTING
launchFragment(
bundleOf(
ObjectRelationValueFragment.CTX_KEY to root,
ObjectRelationValueFragment.RELATION_KEY to relationKey,
ObjectRelationValueFragment.TARGET_KEY to target
)
)
val rvMatcher = withRecyclerView(R.id.recycler)
onView(rvMatcher.atPositionOnView(0, R.id.tvStatusName)).apply {
check(matches(withText(option2.text)))
check(matches(WithTextColor(option2Color.text)))
}
}
@Test
fun shouldRenderStatusWithDefaultColor() {
// SETUP
val option1 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "In progress",
color = ""
)
val relationKey = MockDataFactory.randomUuid()
val target = MockDataFactory.randomUuid()
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target,
relationKey to listOf(option1.id)
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
state.value = ObjectSet(
blocks = listOf(
Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(
Relation(
key = relationKey,
defaultValue = null,
isHidden = false,
isReadOnly = false,
isMulti = true,
name = MockDataFactory.randomString(),
source = Relation.Source.values().random(),
format = Relation.Format.STATUS,
selections = listOf(option1)
)
),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
// TESTING
launchFragment(
bundleOf(
ObjectRelationValueFragment.CTX_KEY to root,
ObjectRelationValueFragment.RELATION_KEY to relationKey,
ObjectRelationValueFragment.TARGET_KEY to target
)
)
val rvMatcher = withRecyclerView(R.id.recycler)
onView(rvMatcher.atPositionOnView(0, R.id.tvStatusName)).apply {
check(matches(WithTextColorRes(R.color.default_filter_tag_text_color)))
}
}
@Test
fun shouldRenderEmptyState() {
// SETUP
val option1 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "In progress",
color = MockDataFactory.randomString()
)
val option2 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Done",
color = MockDataFactory.randomString()
)
val option3 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Todo",
color = MockDataFactory.randomString()
)
val relationKey = MockDataFactory.randomUuid()
val target = MockDataFactory.randomUuid()
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
state.value = ObjectSet(
blocks = listOf(
Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(
Relation(
key = relationKey,
defaultValue = null,
isHidden = false,
isReadOnly = false,
isMulti = true,
name = MockDataFactory.randomString(),
source = Relation.Source.values().random(),
format = Relation.Format.STATUS,
selections = listOf(option1, option2, option3)
)
),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
// TESTING
launchFragment(
bundleOf(
ObjectRelationValueFragment.CTX_KEY to root,
ObjectRelationValueFragment.RELATION_KEY to relationKey,
ObjectRelationValueFragment.TARGET_KEY to target
)
)
val rvMatcher = withRecyclerView(R.id.recycler)
onView(rvMatcher.atPositionOnView(0, R.id.tvEmptyMessage)).apply {
check(matches(isDisplayed()))
}
}
private fun launchFragment(args: Bundle): FragmentScenario<TestObjectSetObjectRelationValueFragment> {
return launchFragmentInContainer<TestObjectSetObjectRelationValueFragment>(
fragmentArgs = args,
themeResId = R.style.AppTheme
)
}
}

View file

@ -0,0 +1,518 @@
package com.anytypeio.anytype.features.relations
import android.os.Bundle
import androidx.core.os.bundleOf
import androidx.fragment.app.testing.FragmentScenario
import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.*
import com.anytypeio.anytype.domain.`object`.UpdateDetail
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.config.Gateway
import com.anytypeio.anytype.domain.dataview.interactor.AddDataViewRelationOption
import com.anytypeio.anytype.domain.dataview.interactor.AddTagToDataViewRecord
import com.anytypeio.anytype.domain.dataview.interactor.RemoveTagFromDataViewRecord
import com.anytypeio.anytype.domain.dataview.interactor.UpdateDataViewRecord
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.mocking.MockDataFactory
import com.anytypeio.anytype.presentation.page.editor.ThemeColor
import com.anytypeio.anytype.presentation.relations.ObjectSetConfig
import com.anytypeio.anytype.presentation.relations.providers.DataViewObjectRelationProvider
import com.anytypeio.anytype.presentation.relations.providers.DataViewObjectValueProvider
import com.anytypeio.anytype.presentation.relations.providers.ObjectDetailProvider
import com.anytypeio.anytype.presentation.relations.providers.ObjectTypeProvider
import com.anytypeio.anytype.presentation.sets.ObjectSet
import com.anytypeio.anytype.presentation.sets.ObjectSetObjectRelationValueViewModel
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.database.modals.ObjectRelationValueFragment
import com.anytypeio.anytype.utils.CoroutinesTestRule
import com.anytypeio.anytype.utils.TestUtils.withRecyclerView
import com.anytypeio.anytype.utils.WithTextColor
import com.anytypeio.anytype.utils.WithTextColorRes
import com.bartoszlipinski.disableanimationsrule.DisableAnimationsRule
import kotlinx.coroutines.flow.MutableStateFlow
import org.hamcrest.core.IsNot.not
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
@LargeTest
class DisplayRelationTagValueTest {
@Mock
lateinit var repo: BlockRepository
@Mock
lateinit var gateway: Gateway
@Mock
lateinit var dispatcher: Dispatcher<Payload>
private lateinit var addRelationOption: AddDataViewRelationOption
private lateinit var removeTagFromDataViewRecord: RemoveTagFromDataViewRecord
private lateinit var addTagToDataViewRecord: AddTagToDataViewRecord
private lateinit var updateDataViewRecord: UpdateDataViewRecord
private lateinit var updateDetail: UpdateDetail
private lateinit var urlBuilder: UrlBuilder
@get:Rule
val animationsRule = DisableAnimationsRule()
@get:Rule
val coroutineTestRule = CoroutinesTestRule()
private val root = MockDataFactory.randomUuid()
private val state = MutableStateFlow(ObjectSet.init())
private val session = ObjectSetSession()
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
addRelationOption = AddDataViewRelationOption(repo)
addTagToDataViewRecord = AddTagToDataViewRecord(repo)
removeTagFromDataViewRecord = RemoveTagFromDataViewRecord(repo)
updateDataViewRecord = UpdateDataViewRecord(repo)
updateDetail = UpdateDetail(repo)
urlBuilder = UrlBuilder(gateway)
TestObjectSetObjectRelationValueFragment.testVmFactory = ObjectSetObjectRelationValueViewModel.Factory(
relations = DataViewObjectRelationProvider(state),
values = DataViewObjectValueProvider(state, session),
details = object: ObjectDetailProvider {
override fun provide(): Map<Id, Block.Fields> = state.value.details
},
types = object: ObjectTypeProvider {
override fun provide(): List<ObjectType> = state.value.objectTypes
},
removeTagFromRecord = removeTagFromDataViewRecord,
urlBuilder = urlBuilder,
dispatcher = dispatcher,
updateDataViewRecord = updateDataViewRecord
)
}
@Test
fun shouldDisplayEditButtonAndPlusButton() {
// SETUP
val relation = MockDataFactory.randomUuid()
val target = MockDataFactory.randomUuid()
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
state.value = ObjectSet(
blocks = listOf(
Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(
Relation(
key = relation,
isMulti = true,
name = MockDataFactory.randomString(),
format = Relation.Format.TAG,
source = Relation.Source.values().random()
)
),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
// TESTING
launchFragment(
bundleOf(
ObjectRelationValueFragment.CTX_KEY to root,
ObjectRelationValueFragment.RELATION_KEY to relation,
ObjectRelationValueFragment.TARGET_KEY to target
)
)
// Checking that the buttons are invisible
onView(withId(R.id.btnEditOrDone)).apply {
check(matches(isDisplayed()))
}
onView(withId(R.id.btnAddValue)).apply {
check(matches(isDisplayed()))
}
}
@Test
fun shouldSetRelationName() {
// SETUP
val name = "Tag"
val relation = MockDataFactory.randomUuid()
val target = MockDataFactory.randomUuid()
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
state.value = ObjectSet(
blocks = listOf(
Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(
Relation(
key = relation,
isMulti = true,
name = name,
format = Relation.Format.TAG,
source = Relation.Source.values().random()
)
),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
// TESTING
launchFragment(
bundleOf(
ObjectRelationValueFragment.CTX_KEY to root,
ObjectRelationValueFragment.RELATION_KEY to relation,
ObjectRelationValueFragment.TARGET_KEY to target
)
)
// Checking that the name is set
onView(withId(R.id.tvTagOrStatusRelationHeader)).apply {
check(matches(withText(name)))
}
}
@Test
fun shouldRenderTwoLastTagsFromDvWithFilterContainerInvisible() {
// SETUP
val option1Color = ThemeColor.values().random()
val option2Color = ThemeColor.values().random()
val option1 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Architect",
color = option1Color.title
)
val option2 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Manager",
color = option2Color.title
)
val option3 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Developer",
color = ""
)
val relationKey = MockDataFactory.randomUuid()
val target = MockDataFactory.randomUuid()
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target,
relationKey to listOf(option2.id, option3.id)
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
state.value = ObjectSet(
blocks = listOf(
Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(
Relation(
key = relationKey,
defaultValue = null,
isHidden = false,
isReadOnly = false,
isMulti = true,
name = MockDataFactory.randomString(),
source = Relation.Source.values().random(),
format = Relation.Format.TAG,
selections = listOf(option1, option2, option3)
)
),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
// TESTING
launchFragment(
bundleOf(
ObjectRelationValueFragment.CTX_KEY to root,
ObjectRelationValueFragment.RELATION_KEY to relationKey,
ObjectRelationValueFragment.TARGET_KEY to target
)
)
val rvMatcher = withRecyclerView(R.id.recycler)
onView(withId(R.id.filterInputContainer)).apply {
check(matches(not(isDisplayed())))
}
onView(rvMatcher.atPositionOnView(0, R.id.tvTagName)).apply {
check(matches(withText(option2.text)))
check(matches(WithTextColor(option2Color.text)))
}
onView(rvMatcher.atPositionOnView(1, R.id.tvTagName)).apply {
check(matches(withText(option3.text)))
check(matches(WithTextColorRes(R.color.default_filter_tag_text_color)))
}
}
@Test
fun shouldRenderOnlyOneTagWithDefaultColor() {
// SETUP
val option1 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Architect",
color = ""
)
val relationKey = MockDataFactory.randomUuid()
val target = MockDataFactory.randomUuid()
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target,
relationKey to listOf(option1.id)
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
state.value = ObjectSet(
blocks = listOf(
Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(
Relation(
key = relationKey,
defaultValue = null,
isHidden = false,
isReadOnly = false,
isMulti = true,
name = MockDataFactory.randomString(),
source = Relation.Source.values().random(),
format = Relation.Format.TAG,
selections = listOf(option1)
)
),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
// TESTING
launchFragment(
bundleOf(
ObjectRelationValueFragment.CTX_KEY to root,
ObjectRelationValueFragment.RELATION_KEY to relationKey,
ObjectRelationValueFragment.TARGET_KEY to target
)
)
val rvMatcher = withRecyclerView(R.id.recycler)
onView(rvMatcher.atPositionOnView(0, R.id.tvTagName)).apply {
check(matches(WithTextColorRes(R.color.default_filter_tag_text_color)))
}
}
@Test
fun shouldRenderEmptyState() {
// SETUP
val option1 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "TAG1",
color = MockDataFactory.randomString()
)
val option2 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "TAG 2",
color = MockDataFactory.randomString()
)
val option3 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "TAG 3",
color = MockDataFactory.randomString()
)
val relationKey = MockDataFactory.randomUuid()
val target = MockDataFactory.randomUuid()
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
state.value = ObjectSet(
blocks = listOf(
Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(
Relation(
key = relationKey,
defaultValue = null,
isHidden = false,
isReadOnly = false,
isMulti = true,
name = MockDataFactory.randomString(),
source = Relation.Source.values().random(),
format = Relation.Format.TAG,
selections = listOf(option1, option2, option3)
)
),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
// TESTING
launchFragment(
bundleOf(
ObjectRelationValueFragment.CTX_KEY to root,
ObjectRelationValueFragment.RELATION_KEY to relationKey,
ObjectRelationValueFragment.TARGET_KEY to target
)
)
val rvMatcher = withRecyclerView(R.id.recycler)
onView(rvMatcher.atPositionOnView(0, R.id.tvEmptyMessage)).apply {
check(matches(isDisplayed()))
}
}
private fun launchFragment(args: Bundle): FragmentScenario<TestObjectSetObjectRelationValueFragment> {
return launchFragmentInContainer<TestObjectSetObjectRelationValueFragment>(
fragmentArgs = args,
themeResId = R.style.AppTheme
)
}
}

View file

@ -0,0 +1,328 @@
package com.anytypeio.anytype.features.relations
import android.os.Bundle
import androidx.core.os.bundleOf
import androidx.fragment.app.testing.FragmentScenario
import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.*
import com.anytypeio.anytype.domain.`object`.UpdateDetail
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.config.Gateway
import com.anytypeio.anytype.domain.dataview.interactor.AddDataViewRelationOption
import com.anytypeio.anytype.domain.dataview.interactor.AddTagToDataViewRecord
import com.anytypeio.anytype.domain.dataview.interactor.RemoveTagFromDataViewRecord
import com.anytypeio.anytype.domain.dataview.interactor.UpdateDataViewRecord
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.mocking.MockDataFactory
import com.anytypeio.anytype.presentation.page.editor.ThemeColor
import com.anytypeio.anytype.presentation.relations.ObjectSetConfig
import com.anytypeio.anytype.presentation.relations.providers.DataViewObjectRelationProvider
import com.anytypeio.anytype.presentation.relations.providers.DataViewObjectValueProvider
import com.anytypeio.anytype.presentation.relations.providers.ObjectDetailProvider
import com.anytypeio.anytype.presentation.relations.providers.ObjectTypeProvider
import com.anytypeio.anytype.presentation.sets.ObjectSet
import com.anytypeio.anytype.presentation.sets.ObjectSetObjectRelationValueViewModel
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.database.modals.ObjectRelationValueFragment
import com.anytypeio.anytype.utils.*
import com.bartoszlipinski.disableanimationsrule.DisableAnimationsRule
import com.nhaarman.mockitokotlin2.times
import com.nhaarman.mockitokotlin2.verifyBlocking
import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
@LargeTest
class EditRelationTagValueTest {
@Mock
lateinit var repo: BlockRepository
@Mock
lateinit var gateway: Gateway
@Mock
lateinit var dispatcher: Dispatcher<Payload>
private lateinit var addRelationOption: AddDataViewRelationOption
private lateinit var removeTagFromDataViewRecord: RemoveTagFromDataViewRecord
private lateinit var updateDataViewRecord: UpdateDataViewRecord
private lateinit var addTagToDataViewRecord: AddTagToDataViewRecord
private lateinit var updateDetail: UpdateDetail
private lateinit var urlBuilder: UrlBuilder
@get:Rule
val animationsRule = DisableAnimationsRule()
@get:Rule
val coroutineTestRule = CoroutinesTestRule()
private val ctx = MockDataFactory.randomUuid()
private val state = MutableStateFlow(ObjectSet.init())
private val session = ObjectSetSession()
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
addRelationOption = AddDataViewRelationOption(repo)
addTagToDataViewRecord = AddTagToDataViewRecord(repo)
removeTagFromDataViewRecord = RemoveTagFromDataViewRecord(repo)
updateDataViewRecord = UpdateDataViewRecord(repo)
updateDetail = UpdateDetail(repo)
urlBuilder = UrlBuilder(gateway)
TestObjectSetObjectRelationValueFragment.testVmFactory = ObjectSetObjectRelationValueViewModel.Factory(
relations = DataViewObjectRelationProvider(state),
values = DataViewObjectValueProvider(state, session),
details = object : ObjectDetailProvider {
override fun provide(): Map<Id, Block.Fields> = state.value.details
},
types = object : ObjectTypeProvider {
override fun provide(): List<ObjectType> = state.value.objectTypes
},
removeTagFromRecord = removeTagFromDataViewRecord,
urlBuilder = urlBuilder,
dispatcher = dispatcher,
updateDataViewRecord = updateDataViewRecord
)
}
@Test
fun shouldRenderTwoTagsInReadThenInEditMode() {
// SETUP
val option1Color = ThemeColor.values().random()
val option2Color = ThemeColor.values().random()
val option1 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Architect",
color = option1Color.title
)
val option2 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Manager",
color = option2Color.title
)
val option3 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Developer",
color = ""
)
val relationKey = MockDataFactory.randomUuid()
val target = MockDataFactory.randomUuid()
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target,
relationKey to listOf(option2.id, option3.id)
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
state.value = ObjectSet(
blocks = listOf(
Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(
Relation(
key = relationKey,
defaultValue = null,
isHidden = false,
isReadOnly = false,
isMulti = true,
name = "Roles",
source = Relation.Source.values().random(),
format = Relation.Format.TAG,
selections = listOf(option1, option2, option3)
)
),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
// TESTING
launchFragment(
bundleOf(
ObjectRelationValueFragment.CTX_KEY to ctx,
ObjectRelationValueFragment.RELATION_KEY to relationKey,
ObjectRelationValueFragment.TARGET_KEY to target
)
)
val editOrDoneBtn = R.id.btnEditOrDone.matchView()
editOrDoneBtn.checkHasText(R.string.edit)
with(R.id.recycler.rVMatcher()) {
onItemView(0, R.id.tvTagName).checkHasText(option2.text)
onItemView(0, R.id.tvTagName).checkHasTextColor(option2Color.text)
onItemView(0, R.id.btnRemoveTag).checkIsNotDisplayed()
onItemView(0, R.id.btnDragAndDropTag).checkIsNotDisplayed()
onItemView(1, R.id.tvTagName).checkHasText(option3.text)
onItemView(1, R.id.btnRemoveTag).checkIsNotDisplayed()
onItemView(1, R.id.btnDragAndDropTag).checkIsNotDisplayed()
checkIsRecyclerSize(2)
}
editOrDoneBtn.performClick()
editOrDoneBtn.checkHasText(R.string.done)
with(R.id.recycler.rVMatcher()) {
onItemView(0, R.id.tvTagName).checkHasText(option2.text)
onItemView(0, R.id.tvTagName).checkHasTextColor(option2Color.text)
onItemView(0, R.id.btnRemoveTag).checkIsDisplayed()
onItemView(0, R.id.btnDragAndDropTag).checkIsDisplayed()
onItemView(1, R.id.tvTagName).checkHasText(option3.text)
onItemView(1, R.id.btnRemoveTag).checkIsDisplayed()
onItemView(1, R.id.btnDragAndDropTag).checkIsDisplayed()
checkIsRecyclerSize(2)
}
}
@Test
fun shouldRequestRemovingLastTagWhenRemoveButtonPressed() {
// SETUP
val option1Color = ThemeColor.values().random()
val option2Color = ThemeColor.values().random()
val option1 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Architect",
color = option1Color.title
)
val option2 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Manager",
color = option2Color.title
)
val option3 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Developer",
color = ""
)
val relationKey = MockDataFactory.randomUuid()
val target = MockDataFactory.randomUuid()
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target,
relationKey to listOf(option2.id, option3.id)
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
val dv = Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(
Relation(
key = relationKey,
defaultValue = null,
isHidden = false,
isReadOnly = false,
isMulti = true,
name = "Roles",
source = Relation.Source.values().random(),
format = Relation.Format.TAG,
selections = listOf(option1, option2, option3)
)
),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
state.value = ObjectSet(
blocks = listOf(dv),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
// TESTING
launchFragment(
bundleOf(
ObjectRelationValueFragment.CTX_KEY to ctx,
ObjectRelationValueFragment.DATAVIEW_KEY to dv.id,
ObjectRelationValueFragment.VIEWER_KEY to viewer.id,
ObjectRelationValueFragment.RELATION_KEY to relationKey,
ObjectRelationValueFragment.TARGET_KEY to target
)
)
R.id.btnEditOrDone.performClick()
val rvMatcher = R.id.recycler.rVMatcher()
rvMatcher.onItemView(1, R.id.btnRemoveTag).performClick()
verifyBlocking(repo, times(1)) {
updateDataViewRecord(
context = ctx,
target = dv.id,
record = target,
values = mapOf(
ObjectSetConfig.ID_KEY to target,
relationKey to listOf(option2.id)
)
)
}
}
private fun launchFragment(args: Bundle): FragmentScenario<TestObjectSetObjectRelationValueFragment> {
return launchFragmentInContainer<TestObjectSetObjectRelationValueFragment>(
fragmentArgs = args,
themeResId = R.style.AppTheme
)
}
}

View file

@ -0,0 +1,590 @@
package com.anytypeio.anytype.features.relations
import android.os.Bundle
import androidx.core.os.bundleOf
import androidx.fragment.app.testing.FragmentScenario
import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.Relation
import com.anytypeio.anytype.core_utils.ext.timeInSecondsFormat
import com.anytypeio.anytype.mocking.MockDataFactory
import com.anytypeio.anytype.presentation.relations.ObjectSetConfig
import com.anytypeio.anytype.presentation.relations.providers.DataViewObjectRelationProvider
import com.anytypeio.anytype.presentation.relations.providers.DataViewObjectValueProvider
import com.anytypeio.anytype.presentation.sets.ObjectRelationDateValueViewModel
import com.anytypeio.anytype.presentation.sets.ObjectSet
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
import com.anytypeio.anytype.presentation.sets.TIME_FORMAT_DEFAULT
import com.anytypeio.anytype.ui.relations.ObjectRelationDateValueFragment
import com.anytypeio.anytype.utils.CoroutinesTestRule
import com.bartoszlipinski.disableanimationsrule.DisableAnimationsRule
import kotlinx.coroutines.flow.MutableStateFlow
import org.hamcrest.CoreMatchers.not
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.MockitoAnnotations
import java.util.*
@RunWith(AndroidJUnit4::class)
@LargeTest
class ObjectRelationDateValueTest {
@get:Rule
val animationsRule = DisableAnimationsRule()
@get:Rule
val coroutineTestRule = CoroutinesTestRule()
val root = MockDataFactory.randomUuid()
private val state = MutableStateFlow(ObjectSet.init())
private val session = ObjectSetSession()
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
TestObjectRelationDateValueFragment.testVmFactory =
ObjectRelationDateValueViewModel.Factory(
relations = DataViewObjectRelationProvider(state),
values = DataViewObjectValueProvider(state, session)
)
}
@Test
fun shouldSetNullDateValue() {
// SETUP
val relationText = "Birth date"
val valueDate: Long? = null
val target = MockDataFactory.randomUuid()
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
val relation = Relation(
key = MockDataFactory.randomUuid(),
name = relationText,
format = Relation.Format.DATE,
source = Relation.Source.values().random()
)
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target,
relation.key to valueDate
)
state.value = ObjectSet(
blocks = listOf(
Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(relation),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
launchFragment(
bundleOf(
ObjectRelationDateValueFragment.CONTEXT_ID to root,
ObjectRelationDateValueFragment.RELATION_ID to relation.key,
ObjectRelationDateValueFragment.OBJECT_ID to target,
ObjectRelationDateValueFragment.FLOW_KEY to ObjectRelationDateValueFragment.FLOW_DATAVIEW
)
)
onView(withId(R.id.ivTodayCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.ivYesterdayCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.ivTomorrowCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.ivExactDayCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.tvDate)).check(matches(withText("")))
}
@Test
fun shouldSetTodayDateValue() {
// SETUP
val relationText = "Birth date"
val valueDate: Long = System.currentTimeMillis() / 1000
val target = MockDataFactory.randomUuid()
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
val relation = Relation(
key = MockDataFactory.randomUuid(),
name = relationText,
format = Relation.Format.DATE,
source = Relation.Source.values().random()
)
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target,
relation.key to valueDate
)
state.value = ObjectSet(
blocks = listOf(
Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(relation),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
launchFragment(
bundleOf(
ObjectRelationDateValueFragment.CONTEXT_ID to root,
ObjectRelationDateValueFragment.RELATION_ID to relation.key,
ObjectRelationDateValueFragment.OBJECT_ID to target,
ObjectRelationDateValueFragment.FLOW_KEY to ObjectRelationDateValueFragment.FLOW_DATAVIEW
)
)
onView(withId(R.id.ivTodayCheck)).check(matches(isDisplayed()))
onView(withId(R.id.ivYesterdayCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.ivTomorrowCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.ivExactDayCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.tvDate)).check(matches(withText("")))
}
@Test
fun shouldSetTomorrowDateValue() {
// SETUP
val relationText = "Birth date"
val calendar = Calendar.getInstance().apply { add(Calendar.DATE, 1) }
val valueDate: Long = calendar.timeInMillis / 1000
val target = MockDataFactory.randomUuid()
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
val relation = Relation(
key = MockDataFactory.randomUuid(),
name = relationText,
format = Relation.Format.DATE,
source = Relation.Source.values().random()
)
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target,
relation.key to valueDate
)
state.value = ObjectSet(
blocks = listOf(
Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(relation),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
launchFragment(
bundleOf(
ObjectRelationDateValueFragment.CONTEXT_ID to root,
ObjectRelationDateValueFragment.RELATION_ID to relation.key,
ObjectRelationDateValueFragment.OBJECT_ID to target,
ObjectRelationDateValueFragment.FLOW_KEY to ObjectRelationDateValueFragment.FLOW_DATAVIEW
)
)
onView(withId(R.id.ivTomorrowCheck)).check(matches((isDisplayed())))
onView(withId(R.id.ivTodayCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.ivYesterdayCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.ivExactDayCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.tvDate)).check(matches(withText("")))
}
@Test
fun shouldSetYesterdayDateValue() {
// SETUP
val relationText = "Birth date"
val calendar = Calendar.getInstance().apply { add(Calendar.DATE, -1) }
val valueDate: Long = calendar.timeInMillis / 1000
val target = MockDataFactory.randomUuid()
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
val relation = Relation(
key = MockDataFactory.randomUuid(),
name = relationText,
format = Relation.Format.DATE,
source = Relation.Source.values().random()
)
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target,
relation.key to valueDate
)
state.value = ObjectSet(
blocks = listOf(
Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(relation),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
launchFragment(
bundleOf(
ObjectRelationDateValueFragment.CONTEXT_ID to root,
ObjectRelationDateValueFragment.RELATION_ID to relation.key,
ObjectRelationDateValueFragment.OBJECT_ID to target,
ObjectRelationDateValueFragment.FLOW_KEY to ObjectRelationDateValueFragment.FLOW_DATAVIEW
)
)
onView(withId(R.id.ivYesterdayCheck)).check(matches((isDisplayed())))
onView(withId(R.id.ivTodayCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.ivTomorrowCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.ivExactDayCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.tvDate)).check(matches(withText("")))
}
@Test
fun shouldSetExactDayDateValue() {
// SETUP
val relationText = "Birth date"
val calendar = Calendar.getInstance().apply { add(Calendar.DAY_OF_YEAR, 45) }
val valueDate: Long = calendar.timeInMillis / 1000
val target = MockDataFactory.randomUuid()
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
val relation = Relation(
key = MockDataFactory.randomUuid(),
name = relationText,
format = Relation.Format.DATE,
source = Relation.Source.values().random()
)
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target,
relation.key to valueDate
)
state.value = ObjectSet(
blocks = listOf(
Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(relation),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
launchFragment(
bundleOf(
ObjectRelationDateValueFragment.CONTEXT_ID to root,
ObjectRelationDateValueFragment.RELATION_ID to relation.key,
ObjectRelationDateValueFragment.OBJECT_ID to target,
ObjectRelationDateValueFragment.FLOW_KEY to ObjectRelationDateValueFragment.FLOW_DATAVIEW
)
)
val exactDateFormat = valueDate.timeInSecondsFormat(TIME_FORMAT_DEFAULT)
onView(withId(R.id.ivYesterdayCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.ivTodayCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.ivTomorrowCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.ivExactDayCheck)).check(matches((isDisplayed())))
onView(withId(R.id.tvDate)).check(matches(withText(exactDateFormat)))
}
@Test
fun shouldSetExactDayDateValueAndThenUpdateWithDatePicker() {
// SETUP
val relationText = "Birth date"
val calendar = Calendar.getInstance().apply { add(Calendar.DAY_OF_YEAR, 45) }
val valueDate: Long = calendar.timeInMillis / 1000
val target = MockDataFactory.randomUuid()
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
val relation = Relation(
key = MockDataFactory.randomUuid(),
name = relationText,
format = Relation.Format.DATE,
source = Relation.Source.values().random()
)
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target,
relation.key to valueDate
)
state.value = ObjectSet(
blocks = listOf(
Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(relation),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
val fragment = launchFragment(
bundleOf(
ObjectRelationDateValueFragment.CONTEXT_ID to root,
ObjectRelationDateValueFragment.RELATION_ID to relation.key,
ObjectRelationDateValueFragment.OBJECT_ID to target,
ObjectRelationDateValueFragment.FLOW_KEY to ObjectRelationDateValueFragment.FLOW_DATAVIEW
)
)
val exactDateFormat = valueDate.timeInSecondsFormat(TIME_FORMAT_DEFAULT)
onView(withId(R.id.ivYesterdayCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.ivTodayCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.ivTomorrowCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.ivExactDayCheck)).check(matches((isDisplayed())))
onView(withId(R.id.tvDate)).check(matches(withText(exactDateFormat)))
val dateUpdate = Calendar.getInstance().apply { add(Calendar.DATE, 17) }
val valueUpdate: Long = dateUpdate.timeInMillis / 1000
fragment.onFragment {
it.onPickDate(valueUpdate)
}
val updatedDateFormat = valueUpdate.timeInSecondsFormat(TIME_FORMAT_DEFAULT)
onView(withId(R.id.ivTomorrowCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.ivTodayCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.ivYesterdayCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.ivExactDayCheck)).check(matches((isDisplayed())))
onView(withId(R.id.tvDate)).check(matches(withText(updatedDateFormat)))
}
@Test
fun shouldSetExactDayDateValueAndThenUpdateToTodayTomorrowAndYesterday() {
// SETUP
val relationText = "Birth date"
val calendar = Calendar.getInstance().apply { add(Calendar.DAY_OF_YEAR, 45) }
val valueDate: Long = calendar.timeInMillis / 1000
val target = MockDataFactory.randomUuid()
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
val relation = Relation(
key = MockDataFactory.randomUuid(),
name = relationText,
format = Relation.Format.DATE,
source = Relation.Source.values().random()
)
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target,
relation.key to valueDate
)
state.value = ObjectSet(
blocks = listOf(
Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(relation),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
val fragment = launchFragment(
bundleOf(
ObjectRelationDateValueFragment.CONTEXT_ID to root,
ObjectRelationDateValueFragment.RELATION_ID to relation.key,
ObjectRelationDateValueFragment.OBJECT_ID to target,
ObjectRelationDateValueFragment.FLOW_KEY to ObjectRelationDateValueFragment.FLOW_DATAVIEW
)
)
val exactDateFormat = valueDate.timeInSecondsFormat(TIME_FORMAT_DEFAULT)
onView(withId(R.id.ivYesterdayCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.ivTodayCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.ivTomorrowCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.ivExactDayCheck)).check(matches((isDisplayed())))
onView(withId(R.id.tvDate)).check(matches(withText(exactDateFormat)))
fragment.onFragment { it.vm.onTodayClicked() }
onView(withId(R.id.ivTomorrowCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.ivTodayCheck)).check(matches((isDisplayed())))
onView(withId(R.id.ivYesterdayCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.ivExactDayCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.tvDate)).check(matches(withText("")))
fragment.onFragment { it.vm.onTomorrowClicked() }
onView(withId(R.id.ivTomorrowCheck)).check(matches((isDisplayed())))
onView(withId(R.id.ivTodayCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.ivYesterdayCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.ivExactDayCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.tvDate)).check(matches(withText("")))
fragment.onFragment { it.vm.onYesterdayClicked() }
onView(withId(R.id.ivTomorrowCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.ivTodayCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.ivYesterdayCheck)).check(matches((isDisplayed())))
onView(withId(R.id.ivExactDayCheck)).check(matches(not(isDisplayed())))
onView(withId(R.id.tvDate)).check(matches(withText("")))
}
private fun launchFragment(args: Bundle): FragmentScenario<TestObjectRelationDateValueFragment> {
return launchFragmentInContainer<TestObjectRelationDateValueFragment>(
fragmentArgs = args,
themeResId = R.style.AppTheme
)
}
}

View file

@ -0,0 +1,631 @@
package com.anytypeio.anytype.features.relations
import android.os.Bundle
import androidx.core.os.bundleOf
import androidx.fragment.app.testing.FragmentScenario
import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_models.Relation
import com.anytypeio.anytype.core_utils.ext.toTimeSeconds
import com.anytypeio.anytype.domain.`object`.UpdateDetail
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.config.Gateway
import com.anytypeio.anytype.domain.dataview.interactor.ObjectRelationList
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.mocking.MockDataFactory
import com.anytypeio.anytype.presentation.page.Editor
import com.anytypeio.anytype.presentation.page.editor.DetailModificationManager
import com.anytypeio.anytype.presentation.page.editor.ThemeColor
import com.anytypeio.anytype.presentation.relations.ObjectRelationListViewModelFactory
import com.anytypeio.anytype.presentation.sets.MONTH_DAY_AND_YEAR
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.relations.ObjectRelationListFragment
import com.anytypeio.anytype.utils.*
import com.bartoszlipinski.disableanimationsrule.DisableAnimationsRule
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import java.text.SimpleDateFormat
import java.util.*
@RunWith(AndroidJUnit4::class)
@LargeTest
class ObjectRelationListTest {
@get:Rule
val animationsRule = DisableAnimationsRule()
@get:Rule
val coroutineTestRule = CoroutinesTestRule()
@Mock
lateinit var gateway: Gateway
@Mock
lateinit var repo: BlockRepository
@Mock
lateinit var dispatcher: Dispatcher<Payload>
@Mock
lateinit var detailModificationManager: DetailModificationManager
private lateinit var objectRelationList: ObjectRelationList
private lateinit var updateDetail: UpdateDetail
private val ctx = MockDataFactory.randomUuid()
private val storage = Editor.Storage()
lateinit var urlBuilder: UrlBuilder
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
urlBuilder = UrlBuilder(gateway)
objectRelationList = ObjectRelationList(repo)
updateDetail = UpdateDetail(repo)
TestObjectRelationListFragment.testVmFactory = ObjectRelationListViewModelFactory(
stores = storage,
urlBuilder = urlBuilder,
objectRelationList = objectRelationList,
dispatcher = dispatcher,
detailModificationManager = detailModificationManager,
updateDetail = updateDetail
)
}
@Test(expected = RuntimeException::class)
fun shouldThrowAnExceptionIfArgsNotProvided() {
launchFragment(bundleOf())
}
@Test
fun shouldDisplayOneRelationWithoutValue() {
// SETUP
val name = "Description"
val relation = Relation(
key = MockDataFactory.randomUuid(),
format = Relation.Format.LONG_TEXT,
source = Relation.Source.values().random(),
name = name
)
runBlocking {
storage.relations.update(
listOf(relation)
)
}
launchFragment(bundleOf(
ObjectRelationListFragment.ARG_CTX to ctx,
ObjectRelationListFragment.ARG_MODE to ObjectRelationListFragment.MODE_LIST
))
// TESTING
with(R.id.recycler.rVMatcher()) {
onItemView(0, R.id.tvRelationTitle).checkHasText(name)
onItemView(0, R.id.tvRelationValue).checkHasText("")
checkIsRecyclerSize(1)
}
}
@Test
fun shouldDisplayOnlyFirstRelationBecauseSecondIsHidden() {
// SETUP
val name1 = "Description"
val name2 = "Identifier"
val relation1 = Relation(
key = MockDataFactory.randomUuid(),
format = Relation.Format.LONG_TEXT,
source = Relation.Source.values().random(),
isHidden = true,
name = name1
)
val relation2 = Relation(
key = MockDataFactory.randomUuid(),
format = Relation.Format.LONG_TEXT,
source = Relation.Source.values().random(),
name = name2
)
runBlocking {
storage.relations.update(
listOf(relation1, relation2)
)
}
launchFragment(bundleOf(
ObjectRelationListFragment.ARG_CTX to ctx,
ObjectRelationListFragment.ARG_MODE to ObjectRelationListFragment.MODE_LIST
))
// TESTING
with(R.id.recycler.rVMatcher()) {
onItemView(0, R.id.tvRelationTitle).checkHasText(name2)
onItemView(0, R.id.tvRelationValue).checkHasText("")
checkIsRecyclerSize(1)
}
}
@Test
fun shouldDisplayTwoRelationsWithoutValue() {
// SETUP
val name1 = "Description"
val name2 = "Comment"
val relation1 = Relation(
key = MockDataFactory.randomUuid(),
format = Relation.Format.LONG_TEXT,
source = Relation.Source.values().random(),
name = name1
)
val relation2 = Relation(
key = MockDataFactory.randomUuid(),
format = Relation.Format.LONG_TEXT,
source = Relation.Source.values().random(),
name = name2
)
runBlocking {
storage.relations.update(
listOf(relation1, relation2)
)
}
launchFragment(bundleOf(
ObjectRelationListFragment.ARG_CTX to ctx,
ObjectRelationListFragment.ARG_MODE to ObjectRelationListFragment.MODE_LIST
))
// TESTING
with(R.id.recycler.rVMatcher()) {
onItemView(0, R.id.tvRelationTitle).checkHasText(name1)
onItemView(0, R.id.tvRelationValue).checkHasText("")
onItemView(1, R.id.tvRelationTitle).checkHasText(name2)
onItemView(1, R.id.tvRelationValue).checkHasText("")
checkIsRecyclerSize(2)
}
}
@Test
fun shouldDisplayTwoRelationsWithValues() {
// SETUP
val name1 = "Description"
val name2 = "Comment"
val value1 = "A mountain is an elevated portion of the Earth's crust, generally with steep sides that show significant exposed bedrock."
val value2 = "We've never seen that mountain before."
val relation1 = Relation(
key = MockDataFactory.randomUuid(),
format = Relation.Format.LONG_TEXT,
source = Relation.Source.values().random(),
name = name1
)
val relation2 = Relation(
key = MockDataFactory.randomUuid(),
format = Relation.Format.LONG_TEXT,
source = Relation.Source.values().random(),
name = name2
)
val relations = listOf(relation1, relation2)
val details = Block.Details(
mapOf(
ctx to Block.Fields(
mapOf(
relation1.key to value1,
relation2.key to value2,
)
)
)
)
runBlocking {
storage.relations.update(relations)
storage.details.update(details)
}
launchFragment(
bundleOf(
ObjectRelationListFragment.ARG_CTX to ctx,
ObjectRelationListFragment.ARG_MODE to ObjectRelationListFragment.MODE_LIST
)
)
// TESTING
with(R.id.recycler.rVMatcher()) {
onItemView(0, R.id.tvRelationTitle).checkHasText(name1)
onItemView(0, R.id.tvRelationValue).checkHasText(value1)
onItemView(1, R.id.tvRelationTitle).checkHasText(name2)
onItemView(1, R.id.tvRelationValue).checkHasText(value2)
checkIsRecyclerSize(2)
}
}
@Test
fun shouldDisplayTwoObjectRelationsWithNameAndAvatarInitials() {
// SETUP
val name1 = "Assignee"
val target1: Id = MockDataFactory.randomUuid()
val username1 = "Konstantin"
val name2 = "Created by"
val target2: Id = MockDataFactory.randomUuid()
val username2 = "Roman"
val relation1 = Relation(
key = MockDataFactory.randomUuid(),
format = Relation.Format.OBJECT,
source = Relation.Source.values().random(),
name = name1
)
val relation2 = Relation(
key = MockDataFactory.randomUuid(),
format = Relation.Format.OBJECT,
source = Relation.Source.values().random(),
name = name2
)
val relations = listOf(relation1, relation2)
val details = Block.Details(
mapOf(
ctx to Block.Fields(
mapOf(
relation1.key to target1,
relation2.key to target2,
)
),
target1 to Block.Fields(
mapOf(
Block.Fields.NAME_KEY to username1
)
),
target2 to Block.Fields(
mapOf(
Block.Fields.NAME_KEY to username2
)
)
)
)
runBlocking {
storage.relations.update(relations)
storage.details.update(details)
}
launchFragment(bundleOf(
ObjectRelationListFragment.ARG_CTX to ctx,
ObjectRelationListFragment.ARG_MODE to ObjectRelationListFragment.MODE_LIST
))
// TESTING
with(R.id.recycler.rVMatcher()) {
onItemView(0, R.id.tvRelationTitle).checkHasText(name1)
onItemView(0, R.id.obj0).check(matches(hasDescendant(withText(username1))))
onItemView(1, R.id.tvRelationTitle).checkHasText(name2)
onItemView(1, R.id.obj0).check(matches(hasDescendant(withText(username2))))
checkIsRecyclerSize(2)
}
}
@Test
fun shouldDisplayTwoDateRelations() {
// SETUP
val format = SimpleDateFormat(MONTH_DAY_AND_YEAR, Locale.US)
val name1 = "Date of birth"
val date1 = System.currentTimeMillis()
val date1Screen = format.format(Date(date1))
val name2 = "Last modified at"
val date2 = System.currentTimeMillis()
val date2Screen = format.format(Date(date2))
val relation1 = Relation(
key = MockDataFactory.randomUuid(),
format = Relation.Format.DATE,
source = Relation.Source.values().random(),
name = name1,
)
val relation2 = Relation(
key = MockDataFactory.randomUuid(),
format = Relation.Format.DATE,
source = Relation.Source.values().random(),
name = name2
)
val relations = listOf(relation1, relation2)
val details = Block.Details(
mapOf(
ctx to Block.Fields(
mapOf(
relation1.key to date1.toTimeSeconds(),
relation2.key to date2.toTimeSeconds(),
)
)
)
)
runBlocking {
storage.relations.update(relations)
storage.details.update(details)
}
launchFragment(bundleOf(
ObjectRelationListFragment.ARG_CTX to ctx,
ObjectRelationListFragment.ARG_MODE to ObjectRelationListFragment.MODE_LIST
))
// TESTING
with(R.id.recycler.rVMatcher()) {
onItemView(0, R.id.tvRelationTitle).checkHasText(name1)
onItemView(0, R.id.tvRelationValue).checkHasText(date1Screen)
onItemView(1, R.id.tvRelationTitle).checkHasText(name2)
onItemView(1, R.id.tvRelationValue).checkHasText(date2Screen)
checkIsRecyclerSize(2)
}
}
@Test
fun shouldDisplayTwoStatusRelations() {
// SETUP
val color1 = ThemeColor.RED
val color2 = ThemeColor.TEAL
val option1 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "In progress",
color = color1.title
)
val option2 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Done",
color = color2.title
)
val name1 = "Status 1"
val name2 = "Status 2"
val relation1 = Relation(
key = MockDataFactory.randomUuid(),
format = Relation.Format.STATUS,
source = Relation.Source.values().random(),
name = name1,
selections = listOf(option1)
)
val relation2 = Relation(
key = MockDataFactory.randomUuid(),
format = Relation.Format.STATUS,
source = Relation.Source.values().random(),
name = name2,
selections = listOf(option2)
)
val relations = listOf(relation1, relation2)
val details = Block.Details(
mapOf(
ctx to Block.Fields(
mapOf(
relation1.key to option1.id,
relation2.key to option2.id,
)
)
)
)
runBlocking {
storage.relations.update(relations)
storage.details.update(details)
}
launchFragment(bundleOf(
ObjectRelationListFragment.ARG_CTX to ctx,
ObjectRelationListFragment.ARG_MODE to ObjectRelationListFragment.MODE_LIST
))
// TESTING
with(R.id.recycler.rVMatcher()) {
onItemView(0, R.id.tvRelationTitle).checkHasText(name1)
onItemView(0, R.id.tvRelationValue).checkHasText(option1.text)
onItemView(0, R.id.tvRelationValue).checkHasTextColor(color1.text)
onItemView(1, R.id.tvRelationTitle).checkHasText(name2)
onItemView(1, R.id.tvRelationValue).checkHasText(option2.text)
onItemView(1, R.id.tvRelationValue).checkHasTextColor(color2.text)
checkIsRecyclerSize(2)
}
}
@Test
fun shouldDisplayFourTagRelations() {
// SETUP
val color1 = ThemeColor.RED
val color2 = ThemeColor.TEAL
val color3 = ThemeColor.ICE
val color4 = ThemeColor.PURPLE
val name = "Role"
val option1 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Essayist",
color = color1.title
)
val option2 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Journalist",
color = color2.title
)
val option3 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Politik",
color = color3.title
)
val option4 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Critic",
color = color4.title
)
val relation = Relation(
key = MockDataFactory.randomUuid(),
format = Relation.Format.TAG,
source = Relation.Source.values().random(),
name = name,
selections = listOf(option1, option2, option3, option4)
)
val relations = listOf(relation)
val details = Block.Details(
mapOf(
ctx to Block.Fields(
mapOf(
relation.key to listOf(option1.id, option2.id, option3.id, option4.id)
)
)
)
)
runBlocking {
storage.relations.update(relations)
storage.details.update(details)
}
launchFragment(bundleOf(
ObjectRelationListFragment.ARG_CTX to ctx,
ObjectRelationListFragment.ARG_MODE to ObjectRelationListFragment.MODE_LIST
))
// TESTING
with(R.id.recycler.rVMatcher()) {
onItemView(0, R.id.tvRelationTitle).checkHasText(name)
onItemView(0, R.id.tag0).check(matches((withText(option1.text))))
onItemView(0, R.id.tag1).check(matches((withText(option2.text))))
onItemView(0, R.id.tag2).check(matches((withText(option3.text))))
onItemView(0, R.id.tag3).check(matches((withText(option4.text))))
checkIsRecyclerSize(1)
}
}
@Test
fun shouldDisplayTwoFileRelations() {
// SETUP
val name = "Attachement"
val relation = Relation(
key = MockDataFactory.randomUuid(),
format = Relation.Format.FILE,
source = Relation.Source.values().random(),
name = name,
selections = emptyList()
)
val relations = listOf(relation)
val file1 = MockDataFactory.randomUuid()
val file2 = MockDataFactory.randomUuid()
val details = Block.Details(
mapOf(
ctx to Block.Fields(
mapOf(
relation.key to listOf(file1, file2)
)
),
file1 to Block.Fields(
mapOf(
"name" to "Document",
"ext" to "pdf",
"mime" to "application/pdf"
)
),
file2 to Block.Fields(
mapOf(
"name" to "Image",
"ext" to "jpg",
"mime" to "image/jpeg"
)
)
)
)
runBlocking {
storage.relations.update(relations)
storage.details.update(details)
}
launchFragment(bundleOf(
ObjectRelationListFragment.ARG_CTX to ctx,
ObjectRelationListFragment.ARG_MODE to ObjectRelationListFragment.MODE_LIST
))
// TESTING
with(R.id.recycler.rVMatcher()) {
onItemView(0, R.id.tvRelationTitle).checkHasText(name)
onItemView(0, R.id.file0).check(matches(hasDescendant(withText("Document"))))
onItemView(0, R.id.file1).check(matches(hasDescendant(withText("Image"))))
checkIsRecyclerSize(1)
}
}
private fun launchFragment(args: Bundle): FragmentScenario<TestObjectRelationListFragment> {
return launchFragmentInContainer<TestObjectRelationListFragment>(
fragmentArgs = args,
themeResId = R.style.AppTheme
)
}
}

View file

@ -0,0 +1,18 @@
package com.anytypeio.anytype.features.relations
import com.anytypeio.anytype.presentation.relations.AddObjectSetObjectRelationValueViewModel
import com.anytypeio.anytype.ui.relations.AddObjectSetObjectRelationValueFragment
class TestAddObjectSetObjectRelationValueFragment : AddObjectSetObjectRelationValueFragment() {
init {
factory = testVmFactory
}
override fun injectDependencies() {}
override fun releaseDependencies() {}
override fun proceedWithExiting() {}
companion object {
lateinit var testVmFactory: AddObjectSetObjectRelationValueViewModel.Factory
}
}

View file

@ -0,0 +1,18 @@
package com.anytypeio.anytype.features.relations
import com.anytypeio.anytype.presentation.sets.ObjectRelationDateValueViewModel
import com.anytypeio.anytype.ui.relations.ObjectRelationDateValueFragment
class TestObjectRelationDateValueFragment: ObjectRelationDateValueFragment() {
init {
factory = testVmFactory
}
override fun injectDependencies() {}
override fun releaseDependencies() {}
companion object {
lateinit var testVmFactory: ObjectRelationDateValueViewModel.Factory
}
}

View file

@ -0,0 +1,16 @@
package com.anytypeio.anytype.features.relations
import com.anytypeio.anytype.presentation.relations.ObjectRelationListViewModelFactory
import com.anytypeio.anytype.ui.relations.ObjectRelationListFragment
class TestObjectRelationListFragment : ObjectRelationListFragment() {
override fun injectDependencies() {
factory = testVmFactory
}
override fun releaseDependencies() {}
companion object {
lateinit var testVmFactory: ObjectRelationListViewModelFactory
}
}

View file

@ -0,0 +1,18 @@
package com.anytypeio.anytype.features.relations
import com.anytypeio.anytype.presentation.sets.ObjectRelationTextValueViewModel
import com.anytypeio.anytype.ui.relations.ObjectRelationTextValueFragment
class TestObjectRelationTextValueFragment : ObjectRelationTextValueFragment() {
init {
factory = testVmFactory
}
override fun injectDependencies() {}
override fun releaseDependencies() {}
companion object {
lateinit var testVmFactory: ObjectRelationTextValueViewModel.Factory
}
}

View file

@ -0,0 +1,17 @@
package com.anytypeio.anytype.features.relations
import com.anytypeio.anytype.presentation.sets.ObjectSetObjectRelationValueViewModel
import com.anytypeio.anytype.ui.database.modals.ObjectSetObjectRelationValueFragment
class TestObjectSetObjectRelationValueFragment : ObjectSetObjectRelationValueFragment() {
init {
factory = testVmFactory
}
override fun injectDependencies() {}
override fun releaseDependencies() {}
companion object {
lateinit var testVmFactory: ObjectSetObjectRelationValueViewModel.Factory
}
}

View file

@ -0,0 +1,172 @@
package com.anytypeio.anytype.features.sets.dv
import androidx.core.os.bundleOf
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.DVViewerRelation
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.Relation
import com.anytypeio.anytype.mocking.MockDataFactory
import com.anytypeio.anytype.ui.sets.ObjectSetFragment
import com.anytypeio.anytype.utils.checkHasText
import com.anytypeio.anytype.utils.checkIsRecyclerSize
import com.anytypeio.anytype.utils.onItemView
import com.anytypeio.anytype.utils.rVMatcher
import com.bartoszlipinski.disableanimationsrule.DisableAnimationsRule
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@LargeTest
class ObjectSetGridColumnRenderingTest : TestObjectSetSetup() {
@get:Rule
val animationsRule = DisableAnimationsRule()
override val title: Block = Block(
id = MockDataFactory.randomUuid(),
content = Block.Content.Text(
style = Block.Content.Text.Style.TITLE,
text = "Data View UI Testing",
marks = emptyList()
),
children = emptyList(),
fields = Block.Fields.empty()
)
@Before
override fun setup() {
super.setup()
}
@Test
fun shouldRenderAllColumnHeaderNamesBasedOnViewerRelations() {
val type = ObjectType(
url = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
emoji = MockDataFactory.randomString(),
layout = ObjectType.Layout.PAGE,
relations = emptyList(),
description = "",
isHidden = false
)
val relation1 = Relation(
key = MockDataFactory.randomString(),
name = "Description",
format = Relation.Format.SHORT_TEXT,
source = Relation.Source.values().random()
)
val relation2 = Relation(
key = MockDataFactory.randomString(),
name = "Year",
format = Relation.Format.NUMBER,
source = Relation.Source.values().random()
)
val relation3 = Relation(
key = MockDataFactory.randomString(),
name = "Phone",
format = Relation.Format.PHONE,
source = Relation.Source.values().random()
)
val relation4 = Relation(
key = MockDataFactory.randomString(),
name = "Website",
format = Relation.Format.URL,
source = Relation.Source.values().random()
)
val relation5 = Relation(
key = MockDataFactory.randomString(),
name = "Email",
format = Relation.Format.EMAIL,
source = Relation.Source.values().random()
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = "Default Grid View",
filters = emptyList(),
sorts = emptyList(),
viewerRelations = listOf(
DVViewerRelation(
key = relation1.key,
isVisible = true
),
DVViewerRelation(
key = relation2.key,
isVisible = true
),
DVViewerRelation(
key = relation3.key,
isVisible = true
),
DVViewerRelation(
key = relation4.key,
isVisible = true
),
DVViewerRelation(
key = relation5.key,
isVisible = true
)
),
type = Block.Content.DataView.Viewer.Type.GRID
)
val dataview = Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(relation1, relation2, relation3, relation4, relation5),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
val root = Block(
id = ctx,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(
type = Block.Content.Smart.Type.SET
),
children = listOf(header.id, dataview.id)
)
val set = listOf(root, header, title, dataview)
stubInterceptEvents()
stubOpenObjectSetWithRecord(
set = set,
relations = listOf(relation1, relation2, relation3, relation4, relation5),
details = defaultDetails,
viewer = viewer.id,
dataview = dataview.id,
records = emptyList(),
total = 1,
objectTypes = listOf(type)
)
// TESTING
launchFragment(bundleOf(ObjectSetFragment.CONTEXT_ID_KEY to ctx))
with(R.id.rvHeader.rVMatcher()) {
// There should be 5 column headers + 1 plus button
checkIsRecyclerSize(6)
onItemView(0, R.id.cellText).checkHasText(relation1.name)
onItemView(1, R.id.cellText).checkHasText(relation2.name)
onItemView(2, R.id.cellText).checkHasText(relation3.name)
onItemView(3, R.id.cellText).checkHasText(relation4.name)
onItemView(4, R.id.cellText).checkHasText(relation5.name)
}
}
}

View file

@ -0,0 +1,191 @@
package com.anytypeio.anytype.features.sets.dv
import androidx.core.os.bundleOf
import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.DVViewerRelation
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.Relation
import com.anytypeio.anytype.emojifier.data.DefaultDocumentEmojiIconProvider
import com.anytypeio.anytype.mocking.MockDataFactory
import com.anytypeio.anytype.presentation.relations.ObjectSetConfig
import com.anytypeio.anytype.ui.sets.ObjectSetFragment
import com.anytypeio.anytype.utils.checkHasText
import com.anytypeio.anytype.utils.checkIsRecyclerSize
import com.anytypeio.anytype.utils.onItemView
import com.anytypeio.anytype.utils.rVMatcher
import com.bartoszlipinski.disableanimationsrule.DisableAnimationsRule
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@LargeTest
class ObjectSetGridFileCellRenderingTest : TestObjectSetSetup() {
@get:Rule
val animationsRule = DisableAnimationsRule()
override val title: Block = Block(
id = MockDataFactory.randomUuid(),
content = Block.Content.Text(
style = Block.Content.Text.Style.TITLE,
text = "Data View UI Testing",
marks = emptyList()
),
children = emptyList(),
fields = Block.Fields.empty()
)
@Before
override fun setup() {
super.setup()
}
@Test
fun shouldRenderTwoFilesInTwoRecords() {
// SETUP
val relationName = "Files"
val file1Name = "CharlieChaplin"
val file1Id = MockDataFactory.randomUuid()
val file1Ext = "txt"
val file2Name = "Jean-PierreLéaud"
val file2Id = MockDataFactory.randomUuid()
val file2Ext = "jpeg"
val objectType = ObjectType(
url = MockDataFactory.randomUuid(),
name = "Movie",
relations = emptyList(),
emoji = MockDataFactory.randomString(),
layout = ObjectType.Layout.PROFILE,
description = "",
isHidden = false
)
val relationId = MockDataFactory.randomUuid()
val record1Id = MockDataFactory.randomUuid()
val record2Id = MockDataFactory.randomUuid()
val record1: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to record1Id,
ObjectSetConfig.NAME_KEY to "The Great Dictator",
ObjectSetConfig.EMOJI_KEY to DefaultDocumentEmojiIconProvider.DOCUMENT_SET.random(),
ObjectSetConfig.TYPE_KEY to objectType.url,
relationId to file1Id
)
val record2: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to record2Id,
ObjectSetConfig.NAME_KEY to "Les Quatre Cents Coups",
ObjectSetConfig.EMOJI_KEY to DefaultDocumentEmojiIconProvider.DOCUMENT_SET.random(),
ObjectSetConfig.TYPE_KEY to objectType.url,
relationId to file2Id
)
val relation = Relation(
key = relationId,
name = relationName,
format = Relation.Format.FILE,
source = Relation.Source.values().random()
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = listOf(
DVViewerRelation(
key = relation.key,
isVisible = true
)
),
type = Block.Content.DataView.Viewer.Type.GRID
)
val dataview = Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(relation),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
val details = Block.Details(
details = defaultDetails.details + mapOf(
file1Id to Block.Fields(
mapOf(
ObjectSetConfig.NAME_KEY to file1Name,
ObjectSetConfig.TYPE_KEY to objectType.url,
"fileExt" to file1Ext
)
),
file2Id to Block.Fields(
mapOf(
ObjectSetConfig.NAME_KEY to file2Name,
ObjectSetConfig.TYPE_KEY to objectType.url,
"fileExt" to file2Ext
)
)
)
)
val root = Block(
id = ctx,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(
type = Block.Content.Smart.Type.SET
),
children = listOf(header.id, dataview.id)
)
val set = listOf(root, header, title, dataview)
stubInterceptEvents()
stubOpenObjectSetWithRecord(
set = set,
relations = listOf(relation),
details = details,
viewer = viewer.id,
dataview = dataview.id,
records = listOf(record1, record2),
total = 1,
objectTypes = listOf(objectType)
)
// TESTING
launchFragment(bundleOf(ObjectSetFragment.CONTEXT_ID_KEY to ctx))
with(R.id.rvRows.rVMatcher()) {
checkIsRecyclerSize(2)
onItemView(0, R.id.tvTitle).checkHasText("The Great Dictator")
onItemView(0, R.id.file0).check(
ViewAssertions.matches(
ViewMatchers.hasDescendant(
ViewMatchers.withText(file1Name)
)
)
)
onItemView(1, R.id.tvTitle).checkHasText("Les Quatre Cents Coups")
onItemView(1, R.id.file0).check(
ViewAssertions.matches(
ViewMatchers.hasDescendant(
ViewMatchers.withText(file2Name)
)
)
)
}
}
}

View file

@ -0,0 +1,296 @@
package com.anytypeio.anytype.features.sets.dv
import androidx.core.os.bundleOf
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.DVViewerRelation
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.Relation
import com.anytypeio.anytype.emojifier.data.DefaultDocumentEmojiIconProvider
import com.anytypeio.anytype.mocking.MockDataFactory
import com.anytypeio.anytype.presentation.relations.ObjectSetConfig
import com.anytypeio.anytype.ui.sets.ObjectSetFragment
import com.anytypeio.anytype.utils.checkHasText
import com.anytypeio.anytype.utils.checkIsRecyclerSize
import com.anytypeio.anytype.utils.onItemView
import com.anytypeio.anytype.utils.rVMatcher
import com.bartoszlipinski.disableanimationsrule.DisableAnimationsRule
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@LargeTest
class ObjectSetGridObjectCellRenderingTest : TestObjectSetSetup() {
@get:Rule
val animationsRule = DisableAnimationsRule()
override val title: Block = Block(
id = MockDataFactory.randomUuid(),
content = Block.Content.Text(
style = Block.Content.Text.Style.TITLE,
text = "Data View UI Testing",
marks = emptyList()
),
children = emptyList(),
fields = Block.Fields.empty()
)
@Before
override fun setup() {
super.setup()
}
@Test
fun shouldRenderOneHumanObjectFromEachOfTwoRecords() {
// SETUP
val relationName = "Starring"
val object1Name = "Charlie Chaplin"
val object1Id = MockDataFactory.randomUuid()
val object2Name = "Jean-Pierre Léaud"
val object2Id = MockDataFactory.randomUuid()
val objectType = ObjectType(
url = MockDataFactory.randomUuid(),
name = "Movie",
relations = emptyList(),
emoji = MockDataFactory.randomString(),
layout = ObjectType.Layout.PROFILE,
description = "",
isHidden = false
)
val relationId = MockDataFactory.randomUuid()
val record1Id = MockDataFactory.randomUuid()
val record2Id = MockDataFactory.randomUuid()
val record1: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to record1Id,
ObjectSetConfig.NAME_KEY to "The Great Dictator",
ObjectSetConfig.EMOJI_KEY to DefaultDocumentEmojiIconProvider.DOCUMENT_SET.random(),
ObjectSetConfig.TYPE_KEY to objectType.url,
relationId to object1Id
)
val record2: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to record2Id,
ObjectSetConfig.NAME_KEY to "Les Quatre Cents Coups",
ObjectSetConfig.EMOJI_KEY to DefaultDocumentEmojiIconProvider.DOCUMENT_SET.random(),
ObjectSetConfig.TYPE_KEY to objectType.url,
relationId to object2Id
)
val relation = Relation(
key = relationId,
name = relationName,
format = Relation.Format.OBJECT,
source = Relation.Source.values().random()
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = listOf(
DVViewerRelation(
key = relation.key,
isVisible = true
)
),
type = Block.Content.DataView.Viewer.Type.GRID
)
val dataview = Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(relation),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
val details = Block.Details(
details = defaultDetails.details + mapOf(
object1Id to Block.Fields(
mapOf(
ObjectSetConfig.NAME_KEY to object1Name,
ObjectSetConfig.TYPE_KEY to objectType.url,
"iconEmoji" to "👤"
)
),
object2Id to Block.Fields(
mapOf(
ObjectSetConfig.NAME_KEY to object2Name,
ObjectSetConfig.TYPE_KEY to objectType.url,
"iconEmoji" to "👤"
)
)
)
)
val root = Block(
id = ctx,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(
type = Block.Content.Smart.Type.SET
),
children = listOf(header.id, dataview.id)
)
val set = listOf(root, header, title, dataview)
stubInterceptEvents()
stubOpenObjectSetWithRecord(
set = set,
relations = listOf(relation),
details = details,
viewer = viewer.id,
dataview = dataview.id,
records = listOf(record1, record2),
total = 1,
objectTypes = listOf(objectType)
)
// TESTING
launchFragment(bundleOf(ObjectSetFragment.CONTEXT_ID_KEY to ctx))
with(R.id.rvRows.rVMatcher()) {
checkIsRecyclerSize(2)
onItemView(0, R.id.tvTitle).checkHasText("The Great Dictator")
onItemView(0, R.id.object0).check(matches(hasDescendant(withText(object1Name))))
onItemView(1, R.id.tvTitle).checkHasText("Les Quatre Cents Coups")
onItemView(1, R.id.object0).check(matches(hasDescendant(withText(object2Name))))
}
}
@Test
fun shouldRenderTwoHumanObjectsFromOneRecord() {
// SETUP
val relationName = "Starring"
val object1Name = "Maurice Ronet"
val object1Id = MockDataFactory.randomUuid()
val object2Name = "Jeanne Moreau"
val object2Id = MockDataFactory.randomUuid()
val objectType = ObjectType(
url = MockDataFactory.randomUuid(),
name = "Movie",
relations = emptyList(),
emoji = MockDataFactory.randomString(),
layout = ObjectType.Layout.PROFILE,
description = "",
isHidden = false
)
val relationId = MockDataFactory.randomUuid()
val recordId = MockDataFactory.randomUuid()
val record1: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to recordId,
ObjectSetConfig.NAME_KEY to "Le Feu Follet",
ObjectSetConfig.EMOJI_KEY to DefaultDocumentEmojiIconProvider.DOCUMENT_SET.random(),
ObjectSetConfig.TYPE_KEY to objectType.url,
relationId to listOf(object1Id, object2Id)
)
val relation = Relation(
key = relationId,
name = relationName,
format = Relation.Format.OBJECT,
source = Relation.Source.values().random()
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = listOf(
DVViewerRelation(
key = relation.key,
isVisible = true
)
),
type = Block.Content.DataView.Viewer.Type.GRID
)
val dataview = Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(relation),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
val details = Block.Details(
details = defaultDetails.details + mapOf(
object1Id to Block.Fields(
mapOf(
ObjectSetConfig.NAME_KEY to object1Name,
ObjectSetConfig.TYPE_KEY to objectType.url,
"iconEmoji" to "👤"
)
),
object2Id to Block.Fields(
mapOf(
ObjectSetConfig.NAME_KEY to object2Name,
ObjectSetConfig.TYPE_KEY to objectType.url,
"iconEmoji" to "👤"
)
)
)
)
val root = Block(
id = ctx,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(
type = Block.Content.Smart.Type.SET
),
children = listOf(header.id, dataview.id)
)
val set = listOf(root, header, title, dataview)
stubInterceptEvents()
stubOpenObjectSetWithRecord(
set = set,
relations = listOf(relation),
details = details,
viewer = viewer.id,
dataview = dataview.id,
records = listOf(record1),
total = 1,
objectTypes = listOf(objectType)
)
// TESTING
launchFragment(bundleOf(ObjectSetFragment.CONTEXT_ID_KEY to ctx))
with(R.id.rvRows.rVMatcher()) {
checkIsRecyclerSize(1)
onItemView(0, R.id.tvTitle).checkHasText("Le Feu Follet")
onItemView(0, R.id.object0).check(matches(hasDescendant(withText(object1Name))))
onItemView(0, R.id.object1).check(matches(hasDescendant(withText(object2Name))))
}
}
}

View file

@ -0,0 +1,261 @@
package com.anytypeio.anytype.features.sets.dv
import androidx.core.os.bundleOf
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.DVViewerRelation
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.Relation
import com.anytypeio.anytype.emojifier.data.DefaultDocumentEmojiIconProvider
import com.anytypeio.anytype.mocking.MockDataFactory
import com.anytypeio.anytype.presentation.relations.ObjectSetConfig
import com.anytypeio.anytype.ui.sets.ObjectSetFragment
import com.anytypeio.anytype.utils.*
import com.bartoszlipinski.disableanimationsrule.DisableAnimationsRule
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@LargeTest
class ObjectSetGridPrimitiveRelationTest : TestObjectSetSetup() {
@get:Rule
val animationsRule = DisableAnimationsRule()
override val title: Block = Block(
id = MockDataFactory.randomUuid(),
content = Block.Content.Text(
style = Block.Content.Text.Style.TITLE,
text = "Data View UI Testing",
marks = emptyList()
),
children = emptyList(),
fields = Block.Fields.empty()
)
@Before
override fun setup() {
super.setup()
}
@Test
fun shouldRenderAllObjectPrimitiveRelationsValuesFromTwoRecords() {
val type = ObjectType(
url = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
emoji = MockDataFactory.randomString(),
layout = ObjectType.Layout.PAGE,
relations = emptyList(),
description = "",
isHidden = false
)
val relation1 = Relation(
key = MockDataFactory.randomString(),
name = "Description",
format = Relation.Format.SHORT_TEXT,
source = Relation.Source.values().random()
)
val relation2 = Relation(
key = MockDataFactory.randomString(),
name = "Year",
format = Relation.Format.NUMBER,
source = Relation.Source.values().random()
)
val relation3 = Relation(
key = MockDataFactory.randomString(),
name = "Phone",
format = Relation.Format.PHONE,
source = Relation.Source.values().random()
)
val relation4 = Relation(
key = MockDataFactory.randomString(),
name = "Website",
format = Relation.Format.URL,
source = Relation.Source.values().random()
)
val relation5 = Relation(
key = MockDataFactory.randomString(),
name = "Email",
format = Relation.Format.EMAIL,
source = Relation.Source.values().random()
)
val object1value1 = "Operating environment for the new Internet"
val object1value2 = "2021"
val object1value3 = "+00000000000"
val object1value4 = "https://anytype.io/"
val object1value5 = "team@anytype.io"
val object2value1 = "A peer-to-peer hypermedia protocol designed to make the web faster, safer, and more open."
val object2value2 = "2021"
val object2value3 = "+00000000000"
val object2value4 = "https://ipfs.io/"
val object2value5 = "team@ipfs.io"
val record1 = mapOf(
ObjectSetConfig.ID_KEY to MockDataFactory.randomUuid(),
ObjectSetConfig.TYPE_KEY to type.url,
ObjectSetConfig.NAME_KEY to "Anytype",
"iconEmoji" to DefaultDocumentEmojiIconProvider.DOCUMENT_SET.random(),
relation1.key to object1value1,
relation2.key to object1value2,
relation3.key to object1value3,
relation4.key to object1value4,
relation5.key to object1value5,
)
val record2 = mapOf(
ObjectSetConfig.ID_KEY to MockDataFactory.randomUuid(),
ObjectSetConfig.TYPE_KEY to type.url,
ObjectSetConfig.NAME_KEY to "IPFS",
"iconEmoji" to DefaultDocumentEmojiIconProvider.DOCUMENT_SET.random(),
relation1.key to object2value1,
relation2.key to object2value2,
relation3.key to object2value3,
relation4.key to object2value4,
relation5.key to object2value5,
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = "Default Grid View",
filters = emptyList(),
sorts = emptyList(),
viewerRelations = listOf(
DVViewerRelation(
key = relation1.key,
isVisible = true
),
DVViewerRelation(
key = relation2.key,
isVisible = true
),
DVViewerRelation(
key = relation3.key,
isVisible = true
),
DVViewerRelation(
key = relation4.key,
isVisible = true
),
DVViewerRelation(
key = relation5.key,
isVisible = true
)
),
type = Block.Content.DataView.Viewer.Type.GRID
)
val dataview = Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(relation1, relation2, relation3, relation4, relation5),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
val root = Block(
id = ctx,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(
type = Block.Content.Smart.Type.SET
),
children = listOf(header.id, dataview.id)
)
val set = listOf(root, header, title, dataview)
stubInterceptEvents()
stubOpenObjectSetWithRecord(
set = set,
relations = listOf(relation1, relation2, relation3, relation4, relation5),
details = defaultDetails,
viewer = viewer.id,
dataview = dataview.id,
records = listOf(record1, record2),
total = 1,
objectTypes = listOf(type)
)
// TESTING
launchFragment(bundleOf(ObjectSetFragment.CONTEXT_ID_KEY to ctx))
with(R.id.rvRows.rVMatcher()) {
checkIsRecyclerSize(2)
onItemView(0, R.id.rowCellRecycler)
.checkHasChildViewCount(5)
.checkHasChildViewWithText(
pos = 0,
text = object1value1,
target = R.id.tvText
)
.checkHasChildViewWithText(
pos = 1,
text = object1value2,
target = R.id.tvText
)
.checkHasChildViewWithText(
pos = 2,
text = object1value3,
target = R.id.tvText
)
.checkHasChildViewWithText(
pos = 3,
text = object1value4,
target = R.id.tvText
)
.checkHasChildViewWithText(
pos = 4,
text = object1value5,
target = R.id.tvText
)
onItemView(0, R.id.tvTitle).checkHasText("Anytype")
onItemView(1, R.id.rowCellRecycler)
.checkHasChildViewCount(5)
.checkHasChildViewWithText(
pos = 0,
text = object2value1,
target = R.id.tvText
)
.checkHasChildViewWithText(
pos = 1,
text = object2value2,
target = R.id.tvText
)
.checkHasChildViewWithText(
pos = 2,
text = object2value3,
target = R.id.tvText
)
.checkHasChildViewWithText(
pos = 3,
text = object2value4,
target = R.id.tvText
)
.checkHasChildViewWithText(
pos = 4,
text = object2value5,
target = R.id.tvText
)
onItemView(1, R.id.tvTitle).checkHasText("IPFS")
}
}
}

View file

@ -0,0 +1,202 @@
package com.anytypeio.anytype.features.sets.dv
import androidx.core.os.bundleOf
import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.DVViewerRelation
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.Relation
import com.anytypeio.anytype.emojifier.data.DefaultDocumentEmojiIconProvider
import com.anytypeio.anytype.mocking.MockDataFactory
import com.anytypeio.anytype.presentation.relations.ObjectSetConfig
import com.anytypeio.anytype.ui.sets.ObjectSetFragment
import com.anytypeio.anytype.utils.checkHasText
import com.anytypeio.anytype.utils.checkIsRecyclerSize
import com.anytypeio.anytype.utils.onItemView
import com.anytypeio.anytype.utils.rVMatcher
import com.bartoszlipinski.disableanimationsrule.DisableAnimationsRule
import org.hamcrest.CoreMatchers.not
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@LargeTest
class ObjectSetGridTagCellRenderingTest : TestObjectSetSetup() {
@get:Rule
val animationsRule = DisableAnimationsRule()
override val title: Block = Block(
id = MockDataFactory.randomUuid(),
content = Block.Content.Text(
style = Block.Content.Text.Style.TITLE,
text = "Data View UI Testing",
marks = emptyList()
),
children = emptyList(),
fields = Block.Fields.empty()
)
@Before
override fun setup() {
super.setup()
}
@Test
fun shouldRenderOneSameTagAndTwoDifferentTagsOfTwoRecords() {
// SETUP
val relationName = "FilmTags"
val tag1Name = "Silent film"
val tag1Id = MockDataFactory.randomUuid()
val tag2Name = "Sound film"
val tag2Id = MockDataFactory.randomUuid()
val tag3Name = "Director"
val tag3Id = MockDataFactory.randomUuid()
val objectType = ObjectType(
url = MockDataFactory.randomUuid(),
name = "Film",
relations = emptyList(),
emoji = MockDataFactory.randomString(),
layout = ObjectType.Layout.PAGE,
description = "",
isHidden = false
)
val relationId = MockDataFactory.randomUuid()
val record1Id = MockDataFactory.randomUuid()
val record2Id = MockDataFactory.randomUuid()
val record1: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to record1Id,
ObjectSetConfig.NAME_KEY to "The Face on the Bar Room Floor",
ObjectSetConfig.EMOJI_KEY to DefaultDocumentEmojiIconProvider.DOCUMENT_SET.random(),
ObjectSetConfig.TYPE_KEY to objectType.url,
relationId to listOf(tag1Id, tag3Id)
)
val record2: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to record2Id,
ObjectSetConfig.NAME_KEY to "The Great Dictator",
ObjectSetConfig.EMOJI_KEY to DefaultDocumentEmojiIconProvider.DOCUMENT_SET.random(),
ObjectSetConfig.TYPE_KEY to objectType.url,
relationId to listOf(tag2Id, tag3Id)
)
val relation = Relation(
key = relationId,
name = relationName,
format = Relation.Format.TAG,
selections = listOf(
Relation.Option(id = tag1Id, text = tag1Name, color = "blue"),
Relation.Option(id = tag2Id, text = tag2Name, color = "red"),
Relation.Option(id = tag3Id, text = tag3Name, color = "black"),
Relation.Option(
id = MockDataFactory.randomUuid(),
text = MockDataFactory.randomString(),
color = "black"
)
),
source = Relation.Source.values().random()
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = emptyList(),
viewerRelations = listOf(
DVViewerRelation(
key = relation.key,
isVisible = true
)
),
type = Block.Content.DataView.Viewer.Type.GRID
)
val dataview = Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(relation),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
val details = Block.Details()
val root = Block(
id = ctx,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(
type = Block.Content.Smart.Type.SET
),
children = listOf(header.id, dataview.id)
)
val set = listOf(root, header, title, dataview)
stubInterceptEvents()
stubOpenObjectSetWithRecord(
set = set,
relations = listOf(relation),
details = details,
viewer = viewer.id,
dataview = dataview.id,
records = listOf(record1, record2),
total = 1,
objectTypes = listOf(objectType)
)
// TESTING
launchFragment(bundleOf(ObjectSetFragment.CONTEXT_ID_KEY to ctx))
with(R.id.rvRows.rVMatcher()) {
checkIsRecyclerSize(2)
onItemView(0, R.id.tvTitle).checkHasText("The Face on the Bar Room Floor")
onItemView(0, R.id.tag0).check(
ViewAssertions.matches(
ViewMatchers.withText("Silent film")
)
)
onItemView(0, R.id.tag1).check(
ViewAssertions.matches(
ViewMatchers.withText("Director")
)
)
onItemView(0, R.id.tag2).check(
ViewAssertions.matches(
not(ViewMatchers.isDisplayed())
)
)
onItemView(1, R.id.tvTitle).checkHasText("The Great Dictator")
onItemView(1, R.id.tag0).check(
ViewAssertions.matches(
ViewMatchers.withText("Sound film")
)
)
onItemView(1, R.id.tag1).check(
ViewAssertions.matches(
ViewMatchers.withText("Director")
)
)
onItemView(1, R.id.tag2).check(
ViewAssertions.matches(
not(ViewMatchers.isDisplayed())
)
)
}
}
}

View file

@ -0,0 +1,93 @@
package com.anytypeio.anytype.features.sets.dv
import androidx.core.os.bundleOf
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.ext.content
import com.anytypeio.anytype.mocking.MockDataFactory
import com.anytypeio.anytype.ui.sets.ObjectSetFragment
import com.anytypeio.anytype.utils.checkHasText
import com.bartoszlipinski.disableanimationsrule.DisableAnimationsRule
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@LargeTest
class ObjectSetHeaderTest : TestObjectSetSetup() {
@get:Rule
val animationsRule = DisableAnimationsRule()
override val title: Block = Block(
id = MockDataFactory.randomUuid(),
content = Block.Content.Text(
style = Block.Content.Text.Style.TITLE,
text = "Data View UI Testing",
marks = emptyList()
),
children = emptyList(),
fields = Block.Fields.empty()
)
@Before
override fun setup() {
super.setup()
}
@Test
fun shouldRenderObjectSetTitleWithViewerTitle() {
// SETUP
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = "Default Grid View",
filters = emptyList(),
sorts = emptyList(),
viewerRelations = emptyList(),
type = Block.Content.DataView.Viewer.Type.GRID
)
val dataview = Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = emptyList(),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
val root = Block(
id = ctx,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(
type = Block.Content.Smart.Type.SET
),
children = listOf(header.id, dataview.id)
)
val set = listOf(root, header, title, dataview)
stubInterceptEvents()
stubOpenObjectSet(
set = set,
relations = emptyList(),
details = defaultDetails
)
// TESTING
launchFragment(bundleOf(ObjectSetFragment.CONTEXT_ID_KEY to ctx))
onView(withId(R.id.title)).checkHasText(title.content<Block.Content.Text>().text)
onView(withId(R.id.tvCurrentViewerName)).checkHasText(viewer.name)
}
}

View file

@ -0,0 +1,17 @@
package com.anytypeio.anytype.features.sets.dv
import com.anytypeio.anytype.presentation.sets.ObjectSetViewModelFactory
import com.anytypeio.anytype.ui.sets.ObjectSetFragment
class TestObjectSetFragment : ObjectSetFragment() {
init {
factory = testVmFactory
}
override fun injectDependencies() {}
override fun releaseDependencies() {}
companion object {
lateinit var testVmFactory: ObjectSetViewModelFactory
}
}

View file

@ -0,0 +1,176 @@
package com.anytypeio.anytype.features.sets.dv
import android.os.Bundle
import androidx.fragment.app.testing.FragmentScenario
import androidx.fragment.app.testing.launchFragmentInContainer
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.*
import com.anytypeio.anytype.domain.block.interactor.UpdateText
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.config.Gateway
import com.anytypeio.anytype.domain.dataview.interactor.*
import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.page.CloseBlock
import com.anytypeio.anytype.domain.sets.OpenObjectSet
import com.anytypeio.anytype.emojifier.data.DefaultDocumentEmojiIconProvider
import com.anytypeio.anytype.mocking.MockDataFactory
import com.anytypeio.anytype.presentation.sets.ObjectSetRecordCache
import com.anytypeio.anytype.presentation.sets.ObjectSetReducer
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
import com.anytypeio.anytype.presentation.sets.ObjectSetViewModelFactory
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.stub
import kotlinx.coroutines.flow.emptyFlow
import org.mockito.Mock
import org.mockito.MockitoAnnotations
abstract class TestObjectSetSetup {
private lateinit var openObjectSet: OpenObjectSet
private lateinit var addDataViewRelation: AddDataViewRelation
private lateinit var updateDataViewViewer: UpdateDataViewViewer
private lateinit var updateDataViewRecord: UpdateDataViewRecord
private lateinit var updateText: UpdateText
private lateinit var createDataViewRecord: CreateDataViewRecord
private lateinit var closeBlock: CloseBlock
private lateinit var setActiveViewer: SetActiveViewer
lateinit var urlBuilder: UrlBuilder
@Mock
lateinit var repo: BlockRepository
@Mock
lateinit var gateway: Gateway
@Mock
lateinit var interceptEvents: InterceptEvents
private val session = ObjectSetSession()
private val reducer = ObjectSetReducer()
private val dispatcher: Dispatcher<Payload> = Dispatcher.Default()
private val objectSetRecordCache = ObjectSetRecordCache()
val ctx : Id = MockDataFactory.randomUuid()
abstract val title : Block
val header get() = Block(
id = MockDataFactory.randomUuid(),
content = Block.Content.Layout(
type = Block.Content.Layout.Type.HEADER
),
fields = Block.Fields.empty(),
children = listOf(title.id)
)
val defaultDetails = Block.Details(
mapOf(
ctx to Block.Fields(
mapOf(
"iconEmoji" to DefaultDocumentEmojiIconProvider.DOCUMENT_SET.random()
)
)
)
)
open fun setup() {
MockitoAnnotations.initMocks(this)
addDataViewRelation = AddDataViewRelation(repo)
updateText = UpdateText(repo)
openObjectSet = OpenObjectSet(repo)
createDataViewRecord = CreateDataViewRecord(repo)
updateDataViewRecord = UpdateDataViewRecord(repo)
updateDataViewViewer = UpdateDataViewViewer(repo)
setActiveViewer = SetActiveViewer(repo)
closeBlock = CloseBlock(repo)
urlBuilder = UrlBuilder(gateway)
TestObjectSetFragment.testVmFactory = ObjectSetViewModelFactory(
openObjectSet = openObjectSet,
closeBlock = closeBlock,
addDataViewRelation = addDataViewRelation,
interceptEvents = interceptEvents,
updateDataViewViewer = updateDataViewViewer,
setActiveViewer = setActiveViewer,
createDataViewRecord = createDataViewRecord,
updateDataViewRecord = updateDataViewRecord,
updateText = updateText,
urlBuilder = urlBuilder,
session = session,
dispatcher = dispatcher,
reducer = reducer,
objectSetRecordCache = objectSetRecordCache
)
}
fun stubInterceptEvents() {
interceptEvents.stub {
onBlocking { build(any()) } doReturn emptyFlow()
}
}
fun stubOpenObjectSet(
set: List<Block>,
details: Block.Details = Block.Details(),
relations: List<Relation> = emptyList()
) {
repo.stub {
onBlocking { openObjectSet(ctx) } doReturn Payload(
context = ctx,
events = listOf(
Event.Command.ShowBlock(
context = ctx,
root = ctx,
details = details,
blocks = set,
relations = relations
)
)
)
}
}
fun stubOpenObjectSetWithRecord(
set: List<Block>,
details: Block.Details = Block.Details(),
relations: List<Relation> = emptyList(),
dataview: Id,
viewer: Id,
total: Int,
records: List<DVRecord>,
objectTypes: List<ObjectType>
) {
repo.stub {
onBlocking { openObjectSet(ctx) } doReturn Payload(
context = ctx,
events = listOf(
Event.Command.ShowBlock(
context = ctx,
root = ctx,
details = details,
blocks = set,
relations = relations,
objectTypes = objectTypes
),
Event.Command.DataView.SetRecords(
context = ctx,
id = dataview,
view = viewer,
total = total,
records = records
)
)
)
}
}
fun launchFragment(args: Bundle): FragmentScenario<TestObjectSetFragment> {
return launchFragmentInContainer<TestObjectSetFragment>(
fragmentArgs = args,
themeResId = R.style.AppTheme
)
}
}

View file

@ -0,0 +1,316 @@
package com.anytypeio.anytype.features.sets.filter
import android.os.Bundle
import android.text.InputType
import androidx.core.os.bundleOf
import androidx.fragment.app.testing.FragmentScenario
import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.typeText
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.*
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.config.Gateway
import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects
import com.anytypeio.anytype.domain.dataview.interactor.UpdateDataViewViewer
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.mocking.MockDataFactory
import com.anytypeio.anytype.presentation.relations.ObjectSetConfig
import com.anytypeio.anytype.presentation.sets.ObjectSet
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
import com.anytypeio.anytype.presentation.sets.filter.FilterViewModel
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.sets.modals.filter.ModifyFilterFromInputFieldValueFragment
import com.anytypeio.anytype.utils.CoroutinesTestRule
import com.bartoszlipinski.disableanimationsrule.DisableAnimationsRule
import com.nhaarman.mockitokotlin2.times
import com.nhaarman.mockitokotlin2.verifyBlocking
import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
class ModifyInputValueFilterTest {
@get:Rule
val animationsRule = DisableAnimationsRule()
@get:Rule
val coroutineTestRule = CoroutinesTestRule()
@Mock
lateinit var repo: BlockRepository
@Mock
lateinit var gateway: Gateway
lateinit var updateDataViewViewer: UpdateDataViewViewer
lateinit var searchObjects: SearchObjects
lateinit var urlBuilder: UrlBuilder
private val root = MockDataFactory.randomUuid()
private val session = ObjectSetSession()
private val state = MutableStateFlow(ObjectSet.init())
private val dispatcher = Dispatcher.Default<Payload>()
// @Before
// fun setup() {
// MockitoAnnotations.initMocks(this)
// updateDataViewViewer = UpdateDataViewViewer(repo)
// searchObjects = SearchObjects(repo)
// urlBuilder = UrlBuilder(gateway)
// TestModifyFilterFromInputFieldValueFragment.testVmFactory = FilterViewModel.Factory(
// objectSetState = state,
// session = session,
// updateDataViewViewer = updateDataViewViewer,
// dispatcher = dispatcher,
// searchObjects = searchObjects,
// urlBuilder = urlBuilder
// )
// }
//todo Тесты выключены пока DataView in read mode only 15/03/21
// @Test
// fun shouldTypeTextThenClickActionButtonToApplyChanges() {
//
// val relationKey = MockDataFactory.randomUuid()
//
// val target = MockDataFactory.randomUuid()
//
// val record: Map<String, Any?> = mapOf(
// ObjectSetConfig.ID_KEY to target,
// relationKey to emptyList<String>()
// )
//
// // Defining viewer containing one filter
//
// val initialFilterText = "Foo"
// val textToType = "Bar"
//
// val filter = DVFilter(
// relationKey = relationKey,
// value = initialFilterText,
// condition = DVFilterCondition.EQUAL
// )
//
// val viewer = Block.Content.DataView.Viewer(
// id = MockDataFactory.randomUuid(),
// name = MockDataFactory.randomString(),
// filters = listOf(filter),
// sorts = emptyList(),
// viewerRelations = listOf(
// Block.Content.DataView.Viewer.ViewerRelation(
// key = relationKey,
// isVisible = true
// )
// ),
// type = Block.Content.DataView.Viewer.Type.values().random()
// )
//
// val relation = Relation(
// key = relationKey,
// defaultValue = null,
// isHidden = false,
// isReadOnly = false,
// isMulti = true,
// name = MockDataFactory.randomString(),
// source = Relation.Source.values().random(),
// format = Relation.Format.LONG_TEXT,
// selections = emptyList()
// )
//
// val dv = Block(
// id = MockDataFactory.randomUuid(),
// children = emptyList(),
// fields = Block.Fields.empty(),
// content = Block.Content.DataView(
// relations = listOf(relation),
// viewers = listOf(viewer),
// source = MockDataFactory.randomUuid()
// )
// )
//
// state.value = ObjectSet(
// blocks = listOf(dv),
// viewerDb = mapOf(
// viewer.id to ObjectSet.ViewerData(
// records = listOf(record),
// total = 1
// )
// )
// )
//
// // Launching fragment
//
// launchFragment(
// bundleOf(
// ModifyFilterFromInputFieldValueFragment.CTX_KEY to root,
// ModifyFilterFromInputFieldValueFragment.IDX_KEY to 0,
// ModifyFilterFromInputFieldValueFragment.RELATION_KEY to relationKey,
// )
// )
//
// // Veryfying that the initial filter text is visibile to our user
//
// val inputFieldInteraction = onView(withId(R.id.enterTextValueInputField))
//
// inputFieldInteraction.check(matches(withText(initialFilterText)))
//
// // Checking input type
//
// inputFieldInteraction.check(matches(withInputType(InputType.TYPE_CLASS_TEXT)))
//
// // Typing additional text before pressing action button
//
// inputFieldInteraction.perform(
// typeText(textToType)
// )
//
// // Clicking to apply button, in order to save filter changes
//
// onView(withId(R.id.btnBottomAction)).apply {
// perform(click())
// }
//
// // Veryfying that the appropriate request was made
//
// verifyBlocking(repo, times(1)) {
// updateDataViewViewer(
// context = root,
// target = dv.id,
// viewer = viewer.copy(
// filters = listOf(filter.copy(value = initialFilterText + textToType))
// )
// )
// }
// }
//
// @Test
// fun shouldTypeNumberFilterTextThenClickActionButtonToApplyChanges() {
//
// val relationKey = MockDataFactory.randomUuid()
//
// val target = MockDataFactory.randomUuid()
//
// val record: Map<String, Any?> = mapOf(
// ObjectSetConfig.ID_KEY to target,
// relationKey to emptyList<String>()
// )
//
// // Defining viewer containing one filter
//
// val initialFilterText = "1"
// val textToType = "2"
//
// val filter = DVFilter(
// relationKey = relationKey,
// value = initialFilterText,
// condition = DVFilterCondition.EQUAL
// )
//
// val viewer = Block.Content.DataView.Viewer(
// id = MockDataFactory.randomUuid(),
// name = MockDataFactory.randomString(),
// filters = listOf(filter),
// sorts = emptyList(),
// viewerRelations = listOf(
// Block.Content.DataView.Viewer.ViewerRelation(
// key = relationKey,
// isVisible = true
// )
// ),
// type = Block.Content.DataView.Viewer.Type.values().random()
// )
//
// val relation = Relation(
// key = relationKey,
// defaultValue = null,
// isHidden = false,
// isReadOnly = false,
// isMulti = true,
// name = MockDataFactory.randomString(),
// source = Relation.Source.values().random(),
// format = Relation.Format.NUMBER,
// selections = emptyList()
// )
//
// val dv = Block(
// id = MockDataFactory.randomUuid(),
// children = emptyList(),
// fields = Block.Fields.empty(),
// content = Block.Content.DataView(
// relations = listOf(relation),
// viewers = listOf(viewer),
// source = MockDataFactory.randomUuid()
// )
// )
//
// state.value = ObjectSet(
// blocks = listOf(dv),
// viewerDb = mapOf(
// viewer.id to ObjectSet.ViewerData(
// records = listOf(record),
// total = 1
// )
// )
// )
//
// // Launching fragment
//
// launchFragment(
// bundleOf(
// ModifyFilterFromInputFieldValueFragment.CTX_KEY to root,
// ModifyFilterFromInputFieldValueFragment.IDX_KEY to 0,
// ModifyFilterFromInputFieldValueFragment.RELATION_KEY to relationKey,
// )
// )
//
// // Veryfying that the initial filter text is visibile to our user
//
// val inputFieldInteraction = onView(withId(R.id.enterTextValueInputField))
//
// inputFieldInteraction.check(matches(withText(initialFilterText)))
//
// // Checking input type
//
// inputFieldInteraction.check(matches(withInputType(InputType.TYPE_CLASS_NUMBER)))
//
// // Typing additional text before pressing action button
//
// inputFieldInteraction.perform(
// typeText(textToType)
// )
//
// // Clicking to apply button, in order to save filter changes
//
// onView(withId(R.id.btnBottomAction)).apply {
// perform(click())
// }
//
// // Veryfying that the appropriate request was made
//
// verifyBlocking(repo, times(1)) {
// updateDataViewViewer(
// context = root,
// target = dv.id,
// viewer = viewer.copy(
// filters = listOf(filter.copy(value = initialFilterText + textToType))
// )
// )
// }
// }
private fun launchFragment(args: Bundle): FragmentScenario<TestModifyFilterFromInputFieldValueFragment> {
return launchFragmentInContainer<TestModifyFilterFromInputFieldValueFragment>(
fragmentArgs = args,
themeResId = R.style.AppTheme
)
}
}

View file

@ -0,0 +1,266 @@
package com.anytypeio.anytype.features.sets.filter
import android.os.Bundle
import androidx.core.os.bundleOf
import androidx.fragment.app.testing.FragmentScenario
import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.*
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.config.Gateway
import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects
import com.anytypeio.anytype.domain.dataview.interactor.UpdateDataViewViewer
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.mocking.MockDataFactory
import com.anytypeio.anytype.presentation.relations.ObjectSetConfig
import com.anytypeio.anytype.presentation.sets.ObjectSet
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
import com.anytypeio.anytype.presentation.sets.filter.FilterViewModel
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.sets.modals.filter.ModifyFilterFromSelectedValueFragment
import com.anytypeio.anytype.utils.CoroutinesTestRule
import com.anytypeio.anytype.utils.TestUtils
import com.bartoszlipinski.disableanimationsrule.DisableAnimationsRule
import com.nhaarman.mockitokotlin2.times
import com.nhaarman.mockitokotlin2.verifyBlocking
import kotlinx.coroutines.flow.MutableStateFlow
import org.hamcrest.CoreMatchers.not
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
@LargeTest
class ModifyStatusFilterTest {
@get:Rule
val animationsRule = DisableAnimationsRule()
@get:Rule
val coroutineTestRule = CoroutinesTestRule()
@Mock
lateinit var repo: BlockRepository
@Mock
lateinit var gateway: Gateway
lateinit var updateDataViewViewer: UpdateDataViewViewer
lateinit var searchObjects: SearchObjects
lateinit var urlBuilder: UrlBuilder
private val root = MockDataFactory.randomUuid()
private val session = ObjectSetSession()
private val state = MutableStateFlow(ObjectSet.init())
private val dispatcher = Dispatcher.Default<Payload>()
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
updateDataViewViewer = UpdateDataViewViewer(repo)
searchObjects = SearchObjects(repo)
urlBuilder = UrlBuilder(gateway)
TestModifyFilterFromSelectedValueFragment.testVmFactory = FilterViewModel.Factory(
objectSetState = state,
session = session,
updateDataViewViewer = updateDataViewViewer,
dispatcher = dispatcher,
searchObjects = searchObjects,
urlBuilder = urlBuilder
)
}
@Test
fun shouldSelectSecondStatusAndApplyChangesOnClick() {
val relationKey = MockDataFactory.randomUuid()
val target = MockDataFactory.randomUuid()
// Defining three different statuses:
val option1 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "In progress",
color = MockDataFactory.randomString()
)
val option2 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "In Testing",
color = MockDataFactory.randomString()
)
val option3 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Done",
color = MockDataFactory.randomString()
)
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target,
relationKey to emptyList<String>()
)
// Defining viewer containing one filter
val filter = DVFilter(
relationKey = relationKey,
value = listOf(option1.id),
condition = DVFilterCondition.EQUAL
)
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = listOf(filter),
sorts = emptyList(),
viewerRelations = listOf(
Block.Content.DataView.Viewer.ViewerRelation(
key = relationKey,
isVisible = true
)
),
type = Block.Content.DataView.Viewer.Type.values().random()
)
val relation = Relation(
key = relationKey,
defaultValue = null,
isHidden = false,
isReadOnly = false,
isMulti = true,
name = MockDataFactory.randomString(),
source = Relation.Source.values().random(),
format = Relation.Format.STATUS,
selections = listOf(option1, option2, option3)
)
val dv = Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(relation),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
state.value = ObjectSet(
blocks = listOf(dv),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
// Launching fragment
launchFragment(
bundleOf(
ModifyFilterFromSelectedValueFragment.CTX_KEY to root,
ModifyFilterFromSelectedValueFragment.IDX_KEY to 0,
ModifyFilterFromSelectedValueFragment.RELATION_KEY to relationKey,
)
)
// TESTING
val rvMatcher = TestUtils.withRecyclerView(R.id.rvViewerFilterRecycler)
// Checking names
onView(rvMatcher.atPositionOnView(0, R.id.tvStatusName)).apply {
check(matches(withText(option1.text)))
}
onView(rvMatcher.atPositionOnView(1, R.id.tvStatusName)).apply {
check(matches(withText(option2.text)))
}
onView(rvMatcher.atPositionOnView(2, R.id.tvStatusName)).apply {
check(matches(withText(option3.text)))
}
// Veryfing that only the first status is selected
onView(rvMatcher.atPositionOnView(0, R.id.ivSelectStatusIcon)).apply {
check(matches(isSelected()))
}
onView(rvMatcher.atPositionOnView(1, R.id.ivSelectStatusIcon)).apply {
check(matches(not(isSelected())))
}
onView(rvMatcher.atPositionOnView(2, R.id.ivSelectStatusIcon)).apply {
check(matches(not(isSelected())))
}
// Verifying that the selection counter is equal to 1
onView(withId(R.id.tvOptionCount)).apply {
check(matches(withText("1")))
}
// Performing click, in order to sellect second status
onView(rvMatcher.atPositionOnView(1, R.id.ivSelectStatusIcon)).apply {
perform(click())
}
// Veryfing that only the second status is selected
onView(rvMatcher.atPositionOnView(0, R.id.ivSelectStatusIcon)).apply {
check(matches(not(isSelected())))
}
onView(rvMatcher.atPositionOnView(1, R.id.ivSelectStatusIcon)).apply {
check(matches((isSelected())))
}
onView(rvMatcher.atPositionOnView(2, R.id.ivSelectStatusIcon)).apply {
check(matches(not(isSelected())))
}
// Verifying that the selection counter is still equal to 1
onView(withId(R.id.tvOptionCount)).apply {
check(matches(withText("1")))
}
// Performing a click to apply filter changes.
onView(withId(R.id.btnBottomAction)).apply {
perform(click())
}
// Verifying that viewer's filters are updated.
verifyBlocking(repo, times(1)) {
updateDataViewViewer(
context = root,
target = dv.id,
viewer = viewer.copy(
filters = listOf(filter.copy(value = listOf(option2.id)))
)
)
}
}
private fun launchFragment(args: Bundle): FragmentScenario<TestModifyFilterFromSelectedValueFragment> {
return launchFragmentInContainer<TestModifyFilterFromSelectedValueFragment>(
fragmentArgs = args,
themeResId = R.style.AppTheme
)
}
}

View file

@ -0,0 +1,246 @@
package com.anytypeio.anytype.features.sets.filter
import android.os.Bundle
import androidx.core.os.bundleOf
import androidx.fragment.app.testing.FragmentScenario
import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.*
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.config.Gateway
import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects
import com.anytypeio.anytype.domain.dataview.interactor.UpdateDataViewViewer
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.mocking.MockDataFactory
import com.anytypeio.anytype.presentation.relations.ObjectSetConfig
import com.anytypeio.anytype.presentation.sets.ObjectSet
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
import com.anytypeio.anytype.presentation.sets.filter.FilterViewModel
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.sets.modals.filter.ModifyFilterFromSelectedValueFragment
import com.anytypeio.anytype.utils.CoroutinesTestRule
import com.anytypeio.anytype.utils.TestUtils
import com.bartoszlipinski.disableanimationsrule.DisableAnimationsRule
import kotlinx.coroutines.flow.MutableStateFlow
import org.hamcrest.CoreMatchers.not
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
@LargeTest
class ModifyTagFilterTest {
@get:Rule
val animationsRule = DisableAnimationsRule()
@get:Rule
val coroutineTestRule = CoroutinesTestRule()
@Mock
lateinit var repo: BlockRepository
@Mock
lateinit var gateway: Gateway
lateinit var updateDataViewViewer: UpdateDataViewViewer
lateinit var searchObjects: SearchObjects
lateinit var urlBuilder: UrlBuilder
private val root = MockDataFactory.randomUuid()
private val session = ObjectSetSession()
private val state = MutableStateFlow(ObjectSet.init())
private val dispatcher = Dispatcher.Default<Payload>()
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
updateDataViewViewer = UpdateDataViewViewer(repo)
searchObjects = SearchObjects(repo)
urlBuilder = UrlBuilder(gateway)
TestModifyFilterFromSelectedValueFragment.testVmFactory = FilterViewModel.Factory(
objectSetState = state,
session = session,
updateDataViewViewer = updateDataViewViewer,
dispatcher = dispatcher,
searchObjects = searchObjects,
urlBuilder = urlBuilder
)
}
@Test
fun tagSelectionTest1() {
val relationKey = MockDataFactory.randomUuid()
val target = MockDataFactory.randomUuid()
// Defining three different tags:
val option1 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Architect",
color = MockDataFactory.randomString()
)
val option2 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Manager",
color = MockDataFactory.randomString()
)
val option3 = Relation.Option(
id = MockDataFactory.randomUuid(),
text = "Developer",
color = MockDataFactory.randomString()
)
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target,
relationKey to emptyList<String>()
)
// Defining viewer containing one filter
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = listOf(
DVFilter(
relationKey = relationKey,
value = listOf(option1.id),
condition = DVFilterCondition.ALL_IN
)
),
sorts = emptyList(),
viewerRelations = listOf(
Block.Content.DataView.Viewer.ViewerRelation(
key = relationKey,
isVisible = true
)
),
type = Block.Content.DataView.Viewer.Type.values().random()
)
val relation = Relation(
key = relationKey,
defaultValue = null,
isHidden = false,
isReadOnly = false,
isMulti = true,
name = MockDataFactory.randomString(),
source = Relation.Source.values().random(),
format = Relation.Format.TAG,
selections = listOf(option1, option2, option3)
)
state.value = ObjectSet(
blocks = listOf(
Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(relation),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
// Launching fragment
launchFragment(
bundleOf(
ModifyFilterFromSelectedValueFragment.CTX_KEY to root,
ModifyFilterFromSelectedValueFragment.IDX_KEY to 0,
ModifyFilterFromSelectedValueFragment.RELATION_KEY to relationKey,
)
)
// TESTING
val rvMatcher = TestUtils.withRecyclerView(R.id.rvViewerFilterRecycler)
// Checking names
onView(rvMatcher.atPositionOnView(0, R.id.tvTagName)).apply {
check(matches(withText(option1.text)))
}
onView(rvMatcher.atPositionOnView(1, R.id.tvTagName)).apply {
check(matches(withText(option2.text)))
}
onView(rvMatcher.atPositionOnView(2, R.id.tvTagName)).apply {
check(matches(withText(option3.text)))
}
// Veryfing that only the first tag is selected
onView(rvMatcher.atPositionOnView(0, R.id.ivSelectTagIcon)).apply {
check(matches(isSelected()))
}
onView(rvMatcher.atPositionOnView(1, R.id.ivSelectTagIcon)).apply {
check(matches(not(isSelected())))
}
onView(rvMatcher.atPositionOnView(2, R.id.ivSelectTagIcon)).apply {
check(matches(not(isSelected())))
}
// Verifying that the selection counter is equal to 1
onView(withId(R.id.tvOptionCount)).apply {
check(matches(withText("1")))
}
// Performing click, in order to sellect second status
onView(rvMatcher.atPositionOnView(1, R.id.ivSelectTagIcon)).apply {
perform(click())
}
// Veryfing that only the first tag and the second tag are selected
onView(rvMatcher.atPositionOnView(0, R.id.ivSelectTagIcon)).apply {
check(matches(isSelected()))
}
onView(rvMatcher.atPositionOnView(1, R.id.ivSelectTagIcon)).apply {
check(matches((isSelected())))
}
onView(rvMatcher.atPositionOnView(2, R.id.ivSelectTagIcon)).apply {
check(matches(not(isSelected())))
}
// Verifying that the selection counter is now equal to 2
onView(withId(R.id.tvOptionCount)).apply {
check(matches(withText("2")))
}
}
private fun launchFragment(args: Bundle): FragmentScenario<TestModifyFilterFromSelectedValueFragment> {
return launchFragmentInContainer<TestModifyFilterFromSelectedValueFragment>(
fragmentArgs = args,
themeResId = R.style.AppTheme
)
}
}

View file

@ -0,0 +1,18 @@
package com.anytypeio.anytype.features.sets.filter
import com.anytypeio.anytype.presentation.sets.filter.FilterViewModel
import com.anytypeio.anytype.ui.sets.modals.filter.ModifyFilterFromInputFieldValueFragment
class TestModifyFilterFromInputFieldValueFragment : ModifyFilterFromInputFieldValueFragment() {
init {
factory = testVmFactory
}
override fun injectDependencies() {}
override fun releaseDependencies() {}
companion object {
lateinit var testVmFactory: FilterViewModel.Factory
}
}

View file

@ -0,0 +1,18 @@
package com.anytypeio.anytype.features.sets.filter
import com.anytypeio.anytype.presentation.sets.filter.FilterViewModel
import com.anytypeio.anytype.ui.sets.modals.filter.ModifyFilterFromSelectedValueFragment
class TestModifyFilterFromSelectedValueFragment : ModifyFilterFromSelectedValueFragment() {
init {
factory = testVmFactory
}
override fun injectDependencies() {}
override fun releaseDependencies() {}
companion object {
lateinit var testVmFactory: FilterViewModel.Factory
}
}

View file

@ -0,0 +1,18 @@
package com.anytypeio.anytype.features.sets.sort
import com.anytypeio.anytype.presentation.sets.sort.ViewerSortViewModel
import com.anytypeio.anytype.ui.sets.modals.sort.ViewerSortFragment
class TestViewerSortFragment : ViewerSortFragment() {
init {
factory = testVmFactory
}
override fun injectDependencies() {}
override fun releaseDependencies() {}
companion object {
lateinit var testVmFactory: ViewerSortViewModel.Factory
}
}

View file

@ -0,0 +1,165 @@
package com.anytypeio.anytype.features.sets.sort
import android.os.Bundle
import androidx.core.os.bundleOf
import androidx.fragment.app.testing.FragmentScenario
import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.DVSort
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_models.Relation
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.dataview.interactor.UpdateDataViewViewer
import com.anytypeio.anytype.mocking.MockDataFactory
import com.anytypeio.anytype.presentation.relations.ObjectSetConfig
import com.anytypeio.anytype.presentation.sets.ObjectSet
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
import com.anytypeio.anytype.presentation.sets.sort.ViewerSortViewModel
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.sets.modals.sort.ViewerSortFragment
import com.anytypeio.anytype.utils.CoroutinesTestRule
import com.anytypeio.anytype.utils.TestUtils.withRecyclerView
import com.bartoszlipinski.disableanimationsrule.DisableAnimationsRule
import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
@LargeTest
class ViewerObjectSortTest {
@get:Rule
val animationsRule = DisableAnimationsRule()
@get:Rule
val coroutineTestRule = CoroutinesTestRule()
@Mock
lateinit var repo: BlockRepository
private lateinit var updateDataViewViewer: UpdateDataViewViewer
private val root = MockDataFactory.randomUuid()
private val session = ObjectSetSession()
private val state = MutableStateFlow(ObjectSet.init())
private val dispatcher = Dispatcher.Default<Payload>()
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
updateDataViewViewer = UpdateDataViewViewer(repo)
TestViewerSortFragment.testVmFactory = ViewerSortViewModel.Factory(
state = state,
session = session,
updateDataViewViewer = updateDataViewViewer,
dispatcher = dispatcher
)
}
@Test
fun shouldDisplayObjectRelationSortWithRelationNameAndSortingType() {
// SETUP
val name = "Some object"
val relationId = MockDataFactory.randomUuid()
val target = MockDataFactory.randomUuid()
val record: Map<String, Any?> = mapOf(
ObjectSetConfig.ID_KEY to target,
relationId to emptyList<String>()
)
// Defining viewer containing one filter
val viewer = Block.Content.DataView.Viewer(
id = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
filters = emptyList(),
sorts = listOf(
DVSort(
relationKey = relationId,
type = Block.Content.DataView.Sort.Type.DESC
)
),
viewerRelations = listOf(
Block.Content.DataView.Viewer.ViewerRelation(
key = relationId,
isVisible = true
)
),
type = Block.Content.DataView.Viewer.Type.values().random()
)
val relation = Relation(
key = relationId,
defaultValue = null,
isHidden = false,
isReadOnly = false,
isMulti = true,
name = name,
source = Relation.Source.values().random(),
format = Relation.Format.OBJECT,
selections = emptyList()
)
state.value = ObjectSet(
blocks = listOf(
Block(
id = MockDataFactory.randomUuid(),
children = emptyList(),
fields = Block.Fields.empty(),
content = Block.Content.DataView(
relations = listOf(relation),
viewers = listOf(viewer),
source = MockDataFactory.randomUuid()
)
)
),
viewerDb = mapOf(
viewer.id to ObjectSet.ViewerData(
records = listOf(record),
total = 1
)
)
)
// Launching fragment
launchFragment(bundleOf(ViewerSortFragment.CTX_KEY to root))
// TESTING
val rvMatcher = withRecyclerView(R.id.viewerSortRecycler)
// Checking that the relation name is set
onView(rvMatcher.atPositionOnView(0, R.id.tvTitle)).apply {
check(matches(withText(name)))
}
// Checking that the sorting type is set
onView(rvMatcher.atPositionOnView(0, R.id.tvSubtitle)).apply {
check(matches(withText(R.string.sort_from_z_to_a)))
}
}
private fun launchFragment(args: Bundle): FragmentScenario<TestViewerSortFragment> {
return launchFragmentInContainer<TestViewerSortFragment>(
fragmentArgs = args,
themeResId = R.style.AppTheme
)
}
}

View file

@ -1,6 +1,6 @@
package com.anytypeio.anytype.mocking
import com.anytypeio.anytype.domain.block.model.Block
import com.anytypeio.anytype.core_models.Block
object MockUiTests {

View file

@ -1,11 +1,21 @@
package com.anytypeio.anytype.utils
import android.support.annotation.DimenRes
import android.support.annotation.IntegerRes
import android.support.annotation.StringRes
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.ViewInteraction
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.typeText
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import com.anytypeio.anytype.core_ui.features.page.BlockViewHolder
import com.anytypeio.anytype.utils.TestUtils.withRecyclerView
import org.hamcrest.Matchers.not
fun <T : BlockViewHolder> Int.scrollTo(position: Int) {
onView(withId(this)).perform(RecyclerViewActions.scrollToPosition<T>(position))
@ -13,4 +23,81 @@ fun <T : BlockViewHolder> Int.scrollTo(position: Int) {
fun Int.findItemAt(position: Int, layoutId: Int): ViewInteraction {
return onView(withRecyclerView(this).atPositionOnView(position, layoutId))
}
fun ViewInteraction.performClick(): ViewInteraction = perform(ViewActions.click())
fun Int.matchView(): ViewInteraction = onView(withId(this))
fun Int.performClick(): ViewInteraction = matchView().performClick()
fun Int.type(text: String) = matchView().perform(click(), typeText(text))
fun ViewInteraction.checkHasText(text: String) {
check(matches(ViewMatchers.withText(text)))
}
fun ViewInteraction.checkHasMarginStart(@DimenRes dimen: Int, coefficient: Int) {
check(matches(WithMarginStart(dimen, coefficient)))
}
fun ViewInteraction.checkHasPaddingLeft(@DimenRes dimen: Int, coefficient: Int) {
check(matches(WithPaddingLeft(dimen, coefficient)))
}
fun ViewInteraction.checkIsSelected() {
check(matches(ViewMatchers.isSelected()))
}
fun ViewInteraction.checkIsDisplayed() {
check(matches(isDisplayed()))
}
fun ViewInteraction.checkIsNotDisplayed() {
check(matches(not(isDisplayed())))
}
fun ViewInteraction.checkIsNotSelected() {
check(matches(not(ViewMatchers.isSelected())))
}
fun ViewInteraction.checkHasText(@StringRes resId: Int) {
check(matches(ViewMatchers.withText(resId)))
}
fun ViewInteraction.checkHasTextColor(color: Int) {
check(matches(WithTextColor(color)))
}
fun ViewInteraction.checkHasBackgroundColor(color: Int) {
check(matches(WithBackgroundColor(color)))
}
fun ViewInteraction.checkHasNoBackground() {
check(matches(WithoutBackgroundColor()))
}
fun ViewInteraction.checkHasChildViewCount(count: Int) : ViewInteraction {
return check(matches(WithChildViewCount(count)))
}
fun Int.rVMatcher(): RecyclerViewMatcher = RecyclerViewMatcher(this)
fun Int.checkRecyclerItemCount(expected: Int) = matchView().check(RecyclerViewItemCountAssertion(expected))
fun RecyclerViewMatcher.onItemView(pos: Int, @IntegerRes target: Int): ViewInteraction {
return onView(atPositionOnView(pos, target))
}
fun ViewInteraction.checkHasChildViewWithText(
pos: Int,
text: String,
@IntegerRes target: Int
) : ViewInteraction {
return check(matches(HasChildViewWithText(pos, text, target)))
}
fun RecyclerViewMatcher.onItem(pos: Int): ViewInteraction {
return onView(atPosition(pos))
}
fun RecyclerViewMatcher.checkIsRecyclerSize(expected: Int) {
recyclerViewId.matchView().check(RecyclerViewItemCountAssertion(expected))
}

View file

@ -0,0 +1,19 @@
package com.anytypeio.anytype.utils
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.NoMatchingViewException
import androidx.test.espresso.ViewAssertion
import androidx.test.espresso.matcher.ViewMatchers.assertThat
import org.hamcrest.CoreMatchers.`is`
class RecyclerViewItemCountAssertion(val expected: Int) : ViewAssertion {
override fun check(view: View?, noViewFoundException: NoMatchingViewException?) {
if (noViewFoundException != null) throw noViewFoundException
val recycler = view as RecyclerView
val adapter = recycler.adapter
checkNotNull(adapter) { "Adapter wasn't set" }
assertThat(adapter.itemCount, `is`(expected))
}
}

View file

@ -11,7 +11,7 @@ import org.hamcrest.TypeSafeMatcher;
public class RecyclerViewMatcher {
private final int recyclerViewId;
final int recyclerViewId;
public RecyclerViewMatcher(int recyclerViewId) {
this.recyclerViewId = recyclerViewId;
@ -28,18 +28,21 @@ public class RecyclerViewMatcher {
View childView;
public void describeTo(Description description) {
String idDescription = Integer.toString(recyclerViewId);
String idRecyclerDescription = Integer.toString(recyclerViewId);
String idTargetViewDescription = Integer.toString(targetViewId);
if (this.resources != null) {
try {
idDescription = this.resources.getResourceName(recyclerViewId);
} catch (Resources.NotFoundException var4) {
idDescription = String.format("%s (resource name not found)",
Integer.valueOf
(recyclerViewId));
idRecyclerDescription = this.resources.getResourceName(recyclerViewId);
} catch (Resources.NotFoundException e) {
idRecyclerDescription = String.format("%s (resource name not found)", recyclerViewId);
}
try {
idTargetViewDescription = this.resources.getResourceName(targetViewId);
} catch (Resources.NotFoundException e) {
idTargetViewDescription = String.format("%s (resource name not found)", targetViewId);
}
}
description.appendText("with id: " + idDescription);
description.appendText("\nwith id: [" + idTargetViewDescription + "] inside: [" + idRecyclerDescription + "]");
}
public boolean matchesSafely(View view) {
@ -50,7 +53,14 @@ public class RecyclerViewMatcher {
RecyclerView recyclerView =
view.getRootView().findViewById(recyclerViewId);
if (recyclerView != null && recyclerView.getId() == recyclerViewId) {
childView = recyclerView.findViewHolderForAdapterPosition(position).itemView;
RecyclerView.ViewHolder holder = recyclerView.findViewHolderForAdapterPosition(position);
if (holder == null) {
throw new IllegalStateException(
"No view holder found at position: " + position +
". Actual child count: " + recyclerView.getChildCount()
);
}
childView = holder.itemView;
} else {
return false;
}

View file

@ -102,7 +102,6 @@ public class TestUtils {
public static RecyclerViewMatcher withRecyclerView(final int recyclerViewId) {
return new RecyclerViewMatcher(recyclerViewId);
}
}

View file

@ -0,0 +1,116 @@
package com.anytypeio.anytype.utils
import android.graphics.drawable.ColorDrawable
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.view.marginStart
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.matcher.BoundedMatcher
import com.anytypeio.anytype.core_utils.ext.dimen
import org.hamcrest.Description
import org.hamcrest.TypeSafeMatcher
class WithTextColor(private val expectedColor: Int) : BoundedMatcher<View, TextView>(TextView::class.java) {
override fun matchesSafely(item: TextView) = item.currentTextColor == expectedColor
override fun describeTo(description: Description) {
description.appendText("with text color:")
description.appendValue(expectedColor)
}
}
class WithBackgroundColor(private val expected: Int) : BoundedMatcher<View, View>(View::class.java) {
override fun describeTo(description: Description) {
description.appendText("with background color:")
description.appendValue(expected)
}
override fun matchesSafely(item: View): Boolean {
val actual = (item.background as ColorDrawable).color
return actual == expected
}
}
class WithoutBackgroundColor : BoundedMatcher<View, View>(View::class.java) {
override fun describeTo(description: Description) {
description.appendText("with background color:")
}
override fun matchesSafely(item: View): Boolean {
return item.background == null
}
}
class WithChildViewCount(private val expectedCount: Int) : BoundedMatcher<View, ViewGroup>(ViewGroup::class.java) {
override fun matchesSafely(item: ViewGroup): Boolean = item.childCount == expectedCount
override fun describeTo(description: Description) {
description.appendText("ViewGroup with child-count = $expectedCount");
}
}
class HasChildViewWithText(private val pos: Int, val text: String, val target: Int) : BoundedMatcher<View, RecyclerView>(RecyclerView::class.java) {
private var actual: String? = null
override fun matchesSafely(item: RecyclerView): Boolean {
val holder = item.findViewHolderForLayoutPosition(pos)
checkNotNull(holder) { throw IllegalStateException("No holder at position: $pos") }
val target = holder.itemView.findViewById<TextView>(target)
actual = target.text.toString()
return actual == text
}
override fun describeTo(description: Description) {
if (actual != null) {
description.appendText("Should have text [$text] at position: $pos but was: $actual");
}
}
}
class WithMarginStart(private val dimen: Int, private val coefficient: Int) : TypeSafeMatcher<View>() {
var actual: Int = 0
var expected: Int = 0
override fun describeTo(description: Description) {
description.appendText("with actual margin start:")
description.appendValue(actual)
description.appendText("with expected margin start:")
description.appendValue(expected)
}
override fun matchesSafely(item: View): Boolean {
actual = item.marginStart
expected = (item.context.dimen(dimen) * coefficient).toInt()
return actual == expected
}
}
class WithPaddingLeft(private val dimen: Int, private val coefficient: Int) : TypeSafeMatcher<View>() {
var actual: Int = 0
var expected: Int = 0
override fun describeTo(description: Description) {
description.appendText("with actual padding start:")
description.appendValue(actual)
description.appendText("with expected padding start:")
description.appendValue(expected)
}
override fun matchesSafely(item: View): Boolean {
actual = item.paddingLeft
expected = (item.context.dimen(dimen) * coefficient).toInt()
return actual == expected
}
}
class WithTextColorRes(private val expectedColorRes: Int) : BoundedMatcher<View, TextView>(TextView::class.java) {
override fun matchesSafely(item: TextView): Boolean {
val color = ContextCompat.getColor(item.context, expectedColorRes)
return item.currentTextColor == color
}
override fun describeTo(description: Description) {
description.appendText("with text color:")
description.appendValue(expectedColorRes)
}
}

View file

@ -0,0 +1,39 @@
{
"project_info": {
"project_number": "687711214896",
"project_id": "anytype-debug",
"storage_bucket": "anytype-debug.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:687711214896:android:20001e745ee8af044f7cf5",
"android_client_info": {
"package_name": "com.anytypeio.anytype.debug"
}
},
"oauth_client": [
{
"client_id": "687711214896-otmhog2ql4ngk1gcatbc3b1afkudra8s.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyC83X8q2Ya3-jTMp7b7i85WoY-TeBnX1qQ"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "687711214896-otmhog2ql4ngk1gcatbc3b1afkudra8s.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
}
],
"configuration_version": "1"
}

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">DEBUG-Anytype</string>
<color name="ic_app_back_main">#000</color>
</resources>

View file

@ -0,0 +1,191 @@
package com.anytypeio.anytype.ui.desktop
import android.os.Bundle
import android.view.View
import android.view.View.OVER_SCROLL_NEVER
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_ui.reactive.clicks
import com.anytypeio.anytype.core_utils.ext.dimen
import com.anytypeio.anytype.core_utils.ext.toast
import com.anytypeio.anytype.core_utils.ext.visible
import com.anytypeio.anytype.core_utils.ui.EqualSpacingItemDecoration
import com.anytypeio.anytype.core_utils.ui.EqualSpacingItemDecoration.Companion.GRID
import com.anytypeio.anytype.di.common.componentManager
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.presentation.desktop.DashboardView
import com.anytypeio.anytype.presentation.desktop.HomeDashboardStateMachine.State
import com.anytypeio.anytype.presentation.desktop.HomeDashboardViewModel
import com.anytypeio.anytype.presentation.desktop.HomeDashboardViewModelFactory
import com.anytypeio.anytype.presentation.extension.filterByNotArchivedPages
import com.anytypeio.anytype.ui.base.ViewStateFragment
import com.anytypeio.anytype.ui.page.PageFragment
import kotlinx.android.synthetic.experimental.fragment_desktop.*
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import timber.log.Timber
import javax.inject.Inject
class HomeDashboardFragment : ViewStateFragment<State>(R.layout.fragment_desktop) {
private val vm by viewModels<HomeDashboardViewModel> { factory }
private val dndBehavior by lazy {
DashboardDragAndDropBehavior(
onItemMoved = { from, to ->
dashboardAdapter
.onItemMove(from, to)
.also {
vm.onItemMoved(
views = dashboardAdapter.provideAdapterData(),
from = from,
to = to
)
}
},
onItemDropped = { index ->
try {
vm.onItemDropped(dashboardAdapter.provideAdapterData()[index])
} catch (e: Exception) {
Timber.e(e, "Error while dropping item at index: $index")
}
}
)
}
@Inject
lateinit var factory: HomeDashboardViewModelFactory
@Inject
lateinit var builder: UrlBuilder
private val dashboardAdapter by lazy {
DashboardAdapter(
data = mutableListOf(),
onDocumentClicked = { target, isLoading -> vm.onDocumentClicked(target, isLoading) },
onArchiveClicked = { vm.onArchivedClicked(it) },
onObjectSetClicked = { vm.onObjectSetClicked(it) }
)
}
private val profileAdapter by lazy {
DashboardProfileAdapter(
data = mutableListOf(),
onProfileClicked = { vm.onProfileClicked() }
)
}
private val concatAdapter by lazy {
ConcatAdapter(ProfileContainerAdapter(profileAdapter), dashboardAdapter)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setup()
with(vm) {
state.observe(viewLifecycleOwner, this@HomeDashboardFragment)
navigation.observe(viewLifecycleOwner, navObserver)
}
parseIntent()
}
private fun parseIntent() {
val deepLinkPage = arguments?.getString(PageFragment.ID_KEY, null)
if (deepLinkPage != null) {
arguments?.remove(PageFragment.ID_KEY)
vm.onNavigationDeepLink(deepLinkPage)
} else {
vm.onViewCreated()
}
}
override fun render(state: State) {
when {
state.error != null -> {
requireActivity().toast("Error: ${state.error}")
}
state.isInitialzed -> {
bottomToolbar.visible()
state.blocks.let { views ->
val profile = views.filterIsInstance<DashboardView.Profile>()
val links = views.filterByNotArchivedPages()
if (profile.isNotEmpty()) {
profileAdapter.update(profile)
}
dashboardAdapter.update(links)
}
}
}
}
private fun setup() {
val spacing = requireContext().dimen(R.dimen.default_dashboard_item_spacing).toInt()
val decoration = EqualSpacingItemDecoration(
topSpacing = spacing,
leftSpacing = spacing,
rightSpacing = spacing,
bottomSpacing = 0,
displayMode = GRID
)
desktopRecycler.apply {
overScrollMode = OVER_SCROLL_NEVER
layoutManager = GridLayoutManager(context, 2).apply {
spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return if (position == 0) {
2
} else {
1
}
}
}
}
adapter = concatAdapter
ItemTouchHelper(dndBehavior).attachToRecyclerView(this)
addItemDecoration(decoration)
setHasFixedSize(true)
}
bottomToolbar
.navigationClicks()
.onEach { vm.onPageNavigationClicked() }
.launchIn(lifecycleScope)
bottomToolbar
.addPageClick()
.onEach { vm.onAddNewDocumentClicked() }
.launchIn(lifecycleScope)
bottomToolbar
.searchClicks()
.onEach { vm.onPageSearchClicked() }
.launchIn(lifecycleScope)
createSetButton
.clicks()
.onEach { vm.onCreateNewObjectSetClicked() }
.launchIn(lifecycleScope)
}
override fun injectDependencies() {
componentManager().desktopComponent.get().inject(this)
}
override fun releaseDependencies() {
componentManager().desktopComponent.release()
}
companion object {
const val COLUMN_COUNT = 2
}
}

View file

@ -0,0 +1,159 @@
package com.anytypeio.anytype.ui.page.sheets
import android.graphics.Color
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.lifecycle.lifecycleScope
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Url
import com.anytypeio.anytype.core_ui.extensions.*
import com.anytypeio.anytype.core_ui.reactive.clicks
import com.anytypeio.anytype.core_utils.ext.*
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetFragment
import com.anytypeio.anytype.domain.status.SyncStatus
import kotlinx.android.synthetic.experimental.fragment_doc_menu_bottom_sheet.*
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
class DocMenuBottomSheet : BaseBottomSheetFragment() {
private val title get() = arg<String?>(TITLE_KEY)
private val status get() = SyncStatus.valueOf(arg(STATUS_KEY))
private val image get() = arg<String?>(IMAGE_KEY)
private val emoji get() = arg<String?>(EMOJI_KEY)
private val isProfile get() = arg<Boolean>(IS_PROFILE_KEY)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = inflater.inflate(R.layout.fragment_doc_menu_bottom_sheet, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
bindTitle()
bindSyncStatus(status)
closeButton.clicks().onEach { dismiss() }.launchIn(lifecycleScope)
searchOnPageContainer
.clicks()
.onEach {
withParent<DocumentMenuActionReceiver> { onSearchOnPageClicked() }.also { dismiss() }
}
.launchIn(lifecycleScope)
archiveContainer
.clicks()
.onEach {
withParent<DocumentMenuActionReceiver> { onArchiveClicked() }.also { dismiss() }
}
.launchIn(lifecycleScope)
addCoverContainer
.clicks()
.onEach {
withParent<DocumentMenuActionReceiver> { onAddCoverClicked() }.also { dismiss() }
}
.launchIn(lifecycleScope)
relationContainer
.clicks()
.onEach {
withParent<DocumentMenuActionReceiver> { onDocRelationsClicked() }.also { dismiss() }
}
.launchIn(lifecycleScope)
if (image != null && !isProfile) icon.setImageOrNull(image)
if (emoji != null && !isProfile) icon.setEmojiOrNull(emoji)
if (isProfile) {
avatar.visible()
image?.let { avatar.icon(it) } ?: avatar.bind(
name = title.orEmpty(),
color = title.orEmpty().firstDigitByHash().let {
requireContext().avatarColor(it)
}
)
archiveContainer.gone()
addCoverContainer.setBackgroundResource(R.drawable.rectangle_doc_menu_bottom)
searchOnPageContainer.setBackgroundResource(R.drawable.rectangle_doc_menu_top)
}
}
private fun bindTitle() {
tvTitle.text = title ?: getString(R.string.untitled)
}
private fun bindSyncStatus(status: SyncStatus) {
when (status) {
SyncStatus.UNKNOWN -> {
badge.tint(
color = requireContext().color(R.color.sync_status_red)
)
tvSubtitle.setText(R.string.sync_status_unknown)
}
SyncStatus.FAILED -> {
badge.tint(
color = requireContext().color(R.color.sync_status_red)
)
tvSubtitle.setText(R.string.sync_status_failed)
}
SyncStatus.OFFLINE -> {
badge.tint(
color = requireContext().color(R.color.sync_status_red)
)
tvSubtitle.setText(R.string.sync_status_offline)
}
SyncStatus.SYNCING -> {
badge.tint(
color = requireContext().color(R.color.sync_status_orange)
)
tvSubtitle.setText(R.string.sync_status_syncing)
}
SyncStatus.SYNCED -> {
badge.tint(
color = requireContext().color(R.color.sync_status_green)
)
tvSubtitle.setText(R.string.sync_status_synced)
}
else -> badge.tint(Color.WHITE)
}
}
override fun injectDependencies() {}
override fun releaseDependencies() {}
companion object {
fun new(
title: String?,
status: SyncStatus,
image: Url?,
emoji: String?,
isProfile: Boolean = false
) = DocMenuBottomSheet().apply {
arguments = bundleOf(
TITLE_KEY to title,
STATUS_KEY to status.name,
IMAGE_KEY to image,
EMOJI_KEY to emoji,
IS_PROFILE_KEY to isProfile
)
}
private const val TITLE_KEY = "arg.doc-menu-bottom-sheet.title"
private const val IMAGE_KEY = "arg.doc-menu-bottom-sheet.image"
private const val EMOJI_KEY = "arg.doc-menu-bottom-sheet.emoji"
private const val STATUS_KEY = "arg.doc-menu-bottom-sheet.status"
private const val IS_PROFILE_KEY = "arg.doc-menu-bottom-sheet.is-profile"
}
interface DocumentMenuActionReceiver {
fun onArchiveClicked()
fun onSearchOnPageClicked()
fun onDocRelationsClicked()
fun onAddCoverClicked()
}
}

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/home_dashboard_cover">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:layout_gravity="end"
android:layout_margin="32dp"
android:id="@+id/createSetButton"
android:layout_width="48dp"
android:layout_height="48dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/desktopRecycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:paddingBottom="64dp"
tools:listitem="@layout/item_desktop_page" />
<com.anytypeio.anytype.core_ui.widgets.toolbar.DesktopBottomToolbar
android:id="@+id/bottomToolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom" />
</FrameLayout>

View file

@ -0,0 +1,218 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="12dp"
android:background="@drawable/rectangle_doc_menu_background">
<FrameLayout
android:id="@+id/iconContainer"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/icon"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="center"
tools:src="@drawable/circle_solid_default" />
<com.anytypeio.anytype.core_ui.widgets.AvatarWidget
android:id="@+id/avatar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/circle_solid_default"
android:visibility="invisible" />
</FrameLayout>
<ImageView
android:id="@+id/closeButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="18dp"
android:src="@drawable/ic_doc_menu_close"
app:layout_constraintBottom_toBottomOf="@+id/iconContainer"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/iconContainer" />
<View
android:id="@+id/badge"
android:layout_width="8dp"
android:layout_height="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="3dp"
android:background="@drawable/circle_solid_default"
android:backgroundTint="@color/white"
app:layout_constraintStart_toEndOf="@+id/iconContainer"
app:layout_constraintTop_toBottomOf="@+id/tvTitle"
tools:background="@drawable/circle_solid_default"
tools:backgroundTint="@color/anytype_text_red" />
<TextView
android:id="@+id/tvTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="15dp"
android:layout_marginEnd="8dp"
android:fontFamily="@font/inter_medium"
android:textColor="@color/black"
android:textSize="15sp"
app:layout_constraintEnd_toStartOf="@+id/closeButton"
app:layout_constraintStart_toEndOf="@+id/iconContainer"
app:layout_constraintTop_toTopOf="parent"
tools:text="Spaceship Earth" />
<TextView
android:id="@+id/tvSubtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:textSize="13sp"
android:lineHeight="20sp"
app:layout_constraintBottom_toBottomOf="@+id/badge"
app:layout_constraintStart_toEndOf="@+id/badge"
app:layout_constraintTop_toTopOf="@+id/badge"
tools:text="Offline" />
<View
android:background="#DFDDD0"
android:id="@+id/statusDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="12dp"
app:layout_constraintTop_toBottomOf="@+id/iconContainer" />
<FrameLayout
android:id="@+id/searchOnPageContainer"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="12dp"
android:background="@drawable/rectangle_doc_menu_top"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/statusDivider">
<TextView
style="@style/DocMenuOptionTextStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="16dp"
android:text="@string/search_on_page" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
android:layout_marginEnd="12dp"
android:src="@drawable/ic_doc_menu_search" />
</FrameLayout>
<View
android:id="@+id/optionDivider1"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintTop_toBottomOf="@+id/searchOnPageContainer" />
<FrameLayout
android:id="@+id/addCoverContainer"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:background="@color/white"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/optionDivider1">
<TextView
style="@style/DocMenuOptionTextStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="16dp"
android:text="Add cover" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
android:layout_marginEnd="12dp"
android:src="@drawable/ic_doc_menu_doc_style" />
</FrameLayout>
<View
android:id="@+id/optionDivider2"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintTop_toBottomOf="@+id/addCoverContainer" />
<FrameLayout
android:id="@+id/archiveContainer"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:background="@drawable/rectangle_doc_menu_bottom"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/optionDivider2">
<TextView
style="@style/DocMenuOptionTextStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="16dp"
android:text="@string/archive" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
android:layout_marginEnd="12dp"
android:src="@drawable/ic_doc_menu_move_to_bin" />
</FrameLayout>
<FrameLayout
android:id="@+id/relationContainer"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:background="@drawable/rectangle_doc_menu_default"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/archiveContainer">
<TextView
style="@style/DocMenuOptionTextStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="16dp"
android:text="@string/relations" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -38,9 +38,6 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<meta-data
android:name="firebase_crashlytics_collection_enabled"
android:value="false" />
</application>
</manifest>

View file

@ -14,7 +14,6 @@ import com.anytypeio.anytype.di.main.ContextModule
import com.anytypeio.anytype.di.main.DaggerMainComponent
import com.anytypeio.anytype.di.main.MainComponent
import com.facebook.stetho.Stetho
import com.google.firebase.crashlytics.FirebaseCrashlytics
import timber.log.Timber
import javax.inject.Inject
@ -38,7 +37,6 @@ class AndroidApplication : Application() {
super.onCreate()
main.inject(this)
setupAnalytics()
setupCrashlytics()
setupEmojiCompat()
setupTimber()
setupStetho()
@ -74,11 +72,4 @@ class AndroidApplication : Application() {
Amplitude.getInstance().initialize(this, getString(R.string.amplitude_api_key))
}
}
private fun setupCrashlytics() {
if (BuildConfig.DEBUG)
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(false)
else
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(true)
}
}

View file

@ -1,6 +1,6 @@
package com.anytypeio.anytype.device
import com.anytypeio.anytype.domain.common.Id
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.domain.cover.GradientCollectionProvider
import com.anytypeio.anytype.presentation.page.cover.CoverGradient

View file

@ -1,8 +1,12 @@
package com.anytypeio.anytype.di.common
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.di.feature.*
import com.anytypeio.anytype.di.feature.sets.CreateFilterModule
import com.anytypeio.anytype.di.feature.sets.ModifyFilterModule
import com.anytypeio.anytype.di.feature.sets.PickConditionModule
import com.anytypeio.anytype.di.feature.sets.SelectFilterRelationModule
import com.anytypeio.anytype.di.main.MainComponent
import com.anytypeio.anytype.domain.common.Id
class ComponentManager(private val main: MainComponent) {
@ -134,15 +138,17 @@ class ComponentManager(private val main: MainComponent) {
.build()
}
val documentIconActionMenuComponent = Component {
main
val documentIconActionMenuComponent = DependentComponentMap { ctx ->
pageComponent
.get(ctx)
.documentActionMenuComponentBuilder()
.documentIconActionMenuModule(DocumentIconActionMenuModule())
.build()
}
val documentEmojiIconPickerComponent = Component {
main
val documentEmojiIconPickerComponent = DependentComponentMap { ctx ->
pageComponent
.get(ctx)
.documentEmojiIconPickerComponentBuilder()
.documentIconActionMenuModule(DocumentEmojiIconPickerModule())
.build()
@ -180,6 +186,278 @@ class ComponentManager(private val main: MainComponent) {
.build()
}
val createSetComponent = Component {
main.createSetComponentBuilder()
.module(CreateSetModule)
.build()
}
val createObjectTypeComponent = Component {
main.createObjectTypeComponentBuilder()
.module(CreateObjectTypeModule)
.build()
}
val objectSetComponent = ComponentMap {
main.objectSetComponentBuilder()
.module(ObjectSetModule)
.build()
}
val documentRelationComponent = DependentComponentMap { id ->
pageComponent
.get(id)
.documentRelationSubComponent()
.module(DocumentRelationModule)
.build()
}
val viewerSortByComponent = DependentComponentMap { ctx ->
objectSetComponent
.get(ctx)
.viewerSortBySubComponent()
.module(ViewerSortByModule)
.build()
}
val createDataViewRelationComponent = Component {
main.createDataViewRelationBuilder()
.module(CreateDataViewRelationModule)
.build()
}
val editGridCellComponent = DependentComponentMap { ctx ->
objectSetComponent
.get(ctx)
.editCellsComponent()
.module(EditGridCellModule)
.build()
}
val editRelationCellComponent = DependentComponentMap { ctx ->
pageComponent
.get(ctx)
.editRelationCellComponent()
.module(EditGridCellModule)
.build()
}
val objectSetObjectRelationDataValueComponent = DependentComponentMap { ctx ->
objectSetComponent
.get(ctx)
.editCellDateComponent()
.module(EditGridCellDateModule)
.build()
}
val objectObjectRelationDateValueComponet = DependentComponentMap { ctx ->
pageComponent
.get(ctx)
.editRelationDateComponent()
.module(EditGridCellDateModule)
.build()
}
val documentAddNewBlockComponent = DependentComponentMap { ctx ->
pageComponent
.get(ctx)
.documentAddNewBlockComponentBuilder()
.documentAddNewBlockModule(DocumentAddNewBlockModule)
.build()
}
val viewerFilterComponent = DependentComponentMap { ctx ->
objectSetComponent
.get(ctx)
.viewerFilterBySubComponent()
.module(ViewerFilterModule)
.build()
}
val viewerCustomizeComponent = DependentComponentMap { ctx ->
objectSetComponent
.get(ctx)
.viewerCustomizeSubComponent()
.module(ViewerCustomizeModule)
.build()
}
val objectSetRecordComponent = DependentComponentMap { ctx ->
objectSetComponent
.get(ctx)
.objectSetRecordComponent()
.module(ObjectSetRecordModule)
.build()
}
val createDataViewViewerComponent = DependentComponentMap { ctx ->
objectSetComponent
.get(ctx)
.createDataViewViewerSubComponent()
.module(CreateDataViewViewerModule)
.build()
}
val editDataViewViewerComponent = DependentComponentMap { ctx ->
objectSetComponent
.get(ctx)
.editDataViewViewerComponent()
.module(EditDataViewViewerModule)
.build()
}
val objectSetObjectRelationValueComponent = DependentComponentMap { ctx ->
objectSetComponent
.get(ctx)
.objectRelationValueComponent()
.module(ObjectRelationValueModule)
.build()
}
val addObjectSetObjectRelationValueComponent = DependentComponentMap { ctx ->
objectSetObjectRelationValueComponent
.get(ctx)
.addObjectRelationValueComponent()
.module(AddObjectRelationValueModule)
.build()
}
val objectObjectRelationValueComponent = DependentComponentMap { ctx ->
pageComponent
.get(ctx)
.editDocRelationComponent()
.module(ObjectRelationValueModule)
.build()
}
val addObjectObjectRelationValueComponent = DependentComponentMap { ctx ->
objectObjectRelationValueComponent
.get(ctx)
.addObjectRelationValueComponent()
.module(AddObjectRelationValueModule)
.build()
}
val addObjectSetObjectRelationObjectValueComponent = DependentComponentMap { ctx ->
objectSetObjectRelationValueComponent
.get(ctx)
.addObjectRelationObjectValueComponent()
.module(AddObjectRelationObjectValueModule)
.build()
}
val addObjectRelationObjectValueComponent = DependentComponentMap { ctx ->
objectObjectRelationValueComponent
.get(ctx)
.addObjectRelationObjectValueComponent()
.module(AddObjectRelationObjectValueModule)
.build()
}
val relationFileValueComponent = DependentComponentMap { ctx ->
objectObjectRelationValueComponent
.get(ctx)
.addRelationFileValueAddComponent()
.module(RelationFileValueAddModule)
.build()
}
val relationFileValueDVComponent = DependentComponentMap { ctx ->
objectSetObjectRelationValueComponent
.get(ctx)
.addRelationFileValueAddComponent()
.module(RelationFileValueAddModule)
.build()
}
val manageViewerComponent = DependentComponentMap { ctx ->
objectSetComponent
.get(ctx)
.manageViewerComponent()
.module(ManageViewerModule)
.build()
}
val viewerRelationsComponent = DependentComponentMap { ctx ->
objectSetComponent
.get(ctx)
.viewerRelationsComponent()
.module(ViewerRelationsModule)
.build()
}
val dataviewViewerActionComponent = DependentComponentMap { ctx ->
objectSetComponent
.get(ctx)
.dataviewViewerActionComponent()
.module(DataViewViewerActionModule)
.build()
}
val selectSortRelationComponent = DependentComponentMap { ctx ->
objectSetComponent
.get(ctx)
.selectSortRelationComponent()
.module(SelectSortRelationModule)
.build()
}
val selectFilterRelationComponent = DependentComponentMap { ctx ->
objectSetComponent
.get(ctx)
.selectFilterRelationComponent()
.module(SelectFilterRelationModule)
.build()
}
val createFilterComponent = DependentComponentMap { ctx ->
objectSetComponent
.get(ctx)
.createFilterComponent()
.module(CreateFilterModule)
.build()
}
val pickFilterConditionComponentCreate = DependentComponentMap { ctx ->
createFilterComponent
.get(ctx)
.createPickConditionComponent()
.module(PickConditionModule)
.build()
}
val pickFilterConditionComponentModify = DependentComponentMap { ctx ->
modifyFilterComponent
.get(ctx)
.createPickConditionComponent()
.module(PickConditionModule)
.build()
}
val modifyFilterComponent = DependentComponentMap { ctx ->
objectSetComponent
.get(ctx)
.modifyFilterComponent()
.module(ModifyFilterModule)
.build()
}
val viewerSortComponent = DependentComponentMap { ctx ->
objectSetComponent
.get(ctx)
.viewerSortComponent()
.module(ViewerSortModule)
.build()
}
val modifyViewerSortComponent = DependentComponentMap { ctx ->
objectSetComponent
.get(ctx)
.modifyViewerSortComponent()
.module(ModifyViewerSortModule)
.build()
}
val docCoverGalleryComponent = DependentComponentMap { ctx ->
pageComponent
.get(ctx)

View file

@ -0,0 +1,50 @@
package com.anytypeio.anytype.di.feature
import com.anytypeio.anytype.core_utils.di.scope.PerDialog
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.presentation.relations.AddObjectRelationObjectValueViewModel
import com.anytypeio.anytype.presentation.relations.providers.ObjectRelationProvider
import com.anytypeio.anytype.presentation.relations.providers.ObjectValueProvider
import com.anytypeio.anytype.ui.relations.AddObjectRelationObjectValueFragment
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
@Subcomponent(modules = [AddObjectRelationObjectValueModule::class])
@PerDialog
interface AddObjectRelationObjectValueSubComponent {
@Subcomponent.Builder
interface Builder {
fun module(module: AddObjectRelationObjectValueModule): Builder
fun build(): AddObjectRelationObjectValueSubComponent
}
fun inject(fragment: AddObjectRelationObjectValueFragment)
}
@Module
object AddObjectRelationObjectValueModule {
@JvmStatic
@Provides
@PerDialog
fun provideViewModelFactory(
relations: ObjectRelationProvider,
values: ObjectValueProvider,
searchObjects: SearchObjects,
urlBuilder: UrlBuilder
): AddObjectRelationObjectValueViewModel.Factory =
AddObjectRelationObjectValueViewModel.Factory(
relations, values, searchObjects, urlBuilder
)
@JvmStatic
@Provides
@PerDialog
fun provideSearchObjectsUseCase(
repo: BlockRepository
): SearchObjects = SearchObjects(repo = repo)
}

View file

@ -0,0 +1,102 @@
package com.anytypeio.anytype.di.feature
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_utils.di.scope.PerDialog
import com.anytypeio.anytype.domain.`object`.UpdateDetail
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.dataview.interactor.AddDataViewRelationOption
import com.anytypeio.anytype.domain.dataview.interactor.AddStatusToDataViewRecord
import com.anytypeio.anytype.domain.dataview.interactor.AddTagToDataViewRecord
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.relations.AddObjectRelationOption
import com.anytypeio.anytype.presentation.relations.AddObjectObjectRelationValueViewModel
import com.anytypeio.anytype.presentation.relations.AddObjectSetObjectRelationValueViewModel
import com.anytypeio.anytype.presentation.relations.providers.ObjectDetailProvider
import com.anytypeio.anytype.presentation.relations.providers.ObjectRelationProvider
import com.anytypeio.anytype.presentation.relations.providers.ObjectTypeProvider
import com.anytypeio.anytype.presentation.relations.providers.ObjectValueProvider
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.relations.AddObjectObjectRelationValueFragment
import com.anytypeio.anytype.ui.relations.AddObjectSetObjectRelationValueFragment
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
@Subcomponent(modules = [AddObjectRelationValueModule::class])
@PerDialog
interface AddObjectRelationValueSubComponent {
@Subcomponent.Builder
interface Builder {
fun module(module: AddObjectRelationValueModule): Builder
fun build(): AddObjectRelationValueSubComponent
}
fun inject(fragment: AddObjectSetObjectRelationValueFragment)
fun inject(fragment: AddObjectObjectRelationValueFragment)
}
@Module
object AddObjectRelationValueModule {
@JvmStatic
@Provides
@PerDialog
fun provideViewModelFactoryForSets(
relations: ObjectRelationProvider,
values: ObjectValueProvider,
details: ObjectDetailProvider,
types: ObjectTypeProvider,
dispatcher: Dispatcher<Payload>,
addDataViewRelationOption: AddDataViewRelationOption,
addTagToDataViewRecord: AddTagToDataViewRecord,
addStatusToDataViewRecord: AddStatusToDataViewRecord,
urlBuilder: UrlBuilder
): AddObjectSetObjectRelationValueViewModel.Factory = AddObjectSetObjectRelationValueViewModel.Factory(
relations = relations,
values = values,
details = details,
types = types,
urlBuilder = urlBuilder,
dispatcher = dispatcher,
addDataViewRelationOption = addDataViewRelationOption,
addTagToDataViewRecord = addTagToDataViewRecord,
addStatusToDataViewRecord = addStatusToDataViewRecord
)
@JvmStatic
@Provides
@PerDialog
fun provideViewModelFactoryForObjects(
relations: ObjectRelationProvider,
values: ObjectValueProvider,
details: ObjectDetailProvider,
types: ObjectTypeProvider,
dispatcher: Dispatcher<Payload>,
addObjectRelationOption: AddObjectRelationOption,
updateDetail: UpdateDetail,
urlBuilder: UrlBuilder
): AddObjectObjectRelationValueViewModel.Factory = AddObjectObjectRelationValueViewModel.Factory(
relations = relations,
values = values,
details = details,
types = types,
urlBuilder = urlBuilder,
dispatcher = dispatcher,
addObjectRelationOption = addObjectRelationOption,
updateDetail = updateDetail
)
@JvmStatic
@Provides
@PerDialog
fun provideAddObjectRelationOptionUseCase(
repo: BlockRepository
): AddObjectRelationOption = AddObjectRelationOption(repo = repo)
@JvmStatic
@Provides
@PerDialog
fun provideAddStatusToDataViewRecordUseCase(
repo: BlockRepository
): AddStatusToDataViewRecord = AddStatusToDataViewRecord(repo)
}

View file

@ -4,7 +4,7 @@ import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.core_utils.di.scope.PerScreen
import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
import com.anytypeio.anytype.domain.page.ArchiveDocument
import com.anytypeio.anytype.domain.page.ClosePage
import com.anytypeio.anytype.domain.page.CloseBlock
import com.anytypeio.anytype.domain.page.OpenPage
import com.anytypeio.anytype.presentation.page.DocumentExternalEventReducer
import com.anytypeio.anytype.presentation.page.archive.ArchiveViewModelFactory
@ -42,7 +42,7 @@ object ArchiveModule {
@Provides
fun provideArchiveViewModelFactory(
openPage: OpenPage,
closePage: ClosePage,
closePage: CloseBlock,
archiveDocument: ArchiveDocument,
interceptEvents: InterceptEvents,
renderer: DefaultBlockViewRenderer,

View file

@ -0,0 +1,31 @@
package com.anytypeio.anytype.di.feature;
import com.anytypeio.anytype.core_utils.di.scope.PerScreen
import com.anytypeio.anytype.presentation.relations.CreateDataViewRelationViewModelFactory
import com.anytypeio.anytype.ui.relations.CreateDataViewRelationFragment
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
@Subcomponent(modules = [CreateDataViewRelationModule::class])
@PerScreen
interface CreateDataViewRelationSubComponent {
@Subcomponent.Builder
interface Builder {
fun module(module: CreateDataViewRelationModule): Builder
fun build(): CreateDataViewRelationSubComponent
}
fun inject(fragment: CreateDataViewRelationFragment)
}
@Module
object CreateDataViewRelationModule {
@JvmStatic
@Provides
@PerScreen
fun provideCreateDVRelationViewModelFactory() = CreateDataViewRelationViewModelFactory()
}

View file

@ -0,0 +1,46 @@
package com.anytypeio.anytype.di.feature;
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_utils.di.scope.PerModal
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.dataview.interactor.AddDataViewViewer
import com.anytypeio.anytype.presentation.sets.CreateDataViewViewerViewModel
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.sets.modals.CreateDataViewViewerFragment
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
@Subcomponent(modules = [CreateDataViewViewerModule::class])
@PerModal
interface CreateDataViewViewerSubComponent {
@Subcomponent.Builder
interface Builder {
fun module(module: CreateDataViewViewerModule): Builder
fun build(): CreateDataViewViewerSubComponent
}
fun inject(fragment: CreateDataViewViewerFragment)
}
@Module
object CreateDataViewViewerModule {
@JvmStatic
@Provides
@PerModal
fun provideCreateDataViewViewerViewModelFactory(
dispatcher: Dispatcher<Payload>,
addDataViewViewer: AddDataViewViewer
): CreateDataViewViewerViewModel.Factory = CreateDataViewViewerViewModel.Factory(
dispatcher = dispatcher,
addDataViewViewer = addDataViewViewer
)
@JvmStatic
@Provides
@PerModal
fun provideAddDataViewViewerUseCase(
repo: BlockRepository
): AddDataViewViewer = AddDataViewViewer(repo = repo)
}

View file

@ -0,0 +1,70 @@
package com.anytypeio.anytype.di.feature
import com.anytypeio.anytype.core_utils.di.scope.PerScreen
import com.anytypeio.anytype.domain.block.interactor.sets.CreateObjectSet
import com.anytypeio.anytype.domain.block.interactor.sets.CreateObjectType
import com.anytypeio.anytype.domain.block.interactor.sets.GetObjectTypes
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.icon.DocumentEmojiIconProvider
import com.anytypeio.anytype.presentation.sets.CreateObjectSetViewModel
import com.anytypeio.anytype.ui.sets.CreateObjectSetFragment
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
@Subcomponent(modules = [CreateSetModule::class])
@PerScreen
interface CreateSetSubComponent {
@Subcomponent.Builder
interface Builder {
fun module(module: CreateSetModule): Builder
fun build(): CreateSetSubComponent
}
fun inject(fragment: CreateObjectSetFragment)
}
@Module
object CreateSetModule {
@JvmStatic
@Provides
@PerScreen
fun provideCreateSetViewModelFactory(
getObjectTypes: GetObjectTypes,
createObjectSet: CreateObjectSet,
createObjectType: CreateObjectType
): CreateObjectSetViewModel.Factory {
return CreateObjectSetViewModel.Factory(
getObjectTypes = getObjectTypes,
createObjectSet = createObjectSet,
createObjectType = createObjectType
)
}
@JvmStatic
@Provides
@PerScreen
fun provideCreateObjectTypeUseCase(
repo: BlockRepository,
documentEmojiProvider: DocumentEmojiIconProvider
): CreateObjectType = CreateObjectType(
repo = repo,
documentEmojiProvider = documentEmojiProvider
)
@JvmStatic
@Provides
@PerScreen
fun provideGetObjectTypesUseCase(
repo: BlockRepository
): GetObjectTypes = GetObjectTypes(repo = repo)
@JvmStatic
@Provides
@PerScreen
fun provideCreateObjectSetUseCase(
repo: BlockRepository
): CreateObjectSet = CreateObjectSet(repo = repo)
}

View file

@ -0,0 +1,33 @@
package com.anytypeio.anytype.di.feature
import com.anytypeio.anytype.presentation.sets.CreateObjectTypeViewModel
import com.anytypeio.anytype.core_utils.di.scope.PerScreen
import com.anytypeio.anytype.ui.sets.CreateObjectTypeFragment
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
@Subcomponent(modules = [CreateObjectTypeModule::class])
@PerScreen
interface CreateObjectTypeSubComponent {
@Subcomponent.Builder
interface Builder {
fun module(module: CreateObjectTypeModule): Builder
fun build(): CreateObjectTypeSubComponent
}
fun inject(fragment: CreateObjectTypeFragment)
}
@Module
object CreateObjectTypeModule {
@JvmStatic
@Provides
@PerScreen
fun provideCreateObjectTypeViewModelFactory(
): CreateObjectTypeViewModel.Factory {
return CreateObjectTypeViewModel.Factory()
}
}

View file

@ -0,0 +1,68 @@
package com.anytypeio.anytype.di.feature;
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_utils.di.scope.PerModal
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.dataview.interactor.DeleteDataViewViewer
import com.anytypeio.anytype.domain.dataview.interactor.DuplicateDataViewViewer
import com.anytypeio.anytype.domain.dataview.interactor.RenameDataViewViewer
import com.anytypeio.anytype.presentation.sets.DataViewViewerActionViewModel
import com.anytypeio.anytype.presentation.sets.ObjectSet
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.sets.modals.DataViewViewerActionFragment
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
import kotlinx.coroutines.flow.StateFlow
@Subcomponent(modules = [DataViewViewerActionModule::class])
@PerModal
interface DataViewViewerActionSubComponent {
@Subcomponent.Builder
interface Builder {
fun module(module: DataViewViewerActionModule): Builder
fun build(): DataViewViewerActionSubComponent
}
fun inject(fragment: DataViewViewerActionFragment)
}
@Module
object DataViewViewerActionModule {
@JvmStatic
@Provides
@PerModal
fun provideEditDataViewViewerViewModelFactory(
duplicateDataViewViewer: DuplicateDataViewViewer,
deleteDataViewViewer: DeleteDataViewViewer,
dispatcher: Dispatcher<Payload>,
objectSetState: StateFlow<ObjectSet>
): DataViewViewerActionViewModel.Factory = DataViewViewerActionViewModel.Factory(
duplicateDataViewViewer = duplicateDataViewViewer,
deleteDataViewViewer = deleteDataViewViewer,
dispatcher = dispatcher,
objectSetState = objectSetState
)
@JvmStatic
@Provides
@PerModal
fun provideRenameDataViewViewerUseCase(
repo: BlockRepository
): RenameDataViewViewer = RenameDataViewViewer(repo = repo)
@JvmStatic
@Provides
@PerModal
fun provideDuplicateDataViewViewerUseCase(
repo: BlockRepository
): DuplicateDataViewViewer = DuplicateDataViewViewer(repo = repo)
@JvmStatic
@Provides
@PerModal
fun provideDeleteDataViewViewerUseCase(
repo: BlockRepository
): DeleteDataViewViewer = DeleteDataViewViewer(repo = repo)
}

View file

@ -1,9 +1,11 @@
package com.anytypeio.anytype.di.feature
import com.anytypeio.anytype.core_utils.di.scope.PerScreen
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.config.GetDebugSettings
import com.anytypeio.anytype.domain.config.InfrastructureRepository
import com.anytypeio.anytype.domain.config.UseCustomContextMenu
import com.anytypeio.anytype.domain.dataview.interactor.DebugSync
import com.anytypeio.anytype.ui.settings.DebugSettingsFragment
import dagger.Module
import dagger.Provides
@ -37,4 +39,8 @@ class DebugSettingsModule {
): GetDebugSettings = GetDebugSettings(
repo = repo
)
@Provides
@PerScreen
fun provideDebugSync(repo: BlockRepository) : DebugSync = DebugSync(repo = repo)
}

View file

@ -0,0 +1,40 @@
package com.anytypeio.anytype.di.feature
import com.anytypeio.anytype.core_utils.di.scope.PerModal
import com.anytypeio.anytype.domain.block.interactor.sets.GetObjectTypes
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.presentation.page.picker.DocumentAddBlockViewModelFactory
import com.anytypeio.anytype.ui.page.modals.AddBlockFragment
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
@Subcomponent(modules = [DocumentAddNewBlockModule::class])
@PerModal
interface DocumentAddNewBlockSubComponent{
@Subcomponent.Builder
interface Builder {
fun documentAddNewBlockModule(module: DocumentAddNewBlockModule): Builder
fun build(): DocumentAddNewBlockSubComponent
}
fun inject(fragment: AddBlockFragment)
}
@Module
object DocumentAddNewBlockModule {
@JvmStatic
@Provides
@PerModal
fun provideGetObjectTypesUseCase(
repo: BlockRepository
): GetObjectTypes = GetObjectTypes(repo = repo)
@JvmStatic
@Provides
@PerModal
fun provideFactory(getObjectTypes: GetObjectTypes): DocumentAddBlockViewModelFactory =
DocumentAddBlockViewModelFactory(getObjectTypes)
}

View file

@ -1,20 +1,21 @@
package com.anytypeio.anytype.di.feature
import com.anytypeio.anytype.core_utils.di.scope.PerScreen
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_utils.di.scope.PerModal
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.event.model.Payload
import com.anytypeio.anytype.domain.icon.SetDocumentEmojiIcon
import com.anytypeio.anytype.emojifier.data.Emoji
import com.anytypeio.anytype.emojifier.suggest.EmojiSuggester
import com.anytypeio.anytype.presentation.page.editor.DetailModificationManager
import com.anytypeio.anytype.presentation.page.picker.DocumentEmojiIconPickerViewModelFactory
import com.anytypeio.anytype.presentation.util.Bridge
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.page.modals.DocumentEmojiIconPickerFragment
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
@Subcomponent(modules = [DocumentEmojiIconPickerModule::class])
@PerScreen
@PerModal
interface DocumentEmojiIconPickerSubComponent {
@Subcomponent.Builder
@ -30,20 +31,22 @@ interface DocumentEmojiIconPickerSubComponent {
class DocumentEmojiIconPickerModule {
@Provides
@PerScreen
@PerModal
fun provideDocumentEmojiIconPickerViewModel(
setEmojiIcon: SetDocumentEmojiIcon,
emojiSuggester: EmojiSuggester,
bridge: Bridge<Payload>
dispatcher: Dispatcher<Payload>,
details: DetailModificationManager
): DocumentEmojiIconPickerViewModelFactory = DocumentEmojiIconPickerViewModelFactory(
setEmojiIcon = setEmojiIcon,
emojiSuggester = emojiSuggester,
emojiProvider = Emoji,
bridge = bridge
dispatcher = dispatcher,
details = details
)
@Provides
@PerScreen
@PerModal
fun provideSetDocumentEmojiIconUseCase(
repo: BlockRepository
): SetDocumentEmojiIcon = SetDocumentEmojiIcon(

View file

@ -1,12 +1,13 @@
package com.anytypeio.anytype.di.feature
import com.anytypeio.anytype.core_utils.di.scope.PerScreen
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_utils.di.scope.PerModal
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.event.model.Payload
import com.anytypeio.anytype.domain.icon.SetDocumentEmojiIcon
import com.anytypeio.anytype.domain.icon.SetDocumentImageIcon
import com.anytypeio.anytype.presentation.page.editor.DetailModificationManager
import com.anytypeio.anytype.presentation.page.picker.DocumentIconActionMenuViewModelFactory
import com.anytypeio.anytype.presentation.util.Bridge
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.page.modals.actions.DocumentIconActionMenuFragment
import com.anytypeio.anytype.ui.page.modals.actions.ProfileIconActionMenuFragment
import dagger.Module
@ -14,7 +15,7 @@ import dagger.Provides
import dagger.Subcomponent
@Subcomponent(modules = [DocumentIconActionMenuModule::class])
@PerScreen
@PerModal
interface DocumentActionMenuSubComponent {
@Subcomponent.Builder
@ -31,19 +32,21 @@ interface DocumentActionMenuSubComponent {
class DocumentIconActionMenuModule {
@Provides
@PerScreen
@PerModal
fun provideDocumentIconActionMenuViewModelFactory(
setEmojiIcon: SetDocumentEmojiIcon,
setImageIcon: SetDocumentImageIcon,
bridge: Bridge<Payload>
dispatcher: Dispatcher<Payload>,
details: DetailModificationManager
): DocumentIconActionMenuViewModelFactory = DocumentIconActionMenuViewModelFactory(
setEmojiIcon = setEmojiIcon,
setImageIcon = setImageIcon,
bridge = bridge
dispatcher = dispatcher,
details = details
)
@Provides
@PerScreen
@PerModal
fun provideSetDocumentEmojiIconUseCase(
repo: BlockRepository
): SetDocumentEmojiIcon = SetDocumentEmojiIcon(
@ -51,7 +54,7 @@ class DocumentIconActionMenuModule {
)
@Provides
@PerScreen
@PerModal
fun provideSetDocumentImageIconUseCase(
repo: BlockRepository
): SetDocumentImageIcon = SetDocumentImageIcon(

View file

@ -0,0 +1,50 @@
package com.anytypeio.anytype.di.feature;
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_utils.di.scope.PerModal
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.dataview.interactor.RenameDataViewViewer
import com.anytypeio.anytype.presentation.sets.EditDataViewViewerViewModel
import com.anytypeio.anytype.presentation.sets.ObjectSet
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.sets.modals.EditDataViewViewerFragment
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
import kotlinx.coroutines.flow.StateFlow
@Subcomponent(modules = [EditDataViewViewerModule::class])
@PerModal
interface EditDataViewViewerSubComponent {
@Subcomponent.Builder
interface Builder {
fun module(module: EditDataViewViewerModule): Builder
fun build(): EditDataViewViewerSubComponent
}
fun inject(fragment: EditDataViewViewerFragment)
}
@Module
object EditDataViewViewerModule {
@JvmStatic
@Provides
@PerModal
fun provideEditDataViewViewerViewModelFactory(
renameDataViewViewer: RenameDataViewViewer,
dispatcher: Dispatcher<Payload>,
objectSetState: StateFlow<ObjectSet>
): EditDataViewViewerViewModel.Factory = EditDataViewViewerViewModel.Factory(
renameDataViewViewer = renameDataViewViewer,
dispatcher = dispatcher,
objectSetState = objectSetState
)
@JvmStatic
@Provides
@PerModal
fun provideRenameDataViewViewerUseCase(
repo: BlockRepository
): RenameDataViewViewer = RenameDataViewViewer(repo = repo)
}

View file

@ -0,0 +1,61 @@
package com.anytypeio.anytype.di.feature;
import com.anytypeio.anytype.core_utils.di.scope.PerModal
import com.anytypeio.anytype.presentation.relations.providers.ObjectRelationProvider
import com.anytypeio.anytype.presentation.relations.providers.ObjectValueProvider
import com.anytypeio.anytype.presentation.sets.ObjectRelationDateValueViewModel
import com.anytypeio.anytype.presentation.sets.ObjectRelationTextValueViewModel
import com.anytypeio.anytype.ui.relations.ObjectRelationDateValueFragment
import com.anytypeio.anytype.ui.relations.ObjectRelationTextValueFragment
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
@Subcomponent(modules = [EditGridCellModule::class])
@PerModal
interface EditGridCellSubComponent {
@Subcomponent.Builder
interface Builder {
fun module(module: EditGridCellModule): Builder
fun build(): EditGridCellSubComponent
}
fun inject(fragment: ObjectRelationTextValueFragment)
}
@Module
object EditGridCellModule {
@JvmStatic
@Provides
@PerModal
fun provideEditGridCellViewModelFactory(
relations: ObjectRelationProvider,
values: ObjectValueProvider
) = ObjectRelationTextValueViewModel.Factory(relations, values)
}
@Subcomponent(modules = [EditGridCellDateModule::class])
@PerModal
interface EditGridCellDateSubComponent {
@Subcomponent.Builder
interface Builder {
fun module(module: EditGridCellDateModule): Builder
fun build(): EditGridCellDateSubComponent
}
fun inject(fragment: ObjectRelationDateValueFragment)
}
@Module
object EditGridCellDateModule {
@JvmStatic
@Provides
@PerModal
fun provideEditGridCellViewModelFactory(
relations: ObjectRelationProvider,
values: ObjectValueProvider
) = ObjectRelationDateValueViewModel.Factory(relations, values)
}

View file

@ -0,0 +1,44 @@
package com.anytypeio.anytype.di.feature;
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_utils.di.scope.PerModal
import com.anytypeio.anytype.domain.dataview.interactor.SetActiveViewer
import com.anytypeio.anytype.presentation.sets.ManageViewerViewModel
import com.anytypeio.anytype.presentation.sets.ObjectSet
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.sets.modals.ManageViewerFragment
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
import kotlinx.coroutines.flow.StateFlow
@Subcomponent(modules = [ManageViewerModule::class])
@PerModal
interface ManageViewerSubComponent {
@Subcomponent.Builder
interface Builder {
fun module(module: ManageViewerModule): Builder
fun build(): ManageViewerSubComponent
}
fun inject(fragment: ManageViewerFragment)
}
@Module
object ManageViewerModule {
@JvmStatic
@Provides
@PerModal
fun provideManageViewerViewModelFactory(
state: StateFlow<ObjectSet>,
session: ObjectSetSession,
dispatcher: Dispatcher<Payload>,
setActiveViewer: SetActiveViewer
): ManageViewerViewModel.Factory = ManageViewerViewModel.Factory(
state = state,
session = session,
dispatcher = dispatcher,
setActiveViewer = setActiveViewer
)
}

View file

@ -0,0 +1,44 @@
package com.anytypeio.anytype.di.feature;
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_utils.di.scope.PerModal
import com.anytypeio.anytype.domain.dataview.interactor.UpdateDataViewViewer
import com.anytypeio.anytype.presentation.sets.ObjectSet
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
import com.anytypeio.anytype.presentation.sets.sort.ModifyViewerSortViewModel
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.sets.modals.sort.ModifyViewerSortFragment
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
import kotlinx.coroutines.flow.StateFlow
@Subcomponent(modules = [ModifyViewerSortModule::class])
@PerModal
interface ModifyViewerSortSubComponent {
@Subcomponent.Builder
interface Builder {
fun module(module: ModifyViewerSortModule): Builder
fun build(): ModifyViewerSortSubComponent
}
fun inject(fragment: ModifyViewerSortFragment)
}
@Module
object ModifyViewerSortModule {
@JvmStatic
@Provides
@PerModal
fun provideViewModelFactory(
state: StateFlow<ObjectSet>,
session: ObjectSetSession,
dispatcher: Dispatcher<Payload>,
updateDataViewViewer: UpdateDataViewViewer
): ModifyViewerSortViewModel.Factory = ModifyViewerSortViewModel.Factory(
state = state,
session = session,
dispatcher = dispatcher,
updateDataViewViewer = updateDataViewViewer
)
}

View file

@ -0,0 +1,60 @@
package com.anytypeio.anytype.di.feature
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_utils.di.scope.PerModal
import com.anytypeio.anytype.domain.`object`.UpdateDetail
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.dataview.interactor.ObjectRelationList
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.presentation.page.Editor
import com.anytypeio.anytype.presentation.page.editor.DetailModificationManager
import com.anytypeio.anytype.presentation.relations.ObjectRelationListViewModelFactory
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.relations.ObjectRelationListFragment
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
@Subcomponent(modules = [DocumentRelationModule::class])
@PerModal
interface DocumentRelationSubComponent {
@Subcomponent.Builder
interface Builder {
fun module(module: DocumentRelationModule): Builder
fun build(): DocumentRelationSubComponent
}
fun inject(fragment: ObjectRelationListFragment)
}
@Module
object DocumentRelationModule {
@JvmStatic
@Provides
@PerModal
fun provideObjectRelationViewModelFactory(
stores: Editor.Storage,
urlBuilder: UrlBuilder,
objectRelationList: ObjectRelationList,
dispatcher: Dispatcher<Payload>,
updateDetail: UpdateDetail,
detailModificationManager: DetailModificationManager
): ObjectRelationListViewModelFactory {
return ObjectRelationListViewModelFactory(
stores = stores,
urlBuilder = urlBuilder,
objectRelationList = objectRelationList,
dispatcher = dispatcher,
updateDetail = updateDetail,
detailModificationManager = detailModificationManager
)
}
@JvmStatic
@Provides
@PerModal
fun provideObjectRelationListUseCase(
repository: BlockRepository
) : ObjectRelationList = ObjectRelationList(repository)
}

View file

@ -0,0 +1,130 @@
package com.anytypeio.anytype.di.feature
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_utils.di.scope.PerModal
import com.anytypeio.anytype.domain.`object`.UpdateDetail
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.dataview.interactor.AddDataViewRelationOption
import com.anytypeio.anytype.domain.dataview.interactor.AddTagToDataViewRecord
import com.anytypeio.anytype.domain.dataview.interactor.RemoveTagFromDataViewRecord
import com.anytypeio.anytype.domain.dataview.interactor.UpdateDataViewRecord
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.presentation.relations.providers.ObjectDetailProvider
import com.anytypeio.anytype.presentation.relations.providers.ObjectRelationProvider
import com.anytypeio.anytype.presentation.relations.providers.ObjectTypeProvider
import com.anytypeio.anytype.presentation.relations.providers.ObjectValueProvider
import com.anytypeio.anytype.presentation.sets.ObjectObjectRelationValueViewModel
import com.anytypeio.anytype.presentation.sets.ObjectSetObjectRelationValueViewModel
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.database.modals.ObjectObjectRelationValueFragment
import com.anytypeio.anytype.ui.database.modals.ObjectSetObjectRelationValueFragment
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
@Subcomponent(modules = [ObjectRelationValueModule::class, ObjectSetObjectRelationValueModule::class])
@PerModal
interface ObjectSetObjectRelationValueSubComponent {
@Subcomponent.Builder
interface Builder {
fun module(module: ObjectRelationValueModule): Builder
fun build(): ObjectSetObjectRelationValueSubComponent
}
fun inject(fragment: ObjectSetObjectRelationValueFragment)
fun addObjectRelationValueComponent(): AddObjectRelationValueSubComponent.Builder
fun addObjectRelationObjectValueComponent(): AddObjectRelationObjectValueSubComponent.Builder
fun addRelationFileValueAddComponent() : RelationFileValueAddSubComponent.Builder
}
@Subcomponent(modules = [ObjectRelationValueModule::class, ObjectObjectRelationValueModule::class])
@PerModal
interface ObjectObjectRelationValueSubComponent {
@Subcomponent.Builder
interface Builder {
fun module(module: ObjectRelationValueModule): Builder
fun build(): ObjectObjectRelationValueSubComponent
}
fun inject(fragment: ObjectObjectRelationValueFragment)
fun addObjectRelationValueComponent(): AddObjectRelationValueSubComponent.Builder
fun addObjectRelationObjectValueComponent(): AddObjectRelationObjectValueSubComponent.Builder
fun addRelationFileValueAddComponent() : RelationFileValueAddSubComponent.Builder
}
@Module
object ObjectRelationValueModule {
@JvmStatic
@Provides
@PerModal
fun provideAddRelationOptionUseCase(
repo: BlockRepository
): AddDataViewRelationOption = AddDataViewRelationOption(repo = repo)
@JvmStatic
@Provides
@PerModal
fun provideAddTagToDataViewRecordUseCase(
repo: BlockRepository
): AddTagToDataViewRecord = AddTagToDataViewRecord(repo = repo)
@JvmStatic
@Provides
@PerModal
fun provideRemoveTagFromDataViewRecordUseCase(
repo: BlockRepository
): RemoveTagFromDataViewRecord = RemoveTagFromDataViewRecord(repo = repo)
}
@Module
object ObjectSetObjectRelationValueModule {
@JvmStatic
@Provides
@PerModal
fun provideViewModelFactoryForDataView(
relations: ObjectRelationProvider,
values: ObjectValueProvider,
details: ObjectDetailProvider,
types: ObjectTypeProvider,
removeTagFromDataViewRecord: RemoveTagFromDataViewRecord,
urlBuilder: UrlBuilder,
dispatcher: Dispatcher<Payload>,
updateDataViewRecord: UpdateDataViewRecord
): ObjectSetObjectRelationValueViewModel.Factory = ObjectSetObjectRelationValueViewModel.Factory(
relations = relations,
values = values,
details = details,
types = types,
removeTagFromRecord = removeTagFromDataViewRecord,
urlBuilder = urlBuilder,
dispatcher = dispatcher,
updateDataViewRecord = updateDataViewRecord
)
}
@Module
object ObjectObjectRelationValueModule {
@JvmStatic
@Provides
@PerModal
fun provideViewModelFactoryForObject(
relations: ObjectRelationProvider,
values: ObjectValueProvider,
details: ObjectDetailProvider,
types: ObjectTypeProvider,
urlBuilder: UrlBuilder,
dispatcher: Dispatcher<Payload>,
updateDetail: UpdateDetail,
): ObjectObjectRelationValueViewModel.Factory = ObjectObjectRelationValueViewModel.Factory(
relations = relations,
values = values,
details = details,
types = types,
urlBuilder = urlBuilder,
dispatcher = dispatcher,
updateDetail = updateDetail
)
}

View file

@ -0,0 +1,230 @@
package com.anytypeio.anytype.di.feature
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_utils.di.scope.PerScreen
import com.anytypeio.anytype.di.feature.sets.CreateFilterSubComponent
import com.anytypeio.anytype.di.feature.sets.ModifyFilterSubComponent
import com.anytypeio.anytype.di.feature.sets.SelectFilterRelationSubComponent
import com.anytypeio.anytype.domain.`object`.UpdateDetail
import com.anytypeio.anytype.domain.block.interactor.UpdateText
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.dataview.interactor.*
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.page.CloseBlock
import com.anytypeio.anytype.domain.sets.OpenObjectSet
import com.anytypeio.anytype.presentation.relations.providers.*
import com.anytypeio.anytype.presentation.sets.*
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.sets.ObjectSetFragment
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.StateFlow
@Subcomponent(modules = [ObjectSetModule::class])
@PerScreen
interface ObjectSetSubComponent {
@Subcomponent.Builder
interface Builder {
fun module(module: ObjectSetModule): Builder
fun build(): ObjectSetSubComponent
}
fun inject(fragment: ObjectSetFragment)
fun objectSetRecordComponent(): ObjectSetRecordSubComponent.Builder
fun viewerCustomizeSubComponent(): ViewerCustomizeSubComponent.Builder
fun viewerSortBySubComponent(): ViewerSortBySubComponent.Builder
fun viewerFilterBySubComponent(): ViewerFilterSubComponent.Builder
fun createDataViewViewerSubComponent(): CreateDataViewViewerSubComponent.Builder
fun editDataViewViewerComponent(): EditDataViewViewerSubComponent.Builder
fun objectRelationValueComponent(): ObjectSetObjectRelationValueSubComponent.Builder
fun manageViewerComponent(): ManageViewerSubComponent.Builder
fun viewerRelationsComponent(): ViewerRelationsSubComponent.Builder
fun dataviewViewerActionComponent(): DataViewViewerActionSubComponent.Builder
fun selectSortRelationComponent(): SelectSortRelationSubComponent.Builder
fun selectFilterRelationComponent(): SelectFilterRelationSubComponent.Builder
fun createFilterComponent(): CreateFilterSubComponent.Builder
fun modifyFilterComponent(): ModifyFilterSubComponent.Builder
fun viewerSortComponent(): ViewerSortSubComponent.Builder
fun modifyViewerSortComponent(): ModifyViewerSortSubComponent.Builder
fun editCellsComponent(): EditGridCellSubComponent.Builder
fun editCellDateComponent(): EditGridCellDateSubComponent.Builder
}
@Module
object ObjectSetModule {
@JvmStatic
@Provides
@PerScreen
fun provideObjectSetViewModelFactory(
openObjectSet: OpenObjectSet,
closeBlock: CloseBlock,
setActiveViewer: SetActiveViewer,
addDataViewRelation: AddDataViewRelation,
updateDataViewViewer: UpdateDataViewViewer,
updateDataViewRecord: UpdateDataViewRecord,
updateText: UpdateText,
interceptEvents: InterceptEvents,
createDataViewRecord: CreateDataViewRecord,
reducer: ObjectSetReducer,
dispatcher: Dispatcher<Payload>,
objectSetRecordCache: ObjectSetRecordCache,
urlBuilder: UrlBuilder,
session: ObjectSetSession
): ObjectSetViewModelFactory = ObjectSetViewModelFactory(
openObjectSet = openObjectSet,
closeBlock = closeBlock,
setActiveViewer = setActiveViewer,
addDataViewRelation = addDataViewRelation,
updateDataViewViewer = updateDataViewViewer,
updateDataViewRecord = updateDataViewRecord,
createDataViewRecord = createDataViewRecord,
updateText = updateText,
interceptEvents = interceptEvents,
reducer = reducer,
dispatcher = dispatcher,
objectSetRecordCache = objectSetRecordCache,
urlBuilder = urlBuilder,
session = session
)
@JvmStatic
@Provides
@PerScreen
fun provideOpenObjectSetUseCase(repo: BlockRepository): OpenObjectSet = OpenObjectSet(repo)
@JvmStatic
@Provides
@PerScreen
fun provideSetActiveViewerUseCase(
repo: BlockRepository
): SetActiveViewer = SetActiveViewer(repo)
@JvmStatic
@Provides
@PerScreen
fun provideAddDataViewRelationUseCase(
repo: BlockRepository
): AddDataViewRelation = AddDataViewRelation(repo = repo)
@JvmStatic
@Provides
@PerScreen
fun provideUpdateDataViewViewerUseCase(
repo: BlockRepository
): UpdateDataViewViewer = UpdateDataViewViewer(repo = repo)
@JvmStatic
@Provides
@PerScreen
fun provideCreateDataViewRecordUseCase(
repo: BlockRepository
): CreateDataViewRecord = CreateDataViewRecord(repo = repo)
@JvmStatic
@Provides
@PerScreen
fun provideUpdateDataViewRecordUseCase(
repo: BlockRepository
): UpdateDataViewRecord = UpdateDataViewRecord(repo = repo)
@JvmStatic
@Provides
@PerScreen
fun provideUpdateTextUseCase(
repo: BlockRepository
): UpdateText = UpdateText(repo = repo)
@JvmStatic
@Provides
@PerScreen
fun provideInterceptEventsUseCase(
channel: EventChannel
): InterceptEvents = InterceptEvents(
channel = channel,
context = Dispatchers.IO
)
@JvmStatic
@Provides
@PerScreen
fun provideCloseBlockUseCase(
repo: BlockRepository
): CloseBlock = CloseBlock(repo)
@JvmStatic
@Provides
@PerScreen
fun provideObjectSetReducer(): ObjectSetReducer = ObjectSetReducer()
@JvmStatic
@Provides
@PerScreen
fun provideState(
reducer: ObjectSetReducer
): StateFlow<ObjectSet> = reducer.state
@JvmStatic
@Provides
@PerScreen
fun provideObjectSetSession(): ObjectSetSession = ObjectSetSession()
@JvmStatic
@Provides
@PerScreen
fun provideDispatcher(): Dispatcher<Payload> = Dispatcher.Default()
@JvmStatic
@Provides
@PerScreen
fun provideObjectSetRecordCache(): ObjectSetRecordCache = ObjectSetRecordCache()
@JvmStatic
@Provides
@PerScreen
fun provideDataViewObjectRelationProvider(
state: StateFlow<ObjectSet>
) : ObjectRelationProvider = DataViewObjectRelationProvider(state)
@JvmStatic
@Provides
@PerScreen
fun provideDataViewObjectValueProvider(
state: StateFlow<ObjectSet>,
session: ObjectSetSession
) : ObjectValueProvider = DataViewObjectValueProvider(state, session)
@JvmStatic
@Provides
@PerScreen
fun provideObjectTypeProvider(
state: StateFlow<ObjectSet>,
) : ObjectTypeProvider = object : ObjectTypeProvider {
override fun provide(): List<ObjectType> = state.value.objectTypes
}
@JvmStatic
@Provides
@PerScreen
fun provideObjectDetailProvider(
state: StateFlow<ObjectSet>,
) : ObjectDetailProvider = object : ObjectDetailProvider {
override fun provide(): Map<Id, Block.Fields> = state.value.details
}
@JvmStatic
@Provides
@PerScreen
fun provideUpdateDetailUseCase(
repository: BlockRepository
) : UpdateDetail = UpdateDetail(repository)
}

View file

@ -0,0 +1,45 @@
package com.anytypeio.anytype.di.feature;
import com.anytypeio.anytype.domain.dataview.interactor.UpdateDataViewRecord
import com.anytypeio.anytype.presentation.sets.ObjectSet
import com.anytypeio.anytype.presentation.sets.ObjectSetRecordCache
import com.anytypeio.anytype.presentation.sets.ObjectSetRecordViewModel
import com.anytypeio.anytype.ui.sets.SetObjectSetRecordNameFragment
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Scope
@Subcomponent(modules = [ObjectSetRecordModule::class])
@ObjectSetRecordScope
interface ObjectSetRecordSubComponent {
@Subcomponent.Builder
interface Builder {
fun module(module: ObjectSetRecordModule): Builder
fun build(): ObjectSetRecordSubComponent
}
fun inject(fragment: SetObjectSetRecordNameFragment)
}
@Module
object ObjectSetRecordModule {
@JvmStatic
@Provides
@ObjectSetRecordScope
fun provideObjectSetRecordViewModelFactory(
updateDataViewRecord: UpdateDataViewRecord,
objectSetState: StateFlow<ObjectSet>,
objectSetRecordCache: ObjectSetRecordCache
): ObjectSetRecordViewModel.Factory = ObjectSetRecordViewModel.Factory(
objectSetState = objectSetState,
objectSetRecordCache = objectSetRecordCache,
updateDataViewRecord = updateDataViewRecord
)
}
@Scope
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
annotation class ObjectSetRecordScope

View file

@ -1,8 +1,13 @@
package com.anytypeio.anytype.di.feature
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_utils.di.scope.PerScreen
import com.anytypeio.anytype.core_utils.tools.Counter
import com.anytypeio.anytype.domain.`object`.UpdateDetail
import com.anytypeio.anytype.domain.block.UpdateDivider
import com.anytypeio.anytype.domain.block.interactor.*
import com.anytypeio.anytype.domain.block.repo.BlockRepository
@ -11,11 +16,11 @@ import com.anytypeio.anytype.domain.clipboard.Copy
import com.anytypeio.anytype.domain.clipboard.Paste
import com.anytypeio.anytype.domain.cover.RemoveDocCover
import com.anytypeio.anytype.domain.cover.SetDocCoverImage
import com.anytypeio.anytype.domain.dataview.interactor.SetRelationKey
import com.anytypeio.anytype.domain.download.DownloadFile
import com.anytypeio.anytype.domain.download.Downloader
import com.anytypeio.anytype.domain.event.interactor.EventChannel
import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
import com.anytypeio.anytype.domain.event.model.Payload
import com.anytypeio.anytype.domain.icon.DocumentEmojiIconProvider
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.page.*
@ -27,13 +32,16 @@ import com.anytypeio.anytype.presentation.page.DocumentExternalEventReducer
import com.anytypeio.anytype.presentation.page.Editor
import com.anytypeio.anytype.presentation.page.PageViewModelFactory
import com.anytypeio.anytype.presentation.page.cover.CoverImageHashProvider
import com.anytypeio.anytype.presentation.page.editor.DetailModificationManager
import com.anytypeio.anytype.presentation.page.editor.Interactor
import com.anytypeio.anytype.presentation.page.editor.InternalDetailModificationManager
import com.anytypeio.anytype.presentation.page.editor.Orchestrator
import com.anytypeio.anytype.presentation.page.editor.pattern.DefaultPatternMatcher
import com.anytypeio.anytype.presentation.page.render.DefaultBlockViewRenderer
import com.anytypeio.anytype.presentation.page.selection.SelectionStateHolder
import com.anytypeio.anytype.presentation.page.toggle.ToggleStateHolder
import com.anytypeio.anytype.presentation.util.Bridge
import com.anytypeio.anytype.presentation.relations.providers.*
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.providers.DefaultCoverImageHashProvider
import com.anytypeio.anytype.ui.page.PageFragment
import dagger.Module
@ -54,16 +62,26 @@ interface PageSubComponent {
fun inject(fragment: PageFragment)
fun documentEmojiIconPickerComponentBuilder(): DocumentEmojiIconPickerSubComponent.Builder
fun documentActionMenuComponentBuilder(): DocumentActionMenuSubComponent.Builder
fun documentRelationSubComponent(): DocumentRelationSubComponent.Builder
fun editRelationCellComponent(): EditGridCellSubComponent.Builder
fun editDocRelationComponent() : ObjectObjectRelationValueSubComponent.Builder
fun editRelationDateComponent(): EditGridCellDateSubComponent.Builder
fun docCoverGalleryComponentBuilder(): SelectDocCoverSubComponent.Builder
fun uploadDocCoverImageComponentBuilder(): UploadDocCoverImageSubComponent.Builder
fun documentAddNewBlockComponentBuilder(): DocumentAddNewBlockSubComponent.Builder
}
/**
* Sesssion-related dependencies, session being defined as active work with a document visible to our user.
* Session-related dependencies, session being defined as active work with a document visible to our user.
* Hence, these dependencies are stateful and therefore should not be shared between different sessions of the same document.
* Consider the following navigation scenario: Document A > Document B > Document A'.
* In this case, statetul dependencies should not be shared between A and A'.
* In this case, stateful dependencies should not be shared between A and A'.
*/
@Module
object EditorSessionModule {
@ -82,19 +100,21 @@ object EditorSessionModule {
@JvmStatic
@Provides
@PerScreen
fun provideStorage(): Editor.Storage = Editor.Storage()
@JvmStatic
@Provides
fun providePageViewModelFactory(
openPage: OpenPage,
closePage: ClosePage,
closePage: CloseBlock,
interceptEvents: InterceptEvents,
interceptThreadStatus: InterceptThreadStatus,
updateLinkMarks: UpdateLinkMarks,
removeLinkMark: RemoveLinkMark,
createPage: CreatePage,
createDocument: CreateDocument,
createObject: CreateObject,
createNewDocument: CreateNewDocument,
documentExternalEventReducer: DocumentExternalEventReducer,
setDocCoverImage: SetDocCoverImage,
@ -105,12 +125,15 @@ object EditorSessionModule {
orchestrator: Orchestrator,
getListPages: GetListPages,
analytics: Analytics,
bridge: Bridge<Payload>
dispatcher: Dispatcher<Payload>,
detailModificationManager: DetailModificationManager,
updateDetail: UpdateDetail
): PageViewModelFactory = PageViewModelFactory(
openPage = openPage,
closePage = closePage,
createPage = createPage,
createDocument = createDocument,
createObject = createObject,
createNewDocument = createNewDocument,
interceptEvents = interceptEvents,
interceptThreadStatus = interceptThreadStatus,
@ -125,7 +148,9 @@ object EditorSessionModule {
orchestrator = orchestrator,
getListPages = getListPages,
analytics = analytics,
bridge = bridge
dispatcher = dispatcher,
detailModificationManager = detailModificationManager,
updateDetail = updateDetail
)
@JvmStatic
@ -198,6 +223,7 @@ object EditorSessionModule {
paste: Paste,
undo: Undo,
redo: Redo,
setRelationKey: SetRelationKey,
analytics: Analytics
): Orchestrator = Orchestrator(
stores = storage,
@ -231,6 +257,7 @@ object EditorSessionModule {
move = move,
paste = paste,
copy = copy,
setRelationKey = setRelationKey,
analytics = analytics,
updateFields = updateFields,
turnIntoStyle = turnInto
@ -265,7 +292,7 @@ object EditorUseCaseModule {
@PerScreen
fun provideClosePageUseCase(
repo: BlockRepository
): ClosePage = ClosePage(
): CloseBlock = CloseBlock(
repo = repo
)
@ -361,6 +388,15 @@ object EditorUseCaseModule {
repo = repo
)
@JvmStatic
@Provides
@PerScreen
fun provideSetRelationKeyUseCase(
repo: BlockRepository
): SetRelationKey = SetRelationKey(
repo = repo
)
@JvmStatic
@Provides
@PerScreen
@ -457,6 +493,17 @@ object EditorUseCaseModule {
documentEmojiProvider = documentEmojiIconProvider
)
@JvmStatic
@Provides
@PerScreen
fun provideCreateObjectUseCase(
repo: BlockRepository,
documentEmojiIconProvider: DocumentEmojiIconProvider
): CreateObject = CreateObject(
repo = repo,
documentEmojiProvider = documentEmojiIconProvider
)
@JvmStatic
@Provides
@PerScreen
@ -562,6 +609,52 @@ object EditorUseCaseModule {
repo: BlockRepository
): UpdateFields = UpdateFields(repo)
@JvmStatic
@Provides
@PerScreen
fun provideDefaultObjectRelationProvider(
storage: Editor.Storage
) : ObjectRelationProvider = DefaultObjectRelationProvider(storage.relations)
@JvmStatic
@Provides
@PerScreen
fun provideDefaultObjectValueProvider(
storage: Editor.Storage
) : ObjectValueProvider = DefaultObjectValueProvider(storage.details)
@JvmStatic
@Provides
@PerScreen
fun provideObjectTypeProvider(
storage: Editor.Storage
) : ObjectTypeProvider = object : ObjectTypeProvider {
override fun provide(): List<ObjectType> = storage.objectTypes.current()
}
@JvmStatic
@Provides
@PerScreen
fun provideObjectDetailProvider(
storage: Editor.Storage
) : ObjectDetailProvider = object : ObjectDetailProvider {
override fun provide(): Map<Id, Block.Fields> = storage.details.current().details
}
@JvmStatic
@Provides
@PerScreen
fun providePayloadDispatcher() : Dispatcher<Payload> = Dispatcher.Default()
@JvmStatic
@Provides
@PerScreen
fun provideDetailManager(
storage: Editor.Storage
) : DetailModificationManager = InternalDetailModificationManager(
store = storage.details
)
@JvmStatic
@Provides
@PerScreen
@ -580,4 +673,11 @@ object EditorUseCaseModule {
@Provides
@PerScreen
fun provideTurnIntoUseCase(repo: BlockRepository): TurnIntoStyle = TurnIntoStyle(repo)
@JvmStatic
@Provides
@PerScreen
fun provideUpdateDetailUseCase(
repository: BlockRepository
) : UpdateDetail = UpdateDetail(repository)
}

View file

@ -0,0 +1,50 @@
package com.anytypeio.anytype.di.feature
import com.anytypeio.anytype.core_utils.di.scope.PerDialog
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.presentation.relations.RelationFileValueAddViewModel
import com.anytypeio.anytype.presentation.relations.providers.ObjectRelationProvider
import com.anytypeio.anytype.presentation.relations.providers.ObjectValueProvider
import com.anytypeio.anytype.ui.relations.RelationFileValueAddFragment
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
@Subcomponent(modules = [RelationFileValueAddModule::class])
@PerDialog
interface RelationFileValueAddSubComponent {
@Subcomponent.Builder
interface Builder {
fun module(module: RelationFileValueAddModule): Builder
fun build(): RelationFileValueAddSubComponent
}
fun inject(fragment: RelationFileValueAddFragment)
}
@Module
object RelationFileValueAddModule {
@JvmStatic
@Provides
@PerDialog
fun provideViewModelFactory(
relations: ObjectRelationProvider,
values: ObjectValueProvider,
searchObjects: SearchObjects,
urlBuilder: UrlBuilder
): RelationFileValueAddViewModel.Factory =
RelationFileValueAddViewModel.Factory(
relations, values, searchObjects, urlBuilder
)
@JvmStatic
@Provides
@PerDialog
fun provideSearchObjectsUseCase(
repo: BlockRepository
): SearchObjects = SearchObjects(repo = repo)
}

View file

@ -1,15 +1,16 @@
package com.anytypeio.anytype.di.feature;
import android.content.Context
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_utils.di.scope.PerModal
import com.anytypeio.anytype.device.DefaultGradientCollectionProvider
import com.anytypeio.anytype.device.DeviceCoverCollectionProvider
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.cover.*
import com.anytypeio.anytype.domain.event.model.Payload
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.presentation.page.cover.SelectDocCoverViewModel
import com.anytypeio.anytype.presentation.util.Bridge
import com.anytypeio.anytype.presentation.page.editor.DetailModificationManager
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.page.cover.DocCoverGalleryFragment
import com.google.gson.Gson
import dagger.Module
@ -37,17 +38,19 @@ object SelectDocCoverModule {
fun provideSelectDocCoverViewModelFactory(
setDocCoverColor: SetDocCoverColor,
setDocCoverGradient: SetDocCoverGradient,
payloadDispatcher: Bridge<Payload>,
payloadDispatcher: Dispatcher<Payload>,
getCoverCollection: GetCoverImageCollection,
getCoverGradientCollection: GetCoverGradientCollection,
urlBuilder: UrlBuilder
urlBuilder: UrlBuilder,
detailModificationManager: DetailModificationManager
): SelectDocCoverViewModel.Factory = SelectDocCoverViewModel.Factory(
setDocCoverColor = setDocCoverColor,
setDocCoverGradient = setDocCoverGradient,
payloadDispatcher = payloadDispatcher,
dispatcher = payloadDispatcher,
getCoverCollection = getCoverCollection,
getCoverGradientCollection = getCoverGradientCollection,
urlBuilder = urlBuilder
urlBuilder = urlBuilder,
details = detailModificationManager
)
@JvmStatic

View file

@ -0,0 +1,52 @@
package com.anytypeio.anytype.di.feature
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_utils.di.scope.PerModal
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.dataview.interactor.AddDataViewViewerSort
import com.anytypeio.anytype.presentation.sets.ObjectSet
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
import com.anytypeio.anytype.presentation.sets.SelectSortRelationViewModel
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.sets.modals.sort.SelectSortRelationFragment
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
import kotlinx.coroutines.flow.StateFlow
@Subcomponent(modules = [SelectSortRelationModule::class])
@PerModal
interface SelectSortRelationSubComponent {
@Subcomponent.Builder
interface Builder {
fun module(module: SelectSortRelationModule): Builder
fun build(): SelectSortRelationSubComponent
}
fun inject(fragment: SelectSortRelationFragment)
}
@Module
object SelectSortRelationModule {
@JvmStatic
@Provides
@PerModal
fun provideSelectSortRelationViewModelFactory(
state: StateFlow<ObjectSet>,
session: ObjectSetSession,
dispatcher: Dispatcher<Payload>,
addDataViewViewerSort: AddDataViewViewerSort
): SelectSortRelationViewModel.Factory = SelectSortRelationViewModel.Factory(
state = state,
session = session,
dispatcher = dispatcher,
addDataViewViewerSort = addDataViewViewerSort
)
@JvmStatic
@Provides
@PerModal
fun provideAddDataViewViewerSort(
repo: BlockRepository
): AddDataViewViewerSort = AddDataViewViewerSort(repo = repo)
}

View file

@ -1,11 +1,11 @@
package com.anytypeio.anytype.di.feature
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_utils.di.scope.PerModal
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.cover.SetDocCoverImage
import com.anytypeio.anytype.domain.event.model.Payload
import com.anytypeio.anytype.presentation.page.cover.UploadDocCoverImageViewModel
import com.anytypeio.anytype.presentation.util.Bridge
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.page.cover.UploadCoverImageFragment
import dagger.Module
import dagger.Provides
@ -31,7 +31,7 @@ object UploadDocCoverImageModule {
@PerModal
fun provideViewModelFactory(
setDocCoverImage: SetDocCoverImage,
payloadDispatcher: Bridge<Payload>,
payloadDispatcher: Dispatcher<Payload>,
): UploadDocCoverImageViewModel.Factory = UploadDocCoverImageViewModel.Factory(
setDocCoverImage = setDocCoverImage,
payloadDispatcher = payloadDispatcher

View file

@ -0,0 +1,41 @@
package com.anytypeio.anytype.di.feature
import com.anytypeio.anytype.presentation.sets.ObjectSet
import com.anytypeio.anytype.presentation.sets.ViewerCustomizeViewModel
import com.anytypeio.anytype.ui.sets.modals.ViewerCustomizeFragment
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Scope
@Subcomponent(modules = [ViewerCustomizeModule::class])
@ViewerCustomizeScope
interface ViewerCustomizeSubComponent {
@Subcomponent.Builder
interface Builder {
fun module(module: ViewerCustomizeModule): Builder
fun build(): ViewerCustomizeSubComponent
}
fun inject(fragment: ViewerCustomizeFragment)
}
@Module
object ViewerCustomizeModule {
@JvmStatic
@Provides
@ViewerCustomizeScope
fun provideViewerCustomizeViewModelFactory(
state: StateFlow<ObjectSet>
): ViewerCustomizeViewModel.Factory = ViewerCustomizeViewModel.Factory(
state = state
)
}
@Scope
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
annotation class ViewerCustomizeScope

View file

@ -0,0 +1,52 @@
package com.anytypeio.anytype.di.feature;
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.domain.dataview.interactor.UpdateDataViewViewer
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.presentation.sets.ObjectSet
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
import com.anytypeio.anytype.presentation.sets.filter.ViewerFilterViewModel
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.sets.ViewerFilterFragment
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Scope
@Subcomponent(modules = [ViewerFilterModule::class])
@ViewerFilterByScope
interface ViewerFilterSubComponent {
@Subcomponent.Builder
interface Builder {
fun module(module: ViewerFilterModule): Builder
fun build(): ViewerFilterSubComponent
}
fun inject(fragment: ViewerFilterFragment)
}
@Module
object ViewerFilterModule {
@JvmStatic
@Provides
@ViewerFilterByScope
fun provideViewerFilterViewModelFactory(
state: StateFlow<ObjectSet>,
session: ObjectSetSession,
dispatcher: Dispatcher<Payload>,
updateDataViewViewer: UpdateDataViewViewer,
urlBuilder: UrlBuilder
): ViewerFilterViewModel.Factory = ViewerFilterViewModel.Factory(
state = state,
session = session,
dispatcher = dispatcher,
updateDataViewViewer = updateDataViewViewer,
urlBuilder = urlBuilder
)
}
@Scope
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
annotation class ViewerFilterByScope

View file

@ -0,0 +1,56 @@
package com.anytypeio.anytype.di.feature
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_utils.di.scope.PerModal
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.dataview.interactor.ModifyDataViewViewerRelationOrder
import com.anytypeio.anytype.domain.dataview.interactor.UpdateDataViewViewer
import com.anytypeio.anytype.presentation.relations.ViewerRelationsViewModel
import com.anytypeio.anytype.presentation.sets.ObjectSet
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.sets.modals.ViewerRelationsFragment
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
import kotlinx.coroutines.flow.StateFlow
@Subcomponent(modules = [ViewerRelationsModule::class])
@PerModal
interface ViewerRelationsSubComponent {
@Subcomponent.Builder
interface Builder {
fun module(module: ViewerRelationsModule): Builder
fun build(): ViewerRelationsSubComponent
}
fun inject(fragment: ViewerRelationsFragment)
}
@Module
object ViewerRelationsModule {
@JvmStatic
@Provides
@PerModal
fun provideViewerRelationsListViewModelFactory(
state: StateFlow<ObjectSet>,
session: ObjectSetSession,
dispatcher: Dispatcher<Payload>,
modifyViewerRelationOrder: ModifyDataViewViewerRelationOrder,
updateDataViewViewer: UpdateDataViewViewer
): ViewerRelationsViewModel.Factory = ViewerRelationsViewModel.Factory(
state = state,
session = session,
dispatcher = dispatcher,
modifyViewerRelationOrder = modifyViewerRelationOrder,
updateDataViewViewer = updateDataViewViewer
)
@JvmStatic
@Provides
@PerModal
fun provideModifyViewerRelationOrderUseCase(
repo: BlockRepository
): ModifyDataViewViewerRelationOrder = ModifyDataViewViewerRelationOrder(repo = repo)
}

View file

@ -0,0 +1,44 @@
package com.anytypeio.anytype.di.feature;
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_utils.di.scope.PerModal
import com.anytypeio.anytype.domain.dataview.interactor.UpdateDataViewViewer
import com.anytypeio.anytype.presentation.sets.ObjectSet
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
import com.anytypeio.anytype.presentation.sets.sort.ViewerSortViewModel
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.sets.modals.sort.ViewerSortFragment
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
import kotlinx.coroutines.flow.StateFlow
@Subcomponent(modules = [ViewerSortModule::class])
@PerModal
interface ViewerSortSubComponent {
@Subcomponent.Builder
interface Builder {
fun module(module: ViewerSortModule): Builder
fun build(): ViewerSortSubComponent
}
fun inject(fragment: ViewerSortFragment)
}
@Module
object ViewerSortModule {
@JvmStatic
@Provides
@PerModal
fun provideViewModelFactory(
state: StateFlow<ObjectSet>,
session: ObjectSetSession,
updateDataViewViewer: UpdateDataViewViewer,
dispatcher: Dispatcher<Payload>
): ViewerSortViewModel.Factory = ViewerSortViewModel.Factory(
state = state,
session = session,
updateDataViewViewer = updateDataViewViewer,
dispatcher = dispatcher
)
}

View file

@ -0,0 +1,38 @@
package com.anytypeio.anytype.di.feature
import com.anytypeio.anytype.presentation.sets.ObjectSet
import com.anytypeio.anytype.presentation.sets.ViewerSortByViewModel
import com.anytypeio.anytype.ui.sets.ViewerSortByFragment
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Scope
@Subcomponent(modules = [ViewerSortByModule::class])
@ViewerSortByScope
interface ViewerSortBySubComponent {
@Subcomponent.Builder
interface Builder {
fun module(module: ViewerSortByModule): Builder
fun build(): ViewerSortBySubComponent
}
fun inject(fragment: ViewerSortByFragment)
}
@Module
object ViewerSortByModule {
@JvmStatic
@Provides
@ViewerSortByScope
fun provideViewerSortByViewModelFactory(
state: StateFlow<ObjectSet>
): ViewerSortByViewModel.Factory = ViewerSortByViewModel.Factory(state)
}
@Scope
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
annotation class ViewerSortByScope

View file

@ -0,0 +1,62 @@
package com.anytypeio.anytype.di.feature.sets;
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_utils.di.scope.PerModal
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects
import com.anytypeio.anytype.domain.dataview.interactor.UpdateDataViewViewer
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.presentation.sets.ObjectSet
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
import com.anytypeio.anytype.presentation.sets.filter.FilterViewModel
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.sets.modals.filter.CreateFilterFromInputFieldValueFragment
import com.anytypeio.anytype.ui.sets.modals.filter.CreateFilterFromSelectedValueFragment
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
import kotlinx.coroutines.flow.StateFlow
@Subcomponent(modules = [CreateFilterModule::class])
@PerModal
interface CreateFilterSubComponent {
@Subcomponent.Builder
interface Builder {
fun module(module: CreateFilterModule): Builder
fun build(): CreateFilterSubComponent
}
fun inject(fragment: CreateFilterFromSelectedValueFragment)
fun inject(fragment: CreateFilterFromInputFieldValueFragment)
fun createPickConditionComponent(): PickFilterConditionSubComponent.Builder
}
@Module
object CreateFilterModule {
@JvmStatic
@Provides
@PerModal
fun provideViewModelFactory(
state: StateFlow<ObjectSet>,
session: ObjectSetSession,
dispatcher: Dispatcher<Payload>,
updateDataViewViewer: UpdateDataViewViewer,
searchObjects: SearchObjects,
urlBuilder: UrlBuilder
): FilterViewModel.Factory = FilterViewModel.Factory(
objectSetState = state,
session = session,
dispatcher = dispatcher,
updateDataViewViewer = updateDataViewViewer,
searchObjects = searchObjects,
urlBuilder = urlBuilder
)
@JvmStatic
@Provides
@PerModal
fun provideSearchObjectsUseCase(
repo: BlockRepository
): SearchObjects = SearchObjects(repo = repo)
}

View file

@ -0,0 +1,62 @@
package com.anytypeio.anytype.di.feature.sets;
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_utils.di.scope.PerModal
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects
import com.anytypeio.anytype.domain.dataview.interactor.UpdateDataViewViewer
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.presentation.sets.ObjectSet
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
import com.anytypeio.anytype.presentation.sets.filter.FilterViewModel
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.sets.modals.filter.ModifyFilterFromInputFieldValueFragment
import com.anytypeio.anytype.ui.sets.modals.filter.ModifyFilterFromSelectedValueFragment
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
import kotlinx.coroutines.flow.StateFlow
@Subcomponent(modules = [ModifyFilterModule::class])
@PerModal
interface ModifyFilterSubComponent {
@Subcomponent.Builder
interface Builder {
fun module(module: ModifyFilterModule): Builder
fun build(): ModifyFilterSubComponent
}
fun inject(fragment: ModifyFilterFromInputFieldValueFragment)
fun inject(fragment: ModifyFilterFromSelectedValueFragment)
fun createPickConditionComponent(): PickFilterConditionSubComponent.Builder
}
@Module
object ModifyFilterModule {
@JvmStatic
@Provides
@PerModal
fun provideViewModelFactory(
state: StateFlow<ObjectSet>,
session: ObjectSetSession,
dispatcher: Dispatcher<Payload>,
updateDataViewViewer: UpdateDataViewViewer,
searchObjects: SearchObjects,
urlBuilder: UrlBuilder
): FilterViewModel.Factory = FilterViewModel.Factory(
objectSetState = state,
session = session,
dispatcher = dispatcher,
updateDataViewViewer = updateDataViewViewer,
searchObjects = searchObjects,
urlBuilder = urlBuilder
)
@JvmStatic
@Provides
@PerModal
fun provideSearchObjectsUseCase(
repo: BlockRepository
): SearchObjects = SearchObjects(repo = repo)
}

View file

@ -0,0 +1,33 @@
package com.anytypeio.anytype.di.feature.sets
import com.anytypeio.anytype.core_utils.di.scope.PerDialog
import com.anytypeio.anytype.presentation.sets.filter.PickFilterConditionViewModel
import com.anytypeio.anytype.ui.sets.modals.PickFilterConditionFragment
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
@Subcomponent(modules = [PickConditionModule::class])
@PerDialog
interface PickFilterConditionSubComponent {
@Subcomponent.Builder
interface Builder {
fun module(module: PickConditionModule): Builder
fun build(): PickFilterConditionSubComponent
}
fun inject(fragment: PickFilterConditionFragment)
}
@Module
object PickConditionModule {
@JvmStatic
@Provides
@PerDialog
fun provideFactory(): PickFilterConditionViewModel.Factory =
PickFilterConditionViewModel.Factory()
}

View file

@ -0,0 +1,37 @@
package com.anytypeio.anytype.di.feature.sets
import com.anytypeio.anytype.core_utils.di.scope.PerModal
import com.anytypeio.anytype.presentation.sets.ObjectSet
import com.anytypeio.anytype.presentation.sets.ObjectSetSession
import com.anytypeio.anytype.presentation.sets.SelectFilterRelationViewModel
import com.anytypeio.anytype.ui.sets.modals.filter.SelectFilterRelationFragment
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
import kotlinx.coroutines.flow.StateFlow
@Subcomponent(modules = [SelectFilterRelationModule::class])
@PerModal
interface SelectFilterRelationSubComponent {
@Subcomponent.Builder
interface Builder {
fun module(module: SelectFilterRelationModule): Builder
fun build(): SelectFilterRelationSubComponent
}
fun inject(fragment: SelectFilterRelationFragment)
}
@Module
object SelectFilterRelationModule {
@JvmStatic
@Provides
@PerModal
fun provideSelectSortRelationViewModelFactory(
state: StateFlow<ObjectSet>,
session: ObjectSetSession
): SelectFilterRelationViewModel.Factory = SelectFilterRelationViewModel.Factory(
state = state,
session = session
)
}

View file

@ -1,17 +0,0 @@
package com.anytypeio.anytype.di.main
import com.anytypeio.anytype.domain.event.model.Payload
import com.anytypeio.anytype.presentation.util.Bridge
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
@Module
object BridgeModule {
@JvmStatic
@Provides
@Singleton
fun providePayloadPridge(): Bridge<Payload> = Bridge()
}

View file

@ -3,7 +3,7 @@ package com.anytypeio.anytype.di.main
import com.anytypeio.anytype.data.auth.repo.config.Configuration
import com.anytypeio.anytype.data.auth.repo.config.Configurator
import com.anytypeio.anytype.data.auth.repo.config.GatewayProvider
import com.anytypeio.anytype.domain.config.Config
import com.anytypeio.anytype.core_models.Config
import com.anytypeio.anytype.domain.config.Gateway
import com.anytypeio.anytype.middleware.config.DefaultConfigurator
import dagger.Module

View file

@ -21,7 +21,6 @@ import com.anytypeio.anytype.middleware.auth.AuthMiddleware
import com.anytypeio.anytype.middleware.block.BlockMiddleware
import com.anytypeio.anytype.middleware.interactor.Middleware
import com.anytypeio.anytype.middleware.interactor.MiddlewareFactory
import com.anytypeio.anytype.middleware.interactor.MiddlewareMapper
import com.anytypeio.anytype.middleware.service.MiddlewareService
import com.anytypeio.anytype.middleware.service.MiddlewareServiceImplementation
import com.anytypeio.anytype.persistence.db.AnytypeDatabase
@ -213,20 +212,14 @@ object DataModule {
@Singleton
fun provideMiddleware(
service: MiddlewareService,
factory: MiddlewareFactory,
mapper: MiddlewareMapper
): Middleware = Middleware(service, factory, mapper)
factory: MiddlewareFactory
): Middleware = Middleware(service, factory)
@JvmStatic
@Provides
@Singleton
fun provideMiddlewareFactory(): MiddlewareFactory = MiddlewareFactory()
@JvmStatic
@Provides
@Singleton
fun provideMiddlewareMapper(): MiddlewareMapper = MiddlewareMapper()
@JvmStatic
@Provides
@Singleton

View file

@ -16,8 +16,7 @@ import javax.inject.Singleton
UtilModule::class,
EmojiModule::class,
ClipboardModule::class,
AnalyticsModule::class,
BridgeModule::class
AnalyticsModule::class
]
)
interface MainComponent {
@ -39,8 +38,6 @@ interface MainComponent {
fun pageComponentBuilder(): PageSubComponent.Builder
fun archiveComponentBuilder(): ArchiveSubComponent.Builder
fun linkAddComponentBuilder(): LinkSubComponent.Builder
fun documentActionMenuComponentBuilder(): DocumentActionMenuSubComponent.Builder
fun documentEmojiIconPickerComponentBuilder(): DocumentEmojiIconPickerSubComponent.Builder
fun createBookmarkBuilder(): CreateBookmarkSubComponent.Builder
fun debugSettingsBuilder(): DebugSettingsSubComponent.Builder
fun navigationComponentBuilder(): PageNavigationSubComponent.Builder
@ -48,4 +45,8 @@ interface MainComponent {
fun moveToBuilder(): MoveToSubComponent.Builder
fun pageSearchComponentBuilder(): PageSearchSubComponent.Builder
fun mainEntryComponentBuilder(): MainEntrySubComponent.Builder
fun createSetComponentBuilder(): CreateSetSubComponent.Builder
fun createObjectTypeComponentBuilder(): CreateObjectTypeSubComponent.Builder
fun objectSetComponentBuilder(): ObjectSetSubComponent.Builder
fun createDataViewRelationBuilder(): CreateDataViewRelationSubComponent.Builder
}

View file

@ -4,9 +4,9 @@ import android.text.Editable
import android.text.Spanned
import com.anytypeio.anytype.core_ui.common.Span
import com.anytypeio.anytype.core_ui.widgets.text.MentionSpan
import com.anytypeio.anytype.domain.block.model.Block.Content.Text.Mark
import com.anytypeio.anytype.domain.ext.overlap
import com.anytypeio.anytype.domain.misc.Overlap
import com.anytypeio.anytype.core_models.Block.Content.Text.Mark
import com.anytypeio.anytype.core_models.ext.overlap
import com.anytypeio.anytype.core_models.misc.Overlap
import com.anytypeio.anytype.presentation.page.editor.ThemeColor
fun Editable.extractMarks(): List<Mark> = getSpans(0, length, Span::class.java).mapNotNull { span ->

Some files were not shown because too many files have changed in this diff Show more