main bcc9a45

controls_extra_test.go RawHistoryFolder

1package ui
2
3import (
4	"github.com/bnema/nefergui/internal/css"
5	"github.com/bnema/nefergui/internal/edit"
6	"testing"
7)
8
9func TestRadioSliderAndEditingIdentity(t *testing.T) {
10	r := newRuntime()
11	r.styles = css.Compile(css.UA(), css.Parse(`input,radio,slider {height:20px}`))
12	var choice string
13	v := 3.0
14	name := "e\u0301"
15	order := []string{"a", "b"}
16	view := func(f *Frame) {
17		root := f.Root()
18		for _, key := range order {
19			root.Input(key, &name, Key(key))
20		}
21		root.Radio("One", "one", &choice, Key("r1"))
22		root.Radio("Two", "two", &choice, Key("r2"))
23		root.Slider("level", &v, 0, 10, 1, Key("s"))
24	}
25	r.Build(view)
26	r.Queue(inputEvent{Target: r.Target(3), Kind: "activate"})
27	r.Build(view)
28	if choice != "two" {
29		t.Fatal(choice)
30	}
31	r.route(key("Tab"))
32	r.Build(view)
33	for !r.state.focus.same(r.Target(4)) {
34		r.route(key("Tab"))
35		r.Build(view)
36		if r.state.focus.same(r.Target(0)) {
37			t.Fatalf("slider skipped: %v", func() []string {
38				var ids []string
39				for _, e := range r.focusOrder() {
40					ids = append(ids, idKey(e.identity))
41				}
42				return ids
43			}())
44		}
45	}
46	if !r.state.focus.same(r.Target(4)) {
47		t.Fatalf("slider focus: focus=%s order=%v", idKey(r.state.focus), func() []string {
48			var ids []string
49			for _, e := range r.focusOrder() {
50				ids = append(ids, idKey(e.identity))
51			}
52			return ids
53		}())
54	}
55	r.route(key("Right"))
56	r.Build(view)
57	if v != 4 {
58		t.Fatal(v)
59	}
60	r.Queue(inputEvent{Target: r.Target(0), Kind: "edit-key", Text: "backspace"})
61	r.Build(view)
62	if name != "" {
63		t.Fatal("grapheme delete", name)
64	}
65	order = []string{"b", "a"}
66	r.Redraw()
67	r.Build(view)
68	if r.edits[idKey(r.Target(0))] == r.edits[idKey(r.Target(1))] {
69		t.Fatal("state aliased")
70	}
71}
72func TestDisabledEditorRejectsFocusAndText(t *testing.T) {
73	r := newRuntime()
74	r.styles = css.Compile(css.UA(), css.Parse(`input {height:20px}`))
75	value := "keep"
76	view := func(f *Frame) { f.Root().Input("name", &value, Key("name"), Disabled(true)) }
77	r.Build(view)
78	r.route(key("Tab"))
79	r.Build(view)
80	if r.state.focus != nil {
81		t.Fatalf("disabled input focused: %s", idKey(r.state.focus))
82	}
83	r.route(platformInput{Kind: "key", Key: keyEvent{Name: "q", Text: "x", Pressed: true}})
84	r.Build(view)
85	if value != "keep" {
86		t.Fatalf("disabled input edited: %q", value)
87	}
88}
89
90func TestEditFocusIMEAndClipboard(t *testing.T) {
91	r := newRuntime()
92	r.styles = css.Compile(css.UA(), css.Parse(`input {height:20px}`))
93	var value string
94	view := func(f *Frame) { f.Root().Input("secret", &value, Key("secret"), Password(true)) }
95	r.Build(view)
96	r.clipboard = NewMockClipboard(t) // no clipboard access expected
97	r.route(key("Tab"))
98	r.Build(view)
99	r.route(platformInput{Kind: "key", Key: keyEvent{Name: "q", Text: "x", Pressed: true}})
100	r.Build(view)
101	if value != "x" || r.committed.children[0].text != "•" {
102		t.Fatalf("value=%q display=%q focus=%s", value, r.committed.children[0].text, idKey(r.state.focus))
103	}
104	r.route(platformInput{Kind: "ime-done", IME: edit.IMEBatch{Preedit: "候補"}})
105	r.Build(view)
106	if value != "x" {
107		t.Fatal(value)
108	}
109	r.route(platformInput{Kind: "focus-out"})
110	r.Build(view)
111	if r.edits[idKey(r.Target(0))].Preedit != "" {
112		t.Fatal("preedit survives blur")
113	}
114}

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 1

Tags 12

↑↓ moveenter openesc close