feat/hints a471fa4

persist_test.go RawHistoryFolder

1package core_test
2
3import (
4	"context"
5	"errors"
6	"testing"
7	"time"
8
9	"github.com/bnema/neferwl/internal/core"
10	portsmocks "github.com/bnema/neferwl/internal/mocks/ports"
11	"github.com/bnema/neferwl/internal/ports"
12	"github.com/stretchr/testify/mock"
13)
14
15// steppingClock returns a clock that moves a second forward on each Now:
16// a fullscreen request is never within fullscreenGrace of the mapping.
17func steppingClock(t *testing.T) *portsmocks.MockClock {
18	t.Helper()
19	now := time.Unix(0, 0)
20	clock := portsmocks.NewMockClock(t)
21	clock.EXPECT().Now().RunAndReturn(func() time.Time {
22		now = now.Add(time.Second)
23		return now
24	}).Maybe()
25	// Timers never fire: tests drive events only.
26	clock.EXPECT().NewTimer(mock.Anything).RunAndReturn(func(time.Duration) ports.Timer {
27		timer := portsmocks.NewMockTimer(t)
28		timer.EXPECT().C().Return(make(chan time.Time)).Maybe()
29		timer.EXPECT().Stop().Return(true).Maybe()
30		timer.EXPECT().Reset(mock.Anything).Return(false).Maybe()
31		return timer
32	}).Maybe()
33	return clock
34}
35
36// persistClock returns a clock whose debounce timer fires when the test
37// sends on the returned channel. Each change restarts the timer.
38func persistClock(t *testing.T, debounce time.Duration, changes int) (*portsmocks.MockClock, chan time.Time) {
39	t.Helper()
40	fire := make(chan time.Time)
41	timer := portsmocks.NewMockTimer(t)
42	timer.EXPECT().Stop().Return(true)
43	timer.EXPECT().Reset(debounce).Return(false).Times(changes)
44	timer.EXPECT().C().Return(fire)
45	clock := portsmocks.NewMockClock(t)
46	clock.EXPECT().NewTimer(debounce).Return(timer).Once()
47	return clock, fire
48}
49
50// A burst of scale changes saves only the last scale of each output, once.
51func TestPersistScalesDebounces(t *testing.T) {
52	store := portsmocks.NewMockOutputScaleStore(t)
53	notes := portsmocks.NewMockNotifier(t)
54	store.EXPECT().SaveOutputScale("DP-1", 1.25).Return(nil).Once()
55	store.EXPECT().SaveOutputScale("DP-2", 2.0).Return(nil).Once()
56	notes.EXPECT().Notify("DP-1 scale saved", "Scale 1.25 is kept after restart").Once()
57	notes.EXPECT().Notify("DP-2 scale saved", "Scale 2 is kept after restart").Once()
58	clock, fire := persistClock(t, time.Second, 4)
59	changes := make(chan ports.ScaleChanged)
60	ctx, cancel := context.WithCancel(context.Background())
61	stopped := make(chan struct{})
62	go func() { core.PersistScales(ctx, changes, store, notes, clock, time.Second); close(stopped) }()
63	for _, ev := range []ports.ScaleChanged{{Output: "DP-2", Scale: 1.5}, {Output: "DP-1", Scale: 1.25}, {Output: "DP-2", Scale: 2}} {
64		changes <- ev
65	}
66	fire <- time.Time{}
67	// The next change is received only after the fired batch is saved.
68	changes <- ports.ScaleChanged{Output: "DP-1", Scale: 1.25}
69	cancel()
70	<-stopped
71}
72
73// A failed save is reported and not retried.
74func TestPersistScalesReportsFailure(t *testing.T) {
75	store := portsmocks.NewMockOutputScaleStore(t)
76	notes := portsmocks.NewMockNotifier(t)
77	store.EXPECT().SaveOutputScale("DP-1", 1.5).Return(errors.New("read-only file system")).Once()
78	notes.EXPECT().Notify("DP-1 scale not saved", "read-only file system").Once()
79	clock, fire := persistClock(t, time.Second, 1)
80	changes := make(chan ports.ScaleChanged)
81	ctx, cancel := context.WithCancel(context.Background())
82	stopped := make(chan struct{})
83	go func() { core.PersistScales(ctx, changes, store, notes, clock, time.Second); close(stopped) }()
84	changes <- ports.ScaleChanged{Output: "DP-1", Scale: 1.5}
85	fire <- time.Time{}
86	cancel()
87	<-stopped
88}
89
90// Scale binds report the new scale of the focused output; a bind at the end
91// of the range changes nothing and reports nothing.
92func TestScaleBindReportsScale(t *testing.T) {
93	cfg := scrollDefaults()
94	input := make(chan ports.InputEvent, 8)
95	output := make(chan ports.OutputEvent, 8)
96	scales := make(chan ports.ScaleChanged, 8)
97	scenes := make(chan []ports.Scene, 1)
98	c, err := core.New(cfg, core.Channels{Input: input, Output: output, Commands: make(chan ports.ClientCommand, 64), Scenes: scenes, Scales: scales}, core.Options{})
99	if err != nil {
100		t.Fatal(err)
101	}
102	ctx, cancel := context.WithCancel(context.Background())
103	defer cancel()
104	go c.Run(ctx)
105	output <- ports.OutputAdded{Info: ports.OutputInfo{Name: "DP-2", Width: 5120, Height: 2160}}
106	scene(t, scenes)
107	press := func(sym string, code uint32) {
108		input <- ports.KeyEvent{Keysym: sym, Keycode: code, Mods: ports.ModSuper, Pressed: true}
109		input <- ports.KeyEvent{Keysym: sym, Keycode: code, Mods: ports.ModSuper}
110	}
111	press("minus", 12) // already at 1
112	press("equal", 13)
113	if got := receive(t, scales); got.Output != "DP-2" || got.Scale <= 1 {
114		t.Fatalf("%+v", got)
115	}
116	if len(scales) != 0 {
117		t.Fatalf("scale-down at 1 reported %+v", <-scales)
118	}
119}

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 25

Tags 10

↑↓ moveenter openesc close