mastodon-web-like trailing hashtag bar (#4761)

Rationale: Since the mastodon web UI has started stripping "trailing"
hashtags from post content and shoving it into an ellipsized section at
the bottom of posts, the general hashtag : content ratio is rising.

This is an attempt at adopting a similar functionality for Tusky.

Before:

<img width="420" alt="Screenshot of a hashtag-heavy post on Tusky
nightly"
src="https://github.com/user-attachments/assets/09c286e8-6822-482a-904c-5cb3323ea0e1">


After:
![Screenshot of the same post on this
branch](https://github.com/user-attachments/assets/fa99964d-a057-4727-b9f0-1251a199d5f8)
This commit is contained in:
Levi Bard 2024-11-28 19:15:31 +01:00 committed by GitHub
commit d3feca3a10
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 190 additions and 15 deletions

View file

@ -315,6 +315,74 @@ class LinkHelperTest {
}
}
@Test
fun `get trailing hashtags with empty content returns empty list`() {
assert(getTrailingHashtags(SpannableStringBuilder("")).second.isEmpty())
}
@Test
fun `get trailing hashtags with no hashtags returns empty list`() {
assert(getTrailingHashtags(SpannableStringBuilder("some untagged content")).second.isEmpty())
}
@Test
fun `get trailing hashtags with all inline hashtags returns empty list`() {
assert(getTrailingHashtags(SpannableStringBuilder("some #inline #tagged #content")).second.isEmpty())
}
@Test
fun `get trailing hashtags with one tag`() {
val content = SpannableStringBuilder("some content followed by tags:\n").apply {
tags.first().let { append("#${it.name}", URLSpan(it.url), 0) }
}
val (_, trailingHashtags) = getTrailingHashtags(content)
assertEquals(tags.first().name, trailingHashtags.single().name)
assertEquals(tags.first().url, trailingHashtags.single().url)
}
@Test
fun `get trailing hashtags with multiple tags`() {
for (separator in listOf(" ", "\t", "\n", "\r\n")) {
val content = SpannableStringBuilder("some content followed by tags:\n").apply {
for (tag in tags) {
append(separator)
append("#${tag.name}", URLSpan(tag.url), 0)
append(separator)
}
}
val (_, trailingHashtags) = getTrailingHashtags(content)
assertEquals(tags.size, trailingHashtags.size)
tags.forEachIndexed { index, tag ->
assertEquals(tag.name, trailingHashtags[index].name)
assertEquals(tag.url, trailingHashtags[index].url)
}
}
}
@Test
fun `get trailing hashtags ignores inline tags`() {
for (separator in listOf(" ", "\t", "\n", "\r\n")) {
val content = SpannableStringBuilder("some content with inline tag ").apply {
append("#inline", URLSpan("https://example.com/tag/inline"), 0)
append(" followed by trailing tags\n")
for (tag in tags) {
append(separator)
append("#${tag.name}", URLSpan(tag.url), 0)
append(separator)
}
}
val (_, trailingHashtags) = getTrailingHashtags(content)
assertEquals(tags.size, trailingHashtags.size)
tags.forEachIndexed { index, tag ->
assertEquals(tag.name, trailingHashtags[index].name)
assertEquals(tag.url, trailingHashtags[index].url)
}
}
}
@RunWith(Parameterized::class)
class UrlMatchingTests(private val url: String, private val expectedResult: Boolean) {
companion object {