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// The @mention render-layer scan (Mentions.swift) — the native twin of the
5// web's renderRichText mention rules (apps/chat.js), so both feeds agree on
6// exactly which "@…" runs are mentions.
7final class MentionsTests: XCTestCase {
8 private func index(me: String = "me") -> MentionIndex {
9 MentionIndex(entries: [
10 .init(label: "Ada", uid: "u1", display: "Ada", tappable: true),
11 .init(label: "Ada Lovelace", uid: "u2", display: "Ada L", tappable: true),
12 .init(label: "Bob", uid: "u3", display: "Bobby", tappable: true),
13 .init(label: "Me", uid: me, display: "Me", tappable: false),
14 ])
15 }
16
17 func testBasicMention() {
18 let segs = index().split("hi @Bob!")
19 XCTAssertEqual(segs, [
20 .text("hi "),
21 .mention(.init(label: "Bob", uid: "u3", display: "Bobby", tappable: true)),
22 .text("!"),
23 ])
24 }
25
26 func testLongestLabelWins() {
27 // "@Ada Lovelace" must resolve to the two-word member, never "Ada"+text.
28 let segs = index().split("@Ada Lovelace hello")
29 guard case .mention(let e) = segs.first else { return XCTFail("no mention") }
30 XCTAssertEqual(e.uid, "u2")
31 XCTAssertEqual(segs.dropFirst().first, .text(" hello"))
32 }
33
34 func testMidWordAndEmailNeverMatch() {
35 // The web's prevCh guard: /[\w@.]/ blocks mid-word @, emails, and
36 // domain-ish text.
37 XCTAssertEqual(index().split("mail@Bob.com"), [.text("mail@Bob.com")])
38 XCTAssertEqual(index().split("x@Bob"), [.text("x@Bob")])
39 XCTAssertEqual(index().split("a.@Bob"), [.text("a.@Bob")])
40 }
41
42 func testTrailingWordCharBlocks() {
43 // (?![\w]) — "@Bobcat" is not a mention of Bob…
44 XCTAssertEqual(index().split("@Bobcat"), [.text("@Bobcat")])
45 // …but trailing punctuation is fine.
46 guard case .mention(let e)? = index().split("@Bob,").first else {
47 return XCTFail("no mention")
48 }
49 XCTAssertEqual(e.uid, "u3")
50 }
51
52 func testStartAndAfterWhitespaceMatch() {
53 XCTAssertEqual(index().split("@Bob").count, 1)
54 let segs = index().split("(@Bob)")
55 XCTAssertEqual(segs, [
56 .text("("),
57 .mention(.init(label: "Bob", uid: "u3", display: "Bobby", tappable: true)),
58 .text(")"),
59 ])
60 }
61
62 func testSelfMentionNotTappable() {
63 guard case .mention(let e)? = index().split("@Me too").first else {
64 return XCTFail("no mention")
65 }
66 XCTAssertFalse(e.tappable)
67 }
68
69 func testAdjacentMentions() {
70 // "@Ada@Bob" — the web's regex leaves the second as plain text (its
71 // prev char is a word char / the chip boundary).
72 XCTAssertEqual(index().split("@Ada@Bob"), [
73 .mention(.init(label: "Ada", uid: "u1", display: "Ada", tappable: true)),
74 .text("@Bob"),
75 ])
76 // Separated by a space both match.
77 let both = index().split("@Ada @Bob")
78 XCTAssertEqual(both.count, 3)
79 }
80
81 func testUnknownNameStaysText() {
82 XCTAssertEqual(index().split("@Nobody here"), [.text("@Nobody here")])
83 }
84
85 func testMayMentionFastPath() {
86 XCTAssertFalse(index().mayMention("no at sign"))
87 XCTAssertTrue(index().mayMention("hi @Bob"))
88 XCTAssertFalse(MentionIndex(entries: []).mayMention("hi @Bob"))
89 }
90
91 func testRevChangesWithDisplay() {
92 // The memo key: a rename (display change) must produce a new rev.
93 let a = index()
94 let b = MentionIndex(entries: [
95 .init(label: "Ada", uid: "u1", display: "Ada", tappable: true),
96 .init(label: "Ada Lovelace", uid: "u2", display: "Ada L", tappable: true),
97 .init(label: "Bob", uid: "u3", display: "Robert", tappable: true),
98 .init(label: "Me", uid: "me", display: "Me", tappable: false),
99 ])
100 XCTAssertNotEqual(a.rev, b.rev)
101 }
102
103 func testUnicodeTextAroundMentions() {
104 let segs = index().split("héllo 👋 @Bob — ça va?")
105 XCTAssertEqual(segs, [
106 .text("héllo 👋 "),
107 .mention(.init(label: "Bob", uid: "u3", display: "Bobby", tappable: true)),
108 .text(" — ça va?"),
109 ])
110 }
111
112 func testMentionLinkRoundTrip() {
113 let uid = "user id/with?odd#chars"
114 guard let url = MentionLink.url(uid: uid) else { return XCTFail("no url") }
115 XCTAssertEqual(MentionLink.uid(from: url), uid)
116 XCTAssertNil(MentionLink.uid(from: URL(string: "https://example.com/x")!))
117 }
118}
119