1package ansi_test
2
3import (
4 "fmt"
5 "testing"
6
7 vt "github.com/bnema/vev-vt"
8 "github.com/bnema/vev-vt/ansi"
9)
10
11// An embedded source exercises the generic path rather than Frame dispatch.
12type scrollCellSource struct{ vt.CellSource }
13
14func TestScrollExposedCellsRequireBlankOrRepaint(t *testing.T) {
15 for _, down := range []bool{false, true} {
16 for _, sourceKind := range []string{"frame", "pointer", "generic"} {
17 for _, tc := range []struct {
18 name string
19 cell vt.Cell
20 coverage int
21 kind vt.DamageKind
22 fallback bool
23 }{
24 {"blank", vt.BlankCell(), 0, vt.DamageText, false},
25 {"uncovered text", vt.Cell{Rune: 'X', Style: vt.DefaultStyle()}, 0, vt.DamageText, true},
26 {"uncovered styled blank", vt.Cell{Rune: ' ', Style: vt.Style{Bold: true}}, 0, vt.DamageText, true},
27 {"partial coverage", vt.Cell{Rune: 'X', Style: vt.DefaultStyle()}, 1, vt.DamageText, true},
28 {"text coverage", vt.Cell{Rune: 'X', Style: vt.DefaultStyle()}, 2, vt.DamageText, false},
29 {"clear coverage", vt.Cell{Rune: 'X', Style: vt.DefaultStyle()}, 2, vt.DamageClear, false},
30 } {
31 t.Run(fmt.Sprintf("down=%v/%s/%s", down, sourceKind, tc.name), func(t *testing.T) {
32 committed := vt.NewFrame(12, 10)
33 for y := range committed.Height {
34 committed.FillRow(y, 0, committed.Width, vt.Cell{Rune: rune('a' + y), Style: vt.DefaultStyle()})
35 }
36 frame := committed.Clone()
37 kind, exposed := vt.DamageScrollUp, 6
38 if down {
39 kind, exposed = vt.DamageScrollDown, 2
40 frame.ScrollDown(2, 7, 2)
41 } else {
42 frame.ScrollUp(2, 7, 2)
43 }
44 for y := exposed; y < exposed+2; y++ {
45 frame.Set(0, y, tc.cell)
46 }
47 damage := []vt.Damage{{Kind: kind, Y: 2, Width: 12, Height: 6, Count: 2}}
48 if tc.coverage > 0 {
49 damage = append(damage, vt.Damage{Kind: tc.kind, Y: exposed, Width: 12, Height: tc.coverage})
50 }
51 var source vt.CellSource = frame
52 switch sourceKind {
53 case "pointer":
54 source = &frame
55 case "generic":
56 source = scrollCellSource{frame}
57 }
58 candidate, err := ansi.PlanDelta(source, damage, committed, false)
59 if err != nil {
60 t.Fatal(err)
61 }
62 if candidate.Plan.Snapshot != tc.fallback {
63 t.Fatalf("plan = %+v, want fallback %v", candidate.Plan, tc.fallback)
64 }
65 r := ansi.New(ansi.Capabilities{})
66 terminal := vt.NewScreen(12, 10)
67 initial, err := r.Draw(committed, nil)
68 if err != nil {
69 t.Fatal(err)
70 }
71 terminal.Write(initial)
72 data, err := r.Draw(source, damage)
73 if err != nil {
74 t.Fatal(err)
75 }
76 terminal.Write(data)
77 for y := range frame.Height {
78 for x := range frame.Width {
79 if !frame.Cell(x, y).Equal(terminal.Cell(x, y)) {
80 t.Fatalf("replay differs at %d,%d", x, y)
81 }
82 }
83 }
84 data, err = r.Draw(source, nil)
85 if err != nil || len(data) != 0 {
86 t.Fatalf("committed shadow differs: %q, %v", data, err)
87 }
88 })
89 }
90 }
91 }
92}