nandi/frqpublic Fork 0
1403956859648c3d39892e3994df8fee863ec653
Commits
Clone
git clone https://git.rickub.com/nandi/frq.git
git clone ssh://git@rickub.com/nandi/frq.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

trooms.nim · 148 lines · 5.7 KBNim Blame HistoryRaw
The data model, in Nim d333b6f nandi 18h ago1## The list, the overview and the read marker.
2import std/algorithm
3import std/strutils
4##
5## The marker rules are the ones worth the most here: unread is derived rather
6## than counted precisely so a replayed backlog cannot inflate it, and these
7## are the cases that prove it.
8
9import std/[sequtils, tables, unittest]
10import frq/[model, rooms]
11
12proc msg(frm, text: string, at: int64 = 0, id = "", system = false): Message =
13 Message(frm: frm, text: text, at: at, id: id, system: system)
14
15suite "channelList":
16 setup:
17 var chans = initOrderedTable[string, Room]()
18 for (n, acc) in [("#alpha", 0'i64), ("#beta", 5'i64), ("#gamma", 9'i64)]:
19 var c = initRoom(n); c.accessed = acc; chans[n] = c
20
21 test "most recently opened first":
22 check channelList(chans, "").mapIt(it.name) == @["#gamma", "#beta", "#alpha"]
23
24 test "never-opened buffers sort under those, by name":
25 chans["#aaa"] = initRoom("#aaa")
26 chans["#zzz"] = initRoom("#zzz")
27 let names = channelList(chans, "").mapIt(it.name)
28 check names == @["#gamma", "#beta", "#aaa", "#alpha", "#zzz"]
29
30 test "the search box filters, case-insensitively":
31 check channelList(chans, "BET").mapIt(it.name) == @["#beta"]
32 test "a blank search filters nothing":
33 check channelList(chans, " ").len == 3
34
35suite "seenMessage":
36 test "a msgid we already hold is a replay":
37 let msgs = @[msg("a", "hi", id = "1")]
38 check seenMessage(msgs, "1", "a", "hi", "me")
39 check not seenMessage(msgs, "2", "a", "hi", "me")
40
41 test "an untagged line is known by its sender and words":
42 # A replayed line with no tags has no identity, so left alone it arrives
43 # new on every rejoin and the room can never be finished reading.
44 let msgs = @[msg("alice", "hello")]
45 check seenMessage(msgs, "", "alice", "hello", "me")
46 check not seenMessage(msgs, "", "alice", "different", "me")
47
48 test "our own untagged line is never a replay":
49 # A second "ok" from this client is a real event.
50 let msgs = @[msg("me", "ok")]
51 check not seenMessage(msgs, "", "me", "ok", "me")
52
53 test "nor is the system's":
54 let msgs = @[msg("*", "alice joined")]
55 check not seenMessage(msgs, "", "*", "alice joined", "me")
56
57suite "the read marker":
58 setup:
59 var ch = initRoom("#test")
60 ch.messages = @[msg("a", "one", at = 100, id = "1"),
61 msg("b", "two", at = 200, id = "2"),
62 msg("c", "three", at = 300, id = "3")]
63
64 test "everything after the marked id is unread":
65 ch.lastReadId = "1"
66 check ch.afterMarker.mapIt(it.text) == @["two", "three"]
67
68 test "by time when the marked line is no longer held":
69 ch.lastReadId = "gone"
70 ch.lastReadAt = 150
71 check ch.afterMarker.mapIt(it.text) == @["two", "three"]
72
73 test "a replayed backlog cannot inflate the count":
74 # The whole reason unread is derived rather than counted. Two guards act
75 # together and this checks the pair, because either alone is not enough:
76 #
77 # seenMessage keeps the replayed line out of the buffer, and
78 # afterMarker means a line that IS older than the marker counts nothing.
79 #
80 # The first draft of this test appended the backlog twice and expected
81 # zero, which is a state seenMessage exists to make impossible — and the
82 # Clojure answers three to it as well. A test for an unreachable state
83 # tells you nothing about the reachable ones.
84 let marked = ch.markRead
85 check marked.recount("me").unread == 0
86 for m in ch.messages:
87 check seenMessage(marked.messages, m.id, m.frm, m.text, "me")
88
89 test "a line older than the marker counts for nothing":
90 ch.lastReadId = ""
91 ch.lastReadAt = 250
92 check ch.afterMarker.mapIt(it.text) == @["three"]
93
94 test "markRead never walks the marker backwards":
95 ch.lastReadAt = 500 # read past a backlog that then arrived
96 let marked = ch.markRead
97 check marked.lastReadAt == 500
98
99 test "system lines are read but never make a room worth looking at":
100 ch.messages.add msg("*", "you joined", at = 400, system = true)
101 check ch.recount("me").unread == 4 - 1 # the three said lines only...
102 ch.lastReadId = "3"
103 check ch.recount("me").unread == 0
104
105 test "a mention is noticed, and only somebody else's":
106 ch.messages = @[msg("alice", "hey me, look", at = 100),
107 msg("me", "me me me", at = 200)]
108 let r = ch.recount("me")
109 check r.unread == 2
110 check r.mention
111
112 test "no mention where the reader is not named":
113 ch.messages = @[msg("alice", "nothing here", at = 100)]
114 check not ch.recount("me").mention
115
116suite "recentEverywhere":
117 setup:
118 var chans = initOrderedTable[string, Room]()
119 for n in ["#a", "#b"]:
120 var c = initRoom(n)
121 for i in 1 .. 3:
122 c.messages.add msg("u", n & $i, at = i.int64 * 10)
123 chans[n] = c
124
125 test "the room being read is left out":
126 let names = recentEverywhere(chans, "#a").mapIt(it.text)
127 check names.allIt(it.startsWith("#b"))
128
129 test "system lines are left out":
130 var c = initRoom("#c")
131 c.messages = @[msg("*", "joined", at = 99, system = true)]
132 chans["#c"] = c
133 check recentEverywhere(chans, "").allIt(it.text != "joined")
134
135 test "newest first":
136 let ats = recentEverywhere(chans, "").mapIt(it.at)
137 check ats == ats.sorted(SortOrder.Descending)
138
139 test "a turn each, so a busy room cannot crowd a quiet one out":
140 # The reason for round-robin: taking the newest N outright would answer
141 # about whichever room is busiest, which is the one you can already see.
142 var busy = initRoom("#busy")
143 for i in 1 .. 200:
144 busy.messages.add msg("u", "spam" & $i, at = i.int64)
145 chans["#busy"] = busy
146 let got = recentEverywhere(chans, "")
147 check got.anyIt(it.text.startsWith("#a"))
148 check got.len <= overviewLimit