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.

ui_test.gno

7.45 Kb · 215 lines
  1package ui
  2
  3import (
  4	"strings"
  5	"testing"
  6
  7	"gno.land/p/nt/testutils/v0"
  8	"gno.land/p/nt/uassert/v0"
  9)
 10
 11func TestShortN(t *testing.T) {
 12	cases := []struct {
 13		name       string
 14		in         string
 15		head, tail int
 16		want       string
 17	}{
 18		{"empty", "", 8, 4, ""},
 19		{"shorter than the rule", "abc", 8, 4, "abc"},
 20		{"exactly at the threshold", "0123456789abc", 8, 4, "0123456789abc"},
 21		{"one over the threshold", "0123456789abcd", 8, 4, "01234567…abcd"},
 22		{"gno address", "g1manfred47kzduec920z88wfr64ylksmdcedlf5", 8, 4, "g1manfre…dlf5"},
 23		{"head only", "0123456789abcd", 6, 0, "012345…"},
 24		{"tail only", "0123456789abcd", 0, 4, "…abcd"},
 25		{"negative clamps to zero", "0123456789abcd", -3, -3, "…"},
 26		{"multibyte is not split", "éééééééééééééé", 2, 2, "éé…éé"},
 27	}
 28	for _, tc := range cases {
 29		got := ShortN(tc.in, tc.head, tc.tail)
 30		uassert.Equal(t, tc.want, got, tc.name)
 31	}
 32}
 33
 34// ShortN must never make a string longer: that is the property the four
 35// divergent shortAddr implementations disagreed on.
 36func TestShortNeverGrows(t *testing.T) {
 37	inputs := []string{"", "a", "abcdefghijkl", "0123456789abc", "0123456789abcd", strings.Repeat("x", 100)}
 38	for _, in := range inputs {
 39		got := Short(in)
 40		uassert.True(t, len([]rune(got)) <= len([]rune(in)), "Short("+in+") grew the input")
 41	}
 42}
 43
 44func TestAddr(t *testing.T) {
 45	a := testutils.TestAddress("alice")
 46	full := a.String()
 47
 48	uassert.Equal(t, "`"+full+"`", AddrFull(a))
 49	uassert.Equal(t, Short(full), AddrText(a))
 50	uassert.Equal(t, "`"+Short(full)+"`", Addr(a))
 51
 52	// The shortened form keeps the recognisable head and the distinguishing tail.
 53	uassert.True(t, strings.HasPrefix(AddrText(a), full[:Head]), "head is kept")
 54	uassert.True(t, strings.HasSuffix(AddrText(a), full[len(full)-Tail:]), "tail is kept")
 55	uassert.True(t, strings.Contains(AddrText(a), Ellipsis), "ellipsis is present")
 56}
 57
 58// Two different addresses must not collide once shortened, or the display is
 59// worse than useless.
 60func TestAddrOf(t *testing.T) {
 61	a := testutils.TestAddress("alice")
 62	uassert.Equal(t, Addr(a), AddrOf(a.String()))
 63}
 64
 65func TestAddrDistinguishes(t *testing.T) {
 66	seen := map[string]bool{}
 67	for _, name := range []string{"alice", "bob", "carol", "dave", "eve"} {
 68		s := AddrText(testutils.TestAddress(name))
 69		uassert.False(t, seen[s], "shortened address collision on "+name)
 70		seen[s] = true
 71	}
 72}
 73
 74func TestInlineEscapes(t *testing.T) {
 75	// The seven pairs the hand-rolled escapeInline copies covered.
 76	for _, meta := range []string{"*", "_", "[", "]", "`", "\\"} {
 77		got := Inline("a" + meta + "b")
 78		uassert.True(t, strings.Contains(got, "\\"+meta), "Inline did not escape "+meta)
 79	}
 80	// And the one they all missed: a newline lets user text escape its line.
 81	uassert.False(t, strings.Contains(Inline("a\nb"), "\n"), "Inline must fold newlines")
 82}
 83
 84func TestCellEscapesPipe(t *testing.T) {
 85	got := Cell("a|b")
 86	uassert.False(t, strings.Contains(got, " |"), "a bare pipe would open a new column")
 87	uassert.True(t, strings.Contains(got, "\\|"), "pipe must be escaped")
 88}
 89
 90func TestEmpty(t *testing.T) {
 91	uassert.Equal(t, "*No games yet.*\n", Empty("No games yet."))
 92}
 93
 94func TestPodium(t *testing.T) {
 95	cases := []struct {
 96		rank int
 97		want string
 98	}{
 99		{0, "\U0001F947"},
100		{1, "\U0001F948"},
101		{2, "\U0001F949"},
102		{3, ""},
103		{99, ""},
104		{-1, ""},
105	}
106	for _, tc := range cases {
107		uassert.Equal(t, tc.want, Podium(tc.rank))
108	}
109}
110
111func TestTable(t *testing.T) {
112	tbl := NewTable("#", "who", "score")
113	uassert.Equal(t, 0, tbl.Len())
114	uassert.Equal(t, "", tbl.String())
115	uassert.Equal(t, "*nobody yet.*\n", tbl.OrEmpty("nobody yet."))
116
117	tbl.Row("1", "`g1abc…wxyz`", "42").Row("2", "`g1def…uvwx`", "7")
118	uassert.Equal(t, 2, tbl.Len())
119
120	want := "| # | who | score |\n" +
121		"| --- | --- | --- |\n" +
122		"| 1 | `g1abc…wxyz` | 42 |\n" +
123		"| 2 | `g1def…uvwx` | 7 |\n"
124	uassert.Equal(t, want, tbl.String())
125	uassert.Equal(t, want, tbl.OrEmpty("nobody yet."))
126}
127
128// A short row is padded rather than producing a table the renderer misreads.
129func TestTableShortRowIsPadded(t *testing.T) {
130	tbl := NewTable("a", "b", "c")
131	tbl.Row("1")
132	uassert.Equal(t, "| a | b | c |\n| --- | --- | --- |\n| 1 |  |  |\n", tbl.String())
133}
134
135// A cell escaped with Cell must survive Table untouched: stacking Cell on an
136// escaper that also rewrites "|" produced "a\\&#124;b" in the guestbook port.
137func TestTableDoesNotDoubleEscape(t *testing.T) {
138	tbl := NewTable("msg")
139	tbl.Row(Cell("a|b"))
140	got := tbl.String()
141	uassert.True(t, strings.Contains(got, `a\|b`), "expected the GFM pipe escape, got: "+got)
142	uassert.False(t, strings.Contains(got, "&#124;"), "cell was escaped twice: "+got)
143}
144
145func TestJoin(t *testing.T) {
146	uassert.Equal(t, "a\n\nb", Join("\n\n", "a", "", "b"))
147	uassert.Equal(t, "", Join("\n\n", "", ""))
148	uassert.Equal(t, "solo", Join("\n\n", "solo"))
149}
150
151// Action must produce a markdown link whose target carries the function name
152// and the key/value arguments, and must escape the title (which helplink
153// itself does not).
154func TestAction(t *testing.T) {
155	got := Action("Play cell 4", "Move", "gameID", "7", "cell", "4")
156	uassert.True(t, strings.HasPrefix(got, "[Play cell 4]("), "expected a markdown link, got: "+got)
157	uassert.True(t, strings.Contains(got, "Move"), "link must name the function")
158	uassert.True(t, strings.Contains(got, "gameID=7"), "link must carry the args")
159	uassert.True(t, strings.Contains(got, "cell=4"), "link must carry the args")
160
161	esc := Action("a*b", "Fn")
162	uassert.True(t, strings.HasPrefix(esc, "[a\\*b]("), "title must be escaped, got: "+esc)
163}
164
165func TestActionIn(t *testing.T) {
166	got := ActionIn("gno.land/r/moul/gns/v0", "Say hi", "Hello", "name", "moul")
167	uassert.True(t, strings.Contains(got, "/r/moul/gns/v0"), "must target the named realm, got: "+got)
168	uassert.True(t, strings.Contains(got, "Hello"), "must name the function")
169}
170
171func TestExcerpt(t *testing.T) {
172	cases := []struct {
173		name  string
174		in    string
175		width int
176		want  string
177	}{
178		{"empty", "", 10, ""},
179		{"shorter than the width", "hello", 10, "hello"},
180		{"one over is kept whole, as ShortN does", "0123456789a", 10, "0123456789a"},
181		{"two over is cut", "0123456789ab", 10, "0123456789…"},
182		{"zero width", "hello", 0, "…"},
183		{"negative width clamps to zero", "hello", -3, "…"},
184		// The cut counts runes. A byte slice at 10 would split the 11th 'é'
185		// and put invalid UTF-8 on the page.
186		{"multibyte is not split", strings.Repeat("é", 20), 10, strings.Repeat("é", 10) + "…"},
187		// The escaping half: what is kept is escaped, the ellipsis is not.
188		{"escapes what it keeps", "[link](url) and more", 11, "\\[link\\]\\(url\\)…"},
189		{"newline cannot escape the line", "one\ntwo", 10, "one two"},
190	}
191	for _, tc := range cases {
192		uassert.Equal(t, tc.want, Excerpt(tc.in, tc.width), tc.name)
193	}
194}
195
196// Excerpt must never leave a trailing lone backslash: that is the bug that
197// comes from escaping first and cutting second, and it escapes whatever the
198// caller appends after the excerpt.
199func TestExcerptNeverStrandsABackslash(t *testing.T) {
200	// Each of these ends, at some width, on a character the escaper backslashes.
201	for _, in := range []string{
202		"....................", "********************", "[[[[[[[[[[[[[[[[[[[[",
203		"!!!!!!!!!!!!!!!!!!!!", "<<<<<<<<<<<<<<<<<<<<", "--------------------",
204	} {
205		for width := 0; width <= 15; width++ {
206			got := Excerpt(in, width)
207			body := strings.TrimSuffix(got, Ellipsis)
208			trailing := 0
209			for i := len(body) - 1; i >= 0 && body[i] == '\\'; i-- {
210				trailing++
211			}
212			uassert.Equal(t, 0, trailing%2, "odd run of trailing backslashes in Excerpt("+in+")")
213		}
214	}
215}