| Nim owns the screen, Dart owns the pixels 43a02c2 nandi 20h ago | 1 | ## The screen as a pure function of the app, and the reducer that moves it. |
| 2 | import 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 | |
| 9 | import std/[json, strutils] |
| 10 | import std/unittest |
| 11 | import frq/[ui, state] |
| 12 | import frq/screens/connect as cs |
| 13 | |
| 14 | proc find(node: Node, tag: string): seq[Node] = |
| 15 | ## Every node with this tag, depth first. |
| 16 | if node.isNil: return |
| 17 | if node.tag == tag: result.add node |
| 18 | for c in node.children: |
| 19 | result.add c.find(tag) |
| 20 | |
| 21 | proc texts(node: Node, tag: string): seq[string] = |
| 22 | for n in node.find(tag): |
| 23 | result.add n.props{"label"}.getStr() |
| 24 | |
| 25 | suite "the connect screen": |
| 26 | setup: |
| 27 | app = initState() |
| 28 | |
| 29 | test "renders a page with the title and the server fields": |
| 30 | let t = cs.connectScreen(app) |
| 31 | check t.tag == "page" |
| 32 | check "frq" in t.texts("title") |
| 33 | check "Server" in t.texts("label") |
| 34 | check t.find("checkbutton").len == 1 |
| 35 | |
| 36 | test "is pure — twice with no dispatch is the same tree": |
| 37 | check $cs.connectScreen(app).toJson == $cs.connectScreen(app).toJson |
| 38 | |
| 39 | test "guest is the default mode and shows a nickname field": |
| 40 | let keys = cs.connectScreen(app).find("entry").mapIt(it.props{"key"}.getStr()) |
| 41 | check "nick" in keys |
| 42 | check "handle" notin keys |
| 43 | |
| 44 | test "the selected mode is the primary button, and only it": |
| 45 | let t = cs.connectScreen(app) |
| 46 | var primary: seq[string] |
| 47 | for b in t.find("button"): |
| 48 | if b.props{"kind"}.getStr() == "primary": |
| 49 | primary.add b.props{"label"}.getStr() |
| 50 | # Guest is selected; Connect is primary because it is the action. |
| 51 | check "Guest" in primary |
| 52 | check "Bluesky" notin primary |
| 53 | |
| 54 | suite "dispatch": |
| 55 | setup: |
| 56 | app = initState() |
| 57 | |
| 58 | test "switching mode changes which fields are shown": |
| 59 | dispatch(%*{"id": "mode.bluesky"}) |
| 60 | let keys = cs.connectScreen(app).find("entry").mapIt(it.props{"key"}.getStr()) |
| 61 | check "handle" in keys |
| 62 | check "nick" notin keys |
| 63 | |
| 64 | test "typing into the host field lands in the tree": |
| 65 | dispatch(%*{"id": "host.change", "value": "localhost"}) |
| 66 | let host = cs.connectScreen(app).find("entry").filterIt( |
| 67 | it.props{"key"}.getStr() == "host")[0] |
| 68 | check host.props{"text"}.getStr() == "localhost" |
| 69 | |
| 70 | test "the TLS tick carries the port with it": |
| 71 | check app.formPort == "6697" |
| 72 | dispatch(%*{"id": "tls.toggle"}) |
| 73 | check not app.formTls |
| 74 | check app.formPort == "6667" |
| 75 | dispatch(%*{"id": "tls.toggle"}) |
| 76 | check app.formPort == "6697" |
| 77 | |
| 78 | test "connecting swaps the button for a spinner": |
| 79 | check cs.connectScreen(app).find("spinner").len == 0 |
| 80 | dispatch(%*{"id": "connect"}) |
| 81 | let t = cs.connectScreen(app) |
| 82 | check t.find("spinner").len == 1 |
| 83 | check "Connect" notin t.texts("button") |
| 84 | |
| 85 | test "an empty host is refused, and the error is dismissable": |
| 86 | dispatch(%*{"id": "host.change", "value": " "}) |
| 87 | dispatch(%*{"id": "connect"}) |
| 88 | check app.hasError |
| 89 | check "Dismiss" in cs.connectScreen(app).texts("button") |
| 90 | dispatch(%*{"id": "error.dismiss"}) |
| 91 | check not app.hasError |
| 92 | check "Dismiss" notin cs.connectScreen(app).texts("button") |
| 93 | |
| 94 | test "the error note keeps its place in the tree either way": |
| 95 | # The bug the stable wrapper exists for: a renderer matching children by |
| 96 | # position would patch the header into a card when the error appeared. |
| 97 | let before = cs.connectScreen(app).children.mapIt(it.tag) |
| 98 | dispatch(%*{"id": "host.change", "value": ""}) |
| 99 | dispatch(%*{"id": "connect"}) |
| 100 | check cs.connectScreen(app).children.mapIt(it.tag) == before |
| 101 | |
| 102 | test "an unknown event is ignored rather than fatal": |
| 103 | let before = $cs.connectScreen(app).toJson |
| 104 | dispatch(%*{"id": "no.such.event"}) |
| 105 | check $cs.connectScreen(app).toJson == before |