nandi/frqpublic Fork 0
87a28ccbc1ad41565d24fa8c4f68e1f9426fd61e
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.

The data model, in Nim d333b6f · on 87a28ccbc1ad41565d24fa8c4f68e1f9426fd61e · nandi · 18h ago
trooms.nim · 148 lines · 5.7 KBNim Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
## The list, the overview and the read marker.
import std/algorithm
import std/strutils
##
## The marker rules are the ones worth the most here: unread is derived rather
## than counted precisely so a replayed backlog cannot inflate it, and these
## are the cases that prove it.

import std/[sequtils, tables, unittest]
import frq/[model, rooms]

proc msg(frm, text: string, at: int64 = 0, id = "", system = false): Message =
  Message(frm: frm, text: text, at: at, id: id, system: system)

suite "channelList":
  setup:
    var chans = initOrderedTable[string, Room]()
    for (n, acc) in [("#alpha", 0'i64), ("#beta", 5'i64), ("#gamma", 9'i64)]:
      var c = initRoom(n); c.accessed = acc; chans[n] = c

  test "most recently opened first":
    check channelList(chans, "").mapIt(it.name) == @["#gamma", "#beta", "#alpha"]

  test "never-opened buffers sort under those, by name":
    chans["#aaa"] = initRoom("#aaa")
    chans["#zzz"] = initRoom("#zzz")
    let names = channelList(chans, "").mapIt(it.name)
    check names == @["#gamma", "#beta", "#aaa", "#alpha", "#zzz"]

  test "the search box filters, case-insensitively":
    check channelList(chans, "BET").mapIt(it.name) == @["#beta"]
  test "a blank search filters nothing":
    check channelList(chans, "   ").len == 3

suite "seenMessage":
  test "a msgid we already hold is a replay":
    let msgs = @[msg("a", "hi", id = "1")]
    check seenMessage(msgs, "1", "a", "hi", "me")
    check not seenMessage(msgs, "2", "a", "hi", "me")

  test "an untagged line is known by its sender and words":
    # A replayed line with no tags has no identity, so left alone it arrives
    # new on every rejoin and the room can never be finished reading.
    let msgs = @[msg("alice", "hello")]
    check seenMessage(msgs, "", "alice", "hello", "me")
    check not seenMessage(msgs, "", "alice", "different", "me")

  test "our own untagged line is never a replay":
    # A second "ok" from this client is a real event.
    let msgs = @[msg("me", "ok")]
    check not seenMessage(msgs, "", "me", "ok", "me")

  test "nor is the system's":
    let msgs = @[msg("*", "alice joined")]
    check not seenMessage(msgs, "", "*", "alice joined", "me")

suite "the read marker":
  setup:
    var ch = initRoom("#test")
    ch.messages = @[msg("a", "one", at = 100, id = "1"),
                    msg("b", "two", at = 200, id = "2"),
                    msg("c", "three", at = 300, id = "3")]

  test "everything after the marked id is unread":
    ch.lastReadId = "1"
    check ch.afterMarker.mapIt(it.text) == @["two", "three"]

  test "by time when the marked line is no longer held":
    ch.lastReadId = "gone"
    ch.lastReadAt = 150
    check ch.afterMarker.mapIt(it.text) == @["two", "three"]

  test "a replayed backlog cannot inflate the count":
    # The whole reason unread is derived rather than counted. Two guards act
    # together and this checks the pair, because either alone is not enough:
    #
    #   seenMessage keeps the replayed line out of the buffer, and
    #   afterMarker means a line that IS older than the marker counts nothing.
    #
    # The first draft of this test appended the backlog twice and expected
    # zero, which is a state seenMessage exists to make impossible — and the
    # Clojure answers three to it as well. A test for an unreachable state
    # tells you nothing about the reachable ones.
    let marked = ch.markRead
    check marked.recount("me").unread == 0
    for m in ch.messages:
      check seenMessage(marked.messages, m.id, m.frm, m.text, "me")

  test "a line older than the marker counts for nothing":
    ch.lastReadId = ""
    ch.lastReadAt = 250
    check ch.afterMarker.mapIt(it.text) == @["three"]

  test "markRead never walks the marker backwards":
    ch.lastReadAt = 500          # read past a backlog that then arrived
    let marked = ch.markRead
    check marked.lastReadAt == 500

  test "system lines are read but never make a room worth looking at":
    ch.messages.add msg("*", "you joined", at = 400, system = true)
    check ch.recount("me").unread == 4 - 1  # the three said lines only... 
    ch.lastReadId = "3"
    check ch.recount("me").unread == 0

  test "a mention is noticed, and only somebody else's":
    ch.messages = @[msg("alice", "hey me, look", at = 100),
                    msg("me", "me me me", at = 200)]
    let r = ch.recount("me")
    check r.unread == 2
    check r.mention

  test "no mention where the reader is not named":
    ch.messages = @[msg("alice", "nothing here", at = 100)]
    check not ch.recount("me").mention

suite "recentEverywhere":
  setup:
    var chans = initOrderedTable[string, Room]()
    for n in ["#a", "#b"]:
      var c = initRoom(n)
      for i in 1 .. 3:
        c.messages.add msg("u", n & $i, at = i.int64 * 10)
      chans[n] = c

  test "the room being read is left out":
    let names = recentEverywhere(chans, "#a").mapIt(it.text)
    check names.allIt(it.startsWith("#b"))

  test "system lines are left out":
    var c = initRoom("#c")
    c.messages = @[msg("*", "joined", at = 99, system = true)]
    chans["#c"] = c
    check recentEverywhere(chans, "").allIt(it.text != "joined")

  test "newest first":
    let ats = recentEverywhere(chans, "").mapIt(it.at)
    check ats == ats.sorted(SortOrder.Descending)

  test "a turn each, so a busy room cannot crowd a quiet one out":
    # The reason for round-robin: taking the newest N outright would answer
    # about whichever room is busiest, which is the one you can already see.
    var busy = initRoom("#busy")
    for i in 1 .. 200:
      busy.messages.add msg("u", "spam" & $i, at = i.int64)
    chans["#busy"] = busy
    let got = recentEverywhere(chans, "")
    check got.anyIt(it.text.startsWith("#a"))
    check got.len <= overviewLimit