streaks_test.gno
3.75 Kb · 148 lines
1package streaks
2
3import (
4 "strings"
5 "testing"
6
7 "gno.land/p/nt/avl/v0"
8 "gno.land/p/nt/testutils/v0"
9)
10
11func resetState() {
12 streaks = avl.Tree{}
13}
14
15func TestCheckinFirstTimeStartsStreak(t *testing.T) {
16 resetState()
17 alice := testutils.TestAddress("alice")
18
19 checkin(alice, 100)
20
21 r, ok := get(alice)
22 if !ok {
23 t.Fatal("expected a record after first check-in")
24 }
25 if r.current != 1 || r.longest != 1 || r.total != 1 {
26 t.Fatalf("expected current=1 longest=1 total=1, got current=%d longest=%d total=%d", r.current, r.longest, r.total)
27 }
28}
29
30func TestCheckinWithinGraceExtendsStreak(t *testing.T) {
31 resetState()
32 alice := testutils.TestAddress("alice")
33
34 checkin(alice, 100)
35 checkin(alice, 150) // gap of 50 <= graceBlocks(100)
36 checkin(alice, 200) // gap of 50 again
37
38 r, _ := get(alice)
39 if r.current != 3 {
40 t.Fatalf("expected streak of 3, got %d", r.current)
41 }
42 if r.longest != 3 {
43 t.Fatalf("expected longest of 3, got %d", r.longest)
44 }
45}
46
47func TestCheckinAfterGraceResetsStreak(t *testing.T) {
48 resetState()
49 alice := testutils.TestAddress("alice")
50
51 checkin(alice, 100)
52 checkin(alice, 150)
53 checkin(alice, 400) // gap of 250 > graceBlocks(100): lapsed
54
55 r, _ := get(alice)
56 if r.current != 1 {
57 t.Fatalf("expected streak reset to 1, got %d", r.current)
58 }
59 if r.longest != 2 {
60 t.Fatalf("expected longest to stay at 2, got %d", r.longest)
61 }
62 if r.total != 3 {
63 t.Fatalf("expected total check-ins of 3, got %d", r.total)
64 }
65}
66
67func TestCheckinSameOrEarlierHeightPanics(t *testing.T) {
68 resetState()
69 alice := testutils.TestAddress("alice")
70 checkin(alice, 100)
71
72 defer func() {
73 if r := recover(); r == nil {
74 t.Fatal("expected panic checking in at a non-later height")
75 }
76 }()
77 checkin(alice, 100)
78}
79
80func TestProjectReportsLapsedWithoutMutating(t *testing.T) {
81 resetState()
82 alice := testutils.TestAddress("alice")
83 checkin(alice, 100)
84
85 r, _ := get(alice)
86 current, alive := project(r, 100+graceBlocks+1)
87 if alive || current != 0 {
88 t.Fatalf("expected lapsed projection (0, false), got (%d, %v)", current, alive)
89 }
90 // Stored state must be untouched by a read-only projection.
91 if r.current != 1 {
92 t.Fatalf("project must not mutate stored state, got current=%d", r.current)
93 }
94}
95
96func TestCurrentStreakAndLongestStreakQueries(t *testing.T) {
97 resetState()
98 alice := testutils.TestAddress("alice")
99 checkin(alice, 100)
100 checkin(alice, 150)
101
102 if got := LongestStreak(alice.String()); got != 2 {
103 t.Fatalf("LongestStreak() = %d, want 2", got)
104 }
105 if got := LongestStreak(testutils.TestAddress("bob").String()); got != 0 {
106 t.Fatalf("LongestStreak() for unknown address = %d, want 0", got)
107 }
108}
109
110func TestRenderShowsEmptyThenLeaderboardThenDetail(t *testing.T) {
111 resetState()
112 if !strings.Contains(Render(""), "Nobody has checked in yet") {
113 t.Fatal("expected empty-state placeholder")
114 }
115
116 alice := testutils.TestAddress("alice")
117 checkin(alice, 100)
118
119 out := Render("")
120 if !strings.Contains(out, "Rank") {
121 t.Fatal("expected leaderboard table in root render")
122 }
123
124 detail := Render(alice.String())
125 if !strings.Contains(detail, "Current streak") {
126 t.Fatal("expected detail card to show current streak")
127 }
128
129 if !strings.Contains(Render(testutils.TestAddress("bob").String()), "No check-ins yet") {
130 t.Fatal("expected placeholder for an address with no check-ins")
131 }
132}
133
134// TestCheckInViaCrossingCall smoke-tests the exported, realm-crossing entry
135// point end to end (as an on-chain caller would invoke it).
136func TestCheckInViaCrossingCall(cur realm, t *testing.T) {
137 resetState()
138 alice := testutils.TestAddress("alice")
139 testing.SetRealm(testing.NewUserRealm(alice))
140
141 msg := CheckIn(cross(cur))
142 if !strings.Contains(msg, "checked in") {
143 t.Fatalf("expected check-in confirmation, got %q", msg)
144 }
145 if _, ok := get(alice); !ok {
146 t.Fatal("expected a record to be persisted for caller")
147 }
148}