Monorepo for Tangled
tangled.org
1package pipelines
2
3import (
4 "strings"
5 "testing"
6)
7
8func TestAnsiState_SingleLine(t *testing.T) {
9 tests := []struct {
10 name string
11 input string
12 wantContain string // substring expected in rendered HTML
13 }{
14 {"bold", "\033[1mBold\033[0m", "term-fg1"},
15 {"red", "\033[31mRed\033[0m", "term-fg31"},
16 {"green", "\033[32mGreen\033[0m", "term-fg32"},
17 {"yellow", "\033[33mYellow\033[0m", "term-fg33"},
18 {"blue", "\033[34mBlue\033[0m", "term-fg34"},
19 {"magenta", "\033[35mMagenta\033[0m", "term-fg35"},
20 {"cyan", "\033[36mCyan\033[0m", "term-fg36"},
21 {"bold green", "\033[1;32mBold Green\033[0m", "term-fg32"},
22 {"red background", "\033[41m Red background \033[0m", "term-bg41"},
23 {"256 orange", "\033[38;5;208mANSI 256 orange\033[0m", "term-fgx208"},
24 // true color is stripped
25 {"true color", "\033[38;2;255;100;0mTrue color orange\033[0m", "True color orange"},
26 }
27 for _, tt := range tests {
28 t.Run(tt.name, func(t *testing.T) {
29 a := NewAnsiState()
30 got := string(a.Render(tt.input))
31 t.Logf("%s → %s", tt.name, got)
32 if !strings.Contains(got, tt.wantContain) {
33 t.Errorf("expected output to contain %q, got: %s", tt.wantContain, got)
34 }
35 })
36 }
37}
38
39func TestAnsiState_MultiLine_CarryOver(t *testing.T) {
40 // red opened on line 1 without reset — line 2 should carry it over.
41 a := NewAnsiState()
42
43 line1 := a.Render("\033[31mstart of red")
44 if !strings.Contains(string(line1), "term-fg31") {
45 t.Errorf("line1: expected term-fg31, got: %s", line1)
46 }
47
48 line2 := a.Render("still red\033[0m")
49 t.Logf("line2 → %s", line2)
50 if !strings.Contains(string(line2), "term-fg31") {
51 t.Errorf("line2: expected carry-over term-fg31, got: %s", line2)
52 }
53
54 // after the reset, line 3 should have no colour.
55 line3 := a.Render("plain text")
56 t.Logf("line3 → %s", line3)
57 if strings.Contains(string(line3), "term-fg31") {
58 t.Errorf("line3: expected no term-fg31 after reset, got: %s", line3)
59 }
60}
61
62func TestAnsiState_MultiLine_StackedSequences(t *testing.T) {
63 // bold opened line 1, red added line 2 — both should carry to line 2.
64 a := NewAnsiState()
65
66 a.Render("\033[1mBold opened")
67
68 line2 := a.Render("\033[31mRed added")
69 t.Logf("line2 → %s", line2)
70 if !strings.Contains(string(line2), "term-fg1") {
71 t.Errorf("line2: expected carried-over term-fg1, got: %s", line2)
72 }
73 if !strings.Contains(string(line2), "term-fg31") {
74 t.Errorf("line2: expected term-fg31, got: %s", line2)
75 }
76
77 a.Render("\033[0mReset")
78
79 line4 := a.Render("plain")
80 t.Logf("line4 → %s", line4)
81 if strings.Contains(string(line4), "term-") {
82 t.Errorf("line4: expected no term- classes after reset, got: %s", line4)
83 }
84}
85
86func TestAnsiState_Reset_ClearsStack(t *testing.T) {
87 a := NewAnsiState()
88 a.Render("\033[31m\033[1m\033[33mMultiple opens")
89 if len(a.stack) != 3 {
90 t.Errorf("expected stack depth 3, got %d", len(a.stack))
91 }
92 a.Render("\033[0mReset line")
93 if len(a.stack) != 0 {
94 t.Errorf("expected empty stack after reset, got %d: %v", len(a.stack), a.stack)
95 }
96}
97
98func TestAnsiState_NoAnsi(t *testing.T) {
99 a := NewAnsiState()
100 got := string(a.Render("plain text no escapes"))
101 t.Logf("got → %s", got)
102 if strings.Contains(got, "term-") {
103 t.Errorf("expected no term- classes for plain text, got: %s", got)
104 }
105}
106
107func TestAnsiState_Sanitizer_XSS(t *testing.T) {
108 tests := []struct {
109 name string
110 input string
111 shuoldBeAbsent string // must not appear literally (unescaped) in output
112 }{
113 {
114 name: "script tag not executable",
115 input: "<script>alert(1)</script>",
116 shuoldBeAbsent: "<script>",
117 },
118 {
119 name: "img onerror not executable",
120 input: `<img src=x onerror="alert(1)">`,
121 shuoldBeAbsent: "<img",
122 },
123 {
124 name: "ansi with embedded script not executable",
125 input: "\033[31m<script>alert(1)</script>\033[0m",
126 shuoldBeAbsent: "<script>",
127 },
128 {
129 name: "only term- classes survive on span",
130 input: "\033[31mcolored\033[0m",
131 shuoldBeAbsent: "onclick",
132 },
133 }
134 for _, tt := range tests {
135 t.Run(tt.name, func(t *testing.T) {
136 a := NewAnsiState()
137 got := string(a.Render(tt.input))
138 t.Logf("%s → %s", tt.name, got)
139 if strings.Contains(got, tt.shuoldBeAbsent) {
140 t.Errorf("expected %q to be absent (unescaped), got: %s", tt.shuoldBeAbsent, got)
141 }
142 })
143 }
144}