mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-09-19 04:59:59 +00:00
Merge 94872734a3c800fdff66849ec41d361062920600 into c127eb83ab94c069c32d37530d2faecd381cd2a8
This commit is contained in:
commit
9fe5cc3764
@ -1,9 +1,19 @@
|
||||
package com.bitchat.android.ui
|
||||
|
||||
import java.text.BreakIterator
|
||||
|
||||
/**
|
||||
* UI constants/utilities for nickname rendering.
|
||||
*/
|
||||
fun truncateNickname(name: String, maxLen: Int = com.bitchat.android.util.AppConstants.UI.MAX_NICKNAME_LENGTH): String {
|
||||
return if (name.length <= maxLen) name else name.take(maxLen)
|
||||
}
|
||||
if (name.length <= maxLen) return name
|
||||
|
||||
// The limit counts UTF-16 code units, so cutting at it can land inside a
|
||||
// surrogate pair and leave a lone surrogate that renders as a tofu box, or
|
||||
// split a ZWJ sequence into its parts. Step back to the last grapheme
|
||||
// boundary at or before the limit so what is shown is always whole.
|
||||
val boundaries = BreakIterator.getCharacterInstance()
|
||||
boundaries.setText(name)
|
||||
val end = boundaries.preceding(maxLen + 1)
|
||||
return if (end == BreakIterator.DONE) "" else name.substring(0, end)
|
||||
}
|
||||
|
||||
@ -0,0 +1,79 @@
|
||||
package com.bitchat.android.ui
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class ChatUIConstantsTest {
|
||||
|
||||
@Test
|
||||
fun `a name inside the limit is returned unchanged`() {
|
||||
assertEquals("alice", truncateNickname("alice", maxLen = 15))
|
||||
assertEquals("exactlyfifteen!", truncateNickname("exactlyfifteen!", maxLen = 15))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an ascii name is still cut at the limit`() {
|
||||
assertEquals("hello world thi", truncateNickname("hello world this is long", maxLen = 15))
|
||||
}
|
||||
|
||||
/**
|
||||
* The limit counts UTF-16 code units, so the 15th unit of this name is the
|
||||
* high surrogate of the emoji. Cutting there leaves a lone surrogate that
|
||||
* renders as a tofu box.
|
||||
*/
|
||||
@Test
|
||||
fun `a surrogate pair is never split`() {
|
||||
val truncated = truncateNickname("abcdefghijklmn😀", maxLen = 15)
|
||||
|
||||
assertEquals("abcdefghijklmn", truncated)
|
||||
assertTrue(
|
||||
"truncated name must not end in a lone surrogate",
|
||||
truncated.none { Character.isSurrogate(it) }
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an emoji that fits is kept whole`() {
|
||||
assertEquals("abcdefghijkl😀m", truncateNickname("abcdefghijkl😀mno", maxLen = 15))
|
||||
}
|
||||
|
||||
/** A ZWJ sequence is one grapheme, so it is kept or dropped as a whole. */
|
||||
@Test
|
||||
fun `a zwj sequence is not split into its parts`() {
|
||||
val family = "👨👩👧"
|
||||
val truncated = truncateNickname(family + " family chat", maxLen = 15)
|
||||
|
||||
assertEquals(family + " family", truncated)
|
||||
assertTrue("the family must survive whole", truncated.startsWith(family))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a name made only of one long grapheme truncates to empty rather than half a character`() {
|
||||
val family = "👨👩👧👦"
|
||||
val truncated = truncateNickname(family, maxLen = 5)
|
||||
|
||||
assertEquals("", truncated)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `every truncation stays within the limit and holds no partial character`() {
|
||||
val samples = listOf(
|
||||
"abcdefghijklmn😀",
|
||||
"🇮🇳 india mesh",
|
||||
"👍🏽 thumbs up all round",
|
||||
"plain long nickname here"
|
||||
)
|
||||
for (name in samples) {
|
||||
for (limit in 1..20) {
|
||||
val truncated = truncateNickname(name, maxLen = limit)
|
||||
assertTrue("$name at $limit exceeded the limit", truncated.length <= limit)
|
||||
assertTrue("$name at $limit is not a prefix of the name", name.startsWith(truncated))
|
||||
assertTrue(
|
||||
"$name at $limit ended in a lone surrogate",
|
||||
truncated.isEmpty() || !Character.isHighSurrogate(truncated.last())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user