1package selection
2
3import (
4 "testing"
5
6 "github.com/stretchr/testify/assert"
7 "github.com/stretchr/testify/require"
8
9 "github.com/bnema/nefercap/internal/core"
10 "github.com/bnema/nefercap/internal/ports"
11)
12
13func toggleModel(t *testing.T, mode ports.Mode, toggle bool) *model {
14 t.Helper()
15 m := newTestModel(t)
16 m.base, m.mode, m.toggle = mode, mode, toggle
17 m.footerMode = mode
18 m.footerText = footerText(mode, core.PickRegion, false, toggle, false, freezeOff)
19 return m
20}
21
22func TestTabTogglesModeKeepingTarget(t *testing.T) {
23 m := toggleModel(t, ports.Screenshot, true)
24 assert.Equal(t, "shot · Tab rec · R region · M monitor · G grid · Esc cancel\ndrag + release capture region · click capture monitor", m.footerText)
25 assert.Equal(t, "SHOT", badgeText(m.mode))
26 m.input(key("m"))
27 require.Equal(t, core.PickMonitor, m.picker.Kind())
28 assert.True(t, m.input(key("Tab")), "a mode change redraws")
29 assert.Equal(t, ports.Record, m.mode)
30 assert.Equal(t, core.PickMonitor, m.picker.Kind(), "target is kept")
31 assert.Equal(t, "rec · Tab shot · R region · M monitor · G grid · Esc cancel\nEnter record monitor · click record monitor", m.footerText)
32 assert.Equal(t, "REC", badgeText(m.mode))
33 m.input(key("Tab"))
34 assert.Equal(t, ports.Screenshot, m.mode)
35 assert.Contains(t, m.footerText, "Enter capture monitor")
36}
37
38func TestTabIgnoredWhenModeFixed(t *testing.T) {
39 for _, mode := range []ports.Mode{ports.Screenshot, ports.Record} {
40 m := toggleModel(t, mode, false)
41 before := m.footerText
42 assert.False(t, m.input(key("Tab")))
43 assert.Equal(t, mode, m.mode)
44 assert.Equal(t, before, m.footerText)
45 assert.NotContains(t, m.footerText, "Tab")
46 }
47}
48
49func TestTabSharedAcrossOverlaysAndResult(t *testing.T) {
50 sess, _ := testSession()
51 a := newModel(ports.Screenshot, testOutputs, 0, sess, false)
52 b := newModel(ports.Screenshot, testOutputs, 1, sess, false)
53 a.toggle, b.toggle = true, true
54 a.wake, b.wake = make(chan struct{}, 1), make(chan struct{}, 1)
55 sess.wakes = []chan struct{}{a.wake, b.wake}
56 a.resize(1920, 1080, 1)
57 b.resize(1280, 720, 1)
58 a.input(key("Tab"))
59 require.Len(t, b.wake, 1, "the other overlay is woken")
60 b.input(motion(1, 1))
61 assert.Equal(t, ports.Record, b.mode)
62 assert.Contains(t, b.footerText, "rec · Tab shot")
63 s := New(ports.Screenshot, ports.VideoSettings{}, nil, false).AllowToggle()
64 sel := s.selectionFor(modeAfter(s.mode, sess.toggles.Load()), core.PickResult{OutputID: 7, Kind: core.PickMonitor})
65 assert.Equal(t, ports.Record, sel.Mode)
66 assert.Equal(t, DefaultFPS, sel.Video.FPS)
67 sel = s.selectionFor(ports.Screenshot, core.PickResult{OutputID: 7, Kind: core.PickMonitor})
68 assert.Equal(t, ports.VideoSettings{}, sel.Video)
69}
70
71func TestBadgeTopLeftAndClearOfHeaderAndLabel(t *testing.T) {
72 for _, w := range []int{1920, 640, 320, 200, 100, 40} {
73 for _, mode := range []ports.Mode{ports.Screenshot, ports.Record} {
74 m := toggleModel(t, mode, true)
75 m.resize(w, 480, 1)
76 s := m.scene()
77 assert.GreaterOrEqual(t, s.badge.x, 0.0)
78 assert.LessOrEqual(t, s.badge.x+s.badge.w, float64(w))
79 assert.LessOrEqual(t, s.badge.y+s.badge.h, 480.0)
80 if w >= 640 {
81 assert.Equal(t, margin, s.badge.x)
82 assert.Equal(t, margin, s.badge.y)
83 }
84 assert.False(t, overlaps(s.badge, s.header), "badge/header at %d", w)
85 m.input(key("m"))
86 s = m.scene()
87 assert.False(t, overlaps(s.badge, s.header), "badge/header at %d", w)
88 assert.False(t, overlaps(s.label, s.badge), "label/badge at %d", w)
89 }
90 }
91}
92
93func TestBadgeAvoidsWorkspaceLabelAtLeftEdge(t *testing.T) {
94 m, _ := newBareModel()
95 m.setWorkspaces([]ports.Workspace{{ID: 11, OutputID: 7, Active: true, Region: ports.Region{Y: 0, Width: 300, Height: 400}}})
96 m.resize(640, 480, 1)
97 m.input(key("w"))
98 s := m.scene()
99 assert.False(t, overlaps(s.label, s.badge))
100 assert.False(t, overlaps(s.label, s.header))
101}
102
103func TestModeToggleAllocations(t *testing.T) {
104 if raceEnabled {
105 t.Skip("the race detector adds allocations")
106 }
107 m := toggleModel(t, ports.Screenshot, true)
108 m.input(key("m"))
109 assert.Zero(t, testing.AllocsPerRun(100, func() {
110 m.input(motion(10, 10))
111 _ = m.scene()
112 _ = badgeText(m.mode)
113 }), "unchanged mode allocates nothing")
114 n := testing.AllocsPerRun(100, func() {
115 m.input(key("Tab"))
116 _ = m.scene()
117 })
118 t.Logf("Tab toggle allocs/run: %v", n)
119 assert.LessOrEqual(t, n, 1.0, "a toggle allocates only the rebuilt legend text")
120}