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.

canvas_test.gno

4.23 Kb · 141 lines
  1package art
  2
  3import (
  4	"strings"
  5	"testing"
  6
  7	"gno.land/p/nt/uassert/v0"
  8)
  9
 10func TestNewCanvasIsBlank(t *testing.T) {
 11	c := NewCanvas(3, 2)
 12	uassert.Equal(t, 6, len(c.Cells), "cell count")
 13	uassert.Equal(t, "\n", c.Text(), "two empty rows, both trimmed to nothing")
 14
 15	// Negative dimensions must not panic partway through a Render.
 16	empty := NewCanvas(-4, -4)
 17	uassert.Equal(t, 0, empty.W, "negative width clamps to 0")
 18	uassert.Equal(t, "", empty.Text(), "empty canvas is the empty string")
 19}
 20
 21func TestWriteAndClip(t *testing.T) {
 22	cases := []struct {
 23		name string
 24		x, y int
 25		in   string
 26		want string
 27	}{
 28		{"fits", 0, 0, "abc", "abc\n\n"},
 29		{"offset", 1, 1, "xy", "\n xy\n"},
 30		{"clipped at the right edge", 2, 0, "abcde", "  abc\n\n"},
 31		{"negative x drops the leading runes", -2, 0, "abcde", "cde\n\n"},
 32		{"newline returns to the start column", 1, 0, "ab\ncd", " ab\n cd\n"},
 33		{"off the bottom is dropped entirely", 0, 9, "abc", "\n\n"},
 34	}
 35	for _, tc := range cases {
 36		c := NewCanvas(5, 3)
 37		c.Write(tc.x, tc.y, tc.in)
 38		uassert.Equal(t, tc.want, c.Text(), tc.name)
 39	}
 40}
 41
 42func TestTextTrimsTrailingSpaceAndANSIDoesNot(t *testing.T) {
 43	c := NewCanvas(4, 1)
 44	c.Pen(Default, 0xff0000)
 45	c.Put(0, 0, 'x')
 46	c.Put(3, 0, ' ') // a trailing space that carries a background colour
 47
 48	uassert.Equal(t, "x", c.Text(), "text drops the coloured trailing space")
 49
 50	ansi := c.ANSI()
 51	uassert.True(t, strings.HasSuffix(ansi, reset), "a coloured row ends in a reset")
 52	uassert.True(t, strings.Contains(ansi, "48;2;255;0;0"), "the red background is emitted")
 53	// Trimming here would punch a hole in the picture, so the cell survives.
 54	uassert.Equal(t, "x   ", stripSGR(ansi), "all four cells survive in ansi")
 55}
 56
 57func TestANSIEmitsOneEscapePerChange(t *testing.T) {
 58	c := NewCanvas(4, 1)
 59	c.Pen(0x00ff00, Default)
 60	c.Write(0, 0, "aa")
 61	c.Pen(0x0000ff, Default)
 62	c.Write(2, 0, "bb")
 63
 64	got := c.ANSI()
 65	// Two colour changes plus the end-of-row reset, and not one per cell.
 66	uassert.Equal(t, 3, strings.Count(got, "\x1b["), "escape count")
 67	uassert.Equal(t, "aabb", stripSGR(got), "the runes survive intact")
 68}
 69
 70func TestANSIUncolouredRowHasNoEscapes(t *testing.T) {
 71	c := NewCanvas(3, 1)
 72	c.Write(0, 0, "abc")
 73	uassert.Equal(t, "abc", c.ANSI(), "no colour means no escapes and no reset")
 74}
 75
 76func TestBlit(t *testing.T) {
 77	src := NewCanvas(2, 2)
 78	src.Write(0, 0, "ab\ncd")
 79
 80	dst := NewCanvas(4, 4)
 81	dst.Blit(1, 1, src)
 82	uassert.Equal(t, "\n ab\n cd\n", dst.Text(), "placed at the offset")
 83
 84	// Partly off the edge keeps what fits and drops the rest.
 85	edge := NewCanvas(4, 4)
 86	edge.Blit(3, 3, src)
 87	uassert.Equal(t, "\n\n\n   a", edge.Text(), "only the top-left cell lands")
 88
 89	// A nil source is a no-op rather than a panic.
 90	edge.Blit(0, 0, nil)
 91}
 92
 93func TestBoxAndFrame(t *testing.T) {
 94	c := NewCanvas(5, 3)
 95	c.Box(0, 0, 5, 3, ASCII)
 96	uassert.Equal(t, "+---+\n|   |\n+---+", c.Text(), "ascii border")
 97
 98	// Degenerate boxes draw nothing: there is no border with no inside.
 99	small := NewCanvas(5, 3)
100	small.Box(0, 0, 1, 3, Light)
101	small.Box(0, 0, 5, 1, Light)
102	uassert.Equal(t, "\n\n", small.Text(), "width or height under 2 draws nothing")
103
104	inner := NewCanvas(3, 1)
105	inner.Write(0, 0, "hey")
106	framed := inner.Frame("", ASCII)
107	uassert.Equal(t, 5, framed.W, "frame adds two columns")
108	uassert.Equal(t, 3, framed.H, "frame adds two rows")
109	uassert.Equal(t, "+---+\n|hey|\n+---+", framed.Text(), "content centred inside")
110}
111
112func TestFrameTitleIsTruncatedNotOverrun(t *testing.T) {
113	inner := NewCanvas(4, 1)
114	titled := inner.Frame("settlers", ASCII)
115	// 6 columns wide: the corners take 2 and the title starts at x=2, so it
116	// gets 2 cells and must not eat the top-right corner.
117	uassert.True(t, strings.HasPrefix(titled.Text(), "+-se-+\n"), "truncated, corner survives")
118
119	// A canvas too narrow for any title still frames cleanly.
120	tiny := NewCanvas(1, 1)
121	uassert.Equal(t, "+-+\n| |\n+-+", tiny.Frame("nope", ASCII).Text(), "no room, no title")
122}
123
124// stripSGR removes every SGR sequence, so a test can assert on the runes and on
125// the escapes separately instead of pinning one long opaque string.
126func stripSGR(s string) string {
127	var b strings.Builder
128	in := false
129	for _, r := range s {
130		switch {
131		case r == Escape:
132			in = true
133		case in && r == 'm':
134			in = false
135		case in:
136		default:
137			b.WriteRune(r)
138		}
139	}
140	return b.String()
141}