package gui import ( "fmt" "os" "strings" "time" "github.com/gdamore/tcell/v3" "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/gocui" "github.com/jesseduffield/lazygit/pkg/gui/types" integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" ) // this gives our integration test a way of interacting with the gui for sending keypresses // and reading state. type GuiDriver struct { gui *Gui toastChan chan string headless bool } var _ integrationTypes.GuiDriver = &GuiDriver{} func (self *GuiDriver) PressKey(keyStr string) { self.CheckAllToastsAcknowledged() key, ok := config.KeyFromLabel(keyStr) if !ok { self.Fail("Unrecognized key: " + keyStr) } self.gui.g.ReplayKeyEvent(gocui.NewTcellKeyEventWrapper( tcell.NewEventKey(tcell.Key(key.KeyName()), key.Str(), tcell.ModMask(key.Mod())), 0, )) self.waitTillIdle() } func (self *GuiDriver) Click(x, y int) { self.CheckAllToastsAcknowledged() self.gui.g.ReplayMouseEvent(gocui.NewTcellMouseEventWrapper( tcell.NewEventMouse(x, y, tcell.ButtonPrimary, 0), 0, )) self.waitTillIdle() self.gui.g.ReplayMouseEvent(gocui.NewTcellMouseEventWrapper( tcell.NewEventMouse(x, y, tcell.ButtonNone, 0), 0, )) 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.ReplayFocusEvent(gocui.NewTcellFocusEventWrapper( tcell.NewEventFocus(true), 0, )) self.waitTillIdle() } func (self *GuiDriver) PretendMergeOrRebaseStartedInLazygit() { self.gui.onUIThread(func() error { self.gui.State.SetMergeOrRebaseStartedInLazygit(true) return nil }) self.waitTillIdle() } // wait until lazygit is idle (i.e. all processing is done) before continuing func (self *GuiDriver) waitTillIdle() { self.gui.g.WaitUntilIdle() } func (self *GuiDriver) CheckAllToastsAcknowledged() { if t := self.NextToast(); t != nil { self.Fail("Toast not acknowledged: " + *t) } } func (self *GuiDriver) Keys() config.KeybindingConfig { return self.gui.Config.GetUserConfig().Keybinding } func (self *GuiDriver) CurrentContext() types.Context { return self.gui.c.Context().Current() } func (self *GuiDriver) ContextForView(viewName string) types.Context { context, ok := self.gui.helpers.View.ContextForView(viewName) if !ok { return nil } return context } func (self *GuiDriver) Fail(message string) { currentView := self.gui.g.CurrentView() // Check for unacknowledged toast: it may give us a hint as to why the test failed toastMessage := "" if t := self.NextToast(); t != nil { toastMessage = fmt.Sprintf("Unacknowledged toast message: %s\n", *t) } fullMessage := fmt.Sprintf( "%s\nFinal Lazygit state:\n%s\nUpon failure, focused view was '%s'.\n%sLog:\n%s", message, self.gui.g.Snapshot(), currentView.Name(), toastMessage, strings.Join(self.gui.GuiLog, "\n"), ) self.gui.g.Close() // need to give the gui time to close time.Sleep(time.Millisecond * 100) _, err := fmt.Fprintln(os.Stderr, fullMessage) if err != nil { panic("Test failed. Failed writing to stderr") } panic("Test failed") } // logs to the normal place that you log to i.e. viewable with `lazygit --logs` func (self *GuiDriver) Log(message string) { self.gui.c.Log.Warn(message) } // logs in the actual UI (in the commands panel) func (self *GuiDriver) LogUI(message string) { self.gui.c.LogAction(message) } func (self *GuiDriver) CheckedOutRef() *models.Branch { return self.gui.helpers.Refs.GetCheckedOutRef() } func (self *GuiDriver) MainView() *gocui.View { return self.gui.mainView() } func (self *GuiDriver) SecondaryView() *gocui.View { return self.gui.secondaryView() } func (self *GuiDriver) View(viewName string) *gocui.View { view, err := self.gui.g.View(viewName) if err != nil { panic(err) } return view } // TopViewInWindow returns the frontmost visible view in the given window, i.e. // the tab that is currently shown when a window holds several tabbed views. func (self *GuiDriver) TopViewInWindow(windowName string) *gocui.View { return self.gui.helpers.Window.TopViewInWindow(windowName, false) } func (self *GuiDriver) SetCaption(caption string) { self.gui.setCaption(caption) self.waitTillIdle() } func (self *GuiDriver) SetCaptionPrefix(prefix string) { self.gui.setCaptionPrefix(prefix) self.waitTillIdle() } func (self *GuiDriver) NextToast() *string { select { case t := <-self.toastChan: return &t default: return nil } } func (self *GuiDriver) Headless() bool { return self.headless }