jesseduffield.lazygit/pkg/integration/components/test_test.go
Stefan Haller e9faf0325d Add a test that a background refresh keeps the scroll position
The one behaviour that made scrolling the selection into view opt-in in
the first place — a background refresh must not yank the view back to a
selection the user scrolled away from — has never been covered by a test.
It's about to become the one case that the automatic scrolling has to
suppress, so cover it first.

Getting there needs two things from the test harness: mouse wheel events,
which are the only way to scroll a list panel without moving the
selection, and a way to trigger a background refresh. The periodic
routine that issues it is turned off in tests, and turning it on would
mean waiting for its timer and hoping it fires while we're looking, so
drive the refresh directly instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 08:04:13 +02:00

252 lines
6.5 KiB
Go

package components
import (
"os"
"path/filepath"
"testing"
lazycoreUtils "github.com/jesseduffield/lazycore/pkg/utils"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"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"
"github.com/stretchr/testify/assert"
)
// this file is for testing our test code (meta, I know)
type coordinate struct {
x, y int
}
type fakeGuiDriver struct {
failureMessage string
pressedKeys []string
clickedCoordinates []coordinate
heldCoordinates []coordinate
movedCoordinates []coordinate
releasedCoordinates []coordinate
}
var _ integrationTypes.GuiDriver = &fakeGuiDriver{}
func (self *fakeGuiDriver) PressKey(key string) {
self.pressedKeys = append(self.pressedKeys, key)
}
func (self *fakeGuiDriver) PressKeysRapidly(keys ...string) {
self.pressedKeys = append(self.pressedKeys, keys...)
}
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) ScrollWheelDown(x, y int) {
}
func (self *fakeGuiDriver) RefreshInBackground() {
}
func (self *fakeGuiDriver) OnUIThreadAndWait(f func()) {
f()
}
func (self *fakeGuiDriver) FocusIn() {
}
func (self *fakeGuiDriver) FocusInAndClick(x, y int) {
self.clickedCoordinates = append(self.clickedCoordinates, coordinate{x: x, y: y})
}
func (self *fakeGuiDriver) Keys() config.KeybindingConfig {
return config.KeybindingConfig{}
}
func (self *fakeGuiDriver) CurrentContext() types.Context {
return nil
}
func (self *fakeGuiDriver) ContextForView(viewName string) types.Context {
return nil
}
func (self *fakeGuiDriver) Fail(message string) {
self.failureMessage = message
}
func (self *fakeGuiDriver) Log(message string) {
}
func (self *fakeGuiDriver) LogUI(message string) {
}
func (self *fakeGuiDriver) CheckedOutRef() *models.Branch {
return nil
}
func (self *fakeGuiDriver) MainView() *gocui.View {
return nil
}
func (self *fakeGuiDriver) SecondaryView() *gocui.View {
return nil
}
func (self *fakeGuiDriver) View(viewName string) *gocui.View {
return nil
}
func (self *fakeGuiDriver) TopViewInWindow(windowName string) *gocui.View {
return nil
}
func (self *fakeGuiDriver) SetCaption(string) {
}
func (self *fakeGuiDriver) SetCaptionPrefix(string) {
}
func (self *fakeGuiDriver) NextToast() *string {
return nil
}
func (self *fakeGuiDriver) CheckAllToastsAcknowledged() {}
func (self *fakeGuiDriver) Headless() bool { return false }
func (self *fakeGuiDriver) PretendMergeOrRebaseStartedInLazygit() {}
func TestManualFailure(t *testing.T) {
test := NewIntegrationTest(NewIntegrationTestArgs{
Description: unitTestDescription,
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Fail("blah")
},
})
driver := &fakeGuiDriver{}
test.Run(driver)
assert.Equal(t, "blah", driver.failureMessage)
}
func TestSuccess(t *testing.T) {
test := NewIntegrationTest(NewIntegrationTestArgs{
Description: unitTestDescription,
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.press("a")
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)
}
func TestFailingFixture(t *testing.T) {
test := NewIntegrationTest(NewIntegrationTestArgs{
Description: unitTestDescription,
SetupRepo: func(shell *Shell) {
shell.RunCommand([]string{"git", "checkout", "no-such-branch"})
shell.CreateFile("reached.txt", "")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {},
})
paths := NewPaths(t.TempDir())
assert.NoError(t, os.MkdirAll(paths.ActualRepo(), 0o777))
workingDir, err := createFixture(test, paths, lazycoreUtils.GetLazyRootDirectory())
assert.ErrorContains(t, err, "git checkout no-such-branch")
assert.Empty(t, workingDir)
// the steps following the failing one are skipped
assert.NoFileExists(t, filepath.Join(paths.ActualRepo(), "reached.txt"))
}
func TestGitVersionRestriction(t *testing.T) {
scenarios := []struct {
testName string
gitVersion GitVersionRestriction
expectedShouldRun bool
}{
{
testName: "AtLeast, current is newer",
gitVersion: AtLeast("2.24.9"),
expectedShouldRun: true,
},
{
testName: "AtLeast, current is same",
gitVersion: AtLeast("2.25.0"),
expectedShouldRun: true,
},
{
testName: "AtLeast, current is older",
gitVersion: AtLeast("2.26.0"),
expectedShouldRun: false,
},
{
testName: "Before, current is older",
gitVersion: Before("2.24.9"),
expectedShouldRun: false,
},
{
testName: "Before, current is same",
gitVersion: Before("2.25.0"),
expectedShouldRun: false,
},
{
testName: "Before, current is newer",
gitVersion: Before("2.26.0"),
expectedShouldRun: true,
},
{
testName: "Includes, current is included",
gitVersion: Includes("2.23.0", "2.25.0"),
expectedShouldRun: true,
},
{
testName: "Includes, current is not included",
gitVersion: Includes("2.23.0", "2.27.0"),
expectedShouldRun: false,
},
}
currentGitVersion := git_commands.GitVersion{Major: 2, Minor: 25, Patch: 0}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
test := NewIntegrationTest(NewIntegrationTestArgs{
Description: unitTestDescription,
GitVersion: s.gitVersion,
})
shouldRun := test.ShouldRunForGitVersion(&currentGitVersion)
assert.Equal(t, shouldRun, s.expectedShouldRun)
})
}
}