merge: W24 —— 编辑弹层只显示纯文本(原文含标记时如实告知会被替换,绝不静默丢旁白)
This commit is contained in:
commit
1794e53d1c
4 files changed
+333
-11
No files matched your search
@@ -0,0 +1,185 @@
|
||||
package top.yeij.cyrene.domain.chat
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* 编辑框初值:什么时候逐字节恒等,什么时候剥标记,剥完还剩下什么。
|
||||
*
|
||||
* 红线是「不许静默丢东西」,所以两个方向都要钉住:
|
||||
* - **纯文本一个字符都不许动**——哪怕带连续空行、CRLF、全角空格,哪怕里面有 `<`(`1 < 2`、`<div>`
|
||||
* 都是普通文本,见到尖括号就剥是把用户打的字当标签吃掉);
|
||||
* - **真有标记时也不许把旁白/动作丢掉**——旁白是她说的话的一部分,剥掉标签不等于可以丢内容。
|
||||
*
|
||||
* 判据本身在 [editableMessageOf],剥标记完全交给 `domain/parse`(项目里唯一的解析器),
|
||||
* 这里不重复实现它的规则。
|
||||
*/
|
||||
class MessageEditTextTest {
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 一、纯文本:逐字节恒等
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
/** 纯文本路径的唯一断言:`hasMarkup` 为 false,且文本与原文**逐字符**相等。 */
|
||||
private fun assertUntouched(content: String) {
|
||||
val editable = editableMessageOf(content)
|
||||
assertFalse("「$content」不该被判成带标记", editable.hasMarkup)
|
||||
assertEquals("「$content」必须逐字符原样", content, editable.text)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `普通中英文纯文本逐字节恒等`() {
|
||||
assertUntouched("你好呀,今天过得怎么样?Hello, world — 我在这儿。")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `空串就是空串`() {
|
||||
assertEquals(EditableMessage(text = "", hasMarkup = false), editableMessageOf(""))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `首尾空白与各种空白字符原样保留`() {
|
||||
// 用户打的就是这些字:不 trim,也不把全角空格 / 不换行空格 / 零宽空格 / 制表符换成别的。
|
||||
assertUntouched("\t 第一行\u3000\u00A0\u200B\n 第二行 \n")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `连续三个换行不折叠`() {
|
||||
// 解析器的空行切分是**渲染**的规则,编辑框不欠它什么:多打的空行照旧是用户的东西。
|
||||
assertUntouched("第一段\n\n\n\n第二段")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `换行符不统一 —— CRLF 与单独的 CR 都原样保留`() {
|
||||
assertUntouched("第一行\r\n第二行\r第三行\n")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `小于号不是标记 —— 数学符号与普通标签都算普通文本`() {
|
||||
// 判据只认解析器真正认识的那三个标签。见到 `<` 就当标记,就会把用户打的数学符号吃掉。
|
||||
listOf(
|
||||
"1 < 2 且 3 > 2",
|
||||
"if (a < b) { c }",
|
||||
"a<b>c",
|
||||
"<3",
|
||||
"<div>这是一段 HTML</div>",
|
||||
"</span>",
|
||||
"<p2>这不关 p 标签的事</p2>",
|
||||
"<P>大小写不认(解析器只认小写 p)</P>",
|
||||
).forEach(::assertUntouched)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `裸的星号旁白不触发剥标记`() {
|
||||
// 星号式旁白(*…* / *动作:…*)不是本模块认的标记:编辑框原样显示,保存也原样发。
|
||||
assertUntouched("*弯起眼睛笑了*我今天想你了")
|
||||
assertUntouched("*动作:她歪了歪头*")
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 二、真有标记:剥掉标签,内容一件不少
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
fun `线上真实形状 —— 旁白与正文都在,且不再有尖括号`() {
|
||||
val content = "<p><action>把整卷纸条从桌头铺到桌尾,最后终于摸到了尾巴</action>" +
|
||||
"读到头也读到尾啦,亲爱的♪</p>\n\n<p>完整长度 624046 字符。</p>"
|
||||
|
||||
val editable = editableMessageOf(content)
|
||||
|
||||
assertTrue(editable.hasMarkup)
|
||||
assertEquals(
|
||||
"把整卷纸条从桌头铺到桌尾,最后终于摸到了尾巴读到头也读到尾啦,亲爱的♪\n\n完整长度 624046 字符。",
|
||||
editable.text,
|
||||
)
|
||||
assertFalse("剥完不该再剩标签", editable.text.contains('<') || editable.text.contains('>'))
|
||||
// 旁白(动作)与正文都得在:旁白是她说的话的一部分,不是可以顺手丢掉的东西。
|
||||
assertTrue(editable.text.contains("把整卷纸条从桌头铺到桌尾,最后终于摸到了尾巴"))
|
||||
assertTrue(editable.text.contains("读到头也读到尾啦,亲爱的♪"))
|
||||
assertTrue(editable.text.contains("完整长度 624046 字符。"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `纯旁白消息 —— 旁白那句话本身留下`() {
|
||||
val editable = editableMessageOf("<action>弯起眼睛笑了</action>")
|
||||
assertTrue(editable.hasMarkup)
|
||||
assertEquals("弯起眼睛笑了", editable.text)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `narration 标签同样算标记`() {
|
||||
val editable = editableMessageOf("<narration>内心:她也在想你</narration>")
|
||||
assertTrue(editable.hasMarkup)
|
||||
assertEquals("内心:她也在想你", editable.text)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `两个气泡之间只空一行`() {
|
||||
assertEquals("第一段\n\n第二段", editableMessageOf("<p>第一段</p>\n\n<p>第二段</p>").text)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `带属性的 p 标签也认 —— 容差与解析器一致`() {
|
||||
val editable = editableMessageOf("<p class=\"line\">你好呀</p>")
|
||||
assertTrue(editable.hasMarkup)
|
||||
assertEquals("你好呀", editable.text)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `气泡内的换行保留`() {
|
||||
assertEquals("第一行\n第二行", editableMessageOf("<p>第一行\n第二行</p>").text)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `气泡内的 emoji 不被拆坏`() {
|
||||
assertEquals("你好🎀再见", editableMessageOf("<p>你好🎀再见</p>").text)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `head 旁白排在正文之前 —— 按原文顺序,不按渲染分块重排`() {
|
||||
val editable = editableMessageOf("<p><action>她歪了歪头</action>你好呀</p>")
|
||||
assertTrue(editable.hasMarkup)
|
||||
assertEquals("她歪了歪头你好呀", editable.text)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `tail 旁白也留下`() {
|
||||
assertEquals("你好呀她挥了挥手", editableMessageOf("<p>你好呀<action>她挥了挥手</action></p>").text)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `标签外散落的正文不丢`() {
|
||||
assertEquals(
|
||||
"开头这几句没包在标签里\n\n第二段",
|
||||
editableMessageOf("开头这几句没包在标签里\n\n<p>第二段</p>").text,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `气泡内的星号旁白随标签一起剥掉 —— 口径来自解析器`() {
|
||||
// 有标记时怎么剥由 parse 说了算:它认 *笑* 是旁白,所以编辑框里留下旁白那句话、没有星号。
|
||||
assertEquals("hi 笑", editableMessageOf("<p>hi *笑*</p>").text)
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 三、畸形:宁可不剥,也不许丢字
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
fun `没闭合的 p 不抛异常 —— 解析器按普通文本处理,这里就恒等`() {
|
||||
assertUntouched("<p>没闭合就结束")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `没闭合的 action 同样原样留着`() {
|
||||
assertUntouched("<action>她笑了,可是没写结尾")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `只有一对空标签的消息恒等 —— 解析不出气泡就不剥`() {
|
||||
// parse("<p></p>") 是空列表:宁可把 `content` 原样留着,也不赌一把把整条消息删空。
|
||||
assertUntouched("<p></p>")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user