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

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}