1
0
Fork 0
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:
Ivanov Konstantin 2020-06-08 17:58:14 +03:00
parent 6f41e1390a
commit ebad5f126e
2 changed files with 404 additions and 0 deletions

View file

@ -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
}

View file

@ -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)
}
}