render_test.gno
6.91 Kb · 200 lines
1package connect4
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, "+"&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}