package com.keylesspalace.tusky import android.text.Spannable import com.keylesspalace.tusky.util.highlightSpans import org.junit.Assert import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized class SpanUtilsTest { @Test fun matchesMixedSpans() { val input = "one #one two @two three https://thr.ee/meh?foo=bar&wat=@at#hmm four #four five @five" val inputSpannable = FakeSpannable(input) highlightSpans(inputSpannable, 0xffffff) val spans = inputSpannable.spans Assert.assertEquals(5, spans.size) } @Test fun doesntMergeAdjacentURLs() { val firstURL = "http://first.thing" val secondURL = "https://second.thing" val inputSpannable = FakeSpannable("$firstURL $secondURL") highlightSpans(inputSpannable, 0xffffff) val spans = inputSpannable.spans Assert.assertEquals(2, spans.size) Assert.assertEquals(firstURL.length, spans[0].end - spans[0].start) Assert.assertEquals(secondURL.length, spans[1].end - spans[1].start) } @RunWith(Parameterized::class) class MatchingTests(private val thingToHighlight: String) { companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic fun data(): Iterable { return listOf( "@mention", "#tag", "https://thr.ee/meh?foo=bar&wat=@at#hmm", "http://thr.ee/meh?foo=bar&wat=@at#hmm" ) } } @Test fun matchesSpanAtStart() { val inputSpannable = FakeSpannable(thingToHighlight) highlightSpans(inputSpannable, 0xffffff) val spans = inputSpannable.spans Assert.assertEquals(1, spans.size) Assert.assertEquals(thingToHighlight.length, spans[0].end - spans[0].start) } @Test fun matchesSpanNotAtStart() { val inputSpannable = FakeSpannable(" $thingToHighlight") highlightSpans(inputSpannable, 0xffffff) val spans = inputSpannable.spans Assert.assertEquals(1, spans.size) Assert.assertEquals(thingToHighlight.length, spans[0].end - spans[0].start) } @Test fun doesNotMatchSpanEmbeddedInText() { val inputSpannable = FakeSpannable("aa${thingToHighlight}aa") highlightSpans(inputSpannable, 0xffffff) val spans = inputSpannable.spans Assert.assertTrue(spans.isEmpty()) } @Test fun doesNotMatchSpanEmbeddedInAnotherSpan() { val inputSpannable = FakeSpannable("@aa${thingToHighlight}aa") highlightSpans(inputSpannable, 0xffffff) val spans = inputSpannable.spans Assert.assertEquals(1, spans.size) } @Test fun spansDoNotOverlap() { val begin = "@begin" val end = "#end" val inputSpannable = FakeSpannable("$begin $thingToHighlight $end") highlightSpans(inputSpannable, 0xffffff) val spans = inputSpannable.spans Assert.assertEquals(3, spans.size) val middleSpan = spans.single { span -> span.start > 0 && span.end < inputSpannable.lastIndex } Assert.assertEquals(begin.length + 1, middleSpan.start) Assert.assertEquals(inputSpannable.length - end.length - 1, middleSpan.end) } } class FakeSpannable(private val text: String) : Spannable { val spans = mutableListOf() override fun setSpan(what: Any?, start: Int, end: Int, flags: Int) { spans.add(BoundedSpan(what, start, end)) } override fun getSpans(start: Int, end: Int, type: Class?): Array { val matching = if (type == null) { ArrayList() } else { spans.filter { it.start >= start && it.end <= end && type.isAssignableFrom(it.span?.javaClass) } .map { it.span } .let { ArrayList(it) } } return matching.toArray() as Array } override fun removeSpan(what: Any?) { spans.removeIf { span -> span.span == what} } override fun toString(): String { return text } override val length: Int get() = text.length class BoundedSpan(val span: Any?, val start: Int, val end: Int) override fun nextSpanTransition(start: Int, limit: Int, type: Class<*>?): Int { throw NotImplementedError() } override fun getSpanEnd(tag: Any?): Int { throw NotImplementedError() } override fun getSpanFlags(tag: Any?): Int { throw NotImplementedError() } override fun get(index: Int): Char { throw NotImplementedError() } override fun subSequence(startIndex: Int, endIndex: Int): CharSequence { throw NotImplementedError() } override fun getSpanStart(tag: Any?): Int { throw NotImplementedError() } } }