reactions_test.gno
6.12 Kb · 171 lines
1package reactions
2
3import (
4 "strconv"
5 "strings"
6 "testing"
7
8 rx "gno.land/p/moul/reactions/v0"
9 "gno.land/p/nt/testutils/v0"
10 "gno.land/p/nt/uassert/v0"
11)
12
13const homePage = "gno.land/r/moul/home"
14
15var (
16 alice = testutils.TestAddress("alice")
17 bob = testutils.TestAddress("bob")
18)
19
20// reset clears realm state. Realm globals live for the whole test binary, and
21// the Example runs after every Test, so everything that renders starts here.
22func reset() { board = rx.NewBoard() }
23
24func TestReactAndUnreact(cur realm, t *testing.T) {
25 reset()
26
27 testing.SetRealm(testing.NewUserRealm(alice))
28 React(cross(cur), homePage, rx.KeyUp)
29 testing.SetRealm(testing.NewUserRealm(bob))
30 React(cross(cur), homePage, rx.KeyFire)
31
32 uassert.Equal(t, 2, Count(homePage))
33 uassert.Equal(t, 1, CountOf(homePage, rx.KeyUp))
34 uassert.Equal(t, rx.KeyUp, ReactionOf(homePage, alice))
35 uassert.Equal(t, 1, Pages())
36
37 key, who, _ := Last(homePage)
38 uassert.Equal(t, rx.KeyFire, key)
39 uassert.Equal(t, bob, who)
40
41 testing.SetRealm(testing.NewUserRealm(alice))
42 Unreact(cross(cur), homePage)
43 uassert.Equal(t, 1, Count(homePage))
44 uassert.Equal(t, "", ReactionOf(homePage, alice))
45}
46
47func TestReadsOnAnUntouchedPageAreZero(t *testing.T) {
48 reset()
49 uassert.Equal(t, 0, Count("gno.land/r/nobody/here"))
50 uassert.Equal(t, 0, CountOf("gno.land/r/nobody/here", rx.KeyUp))
51 uassert.Equal(t, "", ReactionOf("gno.land/r/nobody/here", alice))
52 key, _, _ := Last("gno.land/r/nobody/here")
53 uassert.Equal(t, "", key)
54}
55
56func TestReactRefusals(cur realm, t *testing.T) {
57 reset()
58 testing.SetRealm(testing.NewUserRealm(alice))
59 uassert.AbortsWithMessage(t, cur, "reactions: no such reaction: poop", func() {
60 React(cross(cur), homePage, "poop")
61 })
62
63 testing.SetRealm(testing.NewUserRealm(alice))
64 uassert.AbortsWithMessage(t, cur, "reactions: not a page key: not a path", func() {
65 React(cross(cur), "not a path", rx.KeyUp)
66 })
67
68 uassert.Equal(t, 0, Pages(), "a refused reaction creates no page")
69}
70
71// TestUnreactAbortsRatherThanNoOp is the deliberate asymmetry: a misspelled
72// page key on Unreact has to be visible, or a reader thinks they removed
73// something they did not.
74func TestUnreactAbortsRatherThanNoOp(cur realm, t *testing.T) {
75 reset()
76 testing.SetRealm(testing.NewUserRealm(alice))
77 uassert.AbortsWithMessage(t, cur, "reactions: nothing to remove on "+homePage, func() {
78 Unreact(cross(cur), homePage)
79 })
80}
81
82// TestRenderBlockForIsWhatTheBlockLinksTo pins the property a host realm
83// depends on: every link in the embedded block points at THIS realm, not at
84// the realm that embedded it, and carries the page key as an argument.
85func TestRenderBlockForIsWhatTheBlockLinksTo(t *testing.T) {
86 reset()
87 got := RenderBlockFor(homePage)
88 uassert.True(t, strings.Contains(got, "(/r/moul/reactions/v0$help&func=React&"),
89 "the txlink targets this realm")
90 uassert.True(t, strings.Contains(got, "page=gno.land%2Fr%2Fmoul%2Fhome"),
91 "and carries the host page as the parameter")
92 uassert.False(t, strings.Contains(got, "/r/moul/home$help"),
93 "never the host realm, which has no React to call")
94}
95
96// TestRenderBlockKeysOnTheCaller is the borrowed-read rule this realm's whole
97// ergonomics rest on: RenderBlock has no cur realm parameter, so gno opens no
98// frame for it and unsafe.CurrentRealm() reports whoever called.
99//
100// In-package there is no other realm to call from, so what this pins is the
101// half that can be pinned here: RenderBlock agrees with RenderBlockFor when
102// the caller IS this realm. The cross-realm half is measured in AGENTS.md
103// (2026-09-22) and is the same mechanism r/moul/config's TopBlock uses.
104func TestRenderBlockKeysOnTheCaller(t *testing.T) {
105 reset()
106 uassert.Equal(t, RenderBlockFor(realmPath), RenderBlock())
107 uassert.NotEqual(t, RenderBlockFor(homePage), RenderBlock(),
108 "and a different page is a different block")
109}
110
111func TestRenderPageView(cur realm, t *testing.T) {
112 reset()
113 testing.SetRealm(testing.NewUserRealm(alice))
114 React(cross(cur), homePage, rx.KeyUp)
115
116 got := Render(homePage)
117 uassert.True(t, strings.HasPrefix(got, "# gno\\.land/r/moul/home\n"),
118 "the heading is the page key, escaped: ui.Inline backslashes the dot")
119 uassert.True(t, strings.Contains(got, "Last reaction: \U0001F44D by "))
120 uassert.True(t, strings.Contains(got, "Remove my reaction"))
121 uassert.True(t, strings.Contains(got, "[The board](/r/moul/reactions/v0)"))
122}
123
124func TestRenderRejectsAPathThatIsNotAPageKey(t *testing.T) {
125 reset()
126 got := Render("[click](http://e.vil)")
127 uassert.True(t, strings.HasPrefix(got, "# Not a page\n"))
128 uassert.False(t, strings.Contains(got, "](http://e.vil)"),
129 "a render path is escaped before it is shown back")
130}
131
132// TestRenderHomeShowsTheBoard uses uassert rather than an Example because the
133// page contains consecutive blank lines, which gno collapses inside an
134// // Output: block.
135func TestRenderHomeShowsTheBoard(cur realm, t *testing.T) {
136 reset()
137 uassert.True(t, strings.Contains(Render(""), "*No page has been reacted to yet.*"))
138
139 testing.SetRealm(testing.NewUserRealm(alice))
140 React(cross(cur), homePage, rx.KeyParty)
141
142 got := Render("")
143 uassert.True(t, strings.Contains(got,
144 "| [gno\\.land/r/moul/home](/r/moul/reactions/v0:gno.land/r/moul/home) | 1 |"),
145 "the cell text is escaped, the URL is not: they are different contexts")
146 uassert.True(t, strings.Contains(got, "import \"gno.land/r/moul/reactions/v0\""),
147 "the homepage is the getting-started doc, and the snippet is copy-pasteable")
148 for _, k := range rx.Keys() {
149 uassert.True(t, strings.Contains(got, "| `"+k+"` | "+rx.Emoji(k)+" |"), k)
150 }
151}
152
153// TestBoardIndexIsCapped: the number of pages is unbounded, the index is not.
154func TestBoardIndexIsCapped(cur realm, t *testing.T) {
155 reset()
156 for i := 0; i < maxListed+3; i++ {
157 testing.SetRealm(testing.NewUserRealm(alice))
158 // Zero-padded, because avl keys sort as strings and ufmt has no width
159 // flags: "p9" would otherwise sort between "p10" and "p11".
160 n := strconv.Itoa(i)
161 if len(n) < 2 {
162 n = "0" + n
163 }
164 React(cross(cur), "gno.land/r/spam/p"+n, rx.KeyUp)
165 }
166 uassert.Equal(t, maxListed+3, Pages())
167
168 got := Render("")
169 uassert.Equal(t, maxListed, strings.Count(got, "(/r/moul/reactions/v0:gno.land/r/spam/"))
170 uassert.True(t, strings.Contains(got, "28 pages in total, 25 shown."))
171}