detached 7d47882

grid_mode_test.go RawHistoryFolder

1package selection
2
3import (
4	"image"
5	"math"
6	"testing"
7
8	"github.com/stretchr/testify/assert"
9	"github.com/stretchr/testify/require"
10
11	"github.com/bnema/nefercap/internal/core"
12	"github.com/bnema/nefercap/internal/ports"
13)
14
15// modeModel is a one-output overlay whose output mode is mw x mh and whose
16// surface is w x h logical at scale.
17func modeModel(t *testing.T, mw, mh, w, h int, scale float64) *model {
18	t.Helper()
19	sess, _ := testSession()
20	outs := []ports.Output{{ID: 7, Name: "DP-1", Width: mw, Height: mh, Scale: 1}}
21	m := newModel(ports.Screenshot, outs, 0, sess, true)
22	m.resize(w, h, scale)
23	require.Equal(t, core.PickActive, m.picker.Status())
24	return m
25}
26
27// renderedGrid returns the scene's lines as the renderer places them.
28func renderedGrid(m *model) (g core.GridLines) {
29	for i, r := range m.scene().grid {
30		g[i] = physicalEdges(r, m.scale)
31	}
32	return g
33}
34
35func TestGridFollowsTheOutputModeNotTheBuffer(t *testing.T) {
36	// The logical surface is round(mode/scale); its buffer is ceil(logical*scale)
37	// and can be one pixel larger than the mode, which the compositor crops.
38	// The guides must sit on the visible mode's thirds.
39	want, err := core.MonitorGridPhysical(901, 601)
40	require.NoError(t, err)
41	for _, scale := range []float64{1.2, 1.25, 1.5, 2} {
42		w, h := int(math.Round(901/scale)), int(math.Round(601/scale))
43		m := modeModel(t, 901, 601, w, h, scale)
44		got := renderedGrid(m)
45		assert.Equal(t, want, got, "scale %g (%dx%d logical)", scale, w, h)
46		for i, x := range []int{300, 450, 600} {
47			assert.Equal(t, image.Rect(x, 0, x+1, 601), got[i], "scale %g vertical %d", scale, i)
48		}
49		for i, y := range []int{200, 300, 400} {
50			assert.Equal(t, image.Rect(0, y, 901, y+1), got[3+i], "scale %g horizontal %d", scale, i)
51		}
52		bw, _ := core.PhysicalExtent(w, scale)
53		bh, _ := core.PhysicalExtent(h, scale)
54		for i, r := range got {
55			assert.True(t, r.In(image.Rect(0, 0, bw, bh)), "scale %g line %d stays inside the %dx%d buffer", scale, i, bw, bh)
56		}
57	}
58}
59
60func TestGridFallsBackToTheBufferWithoutAMatchingMode(t *testing.T) {
61	buffer, err := core.MonitorGrid(751, 501, 1.2)
62	require.NoError(t, err)
63	for name, c := range map[string]struct{ mw, mh int }{
64		"unknown mode":      {0, 0},
65		"negative mode":     {-901, -601},
66		"width mismatches":  {1920, 601},
67		"height mismatches": {901, 1080},
68		"stale config":      {1920, 1080},
69		"one pixel off":     {903, 601}, // 903/1.2 rounds to 753
70	} {
71		m := modeModel(t, c.mw, c.mh, 751, 501, 1.2)
72		assert.Equal(t, buffer, renderedGrid(m), name)
73	}
74}
75
76func TestGridModeIsCheckedAgainstEveryResize(t *testing.T) {
77	m := modeModel(t, 901, 601, 751, 501, 1.2)
78	mode, err := core.MonitorGridPhysical(901, 601)
79	require.NoError(t, err)
80	assert.Equal(t, mode, renderedGrid(m))
81
82	m.resize(751, 501, 1.25) // scale only: 901/1.25 is no longer 751
83	buffer, err := core.MonitorGrid(751, 501, 1.25)
84	require.NoError(t, err)
85	assert.Equal(t, buffer, renderedGrid(m), "the mode no longer explains the surface")
86
87	m.resize(721, 481, 1.25) // round(901/1.25) x round(601/1.25)
88	mode, err = core.MonitorGridPhysical(901, 601)
89	require.NoError(t, err)
90	assert.Equal(t, mode, renderedGrid(m), "and matches again when it does")
91}
92
93func TestGridUsesRotatedModeWhenItMatchesSwapped(t *testing.T) {
94	// A mode reported as 601x901 with a surface of 751x501 logical.
95	m := modeModel(t, 601, 901, 751, 501, 1.2)
96	want, err := core.MonitorGridPhysical(901, 601)
97	require.NoError(t, err)
98	assert.Equal(t, want, renderedGrid(m))
99
100	// An unrelated swapped mode is ignored.
101	m = modeModel(t, 1080, 1920, 751, 501, 1.2)
102	buffer, err := core.MonitorGrid(751, 501, 1.2)
103	require.NoError(t, err)
104	assert.Equal(t, buffer, renderedGrid(m))
105}
106
107func TestGridBufferMoreThanOnePixelPastTheModeFallsBack(t *testing.T) {
108	// At scale 3 the surface is round(901/3) = 300 wide, a 900 buffer: the
109	// mode is larger than the buffer, so the compositor resamples and the
110	// mode is not a safe position source.
111	m := modeModel(t, 901, 601, 300, 200, 3)
112	buffer, err := core.MonitorGrid(300, 200, 3)
113	require.NoError(t, err)
114	assert.Equal(t, buffer, renderedGrid(m))
115
116	// A surface size the mode does not round to: 1000/3 is 333, not 334.
117	m = modeModel(t, 1000, 1000, 334, 334, 3)
118	buffer, err = core.MonitorGrid(334, 334, 3)
119	require.NoError(t, err)
120	assert.Equal(t, buffer, renderedGrid(m))
121}
122
123func TestGridModePerOutputIsIndependent(t *testing.T) {
124	sess, _ := testSession()
125	outs := []ports.Output{
126		{ID: 7, Name: "DP-1", Width: 901, Height: 601, Scale: 1},
127		{ID: 9, Name: "HDMI-A-1", Width: 1280, Height: 720, Scale: 1},
128	}
129	a := newModel(ports.Screenshot, outs, 0, sess, true)
130	b := newModel(ports.Screenshot, outs, 1, sess, true)
131	a.resize(751, 501, 1.2)
132	b.resize(1280, 720, 1)
133	wantA, err := core.MonitorGridPhysical(901, 601)
134	require.NoError(t, err)
135	wantB, err := core.MonitorGridPhysical(1280, 720)
136	require.NoError(t, err)
137	assert.Equal(t, wantA, renderedGrid(a))
138	assert.Equal(t, wantB, renderedGrid(b))
139}
140
141func TestGridModeAllocations(t *testing.T) {
142	if raceEnabled {
143		t.Skip("the race detector adds allocations")
144	}
145	m := modeModel(t, 901, 601, 751, 501, 1.2)
146	n := testing.AllocsPerRun(200, func() {
147		m.resize(751, 501, 1.2)
148		m.resize(751, 501, 1.25)
149		_ = m.scene()
150	})
151	assert.Zero(t, n)
152}

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 9

↑↓ moveenter openesc close