nandi/frqpublic Fork 0
60bbd359ff92543e6cd76ee5fffd0d89a07c4ae9
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.

tprofile.nim · 111 lines · 4.2 KBNim Blame HistoryRaw
A face that opens someone 9bb81a1 nandi 23h ago1## Who someone is, behind the nick. No network: every case here is a body in
2## and fields out, or a question about a nick.
3
4import std/[strutils, unicode]
5import std/[json, unittest]
6import frq/profile
7
8suite "isHandle":
9 test "a domain-shaped nick is a handle":
10 check isHandle("alice.bsky.social")
11 check isHandle("nandi.uk")
12 test "a bare nick is not":
13 # freeq gives an authenticated user their handle by default, while
14 # `sleek5209` is a guest with no profile.
15 check not isHandle("sleek5209")
16 check not isHandle("alice")
17 test "nor is something with no TLD":
18 check not isHandle("alice.")
19 check not isHandle("")
A handle is scanned, not matched 32dfa6e nandi 22h ago20 test "a hyphen is fine inside a label, not at its start":
21 check isHandle("my-host.example.com")
22 check not isHandle("-host.example.com")
23 check not isHandle("host.-example.com")
24 test "the TLD is two or more letters, and only letters":
25 check not isHandle("alice.c")
26 check not isHandle("alice.c0m")
27 check not isHandle("alice.com-")
28 test "no empty labels":
29 check not isHandle(".alice.com")
30 check not isHandle("alice..com")
31 test "nor anything a domain cannot hold":
32 check not isHandle("alice bob.com")
33 check not isHandle("alice@bsky.social")
A face that opens someone 9bb81a1 nandi 23h ago34
35suite "actorFor":
36 test "a DID wins, because it is the identity itself":
37 # A nick is whatever someone chose today.
38 check actorFor("did:plc:abc", "alice.bsky.social") == "did:plc:abc"
39 test "a handle-shaped nick stands in where there is no DID":
40 check actorFor("", "alice.bsky.social") == "alice.bsky.social"
41 test "a guest has nothing to look up":
42 check actorFor("", "sleek5209") == ""
43
44suite "thumbnailUrl":
45 test "asks the CDN for the size we paint":
46 # A 170KB portrait downloaded to draw at 24 points is the thing this
47 # avoids.
48 check thumbnailUrl("https://cdn.bsky.app/img/avatar/plain/did:plc:x/y@jpeg") ==
49 "https://cdn.bsky.app/img/avatar_thumbnail/plain/did:plc:x/y@png"
50 test "replaces an existing format suffix rather than appending":
51 check thumbnailUrl("https://cdn/x@jpeg").endsWith("@png")
52 check "@jpeg" notin thumbnailUrl("https://cdn/x@jpeg")
53 test "no picture is no URL":
54 check thumbnailUrl("") == ""
55
56suite "parseProfile":
57 let body = parseJson("""{
58 "did": "did:plc:abc", "handle": "alice.bsky.social",
59 "displayName": " Alice ", "description": " hello ",
60 "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:abc/p@jpeg",
61 "followersCount": 12, "followsCount": 34, "postsCount": 56 }""")
62
63 test "the fields the panel paints":
64 let p = parseProfile(body)
65 check p.status == psReady
66 check p.did == "did:plc:abc"
67 check p.handle == "alice.bsky.social"
68 check p.displayName == "Alice" # trimmed
69 check p.description == "hello"
70 check p.followers == 12
71 check p.avatar.endsWith("@png")
72
73 test "a body with nothing in it":
74 let p = parseProfile(parseJson("{}"))
75 check p.did == ""
76 check p.avatar == ""
77
78suite "statsLine":
79 test "all three":
80 let p = Profile(followers: 12, follows: 34, posts: 56)
81 check statsLine(p) == "12 followers · 34 following · 56 posts"
82 test "only what is known":
83 check statsLine(Profile(posts: 5)) == "5 posts"
84 test "none is no line at all":
85 check statsLine(Profile()) == ""
86
87suite "webUrl":
88 test "by handle where there is one":
89 check webUrl(Profile(handle: "alice.bsky.social", did: "did:plc:x")) ==
90 "https://bsky.app/profile/alice.bsky.social"
91 test "by DID otherwise":
92 check webUrl(Profile(did: "did:plc:x")) == "https://bsky.app/profile/did:plc:x"
93 test "a leading @ is not part of a handle":
94 check webUrl(Profile(handle: "@alice.bsky.social")) ==
95 "https://bsky.app/profile/alice.bsky.social"
96 test "nobody is no URL":
97 check webUrl(Profile()) == ""
98
99suite "truncate":
100 test "leaves a short bio alone, line breaks and all":
101 # The height of a multi-line bio is part of what it says.
102 check truncate("one\ntwo", 280) == "one\ntwo"
103 test "cuts a long one":
104 let got = truncate("x".repeat(400), 280)
105 check got.runeLen == 280
106 check got.endsWith("")
107
108 test "cuts by character, not by byte":
109 let got = truncate("😀".repeat(400), 280)
110 check got.runeLen == 280
111 check got.validateUtf8 == -1