main f2d166b

repeat_run_test.go RawHistoryFolder

1package core_test
2
3import (
4	"context"
5	"testing"
6	"testing/synctest"
7	"time"
8
9	"github.com/bnema/neferwl/internal/core"
10	"github.com/bnema/neferwl/internal/ports"
11)
12
13// rig is a running Core's channels and config.
14type rig struct {
15	input    chan ports.InputEvent
16	client   chan ports.ClientEvent
17	reload   chan ports.ConfigChanged
18	commands chan ports.ClientCommand
19	cfg      ports.Config
20}
21
22// repeatRig runs a Core with five windows in a row on one output, the last
23// one focused.
24func repeatRig(t *testing.T) rig {
25	t.Helper()
26	input := make(chan ports.InputEvent, 4)
27	client := make(chan ports.ClientEvent, 8)
28	output := make(chan ports.OutputEvent, 1)
29	reload := make(chan ports.ConfigChanged, 1)
30	commands := make(chan ports.ClientCommand, 256)
31	cfg := scrollDefaults()
32	cfg.Layout.MaxColumns = 5
33	c, err := core.New(cfg, core.Channels{Input: input, Client: client, Output: output, Config: reload, ConfigErrors: make(chan error, 1), Commands: commands, Scenes: make(chan []ports.Scene, 1), Spawn: make(chan ports.SpawnRequest, 4)}, core.Options{})
34	if err != nil {
35		t.Fatal(err)
36	}
37	ctx, cancel := context.WithCancel(context.Background())
38	done := make(chan error, 1)
39	go func() { done <- c.Run(ctx) }()
40	t.Cleanup(func() { cancel(); <-done })
41	output <- ports.OutputAdded{Info: ports.OutputInfo{Name: "OUT-1", Width: 500, Height: 100}}
42	for id := ports.WindowID(1); id <= 5; id++ {
43		client <- ports.WindowMapped{ID: id}
44	}
45	synctest.Wait()
46	return rig{input, client, reload, commands, cfg}
47}
48
49// drain counts the focus changes and activity reports sent since the last
50// drain.
51func drain(commands chan ports.ClientCommand) (focus, activity int) {
52	for len(commands) > 0 {
53		switch (<-commands).(type) {
54		case ports.FocusWindow:
55			focus++
56		case ports.UserActivity:
57			activity++
58		}
59	}
60	return focus, activity
61}
62
63var cmdLeft = ports.KeyEvent{Keysym: "Left", Keycode: 113, Mods: ports.ModSuper, Pressed: true}
64
65// Through Run: a held cmd+Left repeats on its timer and each repeat reports
66// user activity, so idle timers do not fire while a key is held.
67func TestHeldKeyRepeatsThroughRun(t *testing.T) {
68	synctest.Test(t, func(t *testing.T) {
69		r := repeatRig(t)
70		input, commands := r.input, r.commands
71		drain(commands)
72		input <- cmdLeft
73		synctest.Wait()
74		if focus, _ := drain(commands); focus != 1 {
75			t.Fatalf("press: %d focus changes, want 1", focus)
76		}
77		// Past the first repeat and the activity throttle.
78		time.Sleep(time.Second)
79		synctest.Wait()
80		if focus, activity := drain(commands); focus < 2 || activity == 0 {
81			t.Fatalf("held: %d focus changes, %d activity reports", focus, activity)
82		}
83	})
84}
85
86// A held key stops repeating when what it ran no longer applies. Each
87// event lands before the first repeat, while windows remain to the left,
88// so a surviving repeat would move focus or, behind a launcher holding the
89// keyboard, still report activity.
90func TestRepeatStopsThroughRun(t *testing.T) {
91	for _, tt := range []struct {
92		name string
93		send func(rig)
94	}{
95		// The new keymap may swallow the release.
96		{"config reload", func(r rig) { r.reload <- ports.ConfigChanged{Config: r.cfg} }},
97		{"launcher takes the keyboard", func(r rig) {
98			r.client <- ports.LayerChanged{Layers: []ports.LayerSurface{{ID: 9, Layer: ports.LayerOverlay, Width: 100, Height: 40, Keyboard: 1}}}
99		}},
100		{"window inhibits shortcuts", func(r rig) { r.client <- ports.ShortcutsInhibit{Window: 4, Active: true} }},
101	} {
102		t.Run(tt.name, func(t *testing.T) {
103			synctest.Test(t, func(t *testing.T) {
104				r := repeatRig(t)
105				drain(r.commands)
106				r.input <- cmdLeft
107				synctest.Wait()
108				if focus, _ := drain(r.commands); focus != 1 {
109					t.Fatalf("press: %d focus changes, want 1", focus)
110				}
111				tt.send(r)
112				synctest.Wait()
113				drain(r.commands)
114				time.Sleep(time.Second)
115				synctest.Wait()
116				if focus, activity := drain(r.commands); focus != 0 || activity != 0 {
117					t.Fatalf("%d focus changes, %d activity reports, want none", focus, activity)
118				}
119			})
120		})
121	}
122}

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