mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-11 10:18:05 +09:00
#483: spans in range + tests
This commit is contained in:
parent
6f41e1390a
commit
ebad5f126e
2 changed files with 404 additions and 0 deletions
|
@ -1,9 +1,12 @@
|
|||
package com.agileburo.anytype.ext
|
||||
|
||||
import android.text.Editable
|
||||
import android.text.Spanned
|
||||
import com.agileburo.anytype.core_ui.common.Span
|
||||
import com.agileburo.anytype.core_ui.common.ThemeColor
|
||||
import com.agileburo.anytype.domain.block.model.Block.Content.Text.Mark
|
||||
import com.agileburo.anytype.domain.ext.overlap
|
||||
import com.agileburo.anytype.domain.misc.Overlap
|
||||
|
||||
fun Editable.extractMarks(): List<Mark> = getSpans(0, length, Span::class.java).mapNotNull { span ->
|
||||
when (span) {
|
||||
|
@ -46,4 +49,30 @@ fun Editable.extractMarks(): List<Mark> = getSpans(0, length, Span::class.java).
|
|||
)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> Spanned.extractSpans(range: IntRange, type: Class<T>): List<T> {
|
||||
val spans = this.getSpans(range.first, range.last, type)
|
||||
return spans.toList()
|
||||
}
|
||||
|
||||
fun <T> isSpanInRange(textRange: IntRange, text: Spanned, type: Class<T>): Boolean {
|
||||
val spans = text.extractSpans(textRange, type)
|
||||
|
||||
for (span in spans) {
|
||||
val start = text.getSpanStart(span)
|
||||
val end = text.getSpanEnd(span)
|
||||
val spanRange = IntRange(start, end)
|
||||
val overlap = textRange.overlap(spanRange)
|
||||
if (overlap in listOf(
|
||||
Overlap.INNER,
|
||||
Overlap.INNER_LEFT,
|
||||
Overlap.INNER_RIGHT,
|
||||
Overlap.EQUAL
|
||||
)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -1,11 +1,18 @@
|
|||
package com.agileburo.anytype
|
||||
|
||||
import android.os.Build
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableString
|
||||
import com.agileburo.anytype.core_ui.common.Markup
|
||||
import com.agileburo.anytype.core_ui.common.Span
|
||||
import com.agileburo.anytype.core_ui.common.ThemeColor
|
||||
import com.agileburo.anytype.core_ui.common.toSpannable
|
||||
import com.agileburo.anytype.domain.block.model.Block.Content.Text.Mark
|
||||
import com.agileburo.anytype.domain.ext.overlap
|
||||
import com.agileburo.anytype.domain.misc.Overlap
|
||||
import com.agileburo.anytype.ext.extractMarks
|
||||
import com.agileburo.anytype.ext.extractSpans
|
||||
import com.agileburo.anytype.ext.isSpanInRange
|
||||
import com.nhaarman.mockitokotlin2.doReturn
|
||||
import com.nhaarman.mockitokotlin2.stub
|
||||
import org.junit.Before
|
||||
|
@ -16,6 +23,8 @@ import org.mockito.MockitoAnnotations
|
|||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@Config(sdk = [Build.VERSION_CODES.P])
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
|
@ -269,4 +278,370 @@ class MarkupExtractTest {
|
|||
on { marks } doReturn listOf(mark)
|
||||
}
|
||||
}
|
||||
|
||||
private fun stubMarkup(
|
||||
source: String,
|
||||
markss: List<Markup.Mark>
|
||||
) {
|
||||
markup.stub {
|
||||
on { body } doReturn source
|
||||
on { marks } doReturn markss
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should find 2 Bold Spans`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
val source = "Everything was in confusion in the Oblonskys’ house"
|
||||
|
||||
val mark = Markup.Mark(
|
||||
from = 0,
|
||||
to = 4,
|
||||
param = null,
|
||||
type = Markup.Type.BOLD
|
||||
)
|
||||
|
||||
val mark2 = Markup.Mark(
|
||||
from = 7,
|
||||
to = 10,
|
||||
param = null,
|
||||
type = Markup.Type.BOLD
|
||||
)
|
||||
|
||||
stubMarkup(source, listOf(mark, mark2))
|
||||
|
||||
val editable = markup.toSpannable()
|
||||
|
||||
// TESTING
|
||||
|
||||
val result = editable.extractSpans(IntRange(0, 16), Span.Bold::class.java)
|
||||
|
||||
assertEquals(expected = 2, actual = result.size)
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should find no spans when selected at the end`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
val source = "Everything was in confusion in the Oblonskys’ house"
|
||||
|
||||
val mark1 = Markup.Mark(
|
||||
from = 0,
|
||||
to = 4,
|
||||
param = null,
|
||||
type = Markup.Type.BOLD
|
||||
)
|
||||
|
||||
val mark2 = Markup.Mark(
|
||||
from = 7,
|
||||
to = 10,
|
||||
param = null,
|
||||
type = Markup.Type.BOLD
|
||||
)
|
||||
|
||||
val mark3 = Markup.Mark(
|
||||
from = 10,
|
||||
to = 18,
|
||||
param = null,
|
||||
type = Markup.Type.ITALIC
|
||||
)
|
||||
|
||||
stubMarkup(source, listOf(mark1, mark2, mark3))
|
||||
|
||||
val editable = markup.toSpannable()
|
||||
|
||||
// TESTING
|
||||
|
||||
val result = editable.extractSpans(IntRange(18, source.length), Span.Bold::class.java)
|
||||
|
||||
assertEquals(expected = 0, actual = result.size)
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should find no spans when selected at the start`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
val source = "Everything was in confusion in the Oblonskys’ house"
|
||||
|
||||
val mark1 = Markup.Mark(
|
||||
from = 10,
|
||||
to = 34,
|
||||
param = null,
|
||||
type = Markup.Type.BOLD
|
||||
)
|
||||
|
||||
val mark2 = Markup.Mark(
|
||||
from = 7,
|
||||
to = 10,
|
||||
param = null,
|
||||
type = Markup.Type.BOLD
|
||||
)
|
||||
|
||||
val mark3 = Markup.Mark(
|
||||
from = 10,
|
||||
to = 18,
|
||||
param = null,
|
||||
type = Markup.Type.ITALIC
|
||||
)
|
||||
|
||||
stubMarkup(source, listOf(mark1, mark2, mark3))
|
||||
|
||||
val editable = markup.toSpannable()
|
||||
|
||||
// TESTING
|
||||
|
||||
val result = editable.extractSpans(IntRange(0, 7), Span.Bold::class.java)
|
||||
|
||||
assertEquals(expected = 0, actual = result.size)
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should get bold span with proper start, end`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
val source = "Everything was in confusion in the Oblonskys’ house"
|
||||
|
||||
val mark = Markup.Mark(
|
||||
from = 0,
|
||||
to = 4,
|
||||
param = null,
|
||||
type = Markup.Type.BOLD
|
||||
)
|
||||
|
||||
stubMarkup(source, listOf(mark))
|
||||
|
||||
val editable = markup.toSpannable()
|
||||
|
||||
// TESTING
|
||||
|
||||
val intRange = IntRange(2, source.length)
|
||||
val result = editable.extractSpans(intRange, Span.Bold::class.java)
|
||||
|
||||
val boldSpanStart = editable.getSpanStart(result[0])
|
||||
val boldSpanEnd = editable.getSpanEnd(result[0])
|
||||
|
||||
assertEquals(expected = 0, actual = boldSpanStart)
|
||||
assertEquals(expected = 4, actual = boldSpanEnd)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should be inner overlap`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
val source = "Everything was in confusion in the Oblonskys’ house"
|
||||
|
||||
val mark = Markup.Mark(
|
||||
from = 19,
|
||||
to = 29,
|
||||
param = null,
|
||||
type = Markup.Type.BOLD
|
||||
)
|
||||
|
||||
stubMarkup(source, listOf(mark))
|
||||
|
||||
val editable = markup.toSpannable()
|
||||
|
||||
// TESTING
|
||||
|
||||
val textSelection = IntRange(20, 25)
|
||||
val spans = editable.extractSpans(textSelection, Span.Bold::class.java)
|
||||
|
||||
val boldSpan = spans[0]
|
||||
val boldStart = editable.getSpanStart(boldSpan)
|
||||
val boldEnd = editable.getSpanEnd(boldSpan)
|
||||
|
||||
val result = textSelection.overlap(IntRange(boldStart, boldEnd))
|
||||
|
||||
assertEquals(expected = Overlap.INNER, actual = result)
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `span should be in range with INNER overlap`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
val source = SpannableString("Everything was in confusion in the Oblonskys’ house")
|
||||
source.setSpan(Span.Bold(), 10, 20, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
|
||||
// TESTING
|
||||
|
||||
val textRange = IntRange(11, 19)
|
||||
val result = isSpanInRange(
|
||||
textRange = textRange,
|
||||
type = Span.Bold::class.java,
|
||||
text = source
|
||||
)
|
||||
|
||||
assertTrue(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `span should be in range with INNER_LEFT overlap`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
val source = SpannableString("Everything was in confusion in the Oblonskys’ house")
|
||||
source.setSpan(Span.Bold(), 10, 20, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
|
||||
// TESTING
|
||||
|
||||
val textRange = IntRange(10, 15)
|
||||
val result = isSpanInRange(
|
||||
textRange = textRange,
|
||||
type = Span.Bold::class.java,
|
||||
text = source
|
||||
)
|
||||
|
||||
assertTrue(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `span should be in range with INNER_RIGHT overlap`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
val source = SpannableString("Everything was in confusion in the Oblonskys’ house")
|
||||
source.setSpan(Span.Bold(), 10, 20, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
|
||||
// TESTING
|
||||
|
||||
val textRange = IntRange(15, 20)
|
||||
val result = isSpanInRange(
|
||||
textRange = textRange,
|
||||
type = Span.Bold::class.java,
|
||||
text = source
|
||||
)
|
||||
|
||||
assertTrue(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `span should be in range with EQUAL overlap`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
val source = SpannableString("Everything was in confusion in the Oblonskys’ house")
|
||||
source.setSpan(Span.Bold(), 10, 20, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
|
||||
// TESTING
|
||||
|
||||
val textRange = IntRange(10, 20)
|
||||
val result = isSpanInRange(
|
||||
textRange = textRange,
|
||||
type = Span.Bold::class.java,
|
||||
text = source
|
||||
)
|
||||
|
||||
assertTrue(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `span should not be in range with OUTER overlap`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
val source = SpannableString("Everything was in confusion in the Oblonskys’ house")
|
||||
source.setSpan(Span.Bold(), 10, 20, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
|
||||
// TESTING
|
||||
|
||||
val textRange = IntRange(10, 21)
|
||||
val result = isSpanInRange(
|
||||
textRange = textRange,
|
||||
type = Span.Bold::class.java,
|
||||
text = source
|
||||
)
|
||||
|
||||
assertFalse(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `span should not be in range with LEFT overlap`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
val source = SpannableString("Everything was in confusion in the Oblonskys’ house")
|
||||
source.setSpan(Span.Bold(), 10, 20, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
|
||||
// TESTING
|
||||
|
||||
val textRange = IntRange(9, 13)
|
||||
val result = isSpanInRange(
|
||||
textRange = textRange,
|
||||
type = Span.Bold::class.java,
|
||||
text = source
|
||||
)
|
||||
|
||||
assertFalse(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `span should not be in range with RIGHT overlap`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
val source = SpannableString("Everything was in confusion in the Oblonskys’ house")
|
||||
source.setSpan(Span.Bold(), 10, 20, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
|
||||
// TESTING
|
||||
|
||||
val textRange = IntRange(14, 21)
|
||||
val result = isSpanInRange(
|
||||
textRange = textRange,
|
||||
type = Span.Bold::class.java,
|
||||
text = source
|
||||
)
|
||||
|
||||
assertFalse(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `span should not be in range with BEFORE overlap`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
val source = SpannableString("Everything was in confusion in the Oblonskys’ house")
|
||||
source.setSpan(Span.Bold(), 10, 20, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
|
||||
// TESTING
|
||||
|
||||
val textRange = IntRange(1, 9)
|
||||
val result = isSpanInRange(
|
||||
textRange = textRange,
|
||||
type = Span.Bold::class.java,
|
||||
text = source
|
||||
)
|
||||
|
||||
assertFalse(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `span should not be in range with AFTER overlap`() {
|
||||
|
||||
// SETUP
|
||||
|
||||
val source = SpannableString("Everything was in confusion in the Oblonskys’ house")
|
||||
source.setSpan(Span.Bold(), 10, 20, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
|
||||
// TESTING
|
||||
|
||||
val textRange = IntRange(21, 30)
|
||||
val result = isSpanInRange(
|
||||
textRange = textRange,
|
||||
type = Span.Bold::class.java,
|
||||
text = source
|
||||
)
|
||||
|
||||
assertFalse(result)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue