Add ViewDriver methods for mouse drag gestures in integration tests

Add press/move/release primitives next to the existing Click. The test
driver remembers the last reported position so a release doesn't have
to repeat the coordinates, and RepeatMouseMove lets a test verify that
a held-button motion event within the same cell has no effect.
This commit is contained in:
Stefan Haller 2026-07-22 09:12:49 +02:00
parent 98613957ae
commit ba024ed6c9
5 changed files with 102 additions and 3 deletions

View file

@ -57,6 +57,22 @@ func (self *GuiDriver) Click(x, y int) {
self.replayMouseEvent(x, y, tcell.ButtonNone)
}
func (self *GuiDriver) ClickAndHold(x, y int) {
self.CheckAllToastsAcknowledged()
self.replayMouseEvent(x, y, tcell.ButtonPrimary)
}
// MouseMove reports the mouse at a new position with the left button still
// held down, i.e. a drag movement. (No test needs pointer motion without a
// button held, so that variant doesn't exist.)
func (self *GuiDriver) MouseMove(x, y int) {
self.replayMouseEvent(x, y, tcell.ButtonPrimary)
}
func (self *GuiDriver) MouseRelease(x, y int) {
self.replayMouseEvent(x, y, tcell.ButtonNone)
}
func (self *GuiDriver) replayMouseEvent(x, y int, buttons tcell.ButtonMask) {
self.gui.g.ReplayMouseEvent(gocui.NewTcellMouseEventWrapper(
tcell.NewEventMouse(x, y, buttons, 0),

View file

@ -13,6 +13,8 @@ type TestDriver struct {
gui integrationTypes.GuiDriver
keys config.KeybindingConfig
inputDelay int
mouseX int
mouseY int
*assertionHelper
shell *Shell
}
@ -58,6 +60,30 @@ func (self *TestDriver) click(x, y int) {
self.Wait(self.inputDelay)
}
func (self *TestDriver) clickAndHold(x, y int) {
self.SetCaption(fmt.Sprintf("Clicking and holding %d, %d", x, y))
self.mouseX, self.mouseY = x, y
self.gui.ClickAndHold(x, y)
self.Wait(self.inputDelay)
}
func (self *TestDriver) mouseMove(x, y int) {
self.SetCaption(fmt.Sprintf("Moving mouse to %d, %d", x, y))
self.mouseX, self.mouseY = x, y
self.gui.MouseMove(x, y)
self.Wait(self.inputDelay)
}
func (self *TestDriver) repeatMouseMove() {
self.mouseMove(self.mouseX, self.mouseY)
}
func (self *TestDriver) mouseRelease() {
self.SetCaption(fmt.Sprintf("Releasing mouse at %d, %d", self.mouseX, self.mouseY))
self.gui.MouseRelease(self.mouseX, self.mouseY)
self.Wait(self.inputDelay)
}
// Should only be used in specific cases where you're doing something weird!
// E.g. invoking a global keybinding from within a popup.
// You probably shouldn't use this function, and should instead go through a view like t.Views().Commit().Focus().Press(...)

View file

@ -19,9 +19,12 @@ type coordinate struct {
}
type fakeGuiDriver struct {
failureMessage string
pressedKeys []string
clickedCoordinates []coordinate
failureMessage string
pressedKeys []string
clickedCoordinates []coordinate
heldCoordinates []coordinate
movedCoordinates []coordinate
releasedCoordinates []coordinate
}
var _ integrationTypes.GuiDriver = &fakeGuiDriver{}
@ -38,6 +41,18 @@ func (self *fakeGuiDriver) Click(x, y int) {
self.clickedCoordinates = append(self.clickedCoordinates, coordinate{x: x, y: y})
}
func (self *fakeGuiDriver) ClickAndHold(x, y int) {
self.heldCoordinates = append(self.heldCoordinates, coordinate{x: x, y: y})
}
func (self *fakeGuiDriver) MouseMove(x, y int) {
self.movedCoordinates = append(self.movedCoordinates, coordinate{x: x, y: y})
}
func (self *fakeGuiDriver) MouseRelease(x, y int) {
self.releasedCoordinates = append(self.releasedCoordinates, coordinate{x: x, y: y})
}
func (self *fakeGuiDriver) FocusIn() {
}
@ -123,12 +138,19 @@ func TestSuccess(t *testing.T) {
t.press("b")
t.click(0, 1)
t.click(2, 3)
t.clickAndHold(0, 1)
t.mouseMove(2, 3)
t.repeatMouseMove()
t.mouseRelease()
},
})
driver := &fakeGuiDriver{}
test.Run(driver)
assert.EqualValues(t, []string{"a", "b"}, driver.pressedKeys)
assert.EqualValues(t, []coordinate{{0, 1}, {2, 3}}, driver.clickedCoordinates)
assert.EqualValues(t, []coordinate{{0, 1}}, driver.heldCoordinates)
assert.EqualValues(t, []coordinate{{2, 3}, {2, 3}}, driver.movedCoordinates)
assert.EqualValues(t, []coordinate{{2, 3}}, driver.releasedCoordinates)
assert.Equal(t, "", driver.failureMessage)
}

View file

@ -483,6 +483,38 @@ func (self *ViewDriver) FocusInAndClick(x, y int) *ViewDriver {
return self
}
func (self *ViewDriver) MouseMoveToView(target *ViewDriver, x, y int) *ViewDriver {
offsetX, offsetY, _, _ := target.getView().Dimensions()
self.t.mouseMove(offsetX+1+x, offsetY+1+y)
return self
}
func (self *ViewDriver) Drag(fromX, fromY, toX, toY int) *ViewDriver {
return self.ClickAndHold(fromX, fromY).MouseMove(toX, toY).MouseRelease()
}
func (self *ViewDriver) ClickAndHold(x, y int) *ViewDriver {
offsetX, offsetY, _, _ := self.getView().Dimensions()
self.t.clickAndHold(offsetX+1+x, offsetY+1+y)
return self
}
func (self *ViewDriver) MouseMove(x, y int) *ViewDriver {
offsetX, offsetY, _, _ := self.getView().Dimensions()
self.t.mouseMove(offsetX+1+x, offsetY+1+y)
return self
}
func (self *ViewDriver) RepeatMouseMove() *ViewDriver {
self.t.repeatMouseMove()
return self
}
func (self *ViewDriver) MouseRelease() *ViewDriver {
self.t.mouseRelease()
return self
}
// i.e. pressing down arrow
func (self *ViewDriver) SelectNextItem() *ViewDriver {
return self.PressFast(self.t.keys.Universal.NextItem)

View file

@ -28,6 +28,9 @@ type GuiDriver interface {
// user typing faster than lazygit processes the input.
PressKeysRapidly(...string)
Click(int, int)
ClickAndHold(int, int)
MouseMove(int, int)
MouseRelease(int, int)
// Simulate the terminal window regaining focus (which triggers a reload of
// changed config files)
FocusIn()