main 5d6fa26

model_test.go RawHistoryFolder

1package atspi
2
3import (
4	"testing"
5
6	"github.com/stretchr/testify/require"
7
8	"github.com/bnema/neferwl/internal/ports"
9)
10
11func TestToTargetConvertsWindowCoordinates(t *testing.T) {
12	win := ports.Rect{X: 100, Y: 50, W: 800, H: 600}
13	// The frame origin (fx, fy) is subtracted: toolkit offsets cancel out.
14	got, ok := toTarget(win, 10, 20, element{role: roleLink, x: 110, y: 120, w: 40, h: 16})
15	require.True(t, ok)
16	require.Equal(t, ports.HintTarget{Rect: ports.Rect{X: 200, Y: 150, W: 40, H: 16}, Kind: ports.HintLink}, got)
17}
18
19func TestToTargetClipsToWindow(t *testing.T) {
20	win := ports.Rect{X: 100, Y: 50, W: 200, H: 100}
21	got, ok := toTarget(win, 0, 0, element{role: rolePushButton, x: 180, y: 90, w: 60, h: 30})
22	require.True(t, ok)
23	require.Equal(t, ports.Rect{X: 280, Y: 140, W: 20, H: 10}, got.Rect)
24	require.Equal(t, ports.HintButton, got.Kind)
25}
26
27func TestToTargetDropsEmptyAndOutside(t *testing.T) {
28	win := ports.Rect{X: 0, Y: 0, W: 100, H: 100}
29	for name, e := range map[string]element{
30		"zero width":  {role: roleLink, x: 1, y: 1, w: 0, h: 10},
31		"neg height":  {role: roleLink, x: 1, y: 1, w: 10, h: -1},
32		"outside":     {role: roleLink, x: 500, y: 1, w: 10, h: 10},
33		"above":       {role: roleLink, x: 1, y: -50, w: 10, h: 10},
34		"left of win": {role: roleLink, x: -30, y: 1, w: 10, h: 10},
35	} {
36		_, ok := toTarget(win, 0, 0, e)
37		require.False(t, ok, name)
38	}
39}
40
41func TestRoleKindMapping(t *testing.T) {
42	require.Equal(t, ports.HintLink, kindOf(roleLink))
43	for _, r := range []uint32{rolePushButton, roleToggleButton, roleCheckBox, roleRadioButton, roleMenuItem, rolePageTab, roleComboBox} {
44		require.Equal(t, ports.HintButton, kindOf(r), r)
45		require.True(t, isClickable(r, false), r)
46		require.False(t, isClickable(r, true), r)
47	}
48	require.True(t, isClickable(roleLink, true))
49	require.False(t, isClickable(roleFrame, false))
50	require.True(t, isWindowRole(roleFrame) && isWindowRole(roleWindow) && isWindowRole(roleDialog))
51	require.False(t, isWindowRole(roleLink))
52}
53
54func TestRoleAndStateBits(t *testing.T) {
55	require.Equal(t, []int32{0, 0, 0, 0, 0}, roleBits(nil))
56	// link is role 88: word 2, bit 24; push button is 43: word 1, bit 11.
57	require.Equal(t, []int32{0, 1 << 11, 1 << 24, 0, 0}, roleBits([]uint32{roleLink, rolePushButton}))
58	// showing is state 25, visible 30: both in word 0.
59	require.Equal(t, []int32{1<<25 | 1<<30, 0}, stateBits(stateShowing, stateVisible))
60}
61
62func wins(ws ...ports.HintWindow) []ports.HintWindow { return ws }
63
64func win(id ports.WindowID, w, h int, focused bool) ports.HintWindow {
65	return ports.HintWindow{ID: id, PID: 1, Rect: ports.Rect{W: w, H: h}, Focused: focused}
66}
67
68func TestMatchFramesSingle(t *testing.T) {
69	got := matchFrames(wins(win(1, 800, 600, false)), []frameInfo{{w: 100, h: 100}})
70	require.Equal(t, map[int]int{0: 0}, got)
71}
72
73func TestMatchFramesActive(t *testing.T) {
74	frames := []frameInfo{{w: 800, h: 600}, {w: 800, h: 600, active: true}}
75	got := matchFrames(wins(win(1, 800, 600, true), win(2, 800, 600, false)), frames)
76	// The focused window takes the ACTIVE frame and reserves it; the other
77	// window then only has the remaining frame to match.
78	require.Equal(t, map[int]int{0: 1, 1: 0}, got)
79}
80
81func TestMatchFramesSize(t *testing.T) {
82	frames := []frameInfo{{w: 1000, h: 700}, {w: 400, h: 300}}
83	got := matchFrames(wins(win(1, 1000, 700, false), win(2, 420, 290, false)), frames)
84	require.Equal(t, map[int]int{0: 0, 1: 1}, got)
85}
86
87func TestMatchFramesSizeTolerance(t *testing.T) {
88	frames := []frameInfo{{w: 840, h: 640}, {w: 100, h: 100}}
89	other := win(2, 90, 90, false)
90	// 40 px off still matches; 41 px does not.
91	require.Equal(t, map[int]int{0: 0, 1: 1}, matchFrames(wins(win(1, 800, 600, false), other), frames))
92	require.Equal(t, map[int]int{1: 1}, matchFrames(wins(win(1, 799, 600, false), other), frames))
93}
94
95func TestMatchFramesAmbiguous(t *testing.T) {
96	// Two windows and two frames of the same size, none active: skipped.
97	frames := []frameInfo{{w: 800, h: 600}, {w: 810, h: 600}}
98	got := matchFrames(wins(win(1, 800, 600, false), win(2, 800, 600, false)), frames)
99	require.Empty(t, got)
100	// One window, two same-size frames, not focused: skipped.
101	require.Empty(t, matchFrames(wins(win(1, 800, 600, false)), frames))
102	// No frame close enough.
103	require.Empty(t, matchFrames(wins(win(1, 200, 200, false), win(2, 300, 300, false)), frames))
104}
105
106// The active frame is reserved by the focused window: an unfocused window of
107// the same size as that frame must not be paired with it as well.
108func TestMatchFramesOneToOne(t *testing.T) {
109	frames := []frameInfo{{ref: ref{"a", "/active"}, active: true, w: 400, h: 300}, {ref: ref{"a", "/idle"}, w: 800, h: 600}}
110	got := matchFrames(wins(win(1, 800, 600, true), win(2, 400, 300, false)), frames)
111	seen := map[int]bool{}
112	for _, f := range got {
113		require.False(t, seen[f], "frame %d assigned twice: %v", f, got)
114		seen[f] = true
115	}
116	require.Equal(t, map[int]int{0: 0}, got)
117}

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 23

Tags 10

↑↓ moveenter openesc close