v0.4.0 b6be3a8

drag_internal_test.go RawHistoryFolder

1package core
2
3import (
4	"context"
5	"slices"
6	"testing"
7
8	"github.com/bnema/neferwl/internal/ports"
9)
10
11func TestResized(t *testing.T) {
12	u := Rect{W: 1000, H: 800}
13	r := Rect{X: 100, Y: 100, W: 300, H: 200}
14	for _, tc := range []struct {
15		edges  ports.ResizeEdges
16		dx, dy int
17		want   Rect
18	}{
19		{ports.ResizeRight | ports.ResizeBottom, 50, 20, Rect{X: 100, Y: 100, W: 350, H: 220}},
20		{ports.ResizeLeft | ports.ResizeTop, 50, 20, Rect{X: 150, Y: 120, W: 250, H: 180}},
21		{ports.ResizeLeft, -500, 0, Rect{X: 0, Y: 100, W: 400, H: 200}},
22		{ports.ResizeRight, -1000, 0, Rect{X: 100, Y: 100, W: 64, H: 200}},
23		{ports.ResizeBottom, 0, 2000, Rect{X: 100, Y: 100, W: 300, H: 700}},
24	} {
25		if got := resized(r, tc.edges, tc.dx, tc.dy, 64, u); got != tc.want {
26			t.Errorf("%v %d,%d: %+v, want %+v", tc.edges, tc.dx, tc.dy, got, tc.want)
27		}
28	}
29}
30
31func TestScrollBy(t *testing.T) {
32	w := workspace()
33	w.SetOutput(1000, 800)
34	w.SetGaps(10)
35	for id := WindowID(1); id <= 4; id++ {
36		w.AddWindow(id)
37	}
38	w.FocusID(1)
39	if w.ViewX != 0 {
40		t.Fatal(w.ViewX)
41	}
42	if w.scrollBy(-1) {
43		t.Fatal("scrolled past the start")
44	}
45	steps := 0
46	for w.scrollBy(1) {
47		steps++
48		if steps > 10 {
49			t.Fatal("no end")
50		}
51	}
52	if steps != 2 {
53		t.Fatal(steps)
54	}
55	last := w.columnX(3) + w.columnWidth(3) - w.ViewX
56	if last != w.Usable.X+w.Usable.W-w.gap() {
57		t.Fatal(last)
58	}
59	// Focus did not change.
60	if id, _ := w.Focused(); id != 1 {
61		t.Fatal(id)
62	}
63	for w.scrollBy(-1) {
64	}
65	if w.ViewX != 0 {
66		t.Fatal(w.ViewX)
67	}
68}
69
70func TestDropInHiddenColumns(t *testing.T) {
71	w := workspace()
72	w.SetOutput(1000, 800)
73	w.SetGaps(10)
74	w.Overflow = OverflowFixed
75	w.SetMaxColumns(3)
76	for id := WindowID(1); id <= 3; id++ {
77		w.AddWindow(id)
78	}
79	w.FocusID(2)
80	w.ToggleFullWidth()
81	// Columns 1 and 3 are hidden by the maximized one: the point where
82	// column 3 was hits 2 now, a swap with itself: nothing.
83	if tgt := w.dropIn(2, 900, 400, 4); tgt.kind != dropNone {
84		t.Fatalf("%+v", tgt)
85	}
86	// Dragging 1 (hidden) over 2: top band stacks above 2.
87	if tgt := w.dropIn(1, 500, 30, 4); tgt.kind != dropAbove || tgt.anchor != 2 {
88		t.Fatalf("%+v", tgt)
89	}
90}
91
92// dragCore is a core with one 1000x600 output and no channels but the
93// scene and command ones; tests drive its methods directly.
94func dragCore(t *testing.T) *Core {
95	t.Helper()
96	cfg := ports.Config{}
97	cfg.Keyboard.CmdKey = "super"
98	cfg.Layout.MaxColumns = 3
99	c, err := New(cfg, Channels{Scenes: make(chan []ports.Scene, 1), Commands: make(chan ports.ClientCommand, 1024)})
100	if err != nil {
101		t.Fatal(err)
102	}
103	c.cur().info = ports.OutputInfo{Name: "OUT", Width: 1000, Height: 600}
104	c.cur().mon.SetOutput(1000, 600)
105	return c
106}
107
108func TestCancelKeepsFloatRaised(t *testing.T) {
109	c := dragCore(t)
110	w := c.cur().mon.Current()
111	w.AddWindow(1)
112	w.AddFloating(2, 1000, 600)
113	w.FocusID(1)
114	if !w.Floats[0].below {
115		t.Fatal("not below")
116	}
117	if err := c.beginDrag(context.Background(), 2, btnLeft, false, 0); err != nil {
118		t.Fatal(err)
119	}
120	c.cancelDrag()
121	if w.Floats[0].below {
122		t.Fatal("escape put the focused float behind the tiles")
123	}
124}
125
126func TestDropReleasesSlot(t *testing.T) {
127	c := dragCore(t)
128	w := c.cur().mon.Current()
129	w.Columns = []Column{{Windows: []WindowID{1}, Slot: 1}, {Windows: []WindowID{2}}}
130	key := slotKey{workspace: w.Name, index: 1}
131	c.slots[key] = &slotState{window: 1}
132	ctx := context.Background()
133	if err := c.beginDrag(ctx, 1, btnLeft, false, 0); err != nil {
134		t.Fatal(err)
135	}
136	c.drag.target = dropTarget{}
137	r, _ := w.placed(2)
138	c.cursorX, c.cursorY = float64(r.X+r.W/2), float64(r.Y+5)
139	if err := c.endDrag(ctx); err != nil {
140		t.Fatal(err)
141	}
142	if !slices.Equal(w.Columns[0].Windows, []WindowID{1, 2}) || w.Columns[0].Slot != 0 {
143		t.Fatal(w.Columns)
144	}
145	if c.slots[key].window != 0 {
146		t.Fatal("slot still holds the window")
147	}
148}

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