workspaces_test.go RawHistoryFolder

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

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