Kind Realm (renderable)
Name home
Namespace g1747t5m2f08plqjlrjk2q0qld7465hxz8gkx59c
Exported functions 11
Module gno.land/r/g1747t5m2f08plqjlrjk2q0qld7465hxz8gkx59c/home
gno 0.9 home_test.gno gno
⧉
1 package home 2 3 import ( 4 "strings" 5 "testing" 6 7 "gno.land/p/nt/avl/v0" 8 Append (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, slug string, body string)
Delete (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, slug string)
DeleteGallery (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, name string)
Get (slug string) string
HasHeart (addr string) bool
Heart (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string})
Hearts () int
Render (path string) string
Revision () int
Set (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, slug string, body string)
SetGallery (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, name string, spec string)
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
"gno.land/p/nt/testutils/v0"
9 "gno.land/p/nt/uassert/v0"
10 )
11
12 var (
13 alice = testutils.TestAddress( "alice" )
14 bob = testutils.TestAddress( "bob" )
15 mallory = testutils.TestAddress( "mallory" )
16 )
17
18 func reset() {
19 galleries = avl.NewTree()
20 slots = avl.NewTree()
21 rev = 0
22 hearts = avl.NewTree()
23 recent = nil
24 }
25
26 func seed(slug, body string ) {
27 rev++
28 slots.Set(slug, &slot{body: body, rev: rev})
29 }
30
31 func TestDefaultPageRendersEverySection(t *testing.T) {
32 // State as left by init: every default slot, no heart yet.
33 page := Render( "" )
34 for _, want := range [] string { "data:image/svg+xml;base64," , "Founder, CEO & CTO" , "## 今 Now" , "## 組 Inside Memba" , "BARRICADE" , "256 merged pull requests" , "Paris P2P Festival" , "Contributing on Gno since | 2022" , "## 源 Open source" , "(https://www.youtube.com/watch?v=aG4M37FX4II)" , "**0** hearts so far." , "gno.land/r/g1747t5m2f08plqjlrjk2q0qld7465hxz8gkx59c/home" } {
35 uassert.True(t, strings.Contains(page, want), "missing: " +want)
36 }
37 for _, s := range defaultSlots {
38 uassert.False(t, strings.Contains(page, ":" +s.slug+ ":" ), "unfilled placeholder :" +s.slug+ ":" )
39 }
40 for _, g := range defaultGalleries {
41 uassert.False(t, strings.Contains(page, ":gallery." +g.name+ ":" ), "unfilled gallery :gallery." +g.name+ ":" )
42 }
43 uassert.False(t, strings.Contains(page, ":hero:" ))
44 }
45
46 func TestSetGetDelete(cur realm, t *testing.T) {
47 reset()
48 testing.SetRealm(testing.NewUserRealm(admin))
49
50 Set(cross(cur), "bio" , "Building on gno." )
51 uassert.Equal(t, "Building on gno." , Get( "bio" ))
52 Append(cross(cur), "bio" , " Still." )
53 uassert.Equal(t, "Building on gno. Still." , Get( "bio" ))
54 uassert.Equal(t, 2 , Revision())
55
56 Delete(cross(cur), "bio" )
57 uassert.Equal(t, "" , Get( "bio" ))
58 uassert.AbortsWithMessage(t, cur, "no such slot: bio" , func () {
59 Delete(cross(cur), "bio" )
60 })
61 }
62
63 func TestWritesAreAdminOnly(cur realm, t *testing.T) {
64 reset()
65 testing.SetRealm(testing.NewUserRealm(mallory))
66 uassert.AbortsWithMessage(t, cur, "restricted to admin" , func () { Set(cross(cur), "bio" , "pwned" ) })
67 uassert.AbortsWithMessage(t, cur, "restricted to admin" , func () { Append(cross(cur), "bio" , "pwned" ) })
68 uassert.AbortsWithMessage(t, cur, "restricted to admin" , func () { Delete(cross(cur), "bio" ) })
69 }
70
71 func TestSlugRules(cur realm, t *testing.T) {
72 reset()
73 testing.SetRealm(testing.NewUserRealm(admin))
74 uassert.AbortsContains(t, cur, "invalid slug" , func () { Set(cross(cur), "Bad" , "x" ) })
75 uassert.AbortsContains(t, cur, "invalid slug" , func () { Set(cross(cur), "a:b" , "x" ) })
76 uassert.AbortsContains(t, cur, "invalid slug" , func () { Set(cross(cur), "" , "x" ) })
77 uassert.AbortsWithMessage(t, cur, "reserved slug: hearts" , func () { Set(cross(cur), "hearts" , "fake 9999" ) })
78 uassert.AbortsContains(t, cur, "reserved slug: gallery." , func () { Set(cross(cur), "gallery.x" , "fake" ) })
79 }
80
81 func TestFillIsSinglePass(t *testing.T) {
82 reset()
83 seed( "layout" , "A :a: B :missing: C 10:30 :b:" )
84 seed( "a" , "[:b:]" )
85 seed( "b" , "bee" )
86 uassert.Equal(t, "A [:b:] B :missing: C 10:30 bee" , Render( "" ))
87 }
88
89 func TestFallbackLayout(t *testing.T) {
90 reset()
91 seed( "bio" , "hi" )
92 uassert.True(t, strings.HasPrefix(Render( "" ), "# zôÖma\n\nhi\n\n**0** hearts" ))
93 }
94
95 func TestHeartOncePerAddress(cur realm, t *testing.T) {
96 reset()
97 seed( "layout" , ":hearts:" )
98
99 testing.SetRealm(testing.NewUserRealm(alice))
100 Heart(cross(cur))
101 uassert.Equal(t, 1 , Hearts())
102 uassert.True(t, HasHeart(alice))
103 uassert.AbortsWithMessage(t, cur, "already sent: one <3 per address" , func () { Heart(cross(cur)) })
104
105 testing.SetRealm(testing.NewUserRealm(bob))
106 Heart(cross(cur))
107 uassert.Equal(t, 2 , Hearts())
108 uassert.Equal(t, bob, recent[ 0 ], "newest first" )
109
110 page := Render( "" )
111 uassert.True(t, strings.Contains(page, "**2** hearts so far." ))
112 uassert.True(t, strings.Contains(page, "/u/" +bob.String()))
113 uassert.True(t, strings.Contains(Render( "hearts" ), "/u/" +alice.String()))
114 }
115
116 func TestRecentIsBounded(cur realm, t *testing.T) {
117 reset()
118 for i := 0 ; i < maxRecent+ 3 ; i++ {
119 testing.SetRealm(testing.NewUserRealm(testutils.TestAddress( "fan" + string ( rune ( 'a' +i)))))
120 Heart(cross(cur))
121 }
122 uassert.Equal(t, maxRecent+ 3 , Hearts())
123 uassert.Equal(t, maxRecent, len(recent))
124 }
125
126 func TestOtherViews(t *testing.T) {
127 reset()
128 seed( "bio" , "hello" )
129 uassert.True(t, strings.Contains(Render( "slots" ), "[bio]" ))
130 uassert.True(t, strings.Contains(Render( "slots/bio" ), "hello" ))
131 uassert.True(t, strings.Contains(Render( "slots/nope" ), "Not found" ))
132 uassert.True(t, strings.Contains(Render( "nope" ), "Not found" ))
133 }
134
135 func TestGallery(cur realm, t *testing.T) {
136 reset()
137 seed( "layout" , ":gallery.demo:" )
138
139 testing.SetRealm(testing.NewUserRealm(mallory))
140 uassert.AbortsWithMessage(t, cur, "restricted to admin" , func () {
141 SetGallery(cross(cur), "demo" , "film | A | b | https://x.y" )
142 })
143
144 testing.SetRealm(testing.NewUserRealm(admin))
145 uassert.AbortsContains(t, cur, "needs 4 or 5 fields" , func () { SetGallery(cross(cur), "demo" , "film | A | b" ) })
146 uassert.AbortsContains(t, cur, "must be https://" , func () { SetGallery(cross(cur), "demo" , "film | A | b | javascript:alert(1)" ) })
147 uassert.AbortsContains(t, cur, "markdown would break" , func () { SetGallery(cross(cur), "demo" , "film | A | b | https://x.y/a)b" ) })
148
149 SetGallery(cross(cur), "demo" , "# comment\nfilm | One <&> | meta | https://a.b/1\nproduct | Two | m | /r/x\nevent | Three | m | https://a.b/3\nreport | Four | m | https://a.b/4" )
150 page := Render( "" )
151 uassert.Equal(t, 2 , strings.Count(page, "<gno-columns>" ), "3 per row" )
152 uassert.Equal(t, 4 , strings.Count(page, "data:image/svg+xml;base64," ))
153 uassert.True(t, strings.Contains(page, "](https://a.b/1)" ))
154 uassert.True(t, strings.Contains(page, "](/r/x)" ))
155
156 // Titles are XML-escaped inside the poster.
157 svg := poster(card{kind: "film" , title: "One <&>" , meta: "m" , url: "https://a.b" })
158 uassert.True(t, strings.Contains(svg, "One <&>" ))
159
160 // An optional fifth field becomes the link's hover text.
161 SetGallery(cross(cur), "demo" , "memba | DAO Management | m | https://a.b/dao | Vote, execute, create." )
162 uassert.True(t, strings.Contains(Render( "" ), `](https://a.b/dao "Vote, execute, create.")` ))
163 uassert.Equal(t, "MEMBA · PRODUCT" , styleOf( "memba" ).label, "every seeded kind has its own style" )
164 uassert.AbortsContains(t, cur, "without quotes" , func () {
165 SetGallery(cross(cur), "demo" , "memba | A | m | https://a.b | say \"hi\"" )
166 })
167
168 DeleteGallery(cross(cur), "demo" )
169 uassert.Equal(t, ":gallery.demo:" , Render( "" ), "a missing gallery stays visible" )
170 }
171
172 func TestWrap(t *testing.T) {
173 uassert.Equal(t, 1 , len(wrap( "Short title" , 24 )))
174 lines := wrap( "The Underground Sistine Chapel is a very long title indeed" , 24 )
175 uassert.Equal(t, 2 , len(lines))
176 uassert.True(t, strings.HasSuffix(lines[ 1 ], "…" ))
177 }
178
vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.