main ab6686c

frame_test.go RawHistoryFolder

1package core
2
3import (
4	"math"
5	"reflect"
6	"testing"
7)
8
9func TestFrameCanonicalOffsetsAndInvariants(t *testing.T) {
10	f := NewFrame(4, 3)
11	for y := range f.Height {
12		if f.page.rows[y] != uint32(y*f.Width) {
13			t.Fatalf("row offset %d = %d, want %d", y, f.page.rows[y], y*f.Width)
14		}
15	}
16	if err := f.CheckInvariants(); err != nil {
17		t.Fatalf("fresh frame invariants: %v", err)
18	}
19}
20
21func TestFrameRowReturnsOwnedCells(t *testing.T) {
22	f := NewFrame(2, 1)
23	f.Set(0, 0, Cell{Rune: 'A'})
24
25	row := f.Row(0)
26	row[0] = BlankCell()
27
28	if got := f.Cell(0, 0).Rune; got != 'A' {
29		t.Fatalf("mutating Row result changed frame cell to %q", got)
30	}
31}
32
33func TestFrameRowMutationOperationsUseLogicalRows(t *testing.T) {
34	f := NewFrame(5, 2)
35	f.WriteRow(0, 0, []Cell{{Rune: 'A'}, {Rune: 'B'}, {Rune: 'C'}, {Rune: 'D'}, {Rune: 'E'}})
36	f.ScrollDown(0, 1, 1)
37
38	f.CopyRow(1, 2, 0, 3)
39	f.FillRow(1, 0, 2, BlankCell())
40
41	got := f.Row(1)
42	want := []rune{' ', ' ', 'A', 'B', 'C'}
43	for x, r := range want {
44		if got[x].Rune != r {
45			t.Fatalf("cell %d = %q, want %q", x, got[x].Rune, r)
46		}
47	}
48}
49
50func TestStoredCellFitsCompactTarget(t *testing.T) {
51	if got := reflect.TypeOf(storedCell{}).Size(); got < 8 || got > 16 {
52		t.Fatalf("storedCell size = %d bytes, want 8..16", got)
53	}
54}
55
56func TestFrameInternsCanonicalStylesLocally(t *testing.T) {
57	f := NewFrame(3, 1)
58	first := Style{Bold: true, Foreground: 2, ForegroundRGB: RGB{R: 9, G: 8, B: 7}}
59	second := first
60	second.ForegroundRGB = RGB{R: 1, G: 2, B: 3}
61
62	f.Set(0, 0, Cell{Rune: 'a', Style: first})
63	f.Set(1, 0, Cell{Rune: 'b', Style: second})
64	f.Set(2, 0, Cell{Rune: 'c', Style: Style{}})
65
66	if f.page.cells[0].styleID != f.page.cells[1].styleID {
67		t.Fatal("semantically equal styles received different page-local IDs")
68	}
69	if f.page.cells[2].styleID == 0 {
70		t.Fatal("zero Style collapsed into default style ID zero")
71	}
72	if got := f.StyleCount(); got != 3 {
73		t.Fatalf("StyleCount() = %d, want default plus two semantic styles", got)
74	}
75	if err := f.CheckInvariants(); err != nil {
76		t.Fatalf("frame invariants: %v", err)
77	}
78}
79
80func TestFrameStyleChurnReusesUnreferencedSlots(t *testing.T) {
81	f := NewFrame(1, 1)
82	for i := range 1000 {
83		f.Set(0, 0, Cell{Rune: 'x', Style: Style{Foreground: i}})
84	}
85	if got := f.StyleCount(); got != 2 {
86		t.Fatalf("StyleCount() after churn = %d, want default plus live style", got)
87	}
88	if got := len(f.page.styles); got > 3 {
89		t.Fatalf("style slots after churn = %d, want at most 3", got)
90	}
91	f.Set(0, 0, BlankCell())
92	if got := f.StyleCount(); got != 1 {
93		t.Fatalf("StyleCount() after clear = %d, want default only", got)
94	}
95	if err := f.CheckInvariants(); err != nil {
96		t.Fatalf("frame invariants: %v", err)
97	}
98}
99
100func TestFrameCrossPageWriteRemapsStyles(t *testing.T) {
101	src := NewFrame(2, 1)
102	src.Set(0, 0, Cell{Rune: 'a', Style: Style{Bold: true, Foreground: 4}})
103	src.Set(1, 0, Cell{Rune: 'b', Style: Style{Italic: true, Background: 7}})
104	dst := NewFrame(2, 1)
105	dst.Set(0, 0, Cell{Rune: 'x', Style: Style{Foreground: 99}})
106
107	dst.WriteRow(0, 0, src.Row(0))
108
109	for x := range 2 {
110		if !dst.Cell(x, 0).Equal(src.Cell(x, 0)) {
111			t.Fatalf("cell %d changed crossing pages: got %+v want %+v", x, dst.Cell(x, 0), src.Cell(x, 0))
112		}
113	}
114	if err := dst.CheckInvariants(); err != nil {
115		t.Fatalf("destination invariants: %v", err)
116	}
117}
118
119func TestFrameClonePreservesStructureWithoutAliasing(t *testing.T) {
120	f := NewFrame(3, 2)
121	f.ScrollUp(0, 1, 1)
122	f.Set(0, 0, Cell{Rune: 'a', Style: Style{Foreground: 1}})
123	f.Set(1, 0, Cell{Rune: 'b', Style: Style{Foreground: 2}})
124	f.Set(1, 0, BlankCell()) // leave one reusable style slot
125
126	clone := f.Clone()
127	if clone.page == f.page || &clone.page.cells[0] == &f.page.cells[0] || &clone.page.rows[0] == &f.page.rows[0] {
128		t.Fatal("Clone shares page slice storage")
129	}
130	if clone.page.rows[0] != f.page.rows[0] || clone.page.cells[clone.offset(0, 0)].styleID != f.page.cells[f.offset(0, 0)].styleID {
131		t.Fatal("Clone changed row rotation or page-local style IDs")
132	}
133	clone.Set(0, 0, Cell{Rune: 'x', Style: Style{Bold: true}})
134	if f.Cell(0, 0).Rune != 'a' {
135		t.Fatal("mutating clone changed source cells")
136	}
137	if _, ok := f.page.styleIndex[Style{Bold: true}.Canonical()]; ok {
138		t.Fatal("mutating clone changed source style map")
139	}
140	if err := clone.CheckInvariants(); err != nil {
141		t.Fatalf("clone invariants: %v", err)
142	}
143}
144
145func TestCheckInvariantsValidatesFreeStyleList(t *testing.T) {
146	newFrameWithFreeStyle := func() Frame {
147		f := NewFrame(1, 1)
148		f.Set(0, 0, Cell{Style: Style{Foreground: 1}})
149		f.Set(0, 0, BlankCell())
150		return f
151	}
152	tests := map[string]func(*Frame){
153		"duplicate": func(f *Frame) { f.page.freeStyles = append(f.page.freeStyles, f.page.freeStyles[0]) },
154		"missing":   func(f *Frame) { f.page.freeStyles = nil },
155		"used":      func(f *Frame) { f.page.freeStyles = append(f.page.freeStyles, 0) },
156		"out of range": func(f *Frame) {
157			f.page.freeStyles = append(f.page.freeStyles, uint32(len(f.page.styles)))
158		},
159	}
160	for name, corrupt := range tests {
161		t.Run(name, func(t *testing.T) {
162			f := newFrameWithFreeStyle()
163			corrupt(&f)
164			if err := f.CheckInvariants(); err == nil {
165				t.Fatal("CheckInvariants accepted corrupt free style list")
166			}
167		})
168	}
169}
170
171func TestFrameCellCountRejectsPortableOverflow(t *testing.T) {
172	if _, ok := frameCellCount(math.MaxInt, 2); ok {
173		t.Fatal("accepted dimensions whose product exceeds int or uint32")
174	}
175	if _, ok := frameCellCount(65_536, 65_536); ok {
176		t.Fatal("accepted dimensions whose product exceeds uint32")
177	}
178}
179
180func TestFrameLogicalBytesAreDeterministic(t *testing.T) {
181	f := NewFrame(3, 2)
182	want := uint64(6*StoredCellLogicalBytes + 2*RowDescriptorLogicalBytes + StyleRecordLogicalBytes)
183	if got := f.LogicalBytes(); got != want {
184		t.Fatalf("LogicalBytes() = %d, want %d", got, want)
185	}
186	f.Set(0, 0, Cell{Rune: 'x', Style: Style{Bold: true}})
187	if got := f.LogicalBytes(); got != want+StyleRecordLogicalBytes {
188		t.Fatalf("LogicalBytes() with one interned style = %d, want %d", got, want+StyleRecordLogicalBytes)
189	}
190}
191
192func TestCheckInvariantsDetectsBrokenRotation(t *testing.T) {
193	f := NewFrame(4, 3)
194	f.page.rows[1] = f.page.rows[0]
195	if err := f.CheckInvariants(); err == nil {
196		t.Fatal("expected duplicate physical row to be rejected")
197	}
198
199	f = NewFrame(4, 3)
200	f.page.rows[0] = 1
201	if err := f.CheckInvariants(); err == nil {
202		t.Fatal("expected non-multiple offset to be rejected")
203	}
204
205	f = NewFrame(4, 3)
206	f.page.rows[2] = 99
207	if err := f.CheckInvariants(); err == nil {
208		t.Fatal("expected out-of-range offset to be rejected")
209	}
210}
211
212func TestReplacePreservesSelfAliasedScrolledRows(t *testing.T) {
213	f := NewFrame(3, 3)
214	for y, row := range []string{"abc", "def", "ghi"} {
215		for x, r := range row {
216			f.Set(x, y, Cell{Rune: r})
217		}
218	}
219	f.ScrollUp(0, 2, 1)
220	want := [][]Cell{f.Row(0), f.Row(1), f.Row(2)}
221
222	f.Replace(f)
223
224	for y := range want {
225		got := f.Row(y)
226		for x := range want[y] {
227			if !got[x].Equal(want[y][x]) {
228				t.Fatalf("cell (%d,%d) = %+v, want %+v", x, y, got[x], want[y][x])
229			}
230		}
231	}
232	if err := f.CheckInvariants(); err != nil {
233		t.Fatalf("replaced frame invariants: %v", err)
234	}
235}
236
237func TestReplacePreservesLogicalRows(t *testing.T) {
238	dst := NewFrame(3, 3)
239	dst.Set(0, 0, Cell{Rune: 'x'})
240	dst.ScrollUp(0, 2, 1)
241
242	src := NewFrame(3, 3)
243	for y, row := range []string{"abc", "def", "ghi"} {
244		for x, r := range row {
245			src.Set(x, y, Cell{Rune: r})
246		}
247	}
248	src.ScrollUp(0, 2, 1)
249
250	dst.Replace(src)
251	for y, want := range []string{"def", "ghi", "   "} {
252		for x, r := range want {
253			if got := dst.At(x, y).Rune; got != r {
254				t.Fatalf("row %d col %d = %q, want %q", y, x, got, r)
255			}
256		}
257	}
258	if err := dst.CheckInvariants(); err != nil {
259		t.Fatalf("replaced frame invariants: %v", err)
260	}
261}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 13

↑↓ moveenter openesc close