eleven / ocho Public

Clone
git clone https://amadan.net/eleven/ocho

Plain git — no account needed to clone.

Download

Download this file

1import XCTest
2@testable import LChatCore
3
4// Parity gate for RichText.swift against web/static/format.js β€” the SAME cases
5// as test/format.test.mjs, so a message formats identically in both feeds. If
6// one of these fails after a format.js change, update RichText.swift in the
7// same change (and vice versa).
8final class RichTextTests: XCTestCase {
9 private func links(_ s: String) -> [String] {
10 richTokens(s).compactMap { if case .link(_, let url) = $0 { return url.absoluteString } else { return nil } }
11 }
12
13 func testPlainTextPassesThrough() {
14 XCTAssertEqual(richTokens("just a normal message"), [.text("just a normal message")])
15 }
16
17 func testHttpsLinkSplits() {
18 XCTAssertEqual(richTokens("see https://example.com/page now"), [
19 .text("see "), .link("https://example.com/page", URL(string: "https://example.com/page")!), .text(" now"),
20 ])
21 }
22
23 func testWwwGetsScheme() {
24 XCTAssertEqual(links("go to www.example.com"), ["https://www.example.com"])
25 }
26
27 func testTrailingPunctuationStripped() {
28 XCTAssertEqual(links("look at https://example.com."), ["https://example.com"])
29 XCTAssertEqual(links("(https://example.com)"), ["https://example.com"])
30 }
31
32 func testJavascriptSchemeIsNotALink() {
33 XCTAssertEqual(links("javascript:alert(1)"), [])
34 }
35
36 func testMultipleLinks() {
37 XCTAssertEqual(links("a https://one.com b http://two.com c"),
38 ["https://one.com", "http://two.com"])
39 }
40
41 func testBareDomainsWithKnownTLDs() {
42 XCTAssertEqual(links("see rte.ie for news"), ["https://rte.ie"])
43 XCTAssertEqual(links("on guardian.co.uk today"), ["https://guardian.co.uk"])
44 XCTAssertEqual(links("rte.ie/news here"), ["https://rte.ie/news"])
45 }
46
47 func testFileNamesAndEmailsAreNotLinks() {
48 for s in ["run node.js today", "open index.html now", "see main.go there", "ping paul@rte.ie now"] {
49 XCTAssertEqual(links(s), [], s)
50 }
51 }
52
53 func testInlineFormatting() {
54 XCTAssertEqual(richTokens("a **bold** move"), [.text("a "), .bold("bold"), .text(" move")])
55 XCTAssertEqual(richTokens("so *italic* here"), [.text("so "), .italic("italic"), .text(" here")])
56 XCTAssertEqual(richTokens("it exists _to test_ :-/"), [.text("it exists "), .italic("to test"), .text(" :-/")])
57 XCTAssertEqual(richTokens("some `code` inline"), [.text("some "), .code("code"), .text(" inline")])
58 XCTAssertEqual(richTokens("well ~nope~ then"), [.text("well "), .strike("nope"), .text(" then")])
59 }
60
61 // test/format.test.mjs "triple asterisk is combined bold italic"
62 func testTripleAsteriskIsCombinedBoldItalic() {
63 XCTAssertEqual(richTokens("***both***"), [.boldItalic("both")])
64 XCTAssertEqual(richTokens("a ***b c*** d"), [.text("a "), .boldItalic("b c"), .text(" d")])
65 // Neighbours don't bleed: bi next to bold, and markers still can't span lines.
66 XCTAssertEqual(richTokens("***x*** **y**"), [.boldItalic("x"), .text(" "), .bold("y")])
67 XCTAssertEqual(richTokens("***a\nb***"), [.text("***a\nb***")])
68 }
69
70 func testUnderscoreItalicsNeedWordBoundaries() {
71 // file_names must not italicize (format.js's lookbehind/lookahead).
72 XCTAssertEqual(richTokens("see file_name_here"), [.text("see file_name_here")])
73 }
74
75 func testLinksAreNeverFormattedInside() {
76 // The underscore inside the URL must not become italics.
77 XCTAssertEqual(links("https://example.com/a_b_c"), ["https://example.com/a_b_c"])
78 }
79
80 // ---- blockTokens β€” the eight cases from test/format.test.mjs ----------
81
82 func testBlockPlainTextIsOneUntouchedBlock() {
83 XCTAssertEqual(blockTokens("hello\n\nworld"), [.p("hello\n\nworld")])
84 XCTAssertEqual(blockTokens(""), [.p("")])
85 }
86
87 func testBlockFencedCodeMarkersVerbatim() {
88 XCTAssertEqual(blockTokens("look:\n```\nif (a > b) { *notBold* }\n```\nnice"), [
89 .p("look:"), .code("if (a > b) { *notBold* }", lang: ""), .p("nice"),
90 ])
91 }
92
93 func testBlockFenceCapturesLanguage() {
94 XCTAssertEqual(blockTokens("```js\nconst x = 1;\n```"),
95 [.code("const x = 1;", lang: "js")])
96 }
97
98 func testBlockUnterminatedFenceRunsToEnd() {
99 XCTAssertEqual(blockTokens("```\nline one\nline two"),
100 [.code("line one\nline two", lang: "")])
101 }
102
103 func testBlockInlineBackticksAreNotAFence() {
104 XCTAssertEqual(blockTokens("```js``` opens a fence"),
105 [.p("```js``` opens a fence")])
106 }
107
108 func testBlockConsecutiveQuoteLinesMerge() {
109 XCTAssertEqual(blockTokens("> two roads diverged\n> in a yellow wood\nso true"), [
110 .quote("two roads diverged\nin a yellow wood"), .p("so true"),
111 ])
112 }
113
114 func testBlockBareQuoteContinuesAsBlankLine() {
115 XCTAssertEqual(blockTokens("> first\n>\n> second"), [.quote("first\n\nsecond")])
116 }
117
118 func testBlockMidLineAngleIsNotAQuote() {
119 XCTAssertEqual(blockTokens("5 > 3 and 2 > 1"), [.p("5 > 3 and 2 > 1")])
120 }
121
122 func testBlockBlankLinesAroundBlocksDropped() {
123 XCTAssertEqual(blockTokens("intro\n\n> quoted\n\noutro"), [
124 .p("intro"), .quote("quoted"), .p("outro"),
125 ])
126 }
127
128 // ---- youtubeId β€” the shapes from test/format.test.mjs ------------------
129
130 func testYouTubeIdParsesCommonShapes() {
131 func yt(_ s: String) -> (String, Int)? {
132 youtubeId(URL(string: s)!).map { ($0.id, $0.start) }
133 }
134 XCTAssertEqual(yt("https://www.youtube.com/watch?v=dQw4w9WgXcQ")?.0, "dQw4w9WgXcQ")
135 XCTAssertEqual(yt("https://youtu.be/dQw4w9WgXcQ")?.0, "dQw4w9WgXcQ")
136 XCTAssertEqual(yt("https://m.youtube.com/watch?v=dQw4w9WgXcQ&pp=xyz")?.0, "dQw4w9WgXcQ")
137 XCTAssertEqual(yt("https://www.youtube.com/shorts/dQw4w9WgXcQ")?.0, "dQw4w9WgXcQ")
138 XCTAssertEqual(yt("https://www.youtube.com/embed/dQw4w9WgXcQ")?.0, "dQw4w9WgXcQ")
139 XCTAssertEqual(yt("https://www.youtube.com/watch?v=dQw4w9WgXcQ&t=90")?.1, 90)
140 XCTAssertEqual(yt("https://youtu.be/dQw4w9WgXcQ?t=1h2m3s")?.1, 3723)
141 XCTAssertNil(yt("https://example.com/watch?v=dQw4w9WgXcQ"))
142 XCTAssertNil(yt("https://www.youtube.com/watch?v=short"))
143 }
144
145 func testEmojiOnlyCount() {
146 XCTAssertEqual(emojiOnlyCount("πŸ‘"), 1)
147 XCTAssertEqual(emojiOnlyCount("πŸ˜€πŸ˜‚"), 2)
148 XCTAssertEqual(emojiOnlyCount("πŸŽ‰πŸŽ‰πŸŽ‰"), 3)
149 XCTAssertEqual(emojiOnlyCount("πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦"), 1, "ZWJ family is one emoji")
150 XCTAssertEqual(emojiOnlyCount("πŸ‘πŸ½"), 1, "skin tone is one emoji")
151 XCTAssertEqual(emojiOnlyCount("πŸ‡¬πŸ‡§"), 1, "flag is one emoji")
152 XCTAssertEqual(emojiOnlyCount("πŸ‘ πŸ‘"), 2, "whitespace between emoji allowed")
153 XCTAssertEqual(emojiOnlyCount("πŸ‘πŸ‘πŸ‘πŸ‘"), 0, "more than 3 renders normally")
154 XCTAssertEqual(emojiOnlyCount("hi πŸ‘"), 0)
155 XCTAssertEqual(emojiOnlyCount("πŸ‘ nice"), 0)
156 XCTAssertEqual(emojiOnlyCount("plain text"), 0)
157 XCTAssertEqual(emojiOnlyCount(""), 0)
158 }
159
160 // The web twin's regression (format.test.mjs): emphasis markers around a
161 // URL must never ride into the href β€” "**github.com/apps/x**" gets a
162 // clean link, stars left as literal text.
163 func testEmphasisMarkersStayOutOfHrefs() {
164 let toks = tokenizeText("go to **github.com/apps/eleven-messenger** and install")
165 let link = toks.first(where: { $0.href != nil })
166 XCTAssertEqual(link?.text, "github.com/apps/eleven-messenger")
167 XCTAssertEqual(link?.href?.absoluteString.contains("*"), false)
168 XCTAssertEqual(toks.map(\.text).joined(), "go to **github.com/apps/eleven-messenger** and install")
169
170 let toks2 = tokenizeText("see https://docs.github.com/rest/issues#list**")
171 let link2 = toks2.first(where: { $0.href != nil })
172 XCTAssertEqual(link2?.text, "https://docs.github.com/rest/issues#list")
173 }
174}
175