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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

hangman_test.gno

3.46 Kb · 136 lines
  1package hangman
  2
  3import (
  4	"strings"
  5	"testing"
  6
  7	"gno.land/p/nt/avl/v0"
  8	"gno.land/p/nt/testutils/v0"
  9	"gno.land/p/nt/uassert/v0"
 10)
 11
 12// TestPureHelpers checks the deterministic, non-crossing logic.
 13func TestPureHelpers(t *testing.T) {
 14	// normalizeLetter
 15	if c, ok := normalizeLetter("A"); !ok || c != "a" {
 16		t.Fatalf("normalizeLetter(A) = %q,%v; want a,true", c, ok)
 17	}
 18	if _, ok := normalizeLetter("ab"); ok {
 19		t.Fatalf("normalizeLetter(ab) should fail")
 20	}
 21	if _, ok := normalizeLetter("3"); ok {
 22		t.Fatalf("normalizeLetter(3) should fail")
 23	}
 24
 25	// wordForDay is deterministic and cycles the list.
 26	w0 := wordForDay(0)
 27	if w0 != words[0] {
 28		t.Fatalf("wordForDay(0) = %q; want %q", w0, words[0])
 29	}
 30	if wordForDay(int64(len(words))) != words[0] {
 31		t.Fatalf("wordForDay should wrap around the list length")
 32	}
 33
 34	// dayKey formatting.
 35	if got := dayKey(0); got != "0" {
 36		t.Fatalf("dayKey(0) = %q; want 0", got)
 37	}
 38	if got := dayKey(123); got != "123" {
 39		t.Fatalf("dayKey(123) = %q; want 123", got)
 40	}
 41
 42	// gallows clamps and grows.
 43	if gallows(0) == gallows(6) {
 44		t.Fatalf("gallows should differ between 0 and 6 wrong")
 45	}
 46	if gallows(99) != gallows(6) {
 47		t.Fatalf("gallows should clamp out-of-range to max stage")
 48	}
 49}
 50
 51// TestRoundMasking checks masked/win/lose logic directly on a round.
 52func TestRoundMasking(t *testing.T) {
 53	r := &round{word: "gno", guessed: avl.NewTree(), wrong: 0}
 54
 55	if got := r.masked(); got != "_ _ _" {
 56		t.Fatalf("initial masked = %q; want _ _ _", got)
 57	}
 58	if r.isWon() {
 59		t.Fatalf("fresh round should not be won")
 60	}
 61
 62	r.guessed.Set("g", struct{}{})
 63	if got := r.masked(); got != "g _ _" {
 64		t.Fatalf("masked after g = %q; want g _ _", got)
 65	}
 66
 67	r.guessed.Set("n", struct{}{})
 68	r.guessed.Set("o", struct{}{})
 69	if !r.isWon() {
 70		t.Fatalf("round should be won after guessing all letters")
 71	}
 72
 73	// Lose path.
 74	r2 := &round{word: "gno", guessed: avl.NewTree(), wrong: maxWrong}
 75	if !r2.isLost() {
 76		t.Fatalf("round with %d wrong should be lost", maxWrong)
 77	}
 78	if !r2.over() {
 79		t.Fatalf("lost round should be over")
 80	}
 81}
 82
 83// TestGuessCrossing exercises the crossing entrypoint end to end.
 84func TestGuessCrossing(cur realm, t *testing.T) {
 85	user := testutils.TestAddress("player")
 86	testing.SetRealm(testing.NewUserRealm(user))
 87
 88	r := current()
 89	word := r.word
 90
 91	// A correct guess: pick the first letter of today's word.
 92	first := string(word[0])
 93	Guess(cross(cur), first)
 94	if !r.guessed.Has(first) {
 95		t.Fatalf("guessed set should contain %q after Guess", first)
 96	}
 97	if r.wrong != 0 {
 98		t.Fatalf("correct guess should not increase wrong count, got %d", r.wrong)
 99	}
100
101	// Re-guessing the same letter aborts.
102	uassert.AbortsWithMessage(t, cur, "letter already guessed: "+first, func() {
103		Guess(cross(cur), first)
104	})
105
106	// Invalid input aborts.
107	uassert.AbortsWithMessage(t, cur, "guess must be a single letter a-z", func() {
108		Guess(cross(cur), "42")
109	})
110
111	// A wrong guess: find a letter not in the word.
112	miss := firstMissing(word)
113	if miss != "" {
114		before := r.wrong
115		Guess(cross(cur), miss)
116		if r.wrong != before+1 {
117			t.Fatalf("wrong guess should increment wrong count: got %d want %d", r.wrong, before+1)
118		}
119	}
120
121	// Render always returns non-empty markdown.
122	out := Render("")
123	if !strings.Contains(out, "Hangman") {
124		t.Fatalf("Render output missing title")
125	}
126}
127
128// firstMissing returns a lowercase letter a-z not present in word, or "".
129func firstMissing(word string) string {
130	for c := byte('a'); c <= 'z'; c++ {
131		if !strings.Contains(word, string(c)) {
132			return string(c)
133		}
134	}
135	return ""
136}