diff --git a/pkg/gui/gui_driver.go b/pkg/gui/gui_driver.go index b922f0705..07a0b7d4c 100644 --- a/pkg/gui/gui_driver.go +++ b/pkg/gui/gui_driver.go @@ -181,6 +181,10 @@ func (self *GuiDriver) CurrentContext() types.Context { return self.gui.State.ContextMgr.Current() } +func (self *GuiDriver) CursorVisible() bool { + return self.gui.g.Cursor +} + func (self *GuiDriver) ContextForView(viewName string) types.Context { context, ok := self.gui.helpers.View.ContextForView(viewName) if !ok { diff --git a/pkg/integration/components/test_driver.go b/pkg/integration/components/test_driver.go index afd55a845..bd5bbfc24 100644 --- a/pkg/integration/components/test_driver.go +++ b/pkg/integration/components/test_driver.go @@ -97,6 +97,24 @@ func (self *TestDriver) GlobalPress(key config.Keybinding) { self.press(key[0]) } +// asserts that the terminal's text cursor is shown, i.e. that there is a text +// field to type into +func (self *TestDriver) CursorIsVisible() *TestDriver { + self.assertWithRetries(func() (bool, string) { + return self.gui.CursorVisible(), "Expected the cursor to be visible" + }) + + return self +} + +func (self *TestDriver) CursorIsHidden() *TestDriver { + self.assertWithRetries(func() (bool, string) { + return !self.gui.CursorVisible(), "Expected the cursor to be hidden" + }) + + return self +} + // 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() { diff --git a/pkg/integration/components/test_test.go b/pkg/integration/components/test_test.go index 2495a07c5..cce96105a 100644 --- a/pkg/integration/components/test_test.go +++ b/pkg/integration/components/test_test.go @@ -88,6 +88,10 @@ func (self *fakeGuiDriver) CurrentContext() types.Context { return nil } +func (self *fakeGuiDriver) CursorVisible() bool { + return false +} + func (self *fakeGuiDriver) ContextForView(viewName string) types.Context { return nil } diff --git a/pkg/integration/components/view_driver.go b/pkg/integration/components/view_driver.go index b743afde7..102a8562a 100644 --- a/pkg/integration/components/view_driver.go +++ b/pkg/integration/components/view_driver.go @@ -41,6 +41,53 @@ func (self *ViewDriver) Title(expected *TextMatcher) *ViewDriver { return self } +// asserts that the view has the expected footer, i.e. the "x of y" text on its +// bottom border +func (self *ViewDriver) Footer(expected *TextMatcher) *ViewDriver { + self.t.assertWithRetries(func() (bool, string) { + actual := self.getView().Footer + return expected.context(fmt.Sprintf("%s footer", self.context)).test(actual) + }) + + return self +} + +// asserts that the view has the expected subtitle +func (self *ViewDriver) Subtitle(expected *TextMatcher) *ViewDriver { + self.t.assertWithRetries(func() (bool, string) { + actual := self.getView().Subtitle + return expected.context(fmt.Sprintf("%s subtitle", self.context)).test(actual) + }) + + return self +} + +// asserts that the view hangs off the bottom of the given one, sharing a border +// with it +func (self *ViewDriver) SharesTopBorderWithBottomOf(upper *ViewDriver) *ViewDriver { + self.t.assertWithRetries(func() (bool, string) { + _, _, _, upperY1 := upper.getView().Dimensions() + _, y0, _, _ := self.getView().Dimensions() + return y0 == upperY1, fmt.Sprintf( + "%s: Expected view to start on row %d, where the view above it ends, but it starts on row %d", + self.context, upperY1, y0) + }) + + return self +} + +// asserts that the view starts on the row below the given one +func (self *ViewDriver) IsImmediatelyBelow(upper *ViewDriver) *ViewDriver { + self.t.assertWithRetries(func() (bool, string) { + _, _, _, upperY1 := upper.getView().Dimensions() + _, y0, _, _ := self.getView().Dimensions() + return y0 == upperY1+1, fmt.Sprintf( + "%s: Expected view to start on row %d, but it starts on row %d", self.context, upperY1+1, y0) + }) + + return self +} + func (self *ViewDriver) Clear() *ViewDriver { // clearing multiple times in case there's multiple lines // (the clear button only clears a single line at a time) diff --git a/pkg/integration/components/views.go b/pkg/integration/components/views.go index 90795d942..5c91b6937 100644 --- a/pkg/integration/components/views.go +++ b/pkg/integration/components/views.go @@ -124,6 +124,14 @@ func (self *Views) Menu() *ViewDriver { return self.regularView("menu") } +func (self *Views) MenuFilter() *ViewDriver { + return self.regularView("menuFilter") +} + +func (self *Views) MenuFilterFrame() *ViewDriver { + return self.regularView("menuFilterFrame") +} + func (self *Views) Confirmation() *ViewDriver { return self.regularView("confirmation") } diff --git a/pkg/integration/types/types.go b/pkg/integration/types/types.go index db9068ad9..325f4ea38 100644 --- a/pkg/integration/types/types.go +++ b/pkg/integration/types/types.go @@ -45,6 +45,8 @@ type GuiDriver interface { FocusInAndClick(int, int) Keys() config.KeybindingConfig CurrentContext() types.Context + // Whether the terminal's text cursor is currently shown + CursorVisible() bool ContextForView(viewName string) types.Context Fail(message string) // These two log methods are for the sake of debugging while testing. There's no need to actually