Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}