v0.6.1 aa12db3

instances_test.go RawHistoryFolder

1package render
2
3import (
4	"github.com/bnema/nefergui/internal/css"
5	"github.com/bnema/nefergui/internal/layout"
6	"github.com/bnema/nefergui/internal/text"
7	"image"
8	"reflect"
9	"testing"
10)
11
12func TestImageBatchOrder(t *testing.T) {
13	a, b := image.NewRGBA(image.Rect(0, 0, 2, 2)), image.NewNRGBA(image.Rect(0, 0, 2, 2))
14	frame := Frame{Quads: []Quad{{Op: "rect"}, {Op: "glyph"}, {Op: "image", Image: a, Color: css.Color{A: .5}}, {Op: "image", Image: b}, {Op: "rect"}, {Op: "border"}, {Op: "image", Image: a}}}
15	instances, batches, stats := ListBatches(frame)
16	if len(instances) != 7 || stats.Drawn != 7 || len(batches) != 5 {
17		t.Fatalf("instances=%d batches=%+v stats=%+v", len(instances), batches, stats)
18	}
19	for i, want := range []struct {
20		first, count uint32
21		source       image.Image
22	}{{0, 2, nil}, {2, 1, a}, {3, 1, b}, {4, 2, nil}, {6, 1, a}} {
23		if batches[i].First != want.first || batches[i].Count != want.count || batches[i].Source != want.source {
24			t.Fatalf("batch %d: %+v", i, batches[i])
25		}
26	}
27	if instances[2].KindLayer[0] != 6 || instances[2].UV != [4]float32{0, 0, 1, 1} || instances[2].Color[3] != .5 {
28		t.Fatalf("image instance: %+v", instances[2])
29	}
30}
31
32func TestListInstances(t *testing.T) {
33	frame := Frame{Quads: []Quad{
34		{Op: "rect", Rect: layout.Rect{X: 1, Y: 2, W: 3, H: 4}, Clip: layout.Rect{W: 10, H: 10}, Color: css.Color{R: 1, G: .5, A: .5}, Radii: [4]float64{5, 6, 7, 8}},
35		{Op: "border"}, {Op: "glyph", Glyph: text.Placement{Page: 2, Rect: image.Rect(10, 20, 14, 26)}},
36	}}
37	got, stats := ListInstances(frame)
38	if stats.Drawn != 3 || len(stats.Skipped) != 0 || len(got) != 3 {
39		t.Fatalf("stats=%+v got=%+v", stats, got)
40	}
41	if got[0].Bounds != [4]float32{1, 2, 3, 4} || got[0].Clip != [4]float32{0, 0, 10, 10} || got[0].Color != [4]float32{1, .5, 0, .5} || got[0].Radii != [4]float32{5, 6, 7, 8} {
42		t.Fatalf("instance=%+v", got[0])
43	}
44	if got[2].KindLayer != [4]float32{1, 2} || got[2].UV != [4]float32{10.0 / 1024, 20.0 / 1024, 14.0 / 1024, 26.0 / 1024} {
45		t.Fatalf("glyph=%+v", got[2])
46	}
47}
48
49func TestListBufferMatchesListBatches(t *testing.T) {
50	a := image.NewRGBA(image.Rect(0, 0, 2, 2))
51	frames := []Frame{
52		listFixture(200),
53		{Quads: []Quad{{Op: "rect"}, {Op: "mystery"}, {Op: "image", Image: a}, {Op: "mystery"}, {Op: "glyph"}, {Op: "glyph"}}},
54		{},
55		listFixture(7),
56	}
57	var buf ListBuffer
58	for round := 0; round < 2; round++ {
59		for i, f := range frames {
60			wi, wb, ws := ListBatches(f)
61			gi, gb, gs := buf.List(f)
62			if !reflect.DeepEqual(wi, gi) || !reflect.DeepEqual(wb, gb) || !reflect.DeepEqual(ws, gs) {
63				t.Fatalf("round %d frame %d differs:\nwant %v %v %v\ngot  %v %v %v", round, i, len(wi), wb, ws, len(gi), gb, gs)
64			}
65		}
66	}
67}
68
69// A result stays valid until the next List; a later smaller frame must not
70// leave stale instances, batches, image references or skip counts visible.
71func TestListBufferReuseLifetime(t *testing.T) {
72	a := image.NewRGBA(image.Rect(0, 0, 2, 2))
73	var buf ListBuffer
74	i1, b1, s1 := buf.List(Frame{Quads: []Quad{{Op: "rect"}, {Op: "image", Image: a}, {Op: "nope"}}})
75	if len(i1) != 2 || len(b1) != 2 || s1.Skipped["nope"] != 1 {
76		t.Fatalf("first: %v %v %v", i1, b1, s1)
77	}
78	i2, b2, s2 := buf.List(Frame{Quads: []Quad{{Op: "glyph"}}})
79	if len(i2) != 1 || len(b2) != 1 || b2[0].Source != nil || len(s2.Skipped) != 0 || i2[0].KindLayer[0] != 1 {
80		t.Fatalf("second: %v %v %v", i2, b2, s2)
81	}
82	buf.Release()
83	for _, batch := range b2[:cap(b2)] {
84		if batch.Source != nil {
85			t.Fatal("Release left an image reference")
86		}
87	}
88	// Results from the non-buffer API never alias the buffer.
89	fi, fb, _ := ListBatches(Frame{Quads: []Quad{{Op: "rect"}}})
90	buf.List(listFixture(50))
91	if fi[0].KindLayer != [4]float32{} || len(fb) != 1 || fb[0].Count != 1 {
92		t.Fatalf("ListBatches result aliased: %v %v", fi, fb)
93	}
94}
95
96func TestListBufferDropsOversizedStorage(t *testing.T) {
97	var buf ListBuffer
98	buf.List(listFixture(20000))
99	if _, _, _ = buf.List(listFixture(10)); cap(buf.instances) > 4*10+64 {
100		t.Fatalf("retained %d instances for 10 quads", cap(buf.instances))
101	}
102}
103
104func TestListBatchesAllocations(t *testing.T) {
105	frame := listFixture(2000)
106	if n := testing.AllocsPerRun(20, func() { ListBatches(frame) }); n > 4 {
107		t.Fatalf("ListBatches allocs=%v, want <= 4 (instances, batches, skipped map)", n)
108	}
109	var buf ListBuffer
110	buf.List(frame)
111	if n := testing.AllocsPerRun(20, func() { buf.List(frame) }); n != 0 {
112		t.Fatalf("ListBuffer.List allocs=%v, want 0 after warmup", n)
113	}
114}

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 1

Tags 12

↑↓ moveenter openesc close