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}