Let integration tests post a focus event

Lazygit reloads changed config files when its terminal window regains
focus, but the test harness had no way to simulate that focus event, so
the live config-reload path was untestable. Add a focus event to the
replayed-events queue and expose it through the GuiDriver as FocusIn.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Haller 2026-06-15 11:29:05 +02:00
parent 922861bb36
commit 30559f1058
6 changed files with 46 additions and 0 deletions

View file

@ -103,6 +103,7 @@ type replayedEvents struct {
Keys chan *TcellKeyEventWrapper
Resizes chan *TcellResizeEventWrapper
MouseEvents chan *TcellMouseEventWrapper
FocusEvents chan *TcellFocusEventWrapper
}
type RecordingConfig struct {
@ -245,6 +246,7 @@ func NewGui(opts NewGuiOpts) (*Gui, error) {
Keys: make(chan *TcellKeyEventWrapper),
Resizes: make(chan *TcellResizeEventWrapper),
MouseEvents: make(chan *TcellMouseEventWrapper),
FocusEvents: make(chan *TcellFocusEventWrapper),
}
}

View file

@ -266,6 +266,22 @@ func (wrapper TcellResizeEventWrapper) toTcellEvent() tcell.Event {
return tcell.NewEventResize(wrapper.Width, wrapper.Height)
}
type TcellFocusEventWrapper struct {
Timestamp int64
Focused bool
}
func NewTcellFocusEventWrapper(event *tcell.EventFocus, timestamp int64) *TcellFocusEventWrapper {
return &TcellFocusEventWrapper{
Timestamp: timestamp,
Focused: event.Focused,
}
}
func (wrapper TcellFocusEventWrapper) toTcellEvent() tcell.Event {
return tcell.NewEventFocus(wrapper.Focused)
}
// pollEvent get tcell.Event and transform it into gocuiEvent
func (g *Gui) pollEvent() GocuiEvent {
var tev tcell.Event
@ -277,6 +293,8 @@ func (g *Gui) pollEvent() GocuiEvent {
tev = (ev).toTcellEvent()
case ev := <-g.ReplayedEvents.MouseEvents:
tev = (ev).toTcellEvent()
case ev := <-g.ReplayedEvents.FocusEvents:
tev = (ev).toTcellEvent()
}
} else {
tev = <-Screen.EventQ()

View file

@ -56,6 +56,18 @@ func (self *GuiDriver) Click(x, y int) {
self.waitTillIdle()
}
// FocusIn simulates the terminal window regaining focus, which is how lazygit
// learns to reload changed config files. Tests use it to exercise the live
// config-reload path.
func (self *GuiDriver) FocusIn() {
self.gui.g.ReplayedEvents.FocusEvents <- gocui.NewTcellFocusEventWrapper(
tcell.NewEventFocus(true),
0,
)
self.waitTillIdle()
}
// wait until lazygit is idle (i.e. all processing is done) before continuing
func (self *GuiDriver) waitTillIdle() {
<-self.isIdleChan

View file

@ -56,6 +56,14 @@ func (self *TestDriver) GlobalPress(key config.Keybinding) {
self.press(key[0])
}
// FocusIn simulates the terminal window regaining focus, which causes lazygit
// to reload any config files that changed while it was in the background.
func (self *TestDriver) FocusIn() {
self.SetCaption("Focusing window")
self.gui.FocusIn()
self.Wait(self.inputDelay)
}
func (self *TestDriver) typeContent(content string) {
for _, char := range content {
self.pressFast(string(char))

View file

@ -34,6 +34,9 @@ func (self *fakeGuiDriver) Click(x, y int) {
self.clickedCoordinates = append(self.clickedCoordinates, coordinate{x: x, y: y})
}
func (self *fakeGuiDriver) FocusIn() {
}
func (self *fakeGuiDriver) Keys() config.KeybindingConfig {
return config.KeybindingConfig{}
}

View file

@ -24,6 +24,9 @@ type IntegrationTest interface {
type GuiDriver interface {
PressKey(string)
Click(int, int)
// Simulate the terminal window regaining focus (which triggers a reload of
// changed config files)
FocusIn()
Keys() config.KeybindingConfig
CurrentContext() types.Context
ContextForView(viewName string) types.Context