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)
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 TestBadgeClassAndText(t *testing.T) {
72 assert.Equal(t, "SHOT", badgeText(ports.Screenshot))
73 assert.Equal(t, "REC", badgeText(ports.Record))
74 assert.NotContains(t, styleSheet, "box.rec", "the rec badge keeps the neutral tag style")
75}
76
77func TestBadgeTopLeftAndClearOfHeaderAndLabel(t *testing.T) {
78 for _, w := range []int{1920, 640, 320, 200, 100, 40} {
79 for _, mode := range []ports.Mode{ports.Screenshot, ports.Record} {
80 m := toggleModel(t, mode, true)
81 m.resize(w, 480, 1)
82 s := m.scene()
83 assert.GreaterOrEqual(t, s.badge.x, 0.0)
84 assert.LessOrEqual(t, s.badge.x+s.badge.w, float64(w))
85 assert.LessOrEqual(t, s.badge.y+s.badge.h, 480.0)
86 if w >= 640 {
87 assert.Equal(t, margin, s.badge.x)
88 assert.Equal(t, margin, s.badge.y)
89 }
90 assert.False(t, overlaps(s.badge, s.header), "badge/header at %d", w)
91 m.input(key("m"))
92 s = m.scene()
93 assert.False(t, overlaps(s.badge, s.header), "badge/header at %d", w)
94 assert.False(t, overlaps(s.label, s.badge), "label/badge at %d", w)
95 }
96 }
97}
98
99func TestBadgeAvoidsWorkspaceLabelAtLeftEdge(t *testing.T) {
100 m, _ := newBareModel()
101 m.setWorkspaces([]ports.Workspace{{ID: 11, OutputID: 7, Active: true, Region: ports.Region{Y: 0, Width: 300, Height: 400}}})
102 m.resize(640, 480, 1)
103 m.input(key("w"))
104 s := m.scene()
105 assert.False(t, overlaps(s.label, s.badge))
106 assert.False(t, overlaps(s.label, s.header))
107}
108
109func TestModeToggleAllocations(t *testing.T) {
110 if raceEnabled {
111 t.Skip("the race detector adds allocations")
112 }
113 m := toggleModel(t, ports.Screenshot, true)
114 m.input(key("m"))
115 assert.Zero(t, testing.AllocsPerRun(100, func() {
116 m.input(motion(10, 10))
117 _ = m.scene()
118 _ = badgeText(m.mode)
119 }), "unchanged mode allocates nothing")
120 n := testing.AllocsPerRun(100, func() {
121 m.input(key("Tab"))
122 _ = m.scene()
123 })
124 t.Logf("Tab toggle allocs/run: %v", n)
125 assert.LessOrEqual(t, n, 1.0, "a toggle allocates only the rebuilt legend text")
126}