devtools_test.gno
5.51 Kb · 154 lines
1package devtools
2
3import (
4 "strings"
5 "testing"
6
7 "gno.land/p/g1magld5ptgzaktqcexxq0pgq5lmdf0kz6re8hlv/fifo/v0"
8 "gno.land/p/nt/testutils/v0"
9 "gno.land/p/nt/uassert/v0"
10 "gno.land/r/g1magld5ptgzaktqcexxq0pgq5lmdf0kz6re8hlv/config/v1"
11)
12
13// reset puts the realm back to a just-deployed state. Realm globals live for
14// the whole test binary and gno runs every Example AFTER every Test, so without
15// this the pinned output would depend on which tests ran before it.
16func reset() { notes = fifo.New(maxNotes) }
17
18func TestAdd(cur realm, t *testing.T) {
19 reset()
20
21 Add(cross(cur), "hello")
22 uassert.Equal(t, 1, notes.Size())
23 uassert.Equal(t, "hello", notes.Get(0).(string))
24
25 uassert.AbortsWithMessage(t, cur, "empty note", func() { Add(cross(cur), " ") })
26 uassert.AbortsContains(t, cur, "note too long", func() {
27 long := ""
28 for i := 0; i < 201; i++ {
29 long += "x"
30 }
31 Add(cross(cur), long)
32 })
33}
34
35// TestAddEscapes is the guard that matters most in a realm anyone can write to:
36// a note is a string a caller typed, and it lands in a markdown list. Without
37// ui.Inline the first line below would close the list item and render as a
38// heading, and the second would become a link.
39func TestAddEscapes(cur realm, t *testing.T) {
40 reset()
41
42 Add(cross(cur), "# not a heading")
43 Add(cross(cur), "[not](https://evil.example.com)")
44
45 for _, got := range []string{notes.Get(0).(string), notes.Get(1).(string)} {
46 uassert.False(t, got == "# not a heading", "markdown survived unescaped")
47 uassert.False(t, got == "[not](https://evil.example.com)", "a link survived unescaped")
48 }
49 uassert.True(t, len(notes.Get(0).(string)) > len("# not a heading"),
50 "escaping should have added characters")
51}
52
53// TestNotesAreBounded pins the reason fifo is here rather than a slice: storage
54// is paid for and never refunded, so a list anyone can append to needs a
55// ceiling in the type, not in a comment.
56func TestNotesAreBounded(cur realm, t *testing.T) {
57 reset()
58
59 for i := 0; i < maxNotes+5; i++ {
60 Add(cross(cur), "note "+string(rune('a'+i)))
61 }
62 uassert.Equal(t, maxNotes, notes.Size(), "the list stops growing at maxNotes")
63 uassert.Equal(t, maxNotes, notes.MaxSize())
64}
65
66// TestAddHonoursThePause is the whole point of importing r/moul/config: this
67// realm can be stopped from outside, by a transaction against another realm,
68// with no redeploy here. It cannot set that from this package (config has its
69// own authority), so it asserts the wiring: Add goes through the pause check
70// and passes while nothing is set.
71func TestAddHonoursThePause(cur realm, t *testing.T) {
72 reset()
73 uassert.False(t, config.IsPausedFor(realmPath), "nothing configured means running")
74 Add(cross(cur), "allowed while running")
75 uassert.Equal(t, 1, notes.Size())
76}
77
78func TestReset(cur realm, t *testing.T) {
79 reset()
80 Add(cross(cur), "one")
81 Reset(cross(cur))
82 uassert.Equal(t, 0, notes.Size())
83}
84
85// ExampleRender pins the root view. It is free of the height and the chain id
86// on purpose: those live under :chain, so this one is stable.
87func ExampleRender() {
88 reset()
89 print(Render(""))
90 // Output:
91 // # allinone: devtools
92 //
93 // Ten packages, used together rather than one at a time. [source](https://mygnoscan.moul.p2p.team/realm/r/moul/x/allinone/devtools/v0)
94 //
95 // ## Notes
96 //
97 // *no notes yet*
98 //
99 // - [add a note](/r/moul/x/allinone/devtools/v0$help&func=Add&body=hello)
100 // - [clear them](/r/moul/x/allinone/devtools/v0$help&func=Reset)
101 // - [live chain values](/r/moul/x/allinone/devtools/v0:chain)
102 // - [toggle the debug panel](/r/moul/x/allinone/devtools/v0:?debug=1)
103 //
104 // ## Who has written the most
105 //
106 // *No notes yet, so nobody is on the board.*
107 //
108 // Storage deposit for the list: **0 GNOT**
109 //
110 // ## What each package does here
111 //
112 // | package | what it contributed |
113 // | --- | --- |
114 // | `r/moul/config` | the notice block above, the pause guard on `Add`, and which explorer to link |
115 // | `p/moul/mygnoscan` | the explorer routes, so a link survives a route change |
116 // | `p/moul/debug` | the `?debug=1` panel, empty otherwise |
117 // | `p/moul/realmpath` | splitting the render path into a view and a query |
118 // | `p/moul/kit/ui` | escaping a caller's note, and this table |
119 // | `p/moul/md` | every heading, link and list on the page |
120 // | `p/moul/txlink` | the call links above, relative to this realm |
121 // | `p/moul/fifo` | the note list, bounded so storage cannot grow forever |
122 // | `p/moul/kit/tally` | the note leaderboard, with ties ordered by address |
123 // | `p/moul/kit/num` | pricing the note list in GNOT instead of raw ugnot |
124}
125
126// TestBoardRanksAuthorsAndPricesTheList exercises the two kit packages this
127// realm adopted, because ExampleRender only ever sees the empty page.
128func TestBoardRanksAuthorsAndPricesTheList(cur realm, t *testing.T) {
129 Reset(cross(cur))
130
131 alice := testutils.TestAddress("alice")
132 bob := testutils.TestAddress("bob")
133
134 testing.SetRealm(testing.NewUserRealm(alice))
135 Add(cross(cur), "one")
136 testing.SetRealm(testing.NewUserRealm(alice))
137 Add(cross(cur), "two")
138 testing.SetRealm(testing.NewUserRealm(bob))
139 Add(cross(cur), "three")
140
141 uassert.Equal(t, int64(2), authors.Score(alice.String()))
142 uassert.Equal(t, int64(1), authors.Score(bob.String()))
143 uassert.Equal(t, 1, authors.Rank(alice.String()))
144 uassert.Equal(t, 2, authors.Rank(bob.String()))
145
146 // "one"+"two"+"three" is 11 bytes at 100 ugnot each, which num renders as
147 // GNOT rather than as 1100.
148 out := Render("")
149 uassert.True(t, strings.Contains(out, "0.0011 GNOT"))
150 uassert.True(t, strings.Contains(out, "## Who has written the most"))
151
152 Reset(cross(cur))
153 uassert.Equal(t, 0, authors.Len())
154}