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.

tui.nim · 153 lines · 5.5 KBNim Blame HistoryRaw
Nim owns the screen, Dart owns the pixels 43a02c2 nandi 22h ago1## The screen as a pure function of the app, and the reducer that moves it.
2import std/sequtils
3##
4## These are the tests the Clojure screens never had and could not easily
5## have: a screen there is hiccup over cells a host installs, so exercising
6## one means standing up a host. Here it is a function from a record to a
7## tree, and a test is a call.
8
9import std/[json, strutils]
10import std/unittest
A message in #test, from Nim 35994d4 nandi 21h ago11import frq/[ui, state, irc]
Nim owns the screen, Dart owns the pixels 43a02c2 nandi 22h ago12import frq/screens/connect as cs
13
A message in #test, from Nim 35994d4 nandi 21h ago14# The reducer calls `irc.start` on a Connect, and a unit test has no business
15# opening a socket to irc.freeq.at — it did, before this stub, and the suite
16# failed on a machine with no network for reasons that had nothing to do with
17# the code. The stub records what it was asked for so the tests can assert on
18# it, which is more than the real one would have told them.
19var dialled: seq[ConnConfig]
20irc.connector = proc(cfg: ConnConfig) {.nimcall, gcsafe.} =
21 {.cast(gcsafe).}: dialled.add cfg
22
Nim owns the screen, Dart owns the pixels 43a02c2 nandi 22h ago23proc find(node: Node, tag: string): seq[Node] =
24 ## Every node with this tag, depth first.
25 if node.isNil: return
26 if node.tag == tag: result.add node
27 for c in node.children:
28 result.add c.find(tag)
29
30proc texts(node: Node, tag: string): seq[string] =
31 for n in node.find(tag):
32 result.add n.props{"label"}.getStr()
33
34suite "the connect screen":
35 setup:
36 app = initState()
37
38 test "renders a page with the title and the server fields":
39 let t = cs.connectScreen(app)
40 check t.tag == "page"
41 check "frq" in t.texts("title")
42 check "Server" in t.texts("label")
43 check t.find("checkbutton").len == 1
44
45 test "is pure — twice with no dispatch is the same tree":
46 check $cs.connectScreen(app).toJson == $cs.connectScreen(app).toJson
47
48 test "guest is the default mode and shows a nickname field":
49 let keys = cs.connectScreen(app).find("entry").mapIt(it.props{"key"}.getStr())
50 check "nick" in keys
51 check "handle" notin keys
52
53 test "the selected mode is the primary button, and only it":
54 let t = cs.connectScreen(app)
55 var primary: seq[string]
56 for b in t.find("button"):
57 if b.props{"kind"}.getStr() == "primary":
58 primary.add b.props{"label"}.getStr()
59 # Guest is selected; Connect is primary because it is the action.
60 check "Guest" in primary
61 check "Bluesky" notin primary
62
63suite "dispatch":
64 setup:
65 app = initState()
A message in #test, from Nim 35994d4 nandi 21h ago66 dialled = @[]
Nim owns the screen, Dart owns the pixels 43a02c2 nandi 22h ago67
68 test "switching mode changes which fields are shown":
69 dispatch(%*{"id": "mode.bluesky"})
70 let keys = cs.connectScreen(app).find("entry").mapIt(it.props{"key"}.getStr())
71 check "handle" in keys
72 check "nick" notin keys
73
74 test "typing into the host field lands in the tree":
75 dispatch(%*{"id": "host.change", "value": "localhost"})
76 let host = cs.connectScreen(app).find("entry").filterIt(
77 it.props{"key"}.getStr() == "host")[0]
78 check host.props{"text"}.getStr() == "localhost"
79
80 test "the TLS tick carries the port with it":
81 check app.formPort == "6697"
82 dispatch(%*{"id": "tls.toggle"})
83 check not app.formTls
84 check app.formPort == "6667"
85 dispatch(%*{"id": "tls.toggle"})
86 check app.formPort == "6697"
87
88 test "connecting swaps the button for a spinner":
89 check cs.connectScreen(app).find("spinner").len == 0
90 dispatch(%*{"id": "connect"})
91 let t = cs.connectScreen(app)
92 check t.find("spinner").len == 1
93 check "Connect" notin t.texts("button")
94
95 test "an empty host is refused, and the error is dismissable":
96 dispatch(%*{"id": "host.change", "value": " "})
97 dispatch(%*{"id": "connect"})
98 check app.hasError
99 check "Dismiss" in cs.connectScreen(app).texts("button")
100 dispatch(%*{"id": "error.dismiss"})
101 check not app.hasError
102 check "Dismiss" notin cs.connectScreen(app).texts("button")
103
104 test "the error note keeps its place in the tree either way":
105 # The bug the stable wrapper exists for: a renderer matching children by
106 # position would patch the header into a card when the error appeared.
107 let before = cs.connectScreen(app).children.mapIt(it.tag)
108 dispatch(%*{"id": "host.change", "value": ""})
109 dispatch(%*{"id": "connect"})
110 check cs.connectScreen(app).children.mapIt(it.tag) == before
111
112 test "an unknown event is ignored rather than fatal":
113 let before = $cs.connectScreen(app).toJson
114 dispatch(%*{"id": "no.such.event"})
115 check $cs.connectScreen(app).toJson == before
A message in #test, from Nim 35994d4 nandi 21h ago116
117suite "connecting":
118 setup:
119 app = initState()
120 dialled = @[]
121
122 test "Connect dials the host and port on the form":
123 dispatch(%*{"id": "host.change", "value": "irc.example.org"})
124 dispatch(%*{"id": "connect"})
125 check dialled.len == 1
126 check dialled[0].host == "irc.example.org"
127 check dialled[0].port == 6697
128 check dialled[0].tls
129 check dialled[0].nick == "frq-guest"
130
131 test "unticking TLS dials the plain port":
132 dispatch(%*{"id": "tls.toggle"})
133 dispatch(%*{"id": "connect"})
134 check dialled[0].port == 6667
135 check not dialled[0].tls
136
137 test "a blank nickname is refused before anything is dialled":
138 dispatch(%*{"id": "nick.change", "value": " "})
139 dispatch(%*{"id": "connect"})
140 check dialled.len == 0
141 check app.hasError
142
143 test "a nonsense port falls back to the one the tick implies":
144 dispatch(%*{"id": "port.change", "value": "not-a-port"})
145 dispatch(%*{"id": "connect"})
146 check dialled[0].port == 6697
147
148 test "sending before registration does not queue a line":
149 dispatch(%*{"id": "draft.change", "value": "hello"})
150 dispatch(%*{"id": "send"})
151 # Still in the box: nothing was sent, and the text was not eaten.
152 check app.draft == "hello"
153 check app.messages.len == 0