1package copy
2
3import (
4 "testing"
5
6 renderer "github.com/bnema/vev-vt"
7 "github.com/stretchr/testify/require"
8)
9
10func TestSelectionRanges(t *testing.T) {
11 doc := documentFromStrings([]string{"alpha", "bravo", "charlie"}, " -_@")
12
13 tests := []struct {
14 name string
15 selection Selection
16 want []CellRange
17 }{
18 {
19 name: "line forward",
20 selection: Selection{Anchor: Pos{0, 2}, Active: Pos{1, 1}, Granularity: Line, Enabled: true},
21 want: []CellRange{{Row: 0, Start: 0, End: 4}, {Row: 1, Start: 0, End: 4}},
22 },
23 {
24 name: "line reverse",
25 selection: Selection{Anchor: Pos{1, 1}, Active: Pos{0, 2}, Granularity: Line, Enabled: true},
26 want: []CellRange{{Row: 0, Start: 0, End: 4}, {Row: 1, Start: 0, End: 4}},
27 },
28 {
29 name: "character forward",
30 selection: Selection{Anchor: Pos{0, 2}, Active: Pos{1, 1}, Granularity: Character, Enabled: true},
31 want: []CellRange{{Row: 0, Start: 2, End: 4}, {Row: 1, Start: 0, End: 1}},
32 },
33 {
34 name: "character reverse",
35 selection: Selection{Anchor: Pos{1, 1}, Active: Pos{0, 2}, Granularity: Character, Enabled: true},
36 want: []CellRange{{Row: 0, Start: 2, End: 4}, {Row: 1, Start: 0, End: 1}},
37 },
38 {
39 name: "word forward",
40 selection: Selection{Anchor: Pos{0, 2}, Active: Pos{1, 1}, Granularity: Word, Enabled: true},
41 want: []CellRange{{Row: 0, Start: 0, End: 4}, {Row: 1, Start: 0, End: 4}},
42 },
43 {
44 name: "word reverse",
45 selection: Selection{Anchor: Pos{1, 1}, Active: Pos{0, 2}, Granularity: Word, Enabled: true},
46 want: []CellRange{{Row: 0, Start: 0, End: 4}, {Row: 1, Start: 0, End: 4}},
47 },
48 {name: "disabled", selection: Selection{}, want: nil},
49 }
50 for _, tt := range tests {
51 t.Run(tt.name, func(t *testing.T) {
52 require.Equal(t, tt.want, tt.selection.Ranges(doc))
53 })
54 }
55}
56
57func TestSelectionRangeForRowMatchesCanonicalRanges(t *testing.T) {
58 wide := []renderer.Cell{{Rune: 'a'}, {Rune: '界'}, {Continuation: true}, {Rune: 'b'}}
59 doc := NewDocument(NewSnapshotFromRows([][]renderer.Cell{row("alpha"), wide, row("bravo")}, 5, 3), " -_@")
60 tests := []struct {
61 name string
62 selection Selection
63 row int
64 want CellRange
65 ok bool
66 }{
67 {"line reverse", Selection{Anchor: Pos{2, 1}, Active: Pos{0, 2}, Granularity: Line, Enabled: true}, 1, CellRange{Row: 1, End: 3}, true},
68 {"character wide", Selection{Anchor: Pos{0, 2}, Active: Pos{1, 1}, Granularity: Character, Enabled: true}, 1, CellRange{Row: 1, End: 2}, true},
69 {"word", Selection{Anchor: Pos{0, 2}, Active: Pos{2, 1}, Granularity: Word, Enabled: true}, 1, CellRange{Row: 1, End: 3}, true},
70 {"outside selection", Selection{Anchor: Pos{0, 2}, Active: Pos{1, 1}, Granularity: Character, Enabled: true}, 2, CellRange{}, false},
71 }
72 for _, tt := range tests {
73 t.Run(tt.name, func(t *testing.T) {
74 got, ok := tt.selection.RangeForRow(doc, tt.row)
75 require.Equal(t, tt.ok, ok)
76 if ok {
77 require.Equal(t, tt.want, got)
78 require.Contains(t, tt.selection.Ranges(doc), got)
79 }
80 })
81 }
82}
83
84func TestSelectionOrdered(t *testing.T) {
85 tests := []struct {
86 name string
87 selection Selection
88 start Pos
89 end Pos
90 }{
91 {"forward", Selection{Anchor: Pos{0, 2}, Active: Pos{1, 1}}, Pos{0, 2}, Pos{1, 1}},
92 {"reverse", Selection{Anchor: Pos{1, 1}, Active: Pos{0, 2}}, Pos{0, 2}, Pos{1, 1}},
93 }
94 for _, tt := range tests {
95 t.Run(tt.name, func(t *testing.T) {
96 start, end := tt.selection.Ordered()
97 require.Equal(t, tt.start, start)
98 require.Equal(t, tt.end, end)
99 })
100 }
101}
102
103func TestSelectionRangesEmptyRowsAndWideGlyphs(t *testing.T) {
104 wide := []renderer.Cell{{Rune: 'a'}, {Rune: '界'}, {Continuation: true}, {Rune: 'b'}}
105 doc := NewDocument(NewSnapshotFromRows([][]renderer.Cell{{}, wide, {}}, 4, 3), " -_@")
106
107 tests := []struct {
108 name string
109 selection Selection
110 want []CellRange
111 }{
112 {
113 name: "line includes empty rows",
114 selection: Selection{Anchor: Pos{0, 0}, Active: Pos{2, 0}, Granularity: Line, Enabled: true},
115 want: []CellRange{{Row: 0, Start: 0, End: 0}, {Row: 1, Start: 0, End: 3}, {Row: 2, Start: 0, End: 0}},
116 },
117 {
118 name: "character expands wide glyph continuation",
119 selection: Selection{Anchor: Pos{1, 1}, Active: Pos{1, 2}, Granularity: Character, Enabled: true},
120 want: []CellRange{{Row: 1, Start: 1, End: 2}},
121 },
122 {
123 name: "character crosses empty row",
124 selection: Selection{Anchor: Pos{0, 0}, Active: Pos{1, 1}, Granularity: Character, Enabled: true},
125 want: []CellRange{{Row: 0, Start: 0, End: 0}, {Row: 1, Start: 0, End: 2}},
126 },
127 }
128 for _, tt := range tests {
129 t.Run(tt.name, func(t *testing.T) {
130 require.Equal(t, tt.want, tt.selection.Ranges(doc))
131 })
132 }
133}
134
135func TestWordSelectionRejectsSeparator(t *testing.T) {
136 doc := documentFromStrings([]string{"alpha/beta"}, "/")
137
138 selection, ok := NewWordSelection(doc, Pos{0, 5})
139 require.False(t, ok)
140 require.False(t, selection.Enabled)
141}
142
143func TestSelectionConstructorsAndMutators(t *testing.T) {
144 doc := documentFromStrings([]string{"alpha", "bravo"}, " -_@")
145
146 selection := NewLineSelection(Pos{0, 2})
147 require.True(t, selection.Enabled)
148 require.Equal(t, Line, selection.Granularity)
149 require.Equal(t, Pos{0, 2}, selection.Anchor)
150
151 selection.Extend(Pos{1, 1})
152 selection.AsCharacter()
153 require.Equal(t, Character, selection.Granularity)
154 require.Equal(t, "pha\nbr", selection.Text(doc))
155
156 word, ok := NewWordSelection(doc, Pos{1, 2})
157 require.True(t, ok)
158 require.Equal(t, "bravo", word.Text(doc))
159 word.Extend(Pos{0, 2})
160 require.Equal(t, "alpha\nbravo", word.Text(doc))
161}