diff --git a/cmd/integration_test/main.go b/cmd/integration_test/main.go index 9de11a2c8..32df7dd0a 100644 --- a/cmd/integration_test/main.go +++ b/cmd/integration_test/main.go @@ -16,7 +16,7 @@ Usage: > go run cmd/integration_test/main.go cli [--slow] [--sandbox] ... If you pass no test names, it runs all tests Accepted environment variables: - KEY_PRESS_DELAY (e.g. 200): the number of milliseconds to wait between keypresses + INPUT_DELAY (e.g. 200): the number of milliseconds to wait between keypresses or mouse clicks TUI mode: > go run cmd/integration_test/main.go tui diff --git a/go.mod b/go.mod index 54228814b..249fe4d46 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/integrii/flaggy v1.4.0 github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d - github.com/jesseduffield/gocui v0.3.1-0.20230806095321-ac7b03108825 + github.com/jesseduffield/gocui v0.3.1-0.20230807090044-83a7161c8727 github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e diff --git a/go.sum b/go.sum index 8a7409da5..643db264b 100644 --- a/go.sum +++ b/go.sum @@ -179,8 +179,8 @@ github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8T github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk= github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE= github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o= -github.com/jesseduffield/gocui v0.3.1-0.20230806095321-ac7b03108825 h1:4Ea8qV/BbZAGcXd8MAufDsbwwfz2pbRZdqIodC/XHZs= -github.com/jesseduffield/gocui v0.3.1-0.20230806095321-ac7b03108825/go.mod h1:trXE7RRGL2hTsv+Ntk+SHLtRobg9JE138n3Ug/X2Cf4= +github.com/jesseduffield/gocui v0.3.1-0.20230807090044-83a7161c8727 h1:cLq698s96uDMm0n5379doAjIKoip3/8ioWIM8pySRLY= +github.com/jesseduffield/gocui v0.3.1-0.20230807090044-83a7161c8727/go.mod h1:trXE7RRGL2hTsv+Ntk+SHLtRobg9JE138n3Ug/X2Cf4= github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0= github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo= github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY= diff --git a/pkg/gui/gui_driver.go b/pkg/gui/gui_driver.go index 6d859eddb..7fd51c952 100644 --- a/pkg/gui/gui_driver.go +++ b/pkg/gui/gui_driver.go @@ -45,6 +45,14 @@ func (self *GuiDriver) PressKey(keyStr string) { self.waitTillIdle() } +func (self *GuiDriver) Click(x, y int) { + self.gui.g.ReplayedEvents.MouseEvents <- gocui.NewTcellMouseEventWrapper( + tcell.NewEventMouse(x, y, tcell.ButtonPrimary, 0), + 0, + ) + self.waitTillIdle() +} + // wait until lazygit is idle (i.e. all processing is done) before continuing func (self *GuiDriver) waitTillIdle() { <-self.isIdleChan diff --git a/pkg/integration/README.md b/pkg/integration/README.md index 8b6ec15df..e0d32665a 100644 --- a/pkg/integration/README.md +++ b/pkg/integration/README.md @@ -48,7 +48,7 @@ The third, the go-test command, intended only for use in CI, to be run along wit The name of a test is based on its path, so the name of the test at `pkg/integration/tests/commit/new_branch.go` is commit/new_branch. So to run it with our test runner you would run `go run cmd/integration_test/main.go cli commit/new_branch`. -You can pass the KEY_PRESS_DELAY env var to the test runner in order to set a delay in milliseconds between keypresses, which helps for watching a test at a realistic speed to understand what it's doing. Or you can pass the '--slow' flag which sets a pre-set 'slow' key delay. In the tui you can press 't' to run the test in slow mode. +You can pass the INPUT_DELAY env var to the test runner in order to set a delay in milliseconds between keypresses or mouse clicks, which helps for watching a test at a realistic speed to understand what it's doing. Or you can pass the '--slow' flag which sets a pre-set 'slow' key delay. In the tui you can press 't' to run the test in slow mode. The resultant repo will be stored in `test/results`, so if you're not sure what went wrong you can go there and inspect the repo. diff --git a/pkg/integration/clients/cli.go b/pkg/integration/clients/cli.go index 79853b224..08cff3f00 100644 --- a/pkg/integration/clients/cli.go +++ b/pkg/integration/clients/cli.go @@ -24,9 +24,9 @@ import ( // If invoked directly, you can specify tests to run by passing their names as positional arguments func RunCLI(testNames []string, slow bool, sandbox bool) { - keyPressDelay := tryConvert(os.Getenv("KEY_PRESS_DELAY"), 0) + inputDelay := tryConvert(os.Getenv("INPUT_DELAY"), 0) if slow { - keyPressDelay = SLOW_KEY_PRESS_DELAY + inputDelay = SLOW_INPUT_DELAY } err := components.RunTests( @@ -35,7 +35,7 @@ func RunCLI(testNames []string, slow bool, sandbox bool) { runCmdInTerminal, runAndPrintFatalError, sandbox, - keyPressDelay, + inputDelay, 1, ) if err != nil { diff --git a/pkg/integration/clients/tui.go b/pkg/integration/clients/tui.go index 904712037..9b873caab 100644 --- a/pkg/integration/clients/tui.go +++ b/pkg/integration/clients/tui.go @@ -19,7 +19,7 @@ import ( // This program lets you run integration tests from a TUI. See pkg/integration/README.md for more info. -var SLOW_KEY_PRESS_DELAY = 600 +var SLOW_INPUT_DELAY = 600 func RunTUI() { rootDir := utils.GetLazyRootDirectory() @@ -111,7 +111,7 @@ func RunTUI() { return nil } - suspendAndRunTest(currentTest, false, SLOW_KEY_PRESS_DELAY) + suspendAndRunTest(currentTest, false, SLOW_INPUT_DELAY) return nil }); err != nil { @@ -271,12 +271,12 @@ func (self *app) wrapEditor(f func(v *gocui.View, key gocui.Key, ch rune, mod go } } -func suspendAndRunTest(test *components.IntegrationTest, sandbox bool, keyPressDelay int) { +func suspendAndRunTest(test *components.IntegrationTest, sandbox bool, inputDelay int) { if err := gocui.Screen.Suspend(); err != nil { panic(err) } - runTuiTest(test, sandbox, keyPressDelay) + runTuiTest(test, sandbox, inputDelay) fmt.Fprintf(os.Stdout, "\n%s", style.FgGreen.Sprint("press enter to return")) fmt.Scanln() // wait for enter press @@ -371,14 +371,14 @@ func quit(g *gocui.Gui, v *gocui.View) error { return gocui.ErrQuit } -func runTuiTest(test *components.IntegrationTest, sandbox bool, keyPressDelay int) { +func runTuiTest(test *components.IntegrationTest, sandbox bool, inputDelay int) { err := components.RunTests( []*components.IntegrationTest{test}, log.Printf, runCmdInTerminal, runAndPrintError, sandbox, - keyPressDelay, + inputDelay, 1, ) if err != nil { diff --git a/pkg/integration/components/runner.go b/pkg/integration/components/runner.go index 908d7e1d8..9d380044a 100644 --- a/pkg/integration/components/runner.go +++ b/pkg/integration/components/runner.go @@ -29,7 +29,7 @@ func RunTests( runCmd func(cmd *exec.Cmd) error, testWrapper func(test *IntegrationTest, f func() error), sandbox bool, - keyPressDelay int, + inputDelay int, maxAttempts int, ) error { projectRootDir := lazycoreUtils.GetLazyRootDirectory() @@ -58,7 +58,7 @@ func RunTests( ) for i := 0; i < maxAttempts; i++ { - err := runTest(test, paths, projectRootDir, logf, runCmd, sandbox, keyPressDelay, gitVersion) + err := runTest(test, paths, projectRootDir, logf, runCmd, sandbox, inputDelay, gitVersion) if err != nil { if i == maxAttempts-1 { return err @@ -83,7 +83,7 @@ func runTest( logf func(format string, formatArgs ...interface{}), runCmd func(cmd *exec.Cmd) error, sandbox bool, - keyPressDelay int, + inputDelay int, gitVersion *git_commands.GitVersion, ) error { if test.Skip() { @@ -100,7 +100,7 @@ func runTest( return err } - cmd, err := getLazygitCommand(test, paths, projectRootDir, sandbox, keyPressDelay) + cmd, err := getLazygitCommand(test, paths, projectRootDir, sandbox, inputDelay) if err != nil { return err } @@ -165,7 +165,7 @@ func getGitVersion() (*git_commands.GitVersion, error) { return git_commands.ParseGitVersion(versionStr) } -func getLazygitCommand(test *IntegrationTest, paths Paths, rootDir string, sandbox bool, keyPressDelay int) (*exec.Cmd, error) { +func getLazygitCommand(test *IntegrationTest, paths Paths, rootDir string, sandbox bool, inputDelay int) (*exec.Cmd, error) { osCommand := oscommands.NewDummyOSCommand() err := os.RemoveAll(paths.Config()) @@ -203,8 +203,8 @@ func getLazygitCommand(test *IntegrationTest, paths Paths, rootDir string, sandb } } - if keyPressDelay > 0 { - cmdObj.AddEnvVars(fmt.Sprintf("KEY_PRESS_DELAY=%d", keyPressDelay)) + if inputDelay > 0 { + cmdObj.AddEnvVars(fmt.Sprintf("INPUT_DELAY=%d", inputDelay)) } cmdObj.AddEnvVars(fmt.Sprintf("%s=%s", GIT_CONFIG_GLOBAL_ENV_VAR, globalGitConfigPath(rootDir))) diff --git a/pkg/integration/components/test.go b/pkg/integration/components/test.go index af307beeb..e0970f6a2 100644 --- a/pkg/integration/components/test.go +++ b/pkg/integration/components/test.go @@ -190,9 +190,9 @@ func (self *IntegrationTest) Run(gui integrationTypes.GuiDriver) { shell := NewShell(pwd, func(errorMsg string) { gui.Fail(errorMsg) }) keys := gui.Keys() - testDriver := NewTestDriver(gui, shell, keys, KeyPressDelay()) + testDriver := NewTestDriver(gui, shell, keys, InputDelay()) - if KeyPressDelay() > 0 { + if InputDelay() > 0 { // Setting caption to clear the options menu from whatever it starts with testDriver.SetCaption("") testDriver.SetCaptionPrefix("") @@ -200,7 +200,7 @@ func (self *IntegrationTest) Run(gui integrationTypes.GuiDriver) { self.run(testDriver, keys) - if KeyPressDelay() > 0 { + if InputDelay() > 0 { // Clear whatever caption there was so it doesn't linger testDriver.SetCaption("") testDriver.SetCaptionPrefix("") @@ -232,10 +232,10 @@ func TestNameFromFilePath(path string) string { return name[:len(name)-len(".go")] } -// this is the delay in milliseconds between keypresses +// this is the delay in milliseconds between keypresses or mouse clicks // defaults to zero -func KeyPressDelay() int { - delayStr := os.Getenv("KEY_PRESS_DELAY") +func InputDelay() int { + delayStr := os.Getenv("INPUT_DELAY") if delayStr == "" { return 0 } diff --git a/pkg/integration/components/test_driver.go b/pkg/integration/components/test_driver.go index 80e4cb948..a862dce06 100644 --- a/pkg/integration/components/test_driver.go +++ b/pkg/integration/components/test_driver.go @@ -10,18 +10,18 @@ import ( ) type TestDriver struct { - gui integrationTypes.GuiDriver - keys config.KeybindingConfig - pushKeyDelay int + gui integrationTypes.GuiDriver + keys config.KeybindingConfig + inputDelay int *assertionHelper shell *Shell } -func NewTestDriver(gui integrationTypes.GuiDriver, shell *Shell, keys config.KeybindingConfig, pushKeyDelay int) *TestDriver { +func NewTestDriver(gui integrationTypes.GuiDriver, shell *Shell, keys config.KeybindingConfig, inputDelay int) *TestDriver { return &TestDriver{ gui: gui, keys: keys, - pushKeyDelay: pushKeyDelay, + inputDelay: inputDelay, assertionHelper: &assertionHelper{gui: gui}, shell: shell, } @@ -32,7 +32,7 @@ func NewTestDriver(gui integrationTypes.GuiDriver, shell *Shell, keys config.Key func (self *TestDriver) press(keyStr string) { self.SetCaption(fmt.Sprintf("Pressing %s", keyStr)) self.gui.PressKey(keyStr) - self.Wait(self.pushKeyDelay) + self.Wait(self.inputDelay) } // for use when typing or navigating, because in demos we want that to happen @@ -40,7 +40,13 @@ func (self *TestDriver) press(keyStr string) { func (self *TestDriver) pressFast(keyStr string) { self.SetCaption("") self.gui.PressKey(keyStr) - self.Wait(self.pushKeyDelay / 5) + self.Wait(self.inputDelay / 5) +} + +func (self *TestDriver) click(x, y int) { + self.SetCaption(fmt.Sprintf("Clicking %d, %d", x, y)) + self.gui.Click(x, y) + self.Wait(self.inputDelay) } // Should only be used in specific cases where you're doing something weird! diff --git a/pkg/integration/components/test_test.go b/pkg/integration/components/test_test.go index b5c1c6055..99e52f5e9 100644 --- a/pkg/integration/components/test_test.go +++ b/pkg/integration/components/test_test.go @@ -14,9 +14,14 @@ import ( // this file is for testing our test code (meta, I know) +type coordinate struct { + x, y int +} + type fakeGuiDriver struct { - failureMessage string - pressedKeys []string + failureMessage string + pressedKeys []string + clickedCoordinates []coordinate } var _ integrationTypes.GuiDriver = &fakeGuiDriver{} @@ -25,6 +30,10 @@ func (self *fakeGuiDriver) PressKey(key string) { self.pressedKeys = append(self.pressedKeys, key) } +func (self *fakeGuiDriver) Click(x, y int) { + self.clickedCoordinates = append(self.clickedCoordinates, coordinate{x: x, y: y}) +} + func (self *fakeGuiDriver) Keys() config.KeybindingConfig { return config.KeybindingConfig{} } @@ -87,11 +96,14 @@ func TestSuccess(t *testing.T) { Run: func(t *TestDriver, keys config.KeybindingConfig) { t.press("a") t.press("b") + t.click(0, 1) + t.click(2, 3) }, }) driver := &fakeGuiDriver{} test.Run(driver) assert.EqualValues(t, []string{"a", "b"}, driver.pressedKeys) + assert.EqualValues(t, []coordinate{{0, 1}, {2, 3}}, driver.clickedCoordinates) assert.Equal(t, "", driver.failureMessage) } diff --git a/pkg/integration/components/view_driver.go b/pkg/integration/components/view_driver.go index 4e6cbd1d1..6778dd8dd 100644 --- a/pkg/integration/components/view_driver.go +++ b/pkg/integration/components/view_driver.go @@ -403,6 +403,14 @@ func (self *ViewDriver) PressFast(keyStr string) *ViewDriver { return self } +func (self *ViewDriver) Click(x, y int) *ViewDriver { + offsetX, offsetY, _, _ := self.getView().Dimensions() + + self.t.click(offsetX+1+x, offsetY+1+y) + + return self +} + // i.e. pressing down arrow func (self *ViewDriver) SelectNextItem() *ViewDriver { return self.PressFast(self.t.keys.Universal.NextItem) diff --git a/pkg/integration/types/types.go b/pkg/integration/types/types.go index 689fef4d0..15a2d514f 100644 --- a/pkg/integration/types/types.go +++ b/pkg/integration/types/types.go @@ -23,6 +23,7 @@ type IntegrationTest interface { // this is the interface through which our integration tests interact with the lazygit gui type GuiDriver interface { PressKey(string) + Click(int, int) Keys() config.KeybindingConfig CurrentContext() types.Context ContextForView(viewName string) types.Context diff --git a/vendor/github.com/jesseduffield/gocui/gui.go b/vendor/github.com/jesseduffield/gocui/gui.go index fa2019a28..89015e67b 100644 --- a/vendor/github.com/jesseduffield/gocui/gui.go +++ b/vendor/github.com/jesseduffield/gocui/gui.go @@ -103,8 +103,9 @@ type GuiMutexes struct { } type replayedEvents struct { - Keys chan *TcellKeyEventWrapper - Resizes chan *TcellResizeEventWrapper + Keys chan *TcellKeyEventWrapper + Resizes chan *TcellResizeEventWrapper + MouseEvents chan *TcellMouseEventWrapper } type RecordingConfig struct { @@ -225,8 +226,9 @@ func NewGui(opts NewGuiOpts) (*Gui, error) { if opts.PlayRecording { g.ReplayedEvents = replayedEvents{ - Keys: make(chan *TcellKeyEventWrapper), - Resizes: make(chan *TcellResizeEventWrapper), + Keys: make(chan *TcellKeyEventWrapper), + Resizes: make(chan *TcellResizeEventWrapper), + MouseEvents: make(chan *TcellMouseEventWrapper), } } diff --git a/vendor/github.com/jesseduffield/gocui/tcell_driver.go b/vendor/github.com/jesseduffield/gocui/tcell_driver.go index ab3f610f6..edd5509ec 100644 --- a/vendor/github.com/jesseduffield/gocui/tcell_driver.go +++ b/vendor/github.com/jesseduffield/gocui/tcell_driver.go @@ -217,6 +217,29 @@ func (wrapper TcellKeyEventWrapper) toTcellEvent() tcell.Event { return tcell.NewEventKey(wrapper.Key, wrapper.Ch, wrapper.Mod) } +type TcellMouseEventWrapper struct { + Timestamp int64 + X int + Y int + ButtonMask tcell.ButtonMask + ModMask tcell.ModMask +} + +func NewTcellMouseEventWrapper(event *tcell.EventMouse, timestamp int64) *TcellMouseEventWrapper { + x, y := event.Position() + return &TcellMouseEventWrapper{ + Timestamp: timestamp, + X: x, + Y: y, + ButtonMask: event.Buttons(), + ModMask: event.Modifiers(), + } +} + +func (wrapper TcellMouseEventWrapper) toTcellEvent() tcell.Event { + return tcell.NewEventMouse(wrapper.X, wrapper.Y, wrapper.ButtonMask, wrapper.ModMask) +} + type TcellResizeEventWrapper struct { Timestamp int64 Width int @@ -246,6 +269,8 @@ func (g *Gui) pollEvent() GocuiEvent { tev = (ev).toTcellEvent() case ev := <-g.ReplayedEvents.Resizes: tev = (ev).toTcellEvent() + case ev := <-g.ReplayedEvents.MouseEvents: + tev = (ev).toTcellEvent() } } else { tev = Screen.PollEvent() diff --git a/vendor/modules.txt b/vendor/modules.txt index 45f6eafa9..9561f09eb 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -124,7 +124,7 @@ github.com/jesseduffield/go-git/v5/utils/merkletrie/filesystem github.com/jesseduffield/go-git/v5/utils/merkletrie/index github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame github.com/jesseduffield/go-git/v5/utils/merkletrie/noder -# github.com/jesseduffield/gocui v0.3.1-0.20230806095321-ac7b03108825 +# github.com/jesseduffield/gocui v0.3.1-0.20230807090044-83a7161c8727 ## explicit; go 1.12 github.com/jesseduffield/gocui # github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10