feat/hints a471fa4

workspaces_test.go RawHistoryFolder

1package core_test
2
3import (
4	"context"
5	"testing"
6
7	"github.com/bnema/neferwl/internal/core"
8	"github.com/bnema/neferwl/internal/ports"
9)
10
11func TestWorkspaceChannelCapacity(t *testing.T) {
12	_, err := core.New(scrollDefaults(), core.Channels{Scenes: make(chan []ports.Scene, 1), Workspaces: make(chan ports.Workspaces, 2)}, core.Options{})
13	if err == nil {
14		t.Fatal("expected capacity-1 validation")
15	}
16}
17
18func TestWorkspaceSnapshotsAndActivation(t *testing.T) {
19	cfg := scrollDefaults()
20	cfg.Workspaces = append(cfg.Workspaces, ports.WorkspaceConfig{Name: "dev"})
21	client := make(chan ports.ClientEvent, 16)
22	input := make(chan ports.InputEvent, 8)
23	output := make(chan ports.OutputEvent, 8)
24	commands := make(chan ports.ClientCommand, 32)
25	snapshots := make(chan ports.Workspaces, 1)
26	scenes := make(chan []ports.Scene, 1)
27	state := make(chan ports.State, 1)
28	spawn := make(chan ports.SpawnRequest, 8)
29	c, err := core.New(cfg, core.Channels{Client: client, Input: input, Output: output, Commands: commands, Workspaces: snapshots, State: state, Scenes: scenes, Spawn: spawn}, core.Options{})
30	if err != nil {
31		t.Fatal(err)
32	}
33	ctx, cancel := context.WithCancel(context.Background())
34	done := make(chan error, 1)
35	go func() { done <- c.Run(ctx) }()
36	t.Cleanup(func() {
37		cancel()
38		if err := <-done; err != nil {
39			t.Error(err)
40		}
41	})
42	output <- ports.OutputAdded{Info: ports.OutputInfo{Name: "A", Width: 100, Height: 80}}
43	first := receive(t, snapshots)
44	for len(first.Outputs) == 0 {
45		first = receive(t, snapshots)
46	}
47	if len(first.Outputs) != 1 || len(first.Outputs[0].Workspaces) < 2 || !first.Outputs[0].Workspaces[0].Active {
48		t.Fatal(first)
49	}
50	hidden := first.Outputs[0].Workspaces[len(first.Outputs[0].Workspaces)-1]
51	if hidden.Configured != "dev" || !hidden.Hidden {
52		t.Fatalf("configured hidden workspace: %+v", hidden)
53	}
54	id := first.Outputs[0].Workspaces[0].ID
55	client <- ports.WindowMapped{ID: 1}
56	added := receive(t, snapshots)
57	if len(added.Outputs[0].Workspaces) < 2 || added.Outputs[0].Workspaces[0].ID != id || !added.Outputs[0].Workspaces[1].Hidden {
58		t.Fatal(added)
59	}
60	spare := added.Outputs[0].Workspaces[1].ID
61	output <- ports.OutputAdded{Info: ports.OutputInfo{Name: "B", Width: 100, Height: 80}}
62	two := receive(t, snapshots)
63	if len(two.Outputs) != 2 || two.Outputs[0].Workspaces[0].ID != id {
64		t.Fatal(two)
65	}
66	// Activation on B also focuses B, without disturbing A's active workspace.
67	bID := two.Outputs[1].Workspaces[0].ID
68	client <- ports.WorkspaceActivate{IDs: []uint64{bID}}
69	for receive(t, state).Output != "B" {
70	}
71	// Both requests are applied in order in one event: A wins final focus.
72	client <- ports.WorkspaceActivate{IDs: []uint64{bID, spare}}
73	for receive(t, state).Output != "A" {
74	}
75	active := receive(t, snapshots)
76	if !active.Outputs[0].Workspaces[1].Active || active.Outputs[1].Workspaces[0].Active != true {
77		t.Fatal(active)
78	}
79	client <- ports.WorkspaceActivate{IDs: []uint64{999999999}}
80	scene(t, scenes)
81	select {
82	case v := <-snapshots:
83		t.Fatalf("unknown ID changed snapshot: %v", v)
84	default:
85	}
86	// Move the populated workspace right; its ID follows it.
87	client <- ports.WorkspaceActivate{IDs: []uint64{id}}
88	receive(t, snapshots)
89	input <- ports.KeyEvent{Keysym: "Right", Mods: ports.ModSuper | ports.ModCtrl | ports.ModShift, Pressed: true}
90	move := receive(t, snapshots)
91	found := false
92	for _, w := range move.Outputs[1].Workspaces {
93		if w.ID == id {
94			found = true
95		}
96	}
97	if !found {
98		t.Fatalf("workspace ID lost on move: %+v", move)
99	}
100	output <- ports.OutputRemoved{Name: "B"}
101	moved := receive(t, snapshots)
102	if len(moved.Outputs) != 1 || moved.Outputs[0].Name != "A" {
103		t.Fatal(moved)
104	}
105}
106
107func TestMoveWorkspaceReordersSnapshot(t *testing.T) {
108	cfg := scrollDefaults()
109	client := make(chan ports.ClientEvent, 16)
110	input := make(chan ports.InputEvent, 8)
111	output := make(chan ports.OutputEvent, 8)
112	snapshots := make(chan ports.Workspaces, 1)
113	scenes := make(chan []ports.Scene, 1)
114	spawn := make(chan ports.SpawnRequest, 8)
115	commands := make(chan ports.ClientCommand, 32)
116	c, err := core.New(cfg, core.Channels{Client: client, Input: input, Output: output, Commands: commands, Workspaces: snapshots, Scenes: scenes, Spawn: spawn}, core.Options{})
117	if err != nil {
118		t.Fatal(err)
119	}
120	ctx, cancel := context.WithCancel(context.Background())
121	done := make(chan error, 1)
122	go func() { done <- c.Run(ctx) }()
123	go func() {
124		for {
125			select {
126			case <-scenes:
127			case <-commands:
128			case <-spawn:
129			case <-ctx.Done():
130				return
131			}
132		}
133	}()
134	t.Cleanup(func() {
135		cancel()
136		if err := <-done; err != nil {
137			t.Error(err)
138		}
139	})
140	output <- ports.OutputAdded{Info: ports.OutputInfo{Name: "A", Width: 100, Height: 80}}
141	until := func(ok func(ws []ports.WorkspaceInfo) bool) []ports.WorkspaceInfo {
142		t.Helper()
143		for {
144			v := receive(t, snapshots)
145			if len(v.Outputs) > 0 && ok(v.Outputs[0].Workspaces) {
146				return v.Outputs[0].Workspaces
147			}
148		}
149	}
150	client <- ports.WindowMapped{ID: 1}
151	until(func(ws []ports.WorkspaceInfo) bool { return len(ws) >= 2 })
152	input <- ports.KeyEvent{Keysym: "Next", Mods: ports.ModSuper, Pressed: true}
153	until(func(ws []ports.WorkspaceInfo) bool { return ws[1].Active })
154	client <- ports.WindowMapped{ID: 2}
155	ws := until(func(ws []ports.WorkspaceInfo) bool { return len(ws) >= 3 && ws[1].Active })
156	before := ports.Workspaces{Outputs: []ports.WorkspaceOutput{{Workspaces: ws}}}
157	first, second := before.Outputs[0].Workspaces[0].ID, before.Outputs[0].Workspaces[1].ID
158	input <- ports.KeyEvent{Keysym: "Up", Mods: ports.ModSuper | ports.ModCtrl | ports.ModShift, Pressed: true}
159	ws = until(func(ws []ports.WorkspaceInfo) bool { return ws[0].Active })
160	if ws[0].ID != second || ws[1].ID != first || !ws[0].Active || ws[0].Index != 0 || ws[1].Index != 1 {
161		t.Fatalf("%+v", ws)
162	}
163}

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