tinyrelayfile
tinyrelay/tinyclient/mobile_header_test.go/

tinyclient/mobile_header_test.go raw
1package tinyclient
2
3import (
4 "net/url"
5 "strings"
6 "testing"
7)
8
9func TestMobileHeaderGeneralRoutes(t *testing.T) {
10 tests := []struct {
11 path, title, wantTitle, wantSubtitle, wantBack string
12 }{
13 {"/", "tiny | relay", "Home", "tiny", ""},
14 {"/social", "social | tiny", "Social", "tiny", "/"},
15 {"/files", "Files | tiny", "Files", "tiny", "/"},
16 {"/wiki", "Wiki | tiny", "Wiki", "tiny", "/"},
17 {"/repos", "Repositories | tiny", "Repositories", "tiny", "/"},
18 {"/manage/people", "people | tiny", "People", "Manage", "/"},
19 {"/social/thread/abc", "Social | tiny", "Social", "tiny", "/social"},
20 {"/file", "File | tiny", "File", "tiny", "/files"},
21 {"/repo", "Repository | tiny", "Repository", "tiny", "/repos"},
22 {"/profile", "Profile | tiny", "Profile", "tiny", "/account"},
23 {"/sites", "sites | tiny", "Sites", "tiny", "/"},
24 }
25 for _, test := range tests {
26 got := mobileHeader(PageData{Path: test.path, Title: test.title, Slug: "tiny"})
27 if got.Title != test.wantTitle || got.Subtitle != test.wantSubtitle || got.BackURL != test.wantBack || got.BackLabel == "" && test.wantBack != "" {
28 t.Errorf("mobileHeader(%q) = %+v, want title=%q subtitle=%q back=%q", test.path, got, test.wantTitle, test.wantSubtitle, test.wantBack)
29 }
30 }
31}
32
33func TestMobileHeaderRoomAndThread(t *testing.T) {
34 room := map[string]any{"room": map[string]any{"id": "general", "name": "The <General> Room", "picture": "https://cdn.example/room.png"}}
35 got := mobileHeader(PageData{Path: "/rooms/general", Tab: "room", Event: room, URL: "https://relay.example"})
36 if got.Title != "The <General> Room" || got.Subtitle != "Chat" || got.BackURL != "/chat" || got.RoomID != "general" || got.Picture == "" {
37 t.Fatalf("room header = %+v", got)
38 }
39 root := strings.Repeat("a", 64)
40 thread := mobileHeader(PageData{Path: "/rooms/general/thread/" + root, Tab: "thread", Event: map[string]any{
41 "room": map[string]any{"id": "general", "name": "General"},
42 "root": map[string]any{"id": root},
43 }})
44 if thread.Title != "Thread" || thread.Subtitle != "General" || thread.BackURL != "/rooms/general#msg-"+root || thread.BackLabel != "Back to room" {
45 t.Fatalf("thread header = %+v", thread)
46 }
47}
48
49func TestThreadHeaderReturnsToCanonicalRoot(t *testing.T) {
50 root, reply := strings.Repeat("a", 64), strings.Repeat("b", 64)
51 header := mobileHeader(PageData{Path: "/rooms/general/thread/" + reply, Tab: "thread", Event: map[string]any{
52 "room": map[string]any{"id": "general", "name": "General"},
53 "root": map[string]any{"id": root},
54 }})
55 if header.BackURL != "/rooms/general#msg-"+root {
56 t.Fatalf("thread back link points to a folded reply: %+v", header)
57 }
58}
59
60func TestMobileHeaderDirectAndHiddenData(t *testing.T) {
61 peer := strings.Repeat("b", 64)
62 got := mobileHeader(PageData{Path: "/chat/dm/" + peer, Tab: "direct", View: peer})
63 if got.Title != "Direct chat" || got.Subtitle != "Private conversation" || got.PubKey != peer || got.BackURL != "/chat" {
64 t.Fatalf("direct header = %+v", got)
65 }
66 private := mobileHeader(PageData{Path: "/rooms/secret", Tab: "room", Private: true, Event: map[string]any{"room": map[string]any{"name": "Hidden", "picture": "https://secret.example/p.png"}}})
67 if private.Title != "Sign in" || private.Subtitle != "" || private.Picture != "" || private.RoomID != "" {
68 t.Fatalf("private header leaked data: %+v", private)
69 }
70 privateDirect := mobileHeader(PageData{Path: "/chat/dm/" + peer, Tab: "direct", View: peer, Private: true, Slug: "tiny", Event: map[string]any{"pubkey": peer}})
71 if privateDirect.Title != "Sign in" || privateDirect.PubKey != "" || privateDirect.RoomID != "" || privateDirect.Picture != "" {
72 t.Fatalf("private direct header leaked data: %+v", privateDirect)
73 }
74 err := mobileHeader(PageData{Path: "/rooms/secret", Tab: "room", Error: "denied", Event: map[string]any{"room": map[string]any{"name": "Hidden"}}})
75 if err.Title != "Chat" || err.RoomID != "" || err.BackURL != "/chat" || err.BackLabel != "Back to chats" {
76 t.Fatalf("error header leaked data: %+v", err)
77 }
78 signin := mobileHeader(PageData{Path: "/manage/people", Tab: "signin", Title: "People | tiny", Slug: "tiny"})
79 if signin.Title != "Sign in" || signin.Subtitle != "tiny" {
80 t.Fatalf("signin header = %+v", signin)
81 }
82}
83
84func TestMobileHeaderUsesDeepLinkContext(t *testing.T) {
85 article := mobileHeader(PageData{Path: "/social/" + strings.Repeat("a", 64), Tab: "social-thread", Slug: "tiny", Event: map[string]any{
86 "post": map[string]any{"title": "A useful article"},
87 }})
88 if article.Title != "A useful article" || article.Subtitle != "Social" || article.BackURL != "/social" || article.BackLabel != "Back to social" {
89 t.Fatalf("article header = %+v", article)
90 }
91 file := mobileHeader(PageData{Path: "/file", Tab: "file", Slug: "tiny", Event: map[string]any{"name": "photo.mp4"}})
92 if file.Title != "photo.mp4" || file.Subtitle != "Files" || file.BackURL != "/files" || file.BackLabel != "Back to files" {
93 t.Fatalf("file header = %+v", file)
94 }
95 repo := mobileHeader(PageData{Path: "/repo", Tab: "repo", Slug: "tiny", Query: url.Values{"repo": []string{"seedmark"}, "view": []string{"issues"}}})
96 if repo.Title != "seedmark" || repo.Subtitle != "issues" || repo.BackURL != "/repos" || repo.BackLabel != "Back to repositories" {
97 t.Fatalf("repo header = %+v", repo)
98 }
99 wiki := mobileHeader(PageData{Path: "/wiki/release-notes", Tab: "wikipage", Slug: "tiny", Query: url.Values{"d": []string{"release-notes"}}, Event: map[string]any{"title": "Release notes"}})
100 if wiki.Title != "Release notes" || wiki.Subtitle != "Wiki" || wiki.BackURL != "/wiki" {
101 t.Fatalf("wiki header = %+v", wiki)
102 }
103 sites := mobileHeader(PageData{Path: "/files", Tab: "files", Slug: "tiny", Query: url.Values{"view": []string{"sites"}}})
104 if sites.Title != "Sites" || sites.Subtitle != "Files" || sites.BackURL != "/" {
105 t.Fatalf("sites header = %+v", sites)
106 }
107}
108
109func TestMobileHeaderManagementParent(t *testing.T) {
110 got := mobileHeader(PageData{Path: "/manage/agents", Tab: "agents", Slug: "tiny"})
111 if got.BackURL != "/manage/people" || got.BackLabel != "Back to manage" {
112 t.Fatalf("management header = %+v", got)
113 }
114}
115
116func TestMobileHeaderSigninReturnValidation(t *testing.T) {
117 valid := mobileHeader(PageData{Tab: "signin", Slug: "tiny", Base: "/r/demo", Query: url.Values{"next": []string{"/r/demo/social?q=hello"}}})
118 if valid.BackURL != "/r/demo/social?q=hello" {
119 t.Fatalf("valid signin return = %+v", valid)
120 }
121 for _, next := range []string{"/\\evil.test", "//evil.test", "/r/other/chat", "/r/demo/%5Cevil", "/signin"} {
122 got := mobileHeader(PageData{Tab: "signin", Slug: "tiny", Base: "/r/demo", Query: url.Values{"next": []string{next}}})
123 if got.BackURL != "" {
124 t.Errorf("unsafe signin return %q accepted as %+v", next, got)
125 }
126 }
127 connected := mobileHeader(PageData{Tab: "signin", Actor: strings.Repeat("a", 64), Slug: "tiny", Query: url.Values{"connect": []string{"1"}}})
128 if connected.Title != "Connect signer" {
129 t.Fatalf("connected signin header = %+v", connected)
130 }
131 account := mobileHeader(PageData{Tab: "signin", Actor: strings.Repeat("a", 64), Slug: "tiny"})
132 if account.Title != "Account" {
133 t.Fatalf("account signin header = %+v", account)
134 }
135}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs