nandi/frqpublic Fork 0
9c1dfe96e622c7a42cc8d4fec7ade9e5ddf074b0
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.

A face that opens someone 9bb81a1 · on 9c1dfe96e622c7a42cc8d4fec7ade9e5ddf074b0 · nandi · 17h ago
tprofile.nim · 97 lines · 3.6 KBNim Blame HistoryRaw
 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
## Who someone is, behind the nick. No network: every case here is a body in
## and fields out, or a question about a nick.

import std/[strutils, unicode]
import std/[json, unittest]
import frq/profile

suite "isHandle":
  test "a domain-shaped nick is a handle":
    check isHandle("alice.bsky.social")
    check isHandle("nandi.uk")
  test "a bare nick is not":
    # freeq gives an authenticated user their handle by default, while
    # `sleek5209` is a guest with no profile.
    check not isHandle("sleek5209")
    check not isHandle("alice")
  test "nor is something with no TLD":
    check not isHandle("alice.")
    check not isHandle("")

suite "actorFor":
  test "a DID wins, because it is the identity itself":
    # A nick is whatever someone chose today.
    check actorFor("did:plc:abc", "alice.bsky.social") == "did:plc:abc"
  test "a handle-shaped nick stands in where there is no DID":
    check actorFor("", "alice.bsky.social") == "alice.bsky.social"
  test "a guest has nothing to look up":
    check actorFor("", "sleek5209") == ""

suite "thumbnailUrl":
  test "asks the CDN for the size we paint":
    # A 170KB portrait downloaded to draw at 24 points is the thing this
    # avoids.
    check thumbnailUrl("https://cdn.bsky.app/img/avatar/plain/did:plc:x/y@jpeg") ==
      "https://cdn.bsky.app/img/avatar_thumbnail/plain/did:plc:x/y@png"
  test "replaces an existing format suffix rather than appending":
    check thumbnailUrl("https://cdn/x@jpeg").endsWith("@png")
    check "@jpeg" notin thumbnailUrl("https://cdn/x@jpeg")
  test "no picture is no URL":
    check thumbnailUrl("") == ""

suite "parseProfile":
  let body = parseJson("""{
    "did": "did:plc:abc", "handle": "alice.bsky.social",
    "displayName": "  Alice  ", "description": "  hello  ",
    "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:abc/p@jpeg",
    "followersCount": 12, "followsCount": 34, "postsCount": 56 }""")

  test "the fields the panel paints":
    let p = parseProfile(body)
    check p.status == psReady
    check p.did == "did:plc:abc"
    check p.handle == "alice.bsky.social"
    check p.displayName == "Alice"      # trimmed
    check p.description == "hello"
    check p.followers == 12
    check p.avatar.endsWith("@png")

  test "a body with nothing in it":
    let p = parseProfile(parseJson("{}"))
    check p.did == ""
    check p.avatar == ""

suite "statsLine":
  test "all three":
    let p = Profile(followers: 12, follows: 34, posts: 56)
    check statsLine(p) == "12 followers · 34 following · 56 posts"
  test "only what is known":
    check statsLine(Profile(posts: 5)) == "5 posts"
  test "none is no line at all":
    check statsLine(Profile()) == ""

suite "webUrl":
  test "by handle where there is one":
    check webUrl(Profile(handle: "alice.bsky.social", did: "did:plc:x")) ==
      "https://bsky.app/profile/alice.bsky.social"
  test "by DID otherwise":
    check webUrl(Profile(did: "did:plc:x")) == "https://bsky.app/profile/did:plc:x"
  test "a leading @ is not part of a handle":
    check webUrl(Profile(handle: "@alice.bsky.social")) ==
      "https://bsky.app/profile/alice.bsky.social"
  test "nobody is no URL":
    check webUrl(Profile()) == ""

suite "truncate":
  test "leaves a short bio alone, line breaks and all":
    # The height of a multi-line bio is part of what it says.
    check truncate("one\ntwo", 280) == "one\ntwo"
  test "cuts a long one":
    let got = truncate("x".repeat(400), 280)
    check got.runeLen == 280
    check got.endsWith("")

  test "cuts by character, not by byte":
    let got = truncate("😀".repeat(400), 280)
    check got.runeLen == 280
    check got.validateUtf8 == -1