nandi/frqpublic Fork 0
1d62d1a437b844c37e3e4d49efa7e255ef4deac7
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.

A message in #test, from Nim 35994d4 · on 1d62d1a437b844c37e3e4d49efa7e255ef4deac7 · nandi · 19h ago
tui.nim · 153 lines · 5.5 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
149
150
151
152
153
## The screen as a pure function of the app, and the reducer that moves it.
import std/sequtils
##
## These are the tests the Clojure screens never had and could not easily
## have: a screen there is hiccup over cells a host installs, so exercising
## one means standing up a host. Here it is a function from a record to a
## tree, and a test is a call.

import std/[json, strutils]
import std/unittest
import frq/[ui, state, irc]
import frq/screens/connect as cs

# The reducer calls `irc.start` on a Connect, and a unit test has no business
# opening a socket to irc.freeq.at — it did, before this stub, and the suite
# failed on a machine with no network for reasons that had nothing to do with
# the code. The stub records what it was asked for so the tests can assert on
# it, which is more than the real one would have told them.
var dialled: seq[ConnConfig]
irc.connector = proc(cfg: ConnConfig) {.nimcall, gcsafe.} =
  {.cast(gcsafe).}: dialled.add cfg

proc find(node: Node, tag: string): seq[Node] =
  ## Every node with this tag, depth first.
  if node.isNil: return
  if node.tag == tag: result.add node
  for c in node.children:
    result.add c.find(tag)

proc texts(node: Node, tag: string): seq[string] =
  for n in node.find(tag):
    result.add n.props{"label"}.getStr()

suite "the connect screen":
  setup:
    app = initState()

  test "renders a page with the title and the server fields":
    let t = cs.connectScreen(app)
    check t.tag == "page"
    check "frq" in t.texts("title")
    check "Server" in t.texts("label")
    check t.find("checkbutton").len == 1

  test "is pure — twice with no dispatch is the same tree":
    check $cs.connectScreen(app).toJson == $cs.connectScreen(app).toJson

  test "guest is the default mode and shows a nickname field":
    let keys = cs.connectScreen(app).find("entry").mapIt(it.props{"key"}.getStr())
    check "nick" in keys
    check "handle" notin keys

  test "the selected mode is the primary button, and only it":
    let t = cs.connectScreen(app)
    var primary: seq[string]
    for b in t.find("button"):
      if b.props{"kind"}.getStr() == "primary":
        primary.add b.props{"label"}.getStr()
    # Guest is selected; Connect is primary because it is the action.
    check "Guest" in primary
    check "Bluesky" notin primary

suite "dispatch":
  setup:
    app = initState()
    dialled = @[]

  test "switching mode changes which fields are shown":
    dispatch(%*{"id": "mode.bluesky"})
    let keys = cs.connectScreen(app).find("entry").mapIt(it.props{"key"}.getStr())
    check "handle" in keys
    check "nick" notin keys

  test "typing into the host field lands in the tree":
    dispatch(%*{"id": "host.change", "value": "localhost"})
    let host = cs.connectScreen(app).find("entry").filterIt(
      it.props{"key"}.getStr() == "host")[0]
    check host.props{"text"}.getStr() == "localhost"

  test "the TLS tick carries the port with it":
    check app.formPort == "6697"
    dispatch(%*{"id": "tls.toggle"})
    check not app.formTls
    check app.formPort == "6667"
    dispatch(%*{"id": "tls.toggle"})
    check app.formPort == "6697"

  test "connecting swaps the button for a spinner":
    check cs.connectScreen(app).find("spinner").len == 0
    dispatch(%*{"id": "connect"})
    let t = cs.connectScreen(app)
    check t.find("spinner").len == 1
    check "Connect" notin t.texts("button")

  test "an empty host is refused, and the error is dismissable":
    dispatch(%*{"id": "host.change", "value": "  "})
    dispatch(%*{"id": "connect"})
    check app.hasError
    check "Dismiss" in cs.connectScreen(app).texts("button")
    dispatch(%*{"id": "error.dismiss"})
    check not app.hasError
    check "Dismiss" notin cs.connectScreen(app).texts("button")

  test "the error note keeps its place in the tree either way":
    # The bug the stable wrapper exists for: a renderer matching children by
    # position would patch the header into a card when the error appeared.
    let before = cs.connectScreen(app).children.mapIt(it.tag)
    dispatch(%*{"id": "host.change", "value": ""})
    dispatch(%*{"id": "connect"})
    check cs.connectScreen(app).children.mapIt(it.tag) == before

  test "an unknown event is ignored rather than fatal":
    let before = $cs.connectScreen(app).toJson
    dispatch(%*{"id": "no.such.event"})
    check $cs.connectScreen(app).toJson == before

suite "connecting":
  setup:
    app = initState()
    dialled = @[]

  test "Connect dials the host and port on the form":
    dispatch(%*{"id": "host.change", "value": "irc.example.org"})
    dispatch(%*{"id": "connect"})
    check dialled.len == 1
    check dialled[0].host == "irc.example.org"
    check dialled[0].port == 6697
    check dialled[0].tls
    check dialled[0].nick == "frq-guest"

  test "unticking TLS dials the plain port":
    dispatch(%*{"id": "tls.toggle"})
    dispatch(%*{"id": "connect"})
    check dialled[0].port == 6667
    check not dialled[0].tls

  test "a blank nickname is refused before anything is dialled":
    dispatch(%*{"id": "nick.change", "value": "   "})
    dispatch(%*{"id": "connect"})
    check dialled.len == 0
    check app.hasError

  test "a nonsense port falls back to the one the tick implies":
    dispatch(%*{"id": "port.change", "value": "not-a-port"})
    dispatch(%*{"id": "connect"})
    check dialled[0].port == 6697

  test "sending before registration does not queue a line":
    dispatch(%*{"id": "draft.change", "value": "hello"})
    dispatch(%*{"id": "send"})
    # Still in the box: nothing was sent, and the text was not eaten.
    check app.draft == "hello"
    check app.messages.len == 0