1package core_test
2
3import (
4 "context"
5 "testing"
6 "testing/synctest"
7 "time"
8
9 "github.com/bnema/neferwl/internal/core"
10 "github.com/bnema/neferwl/internal/ports"
11)
12
13// rig is a running Core's channels and config.
14type rig struct {
15 input chan ports.InputEvent
16 client chan ports.ClientEvent
17 reload chan ports.ConfigChanged
18 commands chan ports.ClientCommand
19 cfg ports.Config
20}
21
22// repeatRig runs a Core with five windows in a row on one output, the last
23// one focused.
24func repeatRig(t *testing.T) rig {
25 t.Helper()
26 input := make(chan ports.InputEvent, 4)
27 client := make(chan ports.ClientEvent, 8)
28 output := make(chan ports.OutputEvent, 1)
29 reload := make(chan ports.ConfigChanged, 1)
30 commands := make(chan ports.ClientCommand, 256)
31 cfg := scrollDefaults()
32 cfg.Layout.MaxColumns = 5
33 c, err := core.New(cfg, core.Channels{Input: input, Client: client, Output: output, Config: reload, ConfigErrors: make(chan error, 1), Commands: commands, Scenes: make(chan []ports.Scene, 1), Spawn: make(chan ports.SpawnRequest, 4)}, core.Options{})
34 if err != nil {
35 t.Fatal(err)
36 }
37 ctx, cancel := context.WithCancel(context.Background())
38 done := make(chan error, 1)
39 go func() { done <- c.Run(ctx) }()
40 t.Cleanup(func() { cancel(); <-done })
41 output <- ports.OutputAdded{Info: ports.OutputInfo{Name: "OUT-1", Width: 500, Height: 100}}
42 for id := ports.WindowID(1); id <= 5; id++ {
43 client <- ports.WindowMapped{ID: id}
44 }
45 synctest.Wait()
46 return rig{input, client, reload, commands, cfg}
47}
48
49// drain counts the focus changes and activity reports sent since the last
50// drain.
51func drain(commands chan ports.ClientCommand) (focus, activity int) {
52 for len(commands) > 0 {
53 switch (<-commands).(type) {
54 case ports.FocusWindow:
55 focus++
56 case ports.UserActivity:
57 activity++
58 }
59 }
60 return focus, activity
61}
62
63var cmdLeft = ports.KeyEvent{Keysym: "Left", Keycode: 113, Mods: ports.ModSuper, Pressed: true}
64
65// Through Run: a held cmd+Left repeats on its timer and each repeat reports
66// user activity, so idle timers do not fire while a key is held.
67func TestHeldKeyRepeatsThroughRun(t *testing.T) {
68 synctest.Test(t, func(t *testing.T) {
69 r := repeatRig(t)
70 input, commands := r.input, r.commands
71 drain(commands)
72 input <- cmdLeft
73 synctest.Wait()
74 if focus, _ := drain(commands); focus != 1 {
75 t.Fatalf("press: %d focus changes, want 1", focus)
76 }
77 // Past the first repeat and the activity throttle.
78 time.Sleep(time.Second)
79 synctest.Wait()
80 if focus, activity := drain(commands); focus < 2 || activity == 0 {
81 t.Fatalf("held: %d focus changes, %d activity reports", focus, activity)
82 }
83 })
84}
85
86// A held key stops repeating when what it ran no longer applies. Each
87// event lands before the first repeat, while windows remain to the left,
88// so a surviving repeat would move focus or, behind a launcher holding the
89// keyboard, still report activity.
90func TestRepeatStopsThroughRun(t *testing.T) {
91 for _, tt := range []struct {
92 name string
93 send func(rig)
94 }{
95 // The new keymap may swallow the release.
96 {"config reload", func(r rig) { r.reload <- ports.ConfigChanged{Config: r.cfg} }},
97 {"launcher takes the keyboard", func(r rig) {
98 r.client <- ports.LayerChanged{Layers: []ports.LayerSurface{{ID: 9, Layer: ports.LayerOverlay, Width: 100, Height: 40, Keyboard: 1}}}
99 }},
100 {"window inhibits shortcuts", func(r rig) { r.client <- ports.ShortcutsInhibit{Window: 4, Active: true} }},
101 } {
102 t.Run(tt.name, func(t *testing.T) {
103 synctest.Test(t, func(t *testing.T) {
104 r := repeatRig(t)
105 drain(r.commands)
106 r.input <- cmdLeft
107 synctest.Wait()
108 if focus, _ := drain(r.commands); focus != 1 {
109 t.Fatalf("press: %d focus changes, want 1", focus)
110 }
111 tt.send(r)
112 synctest.Wait()
113 drain(r.commands)
114 time.Sleep(time.Second)
115 synctest.Wait()
116 if focus, activity := drain(r.commands); focus != 0 || activity != 0 {
117 t.Fatalf("%d focus changes, %d activity reports, want none", focus, activity)
118 }
119 })
120 })
121 }
122}