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
|
## 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]
import frq/screens/connect as cs
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()
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
|