v0.4.0 b6be3a8

persist_test.go RawHistoryFolder

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

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