nandi/frqpublic Fork 0
9bb81a180dfcb7e43f51509c7baab27cfd303e3e
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 · 97 lines · 3.6 KBNim Blame HistoryRaw
A face that opens someone 9bb81a1 nandi 17h 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("")
20
21suite "actorFor":
22 test "a DID wins, because it is the identity itself":
23 # A nick is whatever someone chose today.
24 check actorFor("did:plc:abc", "alice.bsky.social") == "did:plc:abc"
25 test "a handle-shaped nick stands in where there is no DID":
26 check actorFor("", "alice.bsky.social") == "alice.bsky.social"
27 test "a guest has nothing to look up":
28 check actorFor("", "sleek5209") == ""
29
30suite "thumbnailUrl":
31 test "asks the CDN for the size we paint":
32 # A 170KB portrait downloaded to draw at 24 points is the thing this
33 # avoids.
34 check thumbnailUrl("https://cdn.bsky.app/img/avatar/plain/did:plc:x/y@jpeg") ==
35 "https://cdn.bsky.app/img/avatar_thumbnail/plain/did:plc:x/y@png"
36 test "replaces an existing format suffix rather than appending":
37 check thumbnailUrl("https://cdn/x@jpeg").endsWith("@png")
38 check "@jpeg" notin thumbnailUrl("https://cdn/x@jpeg")
39 test "no picture is no URL":
40 check thumbnailUrl("") == ""
41
42suite "parseProfile":
43 let body = parseJson("""{
44 "did": "did:plc:abc", "handle": "alice.bsky.social",
45 "displayName": " Alice ", "description": " hello ",
46 "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:abc/p@jpeg",
47 "followersCount": 12, "followsCount": 34, "postsCount": 56 }""")
48
49 test "the fields the panel paints":
50 let p = parseProfile(body)
51 check p.status == psReady
52 check p.did == "did:plc:abc"
53 check p.handle == "alice.bsky.social"
54 check p.displayName == "Alice" # trimmed
55 check p.description == "hello"
56 check p.followers == 12
57 check p.avatar.endsWith("@png")
58
59 test "a body with nothing in it":
60 let p = parseProfile(parseJson("{}"))
61 check p.did == ""
62 check p.avatar == ""
63
64suite "statsLine":
65 test "all three":
66 let p = Profile(followers: 12, follows: 34, posts: 56)
67 check statsLine(p) == "12 followers · 34 following · 56 posts"
68 test "only what is known":
69 check statsLine(Profile(posts: 5)) == "5 posts"
70 test "none is no line at all":
71 check statsLine(Profile()) == ""
72
73suite "webUrl":
74 test "by handle where there is one":
75 check webUrl(Profile(handle: "alice.bsky.social", did: "did:plc:x")) ==
76 "https://bsky.app/profile/alice.bsky.social"
77 test "by DID otherwise":
78 check webUrl(Profile(did: "did:plc:x")) == "https://bsky.app/profile/did:plc:x"
79 test "a leading @ is not part of a handle":
80 check webUrl(Profile(handle: "@alice.bsky.social")) ==
81 "https://bsky.app/profile/alice.bsky.social"
82 test "nobody is no URL":
83 check webUrl(Profile()) == ""
84
85suite "truncate":
86 test "leaves a short bio alone, line breaks and all":
87 # The height of a multi-line bio is part of what it says.
88 check truncate("one\ntwo", 280) == "one\ntwo"
89 test "cuts a long one":
90 let got = truncate("x".repeat(400), 280)
91 check got.runeLen == 280
92 check got.endsWith("")
93
94 test "cuts by character, not by byte":
95 let got = truncate("😀".repeat(400), 280)
96 check got.runeLen == 280
97 check got.validateUtf8 == -1