1 package cview
2
3 import (
4 "bytes"
5 "fmt"
6 "testing"
7 )
8
9 const (
10
11 randomDataSize = 512
12
13
14 appendSetupWriteCount = 64
15 )
16
17 var (
18 randomData = generateRandomData()
19 textViewTestCases = generateTestCases()
20 )
21
22 type textViewTestCase struct {
23 app bool
24 color bool
25 region bool
26 scroll bool
27 wrap bool
28 wordwrap bool
29 }
30
31 func (c *textViewTestCase) String() string {
32 return fmt.Sprintf("Append=%c/Color=%c/Region=%c/Scroll=%c/Wrap=%c/WordWrap=%c", cl(c.app), cl(c.color), cl(c.region), cl(c.scroll), cl(c.wrap), cl(c.wordwrap))
33 }
34
35 func TestTextViewWrite(t *testing.T) {
36 t.Parallel()
37
38 for _, c := range textViewTestCases {
39 c := c
40
41 t.Run(c.String(), func(t *testing.T) {
42 t.Parallel()
43
44 var (
45 tv = tvc(c)
46 expectedData []byte
47 n int
48 err error
49 )
50
51 if c.app {
52 expectedData, err = prepareAppendTextView(tv)
53 if err != nil {
54 t.Errorf("failed to prepare append TextView: %s", err)
55 }
56
57 expectedData = append(expectedData, randomData...)
58 } else {
59 expectedData = randomData
60 }
61
62 n, err = tv.Write(randomData)
63 if err != nil {
64 t.Errorf("failed to write (successfully wrote %d) bytes: %s", n, err)
65 } else if n != randomDataSize {
66 t.Errorf("failed to write: expected to write %d bytes, wrote %d", randomDataSize, n)
67 }
68
69 contents := tv.GetText(false)
70 if len(contents) != len(expectedData) {
71 t.Errorf("failed to write: incorrect contents: expected %d bytes, got %d", len(contents), len(expectedData))
72 } else if !bytes.Equal([]byte(contents), expectedData) {
73 t.Errorf("failed to write: incorrect contents: values do not match")
74 }
75
76 tv.Clear()
77 })
78 }
79 }
80
81 func BenchmarkTextViewWrite(b *testing.B) {
82 for _, c := range textViewTestCases {
83 c := c
84
85 b.Run(c.String(), func(b *testing.B) {
86 var (
87 tv = tvc(c)
88 n int
89 err error
90 )
91
92 if c.app {
93 _, err = prepareAppendTextView(tv)
94 if err != nil {
95 b.Errorf("failed to prepare append TextView: %s", err)
96 }
97 }
98
99 b.ReportAllocs()
100 b.ResetTimer()
101
102 for i := 0; i < b.N; i++ {
103 n, err = tv.Write(randomData)
104 if err != nil {
105 b.Errorf("failed to write (successfully wrote %d) bytes: %s", n, err)
106 } else if n != randomDataSize {
107 b.Errorf("failed to write: expected to write %d bytes, wrote %d", randomDataSize, n)
108 }
109
110 if !c.app {
111 b.StopTimer()
112 tv.Clear()
113 b.StartTimer()
114 }
115 }
116 })
117 }
118 }
119
120 func BenchmarkTextViewIndex(b *testing.B) {
121 for _, c := range textViewTestCases {
122 c := c
123
124 b.Run(c.String(), func(b *testing.B) {
125 var (
126 tv = tvc(c)
127 n int
128 err error
129 )
130
131 _, err = prepareAppendTextView(tv)
132 if err != nil {
133 b.Errorf("failed to prepare append TextView: %s", err)
134 }
135
136 n, err = tv.Write(randomData)
137 if err != nil {
138 b.Errorf("failed to write (successfully wrote %d) bytes: %s", n, err)
139 } else if n != randomDataSize {
140 b.Errorf("failed to write: expected to write %d bytes, wrote %d", randomDataSize, n)
141 }
142
143 tv.index = nil
144 tv.reindexBuffer(80)
145
146 b.ReportAllocs()
147 b.ResetTimer()
148
149 for i := 0; i < b.N; i++ {
150 tv.index = nil
151 tv.reindexBuffer(80)
152 }
153 })
154 }
155 }
156
157 func TestTextViewGetText(t *testing.T) {
158 t.Parallel()
159
160 tv := NewTextView()
161 tv.SetDynamicColors(true)
162 tv.SetRegions(true)
163
164 n, err := tv.Write(randomData)
165 if err != nil {
166 t.Errorf("failed to write (successfully wrote %d) bytes: %s", n, err)
167 } else if n != randomDataSize {
168 t.Errorf("failed to write: expected to write %d bytes, wrote %d", randomDataSize, n)
169 }
170
171 suffix := []byte(`["start"]outer[b]inner[-]outer[""]`)
172 suffixStripped := []byte("outerinnerouter")
173
174 n, err = tv.Write(suffix)
175 if err != nil {
176 t.Errorf("failed to write (successfully wrote %d) bytes: %s", n, err)
177 }
178
179 if !bytes.Equal(tv.GetBytes(false), append(randomData, suffix...)) {
180 t.Error("failed to get un-stripped text: unexpected suffix")
181 }
182
183 if !bytes.Equal(tv.GetBytes(true), append(randomData, suffixStripped...)) {
184 t.Error("failed to get text stripped text: unexpected suffix")
185 }
186 }
187
188 func BenchmarkTextViewGetText(b *testing.B) {
189 for _, c := range textViewTestCases {
190 c := c
191
192 if c.app {
193 continue
194 }
195
196 b.Run(c.String(), func(b *testing.B) {
197 var (
198 tv = tvc(c)
199 n int
200 err error
201 v []byte
202 )
203
204 _, err = prepareAppendTextView(tv)
205 if err != nil {
206 b.Errorf("failed to prepare append TextView: %s", err)
207 }
208
209 n, err = tv.Write(randomData)
210 if err != nil {
211 b.Errorf("failed to write (successfully wrote %d) bytes: %s", n, err)
212 } else if n != randomDataSize {
213 b.Errorf("failed to write: expected to write %d bytes, wrote %d", randomDataSize, n)
214 }
215
216 v = tv.GetBytes(true)
217
218 b.ReportAllocs()
219 b.ResetTimer()
220
221 for i := 0; i < b.N; i++ {
222 v = tv.GetBytes(true)
223 }
224
225 _ = v
226 })
227 }
228 }
229
230 type textViewResult struct {
231 x int
232 y int
233 str string
234 width int
235 }
236
237 type textViewRegionsTestCase struct {
238 text string
239 normal []textViewResult
240 escaped []textViewResult
241 }
242
243 var textViewHelloWorldResult = []textViewResult{
244 {x: 0, y: 0, str: "H", width: 1},
245 {x: 1, y: 0, str: "e", width: 1},
246 {x: 2, y: 0, str: "l", width: 1},
247 {x: 3, y: 0, str: "l", width: 1},
248 {x: 4, y: 0, str: "o", width: 1},
249 {x: 5, y: 0, str: ",", width: 1},
250 {x: 7, y: 0, str: "w", width: 1},
251 {x: 8, y: 0, str: "o", width: 1},
252 {x: 9, y: 0, str: "r", width: 1},
253 {x: 10, y: 0, str: "l", width: 1},
254 {x: 11, y: 0, str: "d", width: 1},
255 {x: 12, y: 0, str: "!", width: 1},
256 }
257
258 var textViewRegionsTestCases = []textViewRegionsTestCase{
259 {
260 text: `Hello, world!`,
261 normal: textViewHelloWorldResult,
262 escaped: textViewHelloWorldResult,
263 }, {
264 text: "[TEST\033[0m]\033[36mTEST",
265 normal: []textViewResult{
266 {x: 0, y: 0, str: "[", width: 1},
267 {x: 1, y: 0, str: "T", width: 1},
268 {x: 2, y: 0, str: "E", width: 1},
269 {x: 3, y: 0, str: "S", width: 1},
270 {x: 4, y: 0, str: "T", width: 1},
271 {x: 5, y: 0, str: "]", width: 1},
272 {x: 6, y: 0, str: "T", width: 1},
273 {x: 7, y: 0, str: "E", width: 1},
274 {x: 8, y: 0, str: "S", width: 1},
275 {x: 9, y: 0, str: "T", width: 1},
276 },
277 escaped: []textViewResult{
278 {x: 0, y: 0, str: "[", width: 1},
279 {x: 1, y: 0, str: "T", width: 1},
280 {x: 2, y: 0, str: "E", width: 1},
281 {x: 3, y: 0, str: "S", width: 1},
282 {x: 4, y: 0, str: "T", width: 1},
283 {x: 5, y: 0, str: "[", width: 1},
284 {x: 6, y: 0, str: "]", width: 1},
285 {x: 7, y: 0, str: "T", width: 1},
286 {x: 8, y: 0, str: "E", width: 1},
287 {x: 9, y: 0, str: "S", width: 1},
288 {x: 10, y: 0, str: "T", width: 1},
289 },
290 },
291 }
292
293 func TestTextViewANSI(t *testing.T) {
294 t.Parallel()
295
296 for j := 0; j < 2; j++ {
297 for i, c := range textViewRegionsTestCases {
298 label := "Normal"
299 expectedResult := c.normal
300 if j == 1 {
301 label = "Escaped"
302 expectedResult = c.escaped
303 }
304
305 t.Run(fmt.Sprintf("%s/%d", label, i+1), func(t *testing.T) {
306 t.Parallel()
307
308 tv := NewTextView()
309 tv.SetDynamicColors(true)
310
311 app, err := newTestApp(tv)
312 if err != nil {
313 t.Errorf("failed to initialize Application: %s", err)
314 }
315 app.screen.SetSize(screenW, screenH)
316 tv.SetRect(0, 0, screenW, screenH)
317
318 content := c.text
319 if j == 1 {
320 content = Escape(content)
321 }
322
323 content = TranslateANSI(content)
324
325 tv.SetText(content)
326
327 tv.Draw(app.screen)
328 var expected textViewResult
329 for y := 0; y < screenH; y++ {
330 for x := 0; x < screenW; x++ {
331 expected = textViewResult{
332 str: " ",
333 width: 1,
334 }
335 for _, nc := range expectedResult {
336 if nc.x == x && nc.y == y {
337 expected = nc
338 break
339 }
340 }
341
342 str, _, width := app.screen.Get(x, y)
343 if str != expected.str {
344 t.Errorf("unexpected str at %d, %d: expected '%s', got '%s'", x, y, expected.str, str)
345 }
346 if width != expected.width {
347 t.Errorf("unexpected width at %d, %d: expected %d, got %d", x, y, expected.width, width)
348 }
349 }
350 }
351 })
352 }
353 }
354 }
355
356 func TestTextViewDraw(t *testing.T) {
357 t.Parallel()
358
359 for _, c := range textViewTestCases {
360 c := c
361
362 t.Run(c.String(), func(t *testing.T) {
363 t.Parallel()
364
365 tv := tvc(c)
366
367 app, err := newTestApp(tv)
368 if err != nil {
369 t.Errorf("failed to initialize Application: %s", err)
370 }
371
372 if c.app {
373 _, err = prepareAppendTextView(tv)
374 if err != nil {
375 t.Errorf("failed to prepare append TextView: %s", err)
376 }
377
378 tv.Draw(app.screen)
379 }
380
381 n, err := tv.Write(randomData)
382 if err != nil {
383 t.Errorf("failed to write (successfully wrote %d) bytes: %s", n, err)
384 } else if n != randomDataSize {
385 t.Errorf("failed to write: expected to write %d bytes, wrote %d", randomDataSize, n)
386 }
387
388 tv.Draw(app.screen)
389 })
390 }
391 }
392
393 func BenchmarkTextViewDraw(b *testing.B) {
394 for _, c := range textViewTestCases {
395 c := c
396
397 b.Run(c.String(), func(b *testing.B) {
398 tv := tvc(c)
399
400 app, err := newTestApp(tv)
401 if err != nil {
402 b.Errorf("failed to initialize Application: %s", err)
403 }
404
405 if c.app {
406 _, err = prepareAppendTextView(tv)
407 if err != nil {
408 b.Errorf("failed to prepare append TextView: %s", err)
409 }
410
411 tv.Draw(app.screen)
412 }
413
414 n, err := tv.Write(randomData)
415 if err != nil {
416 b.Errorf("failed to write (successfully wrote %d) bytes: %s", n, err)
417 } else if n != randomDataSize {
418 b.Errorf("failed to write: expected to write %d bytes, wrote %d", randomDataSize, n)
419 }
420
421 tv.Draw(app.screen)
422
423 b.ReportAllocs()
424 b.ResetTimer()
425
426 for i := 0; i < b.N; i++ {
427 tv.Draw(app.screen)
428 }
429 })
430 }
431 }
432
433 func TestTextViewMaxLines(t *testing.T) {
434 t.Parallel()
435
436 tv := NewTextView()
437
438
439 for i := 0; i < 100; i++ {
440 _, err := tv.Write([]byte(fmt.Sprintf("L%d\n", i)))
441 if err != nil {
442 t.Errorf("failed to write to TextView: %s", err)
443 }
444 }
445
446
447 count := bytes.Count(tv.GetBytes(true), []byte("\n"))
448 if count != 100 {
449 t.Errorf("expected 100 lines, got %d", count)
450 }
451
452
453 tv.SetMaxLines(20)
454
455 count = len(bytes.Split(tv.GetBytes(true), []byte("\n")))
456 if count != 20 {
457 t.Errorf("expected 20 lines, got %d", count)
458 }
459
460
461 for i := 100; i < 200; i++ {
462 _, err := tv.Write([]byte(fmt.Sprintf("L%d\n", i)))
463 if err != nil {
464 t.Errorf("failed to write to TextView: %s", err)
465 }
466 }
467
468
469 txt := tv.GetBytes(true)
470 lines := bytes.Split(txt, []byte("\n"))
471 count = len(lines)
472 if count != 20 {
473 t.Errorf("expected 20 lines, got %d", count)
474 }
475
476
477 if !bytes.Equal(lines[0], []byte("L181")) {
478 t.Errorf("expected to get L181, got %s", lines[0])
479 }
480 }
481
482 func generateTestCases() []*textViewTestCase {
483 var cases []*textViewTestCase
484 for i := 0; i < 2; i++ {
485 app := i == 1
486 for i := 0; i < 2; i++ {
487 color := i == 1
488 for i := 0; i < 2; i++ {
489 region := i == 1
490 for i := 0; i < 2; i++ {
491 scroll := i == 1
492 for i := 0; i < 2; i++ {
493 wrap := i == 1
494 for i := 0; i < 2; i++ {
495 wordwrap := i == 1
496 if !wrap && wordwrap {
497 continue
498 }
499 cases = append(cases, &textViewTestCase{app, color, region, scroll, wrap, wordwrap})
500 }
501 }
502 }
503 }
504 }
505 }
506 return cases
507 }
508
509 func generateRandomData() []byte {
510 var (
511 b bytes.Buffer
512 r = 33
513 )
514
515 for i := 0; i < randomDataSize; i++ {
516 if i%80 == 0 && i <= 160 {
517 b.WriteRune('\n')
518 } else if i%7 == 0 {
519 b.WriteRune(' ')
520 } else {
521 b.WriteRune(rune(r))
522 }
523
524 r++
525 if r == 127 {
526 r = 33
527 }
528 }
529
530 return b.Bytes()
531 }
532
533 func tvc(c *textViewTestCase) *TextView {
534 tv := NewTextView()
535 tv.SetDynamicColors(c.color)
536 tv.SetRegions(c.region)
537 tv.SetScrollable(c.scroll)
538 tv.SetWrap(c.wrap)
539 tv.SetWordWrap(c.wordwrap)
540 return tv
541 }
542
543 func cl(v bool) rune {
544 if v {
545 return 'Y'
546 }
547 return 'N'
548 }
549
550 func prepareAppendTextView(t *TextView) ([]byte, error) {
551 var b []byte
552 for i := 0; i < appendSetupWriteCount; i++ {
553 b = append(b, randomData...)
554
555 n, err := t.Write(randomData)
556 if err != nil {
557 return nil, fmt.Errorf("failed to write (successfully wrote %d) bytes: %s", n, err)
558 } else if n != randomDataSize {
559 return nil, fmt.Errorf("failed to write: expected to write %d bytes, wrote %d", randomDataSize, n)
560 }
561 }
562
563 return b, nil
564 }
565
View as plain text