v0.6.1 3d41bc5

indicator_test.go RawHistoryFolder

1package indicator
2
3import (
4	"context"
5	"strings"
6	"testing"
7	"time"
8
9	"github.com/bnema/neferclient"
10	"github.com/bnema/wlturbo"
11	"github.com/stretchr/testify/assert"
12	"github.com/stretchr/testify/require"
13
14	"github.com/bnema/nefercap/internal/ports"
15)
16
17func TestAppendElapsed(t *testing.T) {
18	for secs, want := range map[int64]string{
19		0: "00:00", 5: "00:05", 65: "01:05", 3599: "59:59", 3600: "1:00:00", 36061: "10:01:01",
20	} {
21		assert.Equal(t, want, string(appendElapsed(nil, secs)), "%d", secs)
22	}
23}
24
25func TestRefreshFollowsTheReadingAndCachesSecond(t *testing.T) {
26	start := time.Unix(100, 0)
27	h := newHUD(start, make(chan struct{}, 1), "")
28	assert.Equal(t, "● REC 00:00", h.refresh(start))
29	assert.Equal(t, "● REC 00:00", h.refresh(start.Add(999*time.Millisecond)))
30	assert.Equal(t, "● REC 01:01", h.refresh(start.Add(61*time.Second)))
31	assert.Equal(t, "● REC 1:00:00", h.refresh(start.Add(time.Hour)))
32	assert.Equal(t, "● REC 00:00", h.refresh(start.Add(-time.Second)), "a reading before start never shows negative time")
33}
34
35func TestTargetLabel(t *testing.T) {
36	start := time.Unix(100, 0)
37	at := start.Add(5 * time.Second)
38	for label, want := range map[string]string{
39		"":                  "● REC 00:05",
40		"DP-1":              "● REC 00:05 · DP-1",
41		"web":               "● REC 00:05 · web",
42		"123456789012":      "● REC 00:05 · 123456789012",
43		"1234567890123":     "● REC 00:05 · 12345678901…",
44		"espace de travail": "● REC 00:05 · espace de t…",
45		"a\nb\x00c":         "● REC 00:05 · abc",
46		"\n\t":              "● REC 00:05",
47	} {
48		assert.Equal(t, want, newHUD(start, make(chan struct{}, 1), label).refresh(at), "%q", label)
49	}
50	// The longest line still fits the 228 px text area (about 29 cells).
51	long := newHUD(start, make(chan struct{}, 1), "wwwwwwwwwwwwwwwwww").refresh(start.Add(time.Hour))
52	assert.LessOrEqual(t, len([]rune(long)), 29)
53}
54
55func TestTargetLabelIsSingleLinePrintable(t *testing.T) {
56	for name, label := range map[string]string{
57		"line separator":      "a\u2028b",
58		"paragraph separator": "a\u2029b",
59		"bidi override":       "a\u202eb",
60		"bidi isolate":        "a\u2066b\u2069",
61		"bidi mark":           "a\u200fb\u061c",
62		"nbsp":                "a\u00a0b",
63		"zero width":          "a\u200bb",
64		"private use":         "a\ue000b",
65		"next line":           "a\u0085b",
66		"unassigned":          "a\U0010ffffb",
67	} {
68		got := suffixFor(label)
69		assert.Equal(t, " · ab", got, name)
70	}
71	assert.Equal(t, " · café 東京", suffixFor("café 東京"), "printable non-ASCII stays")
72	assert.Equal(t, "", suffixFor("\u202e\u2028"))
73}
74
75func TestSuffixLongInput(t *testing.T) {
76	label := strings.Repeat("\x00界", 100000)
77	assert.Equal(t, " · 界界界界界界界界界界界…", suffixFor(label))
78	assert.Equal(t, " · 123456789012", suffixFor("123456789012"+strings.Repeat("\n", 100000)))
79}
80
81var suffixSink string
82
83func TestSuffixAllocations(t *testing.T) {
84	label := strings.Repeat("\x00界", 100000)
85	allocs := testing.AllocsPerRun(100, func() { suffixSink = suffixFor(label) })
86	t.Logf("long suffix: %.0f allocs/run", allocs)
87	if allocs > 1 {
88		t.Fatalf("suffix allocated %.0f times, want only the returned string", allocs)
89	}
90}
91
92func BenchmarkSuffixFor(b *testing.B) {
93	for name, label := range map[string]string{
94		"short": "workspace",
95		"long":  strings.Repeat("\x00界", 100000),
96	} {
97		b.Run(name, func(b *testing.B) {
98			b.ReportAllocs()
99			for b.Loop() {
100				suffixSink = suffixFor(label)
101			}
102		})
103	}
104}
105
106func TestRefreshAllocations(t *testing.T) {
107	if raceEnabled {
108		t.Skip("the race detector adds allocations")
109	}
110	start := time.Unix(100, 0)
111	h := newHUD(start, make(chan struct{}, 1), "workspace-one")
112	at := start.Add(7 * time.Second)
113	h.refresh(at)
114	assert.Equal(t, 0.0, testing.AllocsPerRun(100, func() { h.refresh(at) }))
115}
116
117func TestRequestStopNeverBlocks(t *testing.T) {
118	stop := make(chan struct{}, 1)
119	h := newHUD(time.Now(), stop, "")
120	h.requestStop()
121	h.requestStop() // channel full: the second request is dropped, not blocked
122	assert.Len(t, stop, 1)
123}
124
125func TestLayerConfigTakesNoFocusAndOnlyStopInput(t *testing.T) {
126	c := layerConfig("DP-1", ports.Region{})
127	assert.Equal(t, neferclient.KeyboardNone, c.Keyboard)
128	assert.Equal(t, neferclient.LayerOverlay, c.Level)
129	assert.Equal(t, neferclient.AnchorTop, c.Anchors)
130	assert.Equal(t, [2]int32{Width, Height}, [2]int32{c.Width, c.Height}, "the compositor needs the size on unanchored axes")
131	assert.Equal(t, int32(-1), c.ExclusiveZone)
132	require.Len(t, c.InputRects, 1)
133	r := c.InputRects[0]
134	assert.Equal(t, neferclient.Rect{X: Width - stopWidth, Width: stopWidth, Height: Height}, r)
135	assert.LessOrEqual(t, r.X+r.Width, int32(Width))
136}
137
138func TestLayerConfigCentresOnTheRecordedFrame(t *testing.T) {
139	c := layerConfig("DP-1", ports.Region{X: 100, Y: 50, Width: 1000, Height: 600})
140	assert.Equal(t, neferclient.AnchorTop|neferclient.AnchorLeft, c.Anchors)
141	assert.Equal(t, [4]int32{50 + topMargin, 0, 0, 100 + (1000-Width)/2}, c.Margin)
142	assert.Equal(t, neferclient.KeyboardNone, c.Keyboard)
143	// A frame narrower than the HUD starts at its left edge.
144	c = layerConfig("DP-1", ports.Region{X: 7, Width: 100, Height: 100})
145	assert.Equal(t, int32(7), c.Margin[3])
146	// No frame: the whole output, centred by the compositor.
147	c = layerConfig("DP-1", ports.Region{})
148	assert.Equal(t, neferclient.AnchorTop, c.Anchors)
149	assert.Equal(t, [4]int32{topMargin, 0, 0, 0}, c.Margin)
150}
151
152func TestHUDReadabilityAndStopGeometry(t *testing.T) {
153	assert.Equal(t, 384, Width)
154	assert.Equal(t, 40, Height)
155	assert.Equal(t, 90, stopWidth)
156	assert.Contains(t, styleSheet, "font-size: 1rem")
157	assert.Contains(t, styleSheet, "width: 5.625rem")
158	assert.Contains(t, styleSheet, "line-height: 2.5rem")
159	assert.Contains(t, styleSheet, "padding: 0; line-height:")
160	assert.Equal(t, neferclient.Rect{X: 294, Y: 0, Width: 90, Height: 40}, stopRect())
161}
162
163func BenchmarkRefreshUnchanged(b *testing.B) {
164	start := time.Unix(100, 0)
165	h := newHUD(start, make(chan struct{}, 1), "workspace")
166	h.refresh(start)
167	b.ReportAllocs()
168	for b.Loop() {
169		h.refresh(start)
170	}
171}
172
173func TestRunRejectsBadSetup(t *testing.T) {
174	stop := make(chan struct{}, 1)
175	ok := func(context.Context, *wlturbo.Display, wlturbo.Proxy) error { return nil }
176	out := ports.Output{ID: 1, Name: "DP-1"}
177
178	assert.ErrorIs(t, New(nil, stop).Run(context.Background(), out), ErrNoAuthorize)
179	assert.ErrorIs(t, New(ok, nil).Run(context.Background(), out), ErrNoStop)
180	assert.NotErrorIs(t, New(ok, nil).Run(context.Background(), out), ErrNoAuthorize)
181	assert.ErrorIs(t, New(ok, stop).Run(context.Background(), ports.Output{ID: 1}), ports.ErrOutputNotFound)
182	ctx, cancel := context.WithCancel(context.Background())
183	cancel()
184	assert.ErrorIs(t, New(ok, stop).Run(ctx, out), context.Canceled)
185}

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