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

render_test.gno

6.91 Kb · 200 lines
  1package connect4_v2
  2
  3import (
  4	"encoding/base64"
  5	"strconv"
  6	"strings"
  7	"testing"
  8	"time"
  9
 10	"gno.land/p/nt/testutils/v0"
 11	"gno.land/p/nt/uassert/v0"
 12)
 13
 14func TestLeaderboardTopTen(t *testing.T) {
 15	reset()
 16	for i := 1; i <= 12; i++ {
 17		statsOf(testutils.TestAddress("p" + strconv.Itoa(i))).Wins = i
 18	}
 19	out := leaderboard(func(s *Stats) int64 { return int64(s.Wins) }, func(v int64) string { return strconv.FormatInt(v, 10) })
 20	uassert.True(t, strings.HasPrefix(strings.Split(out, "\n")[2], "| 1 | "+testutils.TestAddress("p12").String()+" | 12 |"))
 21	uassert.True(t, strings.Contains(out, "| 10 | "+testutils.TestAddress("p3").String()+" | 3 |"))
 22	uassert.False(t, strings.Contains(out, testutils.TestAddress("p2").String()))
 23}
 24
 25func TestGamePageShowsActionsForEveryState(cur realm, t *testing.T) {
 26	reset()
 27	contains := func(out string, want ...string) {
 28		for _, w := range want {
 29			uassert.True(t, strings.Contains(out, w), "missing "+w+" in:\n"+out)
 30		}
 31	}
 32
 33	testing.SetRealm(testing.NewUserRealm(alice))
 34	pay(stake)
 35	id := Offer(cross(cur), "", 10, commit())
 36	page := "game/" + strconv.Itoa(id)
 37	contains(Render(page), "func=Accept", ".send=10000000ugnot", "func=Cancel", "Open offer")
 38
 39	g := mustGet(id)
 40	g.ExpiresAt = time.Now().Add(-time.Second)
 41	out := Render(page)
 42	contains(out, "expired", "func=Cancel")
 43	uassert.False(t, strings.Contains(out, "func=Accept"))
 44
 45	g, p := accepted(cur)
 46	page = "game/" + strconv.Itoa(g.ID)
 47	contains(Render(page), "Waiting for", "func=Reveal", "func=Resign")
 48
 49	testing.SetRealm(testing.NewUserRealm(alice))
 50	pay(0)
 51	Reveal(cross(cur), g.ID, p)
 52	contains(Render(page), "func=Play", "column=7", "func=Resign", "Turn:")
 53
 54	testing.SetRealm(testing.NewUserRealm(alice))
 55	pay(0)
 56	Resign(cross(cur), g.ID)
 57	contains(Render(page), "Winner", bob.String(), gnot(2*stake-fee))
 58
 59	for status, want := range map[Status]string{
 60		Draw:      "both stakes refunded",
 61		Void:      "both stakes refunded",
 62		Cancelled: "refunded to the creator",
 63	} {
 64		g.Status = status
 65		contains(Render(page), want)
 66	}
 67}
 68
 69func TestLobbyLinksEveryGameToItsPage(cur realm, t *testing.T) {
 70	reset()
 71	testing.SetRealm(testing.NewUserRealm(alice))
 72	pay(stake)
 73	id := Offer(cross(cur), "", 10, commit())
 74	g, _ := accepted(cur)
 75	// Render as if deployed elsewhere: links must follow the realm's real path.
 76	testing.SetRealm(testing.NewCodeRealm("gno.land/r/nym-mikecito001/connect4"))
 77	out := Render("")
 78	base := "(/r/nym-mikecito001/connect4:"
 79	uassert.True(t, strings.Contains(out, base+"game/"+strconv.Itoa(id)+")"), out)
 80	uassert.True(t, strings.Contains(out, base+"game/"+strconv.Itoa(g.ID)+")"), out)
 81	uassert.True(t, strings.Contains(out, base+"new)"), out)
 82	uassert.False(t, strings.Contains(out, "samcrew"), "links must not hardcode the realm path")
 83}
 84
 85func TestNewOfferPage(t *testing.T) {
 86	reset()
 87	c := sha256Hex("some four random words")
 88
 89	out := Render("new")
 90	uassert.True(t, strings.Contains(out, `<gno-form path="new">`))
 91	uassert.False(t, strings.Contains(out, "func=Offer"))
 92
 93	out = Render("new?stake=12.5&validFor=10&opponent=" + bob.String() + "&commitment=" + c)
 94	for _, want := range []string{"func=Offer", ".send=12500000ugnot", "validFor=10", "opponent=" + bob.String(), "commitment=" + c, "12.5 GNOT"} {
 95		uassert.True(t, strings.Contains(out, want), "missing "+want+" in:\n"+out)
 96	}
 97
 98	out = Render("new?stake=3&commitment=" + c) // validFor defaults, opponent optional
 99	uassert.True(t, strings.Contains(out, ".send=3000000ugnot"))
100	uassert.True(t, strings.Contains(out, "validFor=15"))
101
102	for _, q := range []string{
103		"stake=%3Cscript%3E&validFor=10&commitment=" + c, // not a number
104		"stake=0.5&validFor=10&commitment=" + c,          // below minimum
105		"stake=1.0000001&validFor=10&commitment=" + c,    // more than 6 decimals
106		"stake=5&validFor=61&commitment=" + c,
107		"stake=5&validFor=10&opponent=%3Cscript%3E&commitment=" + c,
108		"stake=5&validFor=10&commitment=%3Cscript%3E",
109		"stake=5&validFor=10",
110	} {
111		out := Render("new?" + q)
112		uassert.False(t, strings.Contains(out, "func=Offer"), q)
113		uassert.True(t, strings.Contains(out, "Fix the following"), q)
114		uassert.False(t, strings.Contains(out, "<script>"), "form input echoed: "+q)
115	}
116}
117
118// svgs returns the decoded SVG of every data-URI image in out, and the
119// markdown that immediately wraps each image ("[" link or "" for none).
120func svgs(t *testing.T, out string) (images []string, linked []bool) {
121	parts := strings.Split(out, "(data:image/svg+xml;base64,")
122	for i, p := range parts[1:] {
123		end := strings.Index(p, ")")
124		raw, err := base64.StdEncoding.DecodeString(p[:end])
125		if err != nil {
126			t.Fatalf("image %d is not base64: %v", i, err)
127		}
128		images = append(images, string(raw))
129		before := parts[i]
130		img := strings.LastIndex(before, "![")
131		linked = append(linked, img > 0 && before[img-1] == '[')
132	}
133	return images, linked
134}
135
136func TestBoardIsClickableSVGColumns(cur realm, t *testing.T) {
137	reset()
138	g, p := accepted(cur)
139	page := "game/" + strconv.Itoa(g.ID)
140
141	// Reveal phase: the board is one image holding all 7 columns, not a link.
142	out := Render(page)
143	images, linked := svgs(t, out)
144	uassert.Equal(t, 1, len(images))
145	uassert.Equal(t, 7, strings.Count(images[0], `class="col"`))
146	uassert.False(t, linked[0], "no clickable column before the reveal")
147	uassert.True(t, strings.Contains(out, "![board: column 1:"), "alt text")
148	uassert.False(t, strings.Contains(out, "func=Play"))
149
150	testing.SetRealm(testing.NewUserRealm(alice))
151	pay(0)
152	Reveal(cross(cur), g.ID, p)
153	first := g.Turn
154	move(cur, g, 3) // first mover drops in column 3
155
156	out = Render(page)
157	for c := 1; c <= 7; c++ {
158		uassert.True(t, strings.Contains(out, "](/r/samcrew/connect4$help&func=Play&column="+strconv.Itoa(c)+"&id=") ||
159			strings.Contains(out, "$help&func=Play&column="+strconv.Itoa(c)+"&id="), "column link "+strconv.Itoa(c))
160	}
161	images, linked = svgs(t, out)
162	boardImgs := 0
163	for i, img := range images {
164		if strings.Contains(img, `class="col"`) {
165			boardImgs++
166			uassert.True(t, linked[i], "board column must be a link while playing")
167		}
168	}
169	uassert.Equal(t, 7, boardImgs)
170	col3 := images[boardIndex(images, 3)]
171	uassert.True(t, strings.Contains(col3, `fill="`+[...]string{"", red, yellow}[first]+`"`), col3)
172	uassert.True(t, strings.Contains(col3, `stroke="`+lastMoveStroke+`"`), "last move ring")
173	uassert.True(t, strings.Contains(out, "![column 3: "+[...]string{"", "red", "yellow"}[first]), "alt text")
174
175	// Win with four stacked in column 1: the four cells get the win ring.
176	for _, c := range []int{1, 2, 1, 2, 1, 2} {
177		move(cur, g, c)
178	}
179	move(cur, g, 1)
180	uassert.Equal(t, "won", g.Status.String())
181	out = Render(page)
182	images, _ = svgs(t, out)
183	uassert.Equal(t, 1, len(images))
184	uassert.Equal(t, 4, strings.Count(images[0], `stroke="`+winStroke+`"`), images[0])
185	uassert.False(t, strings.Contains(out, "func=Play"))
186}
187
188// boardIndex returns the index in images of board column c (1-based).
189func boardIndex(images []string, c int) int {
190	n := 0
191	for i, img := range images {
192		if strings.Contains(img, `class="col"`) {
193			n++
194			if n == c {
195				return i
196			}
197		}
198	}
199	return -1
200}