1package selection
2
3import (
4 "strings"
5 "testing"
6
7 "github.com/bnema/nefercap/internal/core"
8 "github.com/bnema/nefercap/internal/ports"
9 "github.com/stretchr/testify/assert"
10)
11
12func TestContextualFooter(t *testing.T) {
13 m := newTestModel(t)
14 assert.Contains(t, m.footerText, "R region · M monitor")
15 assert.Contains(t, m.footerText, "G grid · Esc cancel")
16 assert.NotContains(t, m.footerText, "W workspace")
17 assert.Contains(t, m.footerText, "drag + release capture region")
18 m.input(key("m"))
19 assert.Contains(t, m.footerText, "Enter capture monitor")
20 m.input(key("r"))
21 assert.Contains(t, m.footerText, "drag + release capture region")
22
23 m, _ = newBareModel()
24 m.setWorkspaces([]ports.Workspace{{ID: 11, OutputID: 7, Active: true, Region: ports.Region{Width: 640, Height: 480}}})
25 m.resize(640, 480, 1)
26 assert.Contains(t, m.footerText, "W workspace")
27 m.input(key("w"))
28 assert.Equal(t, core.PickWorkspace, m.picker.Kind())
29 assert.Contains(t, m.footerText, "Enter capture workspace")
30 m.resize(600, 400, 1)
31 assert.Contains(t, m.footerText, "drag + release capture region", "resize resets the mode")
32}
33
34func TestHintsBoundsAndMetrics(t *testing.T) {
35 for _, size := range [][2]int{{1920, 1080}, {640, 480}, {480, 320}, {200, 100}, {20, 20}} {
36 m := newTestModel(t)
37 m.resize(size[0], size[1], 1)
38 for _, r := range []frect{m.scene().header, m.scene().footer} {
39 assert.GreaterOrEqual(t, r.x, 0.0)
40 assert.GreaterOrEqual(t, r.y, 0.0)
41 assert.LessOrEqual(t, r.x+r.w, float64(size[0]))
42 assert.LessOrEqual(t, r.y+r.h, float64(size[1]))
43 }
44 }
45 assert.Contains(t, styleSheet, "font-size: 1rem")
46 assert.Contains(t, styleSheet, "justify-content: center")
47 assert.Contains(t, styleSheet, "align-items: center")
48 assert.Equal(t, 2, len(strings.Split(newTestModel(t).footerText, "\n")))
49 assert.GreaterOrEqual(t, footerH-2*tagPadY, 2*baseFont*1.37)
50}
51
52func TestNarrowWorkspaceFooterHoldsFourLines(t *testing.T) {
53 m, _ := newBareModel()
54 m.setWorkspaces([]ports.Workspace{{ID: 11, OutputID: 7, Active: true, Region: ports.Region{Y: 60, Width: 480, Height: 260}}})
55 m.resize(480, 320, 1)
56 m.input(key("w"))
57 s := m.scene()
58 // Each explicit hint row wraps once at 480px: four 24px lines plus
59 // two 4px vertical padding edges. Do not derive this from the helper.
60 assert.GreaterOrEqual(t, s.footer.h, 104.0)
61 assert.LessOrEqual(t, s.footer.y+s.footer.h, 320.0)
62 if !raceEnabled {
63 assert.Zero(t, testing.AllocsPerRun(100, func() { _ = m.scene() }))
64 }
65}
66
67func TestWorkspaceLabelAvoidsOutputHeader(t *testing.T) {
68 m, _ := newBareModel()
69 m.setWorkspaces([]ports.Workspace{{ID: 11, OutputID: 7, Active: true, Region: ports.Region{Y: 60, Width: 640, Height: 360}}})
70 m.resize(640, 480, 1)
71 m.input(key("w"))
72 s := m.scene()
73 assert.Equal(t, "640 × 360", m.label.text)
74 assert.True(t, s.label.x+s.label.w <= s.header.x || s.label.x >= s.header.x+s.header.w ||
75 s.label.y+s.label.h <= s.header.y || s.label.y >= s.header.y+s.header.h, "label and header must not overlap")
76 assert.LessOrEqual(t, s.label.y+s.label.h, 480.0)
77}
78
79func TestRightWorkspaceLabelAvoidsOutputHeader(t *testing.T) {
80 m, _ := newBareModel()
81 m.setWorkspaces([]ports.Workspace{{ID: 11, OutputID: 7, Active: true, Region: ports.Region{X: 470, Y: 60, Width: 170, Height: 360}}})
82 m.resize(640, 480, 1)
83 m.input(key("w"))
84 s := m.scene()
85 assert.Greater(t, s.label.x+s.label.w, s.header.x, "label shares the header's horizontal range")
86 assert.Less(t, s.label.x, s.header.x+s.header.w)
87 assert.GreaterOrEqual(t, s.label.y, s.header.y+s.header.h, "collision moves dimensions below the header")
88 assert.GreaterOrEqual(t, s.label.y, 60.0, "dimensions move inside the workspace")
89}
90
91func TestOutputHeaderAtTopRight(t *testing.T) {
92 for _, width := range []int{1920, 640, 200, 20} {
93 m := newTestModel(t)
94 m.resize(width, 480, 1)
95 s := m.scene()
96 wantRightGap := min(margin, float64(width)-s.header.w)
97 assert.Equal(t, float64(width)-wantRightGap, s.header.x+s.header.w)
98 assert.GreaterOrEqual(t, s.header.x, 0.0)
99 }
100 m := newTestModel(t)
101 m.input(key("m"))
102 s := m.scene()
103 assert.Less(t, s.label.x+s.label.w, s.header.x, "monitor dimensions stay left of the output details")
104}
105
106func TestFooterUnchangedAllocations(t *testing.T) {
107 if raceEnabled {
108 t.Skip("the race detector adds allocations")
109 }
110 m := newTestModel(t)
111 m.input(key("m"))
112 assert.Zero(t, testing.AllocsPerRun(100, func() {
113 m.input(key("m"))
114 m.input(motion(10, 10))
115 _ = m.scene()
116 }))
117}