DROID-1546 Editor | Fix | Link-to-object block should not crash when applying search-on-page highlights (#213)
This commit is contained in:
parent
30eccc5bce
commit
bf1fe10865
|
@ -6,13 +6,10 @@ import android.text.style.LeadingMarginSpan
|
|||
import android.widget.FrameLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.anytypeio.anytype.core_ui.BuildConfig
|
||||
import com.anytypeio.anytype.core_ui.R
|
||||
import com.anytypeio.anytype.core_ui.common.SearchHighlightSpan
|
||||
import com.anytypeio.anytype.core_ui.common.SearchTargetHighlightSpan
|
||||
import com.anytypeio.anytype.core_ui.databinding.ItemBlockObjectLinkBinding
|
||||
import com.anytypeio.anytype.core_ui.extensions.setBlockBackgroundColor
|
||||
import com.anytypeio.anytype.core_ui.features.editor.BlockViewDiffUtil
|
||||
import com.anytypeio.anytype.core_ui.features.editor.BlockViewHolder
|
||||
import com.anytypeio.anytype.core_ui.features.editor.EditorTouchProcessor
|
||||
|
@ -22,7 +19,6 @@ import com.anytypeio.anytype.core_utils.ext.dimen
|
|||
import com.anytypeio.anytype.core_utils.ext.gone
|
||||
import com.anytypeio.anytype.core_utils.ext.removeSpans
|
||||
import com.anytypeio.anytype.core_utils.ext.visible
|
||||
import com.anytypeio.anytype.core_models.ThemeColor
|
||||
import com.anytypeio.anytype.presentation.editor.editor.listener.ListenerType
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView.Searchable.Field.Companion.DEFAULT_SEARCH_FIELD_KEY
|
||||
|
@ -41,7 +37,7 @@ class LinkToObject(
|
|||
private val untitled = itemView.resources.getString(R.string.untitled)
|
||||
val objectIcon = binding.objectIconWidget
|
||||
private val objectIconContainer = binding.iconObjectContainer
|
||||
private val title = binding.text
|
||||
val title = binding.text
|
||||
private val description = binding.tvDescription
|
||||
private val objectType = binding.tvObjectType
|
||||
|
||||
|
@ -63,8 +59,8 @@ class LinkToObject(
|
|||
applyText(item)
|
||||
applyDescription(item)
|
||||
applyObjectType(item)
|
||||
applySearchHighlight(item)
|
||||
applyImageOrEmoji(item)
|
||||
applySearchHighlight(item)
|
||||
itemView.setOnClickListener { clicked(ListenerType.LinkToObject(item.id)) }
|
||||
}
|
||||
|
||||
|
@ -89,7 +85,7 @@ class LinkToObject(
|
|||
)
|
||||
}
|
||||
title.visible()
|
||||
title.text = sb
|
||||
title.setText(sb, TextView.BufferType.EDITABLE)
|
||||
}
|
||||
else -> {
|
||||
val sb = SpannableString(name)
|
||||
|
@ -101,7 +97,7 @@ class LinkToObject(
|
|||
)
|
||||
}
|
||||
title.visible()
|
||||
title.text = sb
|
||||
title.setText(sb, TextView.BufferType.EDITABLE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -181,9 +177,6 @@ class LinkToObject(
|
|||
if (payload.isSelectionChanged) {
|
||||
applySelectedState(item)
|
||||
}
|
||||
if (payload.isSearchHighlightChanged) {
|
||||
applySearchHighlight(item)
|
||||
}
|
||||
if (payload.isObjectTitleChanged) {
|
||||
applyText(item)
|
||||
}
|
||||
|
@ -196,6 +189,9 @@ class LinkToObject(
|
|||
if (payload.isObjectTypeChanged) {
|
||||
applyObjectType(item)
|
||||
}
|
||||
if (payload.isSearchHighlightChanged) {
|
||||
applySearchHighlight(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ class LinkToObjectArchive(
|
|||
|
||||
private val root = binding.root
|
||||
private val untitled = itemView.resources.getString(R.string.untitled)
|
||||
private val title = binding.pageTitle
|
||||
val title = binding.pageTitle
|
||||
|
||||
override val editorTouchProcessor = EditorTouchProcessor(
|
||||
fallback = { e -> itemView.onTouchEvent(e) }
|
||||
|
|
|
@ -77,12 +77,12 @@ abstract class LinkToObjectCard(
|
|||
|
||||
applyBackground(item.background)
|
||||
|
||||
applySearchHighlight(item)
|
||||
|
||||
applyImageOrEmoji(item)
|
||||
|
||||
applyObjectType(item)
|
||||
|
||||
applySearchHighlight(item)
|
||||
|
||||
itemView.setOnClickListener { clicked(ListenerType.LinkToObject(item.id)) }
|
||||
}
|
||||
|
||||
|
@ -97,12 +97,12 @@ abstract class LinkToObjectCard(
|
|||
name.isBlank() -> {
|
||||
titleView.visible()
|
||||
val sb = SpannableString(untitled)
|
||||
titleView.setText(sb, TextView.BufferType.SPANNABLE)
|
||||
titleView.setText(sb, TextView.BufferType.EDITABLE)
|
||||
}
|
||||
else -> {
|
||||
titleView.visible()
|
||||
val sb = SpannableString(name)
|
||||
titleView.setText(sb, TextView.BufferType.SPANNABLE)
|
||||
titleView.setText(sb, TextView.BufferType.EDITABLE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,197 @@
|
|||
package com.anytypeio.anytype.core_ui.features.editor.holders.other
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.TextView
|
||||
import androidx.fragment.app.testing.FragmentScenario
|
||||
import androidx.fragment.app.testing.launchFragmentInContainer
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.anytypeio.anytype.core_models.ThemeColor
|
||||
import com.anytypeio.anytype.core_ui.R
|
||||
import com.anytypeio.anytype.core_ui.common.SearchHighlightSpan
|
||||
import com.anytypeio.anytype.core_ui.common.SearchTargetHighlightSpan
|
||||
import com.anytypeio.anytype.core_ui.databinding.ItemBlockObjectLinkArchiveBinding
|
||||
import com.anytypeio.anytype.core_ui.databinding.ItemBlockObjectLinkBinding
|
||||
import com.anytypeio.anytype.core_ui.databinding.ItemBlockObjectLinkCardMediumIconBinding
|
||||
import com.anytypeio.anytype.core_ui.databinding.ItemBlockObjectLinkCardMediumIconCoverBinding
|
||||
import com.anytypeio.anytype.core_ui.databinding.ItemBlockObjectLinkCardSmallIconBinding
|
||||
import com.anytypeio.anytype.core_ui.databinding.ItemBlockObjectLinkCardSmallIconCoverBinding
|
||||
import com.anytypeio.anytype.presentation.editor.editor.model.BlockView
|
||||
import com.anytypeio.anytype.presentation.objects.ObjectIcon
|
||||
import com.anytypeio.anytype.test_utils.MockDataFactory
|
||||
import com.anytypeio.anytype.test_utils.TestFragment
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.core.IsEqual.equalTo
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(
|
||||
manifest = Config.NONE,
|
||||
sdk = [Build.VERSION_CODES.P],
|
||||
instrumentedPackages = [
|
||||
"androidx.loader.content"
|
||||
]
|
||||
)
|
||||
class LinkToObjectTest {
|
||||
|
||||
private val context: Context = ApplicationProvider.getApplicationContext()
|
||||
lateinit var scenario: FragmentScenario<TestFragment>
|
||||
|
||||
private lateinit var linkToObject: LinkToObject
|
||||
private lateinit var linkToObjectArchived: LinkToObjectArchive
|
||||
private lateinit var linkToObjectCardSmallIcon: LinkToObjectCardSmallIcon
|
||||
private lateinit var linkToObjectCardSmallIconCover: LinkToObjectCardSmallIconCover
|
||||
private lateinit var linkToObjectCardMediumIcon: LinkToObjectCardMediumIcon
|
||||
private lateinit var linkToObjectCardMediumIconCover: LinkToObjectCardMediumIconCover
|
||||
|
||||
private lateinit var item: BlockView.LinkToObject.Default.Text
|
||||
private lateinit var itemArchived: BlockView.LinkToObject.Archived
|
||||
private lateinit var itemCardSmallIcon: BlockView.LinkToObject.Default.Card.SmallIcon
|
||||
private lateinit var itemCardSmallIconCover: BlockView.LinkToObject.Default.Card.SmallIconCover
|
||||
private lateinit var itemCardMediumIcon: BlockView.LinkToObject.Default.Card.MediumIcon
|
||||
private lateinit var itemCardMediumIconCover: BlockView.LinkToObject.Default.Card.MediumIconCover
|
||||
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
context.setTheme(R.style.Theme_MaterialComponents)
|
||||
scenario = launchFragmentInContainer()
|
||||
|
||||
val layoutInflater = LayoutInflater.from(context)
|
||||
linkToObject = LinkToObject(ItemBlockObjectLinkBinding.inflate(layoutInflater))
|
||||
linkToObjectArchived = LinkToObjectArchive(ItemBlockObjectLinkArchiveBinding.inflate(layoutInflater))
|
||||
linkToObjectCardSmallIcon = LinkToObjectCardSmallIcon(ItemBlockObjectLinkCardSmallIconBinding.inflate(layoutInflater))
|
||||
linkToObjectCardSmallIconCover = LinkToObjectCardSmallIconCover(
|
||||
ItemBlockObjectLinkCardSmallIconCoverBinding.inflate(layoutInflater))
|
||||
linkToObjectCardMediumIcon = LinkToObjectCardMediumIcon(
|
||||
ItemBlockObjectLinkCardMediumIconBinding.inflate(layoutInflater))
|
||||
linkToObjectCardMediumIconCover = LinkToObjectCardMediumIconCover(
|
||||
ItemBlockObjectLinkCardMediumIconCoverBinding.inflate(layoutInflater))
|
||||
|
||||
val id = MockDataFactory.randomUuid()
|
||||
|
||||
val searchField = BlockView.Searchable.Field(
|
||||
highlights = listOf(IntRange(0, 4), IntRange(6, 10)),
|
||||
target = IntRange(0, 4)
|
||||
)
|
||||
|
||||
item = BlockView.LinkToObject.Default.Text(
|
||||
id = id,
|
||||
indent = MockDataFactory.randomInt(),
|
||||
isSelected = false,
|
||||
icon = ObjectIcon.None,
|
||||
searchFields = listOf(searchField),
|
||||
text = "Text1 Text2"
|
||||
)
|
||||
|
||||
itemArchived = BlockView.LinkToObject.Archived(
|
||||
id = id,
|
||||
indent = MockDataFactory.randomInt(),
|
||||
isSelected = false,
|
||||
searchFields = listOf(searchField),
|
||||
text = "Text1 Text2"
|
||||
)
|
||||
|
||||
itemCardSmallIcon = BlockView.LinkToObject.Default.Card.SmallIcon(
|
||||
id = id,
|
||||
indent = MockDataFactory.randomInt(),
|
||||
isSelected = false,
|
||||
icon = ObjectIcon.None,
|
||||
searchFields = listOf(searchField),
|
||||
text = "Text1 Text2",
|
||||
background = ThemeColor.BLUE,
|
||||
isPreviousBlockMedia = false
|
||||
)
|
||||
|
||||
itemCardSmallIconCover = BlockView.LinkToObject.Default.Card.SmallIconCover(
|
||||
id = id,
|
||||
indent = MockDataFactory.randomInt(),
|
||||
isSelected = false,
|
||||
icon = ObjectIcon.None,
|
||||
searchFields = listOf(searchField),
|
||||
text = "Text1 Text2",
|
||||
background = ThemeColor.BLUE,
|
||||
isPreviousBlockMedia = false,
|
||||
cover = null
|
||||
)
|
||||
|
||||
itemCardMediumIcon = BlockView.LinkToObject.Default.Card.MediumIcon(
|
||||
id = id,
|
||||
indent = MockDataFactory.randomInt(),
|
||||
isSelected = false,
|
||||
icon = ObjectIcon.None,
|
||||
searchFields = listOf(searchField),
|
||||
text = "Text1 Text2",
|
||||
background = ThemeColor.BLUE,
|
||||
isPreviousBlockMedia = false
|
||||
)
|
||||
|
||||
itemCardMediumIconCover = BlockView.LinkToObject.Default.Card.MediumIconCover(
|
||||
id = id,
|
||||
indent = MockDataFactory.randomInt(),
|
||||
isSelected = false,
|
||||
icon = ObjectIcon.None,
|
||||
searchFields = listOf(searchField),
|
||||
text = "Text1 Text2",
|
||||
background = ThemeColor.BLUE,
|
||||
isPreviousBlockMedia = false,
|
||||
cover = null
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test linkToObject block`() {
|
||||
linkToObject.bind(item, {})
|
||||
assertSearchSpans(linkToObject.title)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test linkToObjectArchive block`() {
|
||||
linkToObjectArchived.bind(itemArchived, {})
|
||||
assertSearchSpans(linkToObjectArchived.title)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test linkToObjectCard block`() {
|
||||
linkToObjectCardSmallIcon.bind(itemCardSmallIcon, {})
|
||||
assertSearchSpans(linkToObjectCardSmallIcon.titleView)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test linkToObjectCardCover block`() {
|
||||
linkToObjectCardSmallIconCover.bind(itemCardSmallIconCover, {})
|
||||
assertSearchSpans(linkToObjectCardSmallIconCover.titleView)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test linkToObjectCardMediumIcon block`() {
|
||||
linkToObjectCardMediumIcon.bind(itemCardMediumIcon, {})
|
||||
assertSearchSpans(linkToObjectCardMediumIcon.titleView)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test linkToObjectCardMediumIconCover block`() {
|
||||
linkToObjectCardMediumIconCover.bind(itemCardMediumIconCover, {})
|
||||
assertSearchSpans(linkToObjectCardMediumIconCover.titleView)
|
||||
}
|
||||
|
||||
private fun assertSearchSpans(textView: TextView) {
|
||||
// verify
|
||||
val spans = textView.editableText.getSpans(0, item.text!!.length, SearchHighlightSpan::class.java)
|
||||
assertThat(spans.size, equalTo(2))
|
||||
assertThat(textView.editableText.getSpanStart(spans[0]), equalTo(0))
|
||||
assertThat(textView.editableText.getSpanEnd(spans[0]), equalTo(4))
|
||||
assertThat(textView.editableText.getSpanStart(spans[1]), equalTo(6))
|
||||
assertThat(textView.editableText.getSpanEnd(spans[1]), equalTo(10))
|
||||
|
||||
val targetSpans = textView.editableText.getSpans(0, textView.text.length, SearchTargetHighlightSpan::class.java)
|
||||
assertThat(targetSpans.size, equalTo(1))
|
||||
assertThat(textView.editableText.getSpanStart(targetSpans[0]), equalTo(0))
|
||||
assertThat(textView.editableText.getSpanEnd(targetSpans[0]), equalTo(4))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user