1package core
2
3import "testing"
4
5func spanWorkspace(mode Overflow, n int) *Workspace {
6 w := workspace()
7 w.Overflow = mode
8 w.MaxColumns = 3
9 w.SetOutput(1000, 800)
10 w.SetGaps(10)
11 for i := 0; i < n; i++ {
12 w.AddWindow(WindowID(i + 1))
13 }
14 w.View = 0
15 w.view = slide{}
16 return w
17}
18
19func stampSpans(w *Workspace, first, last int) {
20 w.spanSeq++
21 for i := first; i <= last; i++ {
22 w.Columns[i].Span = 10000 / (last - first + 1)
23 w.Columns[i].spanStamp = w.spanSeq
24 }
25 w.Columns[last].Span += 10000 % (last - first + 1)
26}
27
28func TestSpansMutations(t *testing.T) {
29 for _, mode := range []Overflow{OverflowFixed, OverflowCascade, OverflowScroll} {
30 for _, mutation := range []string{"add", "remove", "take", "receive", "swap", "grow", "shrink", "action"} {
31 t.Run(string(mode)+"/"+mutation, func(t *testing.T) {
32 w := spanWorkspace(mode, 2)
33 stampSpans(w, 0, 1)
34 switch mutation {
35 case "add":
36 w.AddWindow(3)
37 case "remove":
38 w.RemoveWindow(1)
39 case "take":
40 col, ok := w.takeColumn()
41 if !ok {
42 t.Fatal("take")
43 }
44 other := spanWorkspace(mode, 1)
45 other.receive(col, -1)
46 for _, c := range other.Columns {
47 if c.Span != 0 {
48 t.Fatal("destination kept stale span")
49 }
50 }
51 case "receive":
52 w.receive(Column{Windows: []WindowID{3}}, 1)
53 case "swap":
54 w.Focus = 0
55 w.MoveColumn(1)
56 case "grow":
57 w.GrowColumn(false)
58 case "shrink":
59 w.ShrinkColumn()
60 case "action":
61 w.Apply("set-column-width +1")
62 }
63 for _, c := range w.Columns {
64 if mutation == "swap" {
65 if c.Span == 0 {
66 t.Fatal("swap discarded valid spans")
67 }
68 } else if c.Span != 0 || c.spanStamp != 0 {
69 t.Fatal("stale span", c)
70 }
71 }
72 })
73 }
74 }
75}
76
77func TestSpansCascadeIsolation(t *testing.T) {
78 w := spanWorkspace(OverflowCascade, 5)
79 stampSpans(w, 0, 2)
80 stampSpans(w, 3, 4)
81 w.RemoveWindow(w.Columns[0].Windows[0])
82 if w.Columns[0].Span != 0 || w.Columns[1].Span != 0 {
83 t.Fatal("changed band retained spans")
84 }
85 if w.Columns[2].Span != 5000 || w.Columns[3].Span != 5000 {
86 t.Fatal("unrelated band reset")
87 }
88}
89
90func TestSpanWidths(t *testing.T) {
91 for _, mode := range []Overflow{OverflowFixed, OverflowCascade, OverflowScroll} {
92 w := spanWorkspace(mode, 2)
93 stampSpans(w, 0, 1)
94 w.Columns[0].Span = 3000
95 w.Columns[1].Span = 7000
96 rs := w.columnRects()
97 avail := 970
98 if mode == OverflowScroll {
99 avail = 980
100 }
101 if rs[0].W != avail*3/10 {
102 t.Fatal(mode, rs)
103 }
104 w.SetOutput(1400, 800)
105 if w.columnRects()[0].W != (avail+400)*3/10 {
106 t.Fatal("ratio lost", mode, w.columnRects())
107 }
108 w.Columns[0].FullWidth = true
109 if w.columnWidth(0) != 1380 {
110 t.Fatal("special width lost")
111 }
112 }
113 w := spanWorkspace(OverflowFixed, 4)
114 stampSpans(w, 0, 3)
115 w.scroll()
116 for _, c := range w.Columns {
117 if c.Span != 0 {
118 t.Fatal("spiral spans")
119 }
120 }
121}
122
123func TestSpansDropIntoLane(t *testing.T) {
124 for _, mode := range []Overflow{OverflowFixed, OverflowCascade, OverflowScroll} {
125 w := spanWorkspace(mode, 2)
126 stampSpans(w, 0, 1)
127 src := spanWorkspace(mode, 1)
128 src.Columns[0].Windows[0] = 9
129 m := monitor()
130 m.Workspaces = []*Workspace{w, src}
131 sc := &screen{mon: m}
132 c := &Core{screens: []*screen{sc}}
133 c.drop(9, dropTarget{kind: dropInsert, screen: sc, ws: w, anchor: w.Columns[1].Windows[0]})
134 if len(w.Columns) != 3 {
135 t.Fatal("not dropped", mode, w.Columns)
136 }
137 for _, col := range w.Columns {
138 if col.Span != 0 || col.spanStamp != 0 {
139 t.Fatal("drop retained stamp", mode, col)
140 }
141 }
142 }
143}
144
145func TestSpanLaneIdentity(t *testing.T) {
146 for _, action := range []string{"cross-drop", "within-drop", "within-keyboard"} {
147 t.Run(action, func(t *testing.T) {
148 w := spanWorkspace(OverflowCascade, 4)
149 w.SetMaxColumns(2)
150 for i := range w.Columns {
151 w.Columns[i].BandStart = i%2 == 0
152 }
153 for _, i := range []int{0, 2} {
154 rs := w.columnRects()
155 w.setBorder(border{kind: borderColumn, col: i}, rs[i].X+rs[i].W+5)
156 }
157 for _, col := range w.Columns {
158 if col.Span != 5000 {
159 t.Fatal("not stamped equally", w.Columns)
160 }
161 }
162 cross := action == "cross-drop"
163 if action == "cross-drop" || action == "within-drop" {
164 m := monitor()
165 m.Workspaces = []*Workspace{w}
166 sc := &screen{mon: m}
167 c := &Core{screens: []*screen{sc}}
168 to := 1
169 if cross {
170 to = 2
171 }
172 c.drop(w.Columns[0].Windows[0], dropTarget{kind: dropSwap, screen: sc, ws: w, anchor: w.Columns[to].Windows[0]})
173 } else {
174 w.Focus = 0
175 w.MoveColumn(1)
176 }
177 w.dropStaleSpans()
178 for _, col := range w.Columns {
179 if cross && col.Span != 0 {
180 t.Fatal("cross-band swap retained stamp", w.Columns)
181 }
182 if !cross && col.Span != 5000 {
183 t.Fatal("within-band swap discarded stamp", w.Columns)
184 }
185 }
186 })
187 }
188}
189
190// A column arriving from another workspace carries that workspace's stamp,
191// which may collide with a stamp issued here.
192func TestSpansForeignStampCleared(t *testing.T) {
193 w := spanWorkspace(OverflowScroll, 2)
194 stampSpans(w, 0, 1)
195 stamp := w.spanSeq
196 w.receive(Column{Windows: []WindowID{9}, Span: 3000, spanStamp: stamp}, -1)
197 w.dropStaleSpans()
198 for _, col := range w.Columns {
199 if col.Span != 0 || col.spanStamp != 0 {
200 t.Fatal("foreign stamp joined the lane", w.Columns)
201 }
202 }
203}