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) 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) { func (self *GuiDriver) replayMouseEvent(x, y int, buttons tcell.ButtonMask) {
self.gui.g.ReplayMouseEvent(gocui.NewTcellMouseEventWrapper( self.gui.g.ReplayMouseEvent(gocui.NewTcellMouseEventWrapper(
tcell.NewEventMouse(x, y, buttons, 0), tcell.NewEventMouse(x, y, buttons, 0),

View file

@ -13,6 +13,8 @@ type TestDriver struct {
gui integrationTypes.GuiDriver gui integrationTypes.GuiDriver
keys config.KeybindingConfig keys config.KeybindingConfig
inputDelay int inputDelay int
mouseX int
mouseY int
*assertionHelper *assertionHelper
shell *Shell shell *Shell
} }
@ -58,6 +60,30 @@ func (self *TestDriver) click(x, y int) {
self.Wait(self.inputDelay) 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! // Should only be used in specific cases where you're doing something weird!
// E.g. invoking a global keybinding from within a popup. // 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(...) // 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 { type fakeGuiDriver struct {
failureMessage string failureMessage string
pressedKeys []string pressedKeys []string
clickedCoordinates []coordinate clickedCoordinates []coordinate
heldCoordinates []coordinate
movedCoordinates []coordinate
releasedCoordinates []coordinate
} }
var _ integrationTypes.GuiDriver = &fakeGuiDriver{} 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}) 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() { func (self *fakeGuiDriver) FocusIn() {
} }
@ -123,12 +138,19 @@ func TestSuccess(t *testing.T) {
t.press("b") t.press("b")
t.click(0, 1) t.click(0, 1)
t.click(2, 3) t.click(2, 3)
t.clickAndHold(0, 1)
t.mouseMove(2, 3)
t.repeatMouseMove()
t.mouseRelease()
}, },
}) })
driver := &fakeGuiDriver{} driver := &fakeGuiDriver{}
test.Run(driver) test.Run(driver)
assert.EqualValues(t, []string{"a", "b"}, driver.pressedKeys) assert.EqualValues(t, []string{"a", "b"}, driver.pressedKeys)
assert.EqualValues(t, []coordinate{{0, 1}, {2, 3}}, driver.clickedCoordinates) 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) assert.Equal(t, "", driver.failureMessage)
} }

View file

@ -483,6 +483,38 @@ func (self *ViewDriver) FocusInAndClick(x, y int) *ViewDriver {
return self 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 // i.e. pressing down arrow
func (self *ViewDriver) SelectNextItem() *ViewDriver { func (self *ViewDriver) SelectNextItem() *ViewDriver {
return self.PressFast(self.t.keys.Universal.NextItem) 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. // user typing faster than lazygit processes the input.
PressKeysRapidly(...string) PressKeysRapidly(...string)
Click(int, int) Click(int, int)
ClickAndHold(int, int)
MouseMove(int, int)
MouseRelease(int, int)
// Simulate the terminal window regaining focus (which triggers a reload of // Simulate the terminal window regaining focus (which triggers a reload of
// changed config files) // changed config files)
FocusIn() FocusIn()