1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-02-03 13:21:56 +02:00

another integration test

This commit is contained in:
Jesse Duffield 2022-08-08 21:32:58 +10:00
parent 77881a9c7d
commit 225c563c63
46 changed files with 326 additions and 26 deletions

View File

@ -2,8 +2,10 @@ package gui
import (
"fmt"
"strings"
"time"
guiTypes "github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/integration/types"
)
@ -67,8 +69,23 @@ func (self *AssertImpl) CurrentBranchName(expectedViewName string) {
})
}
func (self *AssertImpl) InListContext() {
self.assertWithRetries(func() (bool, string) {
currentContext := self.gui.currentContext()
_, ok := currentContext.(guiTypes.IListContext)
return ok, fmt.Sprintf("Expected current context to be a list context, but got %s", currentContext.GetKey())
})
}
func (self *AssertImpl) SelectedLineContains(text string) {
self.assertWithRetries(func() (bool, string) {
line := self.gui.currentContext().GetView().SelectedLine()
return strings.Contains(line, text), fmt.Sprintf("Expected selected line to contain '%s', but got '%s'", text, line)
})
}
func (self *AssertImpl) assertWithRetries(test func() (bool, string)) {
waitTimes := []int{0, 100, 200, 400, 800, 1600}
waitTimes := []int{0, 1, 5, 10, 200, 500, 1000}
var message string
for _, waitTime := range waitTimes {
@ -81,6 +98,10 @@ func (self *AssertImpl) assertWithRetries(test func() (bool, string)) {
}
}
self.Fail(message)
}
func (self *AssertImpl) Fail(message string) {
self.gui.g.Close()
// need to give the gui time to close
time.Sleep(time.Millisecond * 100)

View File

@ -1,29 +1,45 @@
package gui
import (
"fmt"
"strings"
"time"
"github.com/gdamore/tcell/v2"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
guiTypes "github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/integration/types"
)
type InputImpl struct {
g *gocui.Gui
keys config.KeybindingConfig
gui *Gui
keys config.KeybindingConfig
assert types.Assert
pushKeyDelay int
}
func NewInputImpl(gui *Gui, keys config.KeybindingConfig, assert types.Assert, pushKeyDelay int) *InputImpl {
return &InputImpl{
gui: gui,
keys: keys,
assert: assert,
pushKeyDelay: pushKeyDelay,
}
}
var _ types.Input = &InputImpl{}
func (self *InputImpl) PushKeys(keyStrs ...string) {
func (self *InputImpl) PressKeys(keyStrs ...string) {
for _, keyStr := range keyStrs {
self.pushKey(keyStr)
self.pressKey(keyStr)
}
}
func (self *InputImpl) pushKey(keyStr string) {
func (self *InputImpl) pressKey(keyStr string) {
self.Wait(self.pushKeyDelay)
key := keybindings.GetKey(keyStr)
var r rune
@ -36,58 +52,115 @@ func (self *InputImpl) pushKey(keyStr string) {
tcellKey = tcell.Key(v)
}
self.g.ReplayedEvents.Keys <- gocui.NewTcellKeyEventWrapper(
self.gui.g.ReplayedEvents.Keys <- gocui.NewTcellKeyEventWrapper(
tcell.NewEventKey(tcellKey, r, tcell.ModNone),
0,
)
}
func (self *InputImpl) SwitchToStatusWindow() {
self.pushKey(self.keys.Universal.JumpToBlock[0])
self.pressKey(self.keys.Universal.JumpToBlock[0])
}
func (self *InputImpl) SwitchToFilesWindow() {
self.pushKey(self.keys.Universal.JumpToBlock[1])
self.pressKey(self.keys.Universal.JumpToBlock[1])
}
func (self *InputImpl) SwitchToBranchesWindow() {
self.pushKey(self.keys.Universal.JumpToBlock[2])
self.pressKey(self.keys.Universal.JumpToBlock[2])
}
func (self *InputImpl) SwitchToCommitsWindow() {
self.pushKey(self.keys.Universal.JumpToBlock[3])
self.pressKey(self.keys.Universal.JumpToBlock[3])
}
func (self *InputImpl) SwitchToStashWindow() {
self.pushKey(self.keys.Universal.JumpToBlock[4])
self.pressKey(self.keys.Universal.JumpToBlock[4])
}
func (self *InputImpl) Type(content string) {
for _, char := range content {
self.pushKey(string(char))
self.pressKey(string(char))
}
}
func (self *InputImpl) Confirm() {
self.pushKey(self.keys.Universal.Confirm)
self.pressKey(self.keys.Universal.Confirm)
}
func (self *InputImpl) Cancel() {
self.pushKey(self.keys.Universal.Return)
self.pressKey(self.keys.Universal.Return)
}
func (self *InputImpl) Select() {
self.pushKey(self.keys.Universal.Select)
self.pressKey(self.keys.Universal.Select)
}
func (self *InputImpl) NextItem() {
self.pushKey(self.keys.Universal.NextItem)
self.pressKey(self.keys.Universal.NextItem)
}
func (self *InputImpl) PreviousItem() {
self.pushKey(self.keys.Universal.PrevItem)
self.pressKey(self.keys.Universal.PrevItem)
}
func (self *InputImpl) ContinueMerge() {
self.PressKeys(self.keys.Universal.CreateRebaseOptionsMenu)
self.assert.SelectedLineContains("continue")
self.Confirm()
}
func (self *InputImpl) ContinueRebase() {
self.ContinueMerge()
}
func (self *InputImpl) Wait(milliseconds int) {
time.Sleep(time.Duration(milliseconds) * time.Millisecond)
}
func (self *InputImpl) log(message string) {
self.gui.c.LogAction(message)
}
// NOTE: this currently assumes that ViewBufferLines returns all the lines that can be accessed.
// If this changes in future, we'll need to update this code to first attempt to find the item
// in the current page and failing that, jump to the top of the view and iterate through all of it,
// looking for the item.
func (self *InputImpl) NavigateToListItemContainingText(text string) {
self.assert.InListContext()
currentContext := self.gui.currentContext().(guiTypes.IListContext)
view := currentContext.GetView()
// first we look for a duplicate on the current screen. We won't bother looking beyond that though.
matchCount := 0
matchIndex := -1
for i, line := range view.ViewBufferLines() {
if strings.Contains(line, text) {
matchCount++
matchIndex = i
}
}
if matchCount > 1 {
self.assert.Fail(fmt.Sprintf("Found %d matches for %s, expected only a single match", matchCount, text))
}
if matchCount == 1 {
selectedLineIdx := view.SelectedLineIdx()
if selectedLineIdx == matchIndex {
return
}
if selectedLineIdx < matchIndex {
for i := selectedLineIdx; i < matchIndex; i++ {
self.NextItem()
}
return
} else {
for i := selectedLineIdx; i > matchIndex; i-- {
self.PreviousItem()
}
return
}
}
self.assert.Fail(fmt.Sprintf("Could not find item containing text: %s", text))
}

View File

@ -21,10 +21,15 @@ func (gui *Gui) handleTestMode() {
go func() {
time.Sleep(time.Millisecond * 100)
shell := &integration.ShellImpl{}
assert := &AssertImpl{gui: gui}
keys := gui.Config.GetUserConfig().Keybinding
input := NewInputImpl(gui, keys, assert, integration.KeyPressDelay())
test.Run(
&integration.ShellImpl{},
&InputImpl{g: gui.g, keys: gui.Config.GetUserConfig().Keybinding},
&AssertImpl{gui: gui},
shell,
input,
assert,
gui.c.UserConfig.Keybinding,
)

View File

@ -2,6 +2,7 @@ package integration
import (
"os"
"strconv"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/integration/types"
@ -31,6 +32,21 @@ func PlayingIntegrationTest() bool {
return IntegrationTestName() != ""
}
// this is the delay in milliseconds between keypresses
// defaults to zero
func KeyPressDelay() int {
delayStr := os.Getenv("KEY_PRESS_DELAY")
if delayStr == "" {
return 0
}
delay, err := strconv.Atoi(delayStr)
if err != nil {
panic(err)
}
return delay
}
// OLD integration test format stuff
func Replaying() bool {

View File

@ -23,12 +23,12 @@ var Suggestions = types.NewTest(types.NewTestArgs{
Run: func(shell types.Shell, input types.Input, assert types.Assert, keys config.KeybindingConfig) {
input.SwitchToBranchesWindow()
input.PushKeys(keys.Branches.CheckoutBranchByName)
input.PressKeys(keys.Branches.CheckoutBranchByName)
assert.CurrentViewName("confirmation")
input.Type("branch-to")
input.PushKeys(keys.Universal.TogglePanel)
input.PressKeys(keys.Universal.TogglePanel)
assert.CurrentViewName("suggestions")
// we expect the first suggestion to be the branch we want because it most

View File

@ -20,7 +20,7 @@ var Commit = types.NewTest(types.NewTestArgs{
input.Select()
input.NextItem()
input.Select()
input.PushKeys(keys.Files.CommitChanges)
input.PressKeys(keys.Files.CommitChanges)
commitMessage := "my commit message"
input.Type(commitMessage)

View File

@ -23,7 +23,7 @@ var NewBranch = types.NewTest(types.NewTestArgs{
assert.CurrentViewName("commits")
input.NextItem()
input.PushKeys(keys.Universal.New)
input.PressKeys(keys.Universal.New)
assert.CurrentViewName("confirmation")

View File

@ -0,0 +1,41 @@
package interactive_rebase
import (
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/integration/types"
)
var One = types.NewTest(types.NewTestArgs{
Description: "Begins an interactive rebase, then fixups, drops, and squashes some commits",
ExtraCmdArgs: "",
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell types.Shell) {
shell.
CreateNCommits(5) // these will appears at commit 05, 04, 04, down to 01
},
Run: func(shell types.Shell, input types.Input, assert types.Assert, keys config.KeybindingConfig) {
input.SwitchToCommitsWindow()
assert.CurrentViewName("commits")
input.NavigateToListItemContainingText("commit 02")
input.PressKeys(keys.Universal.Edit)
assert.SelectedLineContains("YOU ARE HERE")
input.PreviousItem()
input.PressKeys(keys.Commits.MarkCommitAsFixup)
assert.SelectedLineContains("fixup")
input.PreviousItem()
input.PressKeys(keys.Universal.Remove)
assert.SelectedLineContains("drop")
input.PreviousItem()
input.PressKeys(keys.Commits.SquashDown)
assert.SelectedLineContains("squash")
input.ContinueRebase()
assert.CommitCount(2)
},
})

View File

@ -3,6 +3,8 @@ package integration_tests
import (
"github.com/jesseduffield/lazygit/pkg/integration/integration_tests/branch"
"github.com/jesseduffield/lazygit/pkg/integration/integration_tests/commit"
"github.com/jesseduffield/lazygit/pkg/integration/integration_tests/interactive_rebase"
"github.com/jesseduffield/lazygit/pkg/integration/types"
)
@ -13,4 +15,5 @@ var Tests = []types.Test{
commit.Commit,
commit.NewBranch,
branch.Suggestions,
interactive_rebase.One,
}

View File

@ -40,6 +40,10 @@ func (s *ShellImpl) NewBranch(name string) types.Shell {
return s.RunCommand("git checkout -b " + name)
}
func (s *ShellImpl) GitAdd(path string) types.Shell {
return s.RunCommand(fmt.Sprintf("git add \"%s\"", path))
}
func (s *ShellImpl) GitAddAll() types.Shell {
return s.RunCommand("git add -A")
}
@ -51,3 +55,21 @@ func (s *ShellImpl) Commit(message string) types.Shell {
func (s *ShellImpl) EmptyCommit(message string) types.Shell {
return s.RunCommand(fmt.Sprintf("git commit --allow-empty -m \"%s\"", message))
}
func (s *ShellImpl) CreateFileAndAdd(fileName string, fileContents string) types.Shell {
return s.
CreateFile(fileName, fileContents).
GitAdd(fileName)
}
func (s *ShellImpl) CreateNCommits(n int) types.Shell {
for i := 1; i <= n; i++ {
s.CreateFileAndAdd(
fmt.Sprintf("file%02d.txt", i),
fmt.Sprintf("file%02d content", i),
).
Commit(fmt.Sprintf("commit %02d", i))
}
return s
}

View File

@ -31,9 +31,16 @@ type Shell interface {
RunCommand(command string) Shell
CreateFile(name string, content string) Shell
NewBranch(branchName string) Shell
GitAdd(path string) Shell
GitAddAll() Shell
Commit(message string) Shell
EmptyCommit(message string) Shell
// convenience method for creating a file and adding it
CreateFileAndAdd(fileName string, fileContents string) Shell
// creates commits 01, 02, 03, ..., n with a new file in each
// The reason for padding with zeroes is so that it's easier to do string
// matches on the commit messages when there are many of them
CreateNCommits(n int) Shell
}
// through this interface our test interacts with the lazygit gui
@ -41,7 +48,7 @@ type Shell interface {
type Input interface {
// key is something like 'w' or '<space>'. It's best not to pass a direct value,
// but instead to go through the default user config to get a more meaningful key name
PushKeys(keys ...string)
PressKeys(keys ...string)
// for typing into a popup prompt
Type(content string)
// for when you want to allow lazygit to process something before continuing
@ -62,6 +69,15 @@ type Input interface {
NextItem()
// i.e. pressing up arrow
PreviousItem()
// this will look for a list item in the current panel and if it finds it, it will
// enter the keypresses required to navigate to it.
// The test will fail if:
// - the user is not in a list item
// - no list item is found containing the given text
// - multiple list items are found containing the given text in the initial page of items
NavigateToListItemContainingText(text string)
ContinueRebase()
ContinueMerge()
}
// through this interface we assert on the state of the lazygit gui
@ -71,6 +87,10 @@ type Assert interface {
HeadCommitMessage(string)
CurrentViewName(expectedViewName string)
CurrentBranchName(expectedBranchName string)
InListContext()
SelectedLineContains(text string)
// for when you just want to fail the test yourself
Fail(errorMessage string)
}
type TestImpl struct {

View File

@ -0,0 +1,30 @@
# This is a combination of 3 commits.
# This is the 1st commit message:
commit 02
# The commit message #2 will be skipped:
# commit 03
# This is the commit message #3:
commit 05
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Mon Aug 8 21:32:34 2022 +1000
#
# interactive rebase in progress; onto a1a6f7b
# Last commands done (4 commands done):
# drop 84b1ae9 commit 04
# squash aa2585a commit 05
# No commands remaining.
# You are currently rebasing branch 'master' on 'a1a6f7b'.
#
# Changes to be committed:
# new file: file02.txt
# new file: file03.txt
# new file: file05.txt
#

View File

@ -0,0 +1 @@
ref: refs/heads/master

View File

@ -0,0 +1 @@
aa2585aff7d2278341ca816f187e623503d7c4fb

View File

@ -0,0 +1 @@
aa2585aff7d2278341ca816f187e623503d7c4fb

View File

@ -0,0 +1,10 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[user]
email = CI@example.com
name = CI

View File

@ -0,0 +1 @@
Unnamed repository; edit this file 'description' to name the repository.

View File

@ -0,0 +1,7 @@
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
.DS_Store

View File

@ -0,0 +1,10 @@
0000000000000000000000000000000000000000 a1a6f7bda6aeaa08ec75f590845780fde90d901c CI <CI@example.com> 1659958354 +1000 commit (initial): commit 01
a1a6f7bda6aeaa08ec75f590845780fde90d901c cb7e56856ecee89fa44c613e094fcf962fe18cf1 CI <CI@example.com> 1659958354 +1000 commit: commit 02
cb7e56856ecee89fa44c613e094fcf962fe18cf1 6741ab4fe22a3d36b6c64397fc4295dbae1ba71d CI <CI@example.com> 1659958354 +1000 commit: commit 03
6741ab4fe22a3d36b6c64397fc4295dbae1ba71d 84b1ae9d83049341897c9388afffdc9049c3317f CI <CI@example.com> 1659958354 +1000 commit: commit 04
84b1ae9d83049341897c9388afffdc9049c3317f aa2585aff7d2278341ca816f187e623503d7c4fb CI <CI@example.com> 1659958354 +1000 commit: commit 05
aa2585aff7d2278341ca816f187e623503d7c4fb a1a6f7bda6aeaa08ec75f590845780fde90d901c CI <CI@example.com> 1659958355 +1000 rebase (start): checkout a1a6f7bda6aeaa08ec75f590845780fde90d901c
a1a6f7bda6aeaa08ec75f590845780fde90d901c cb7e56856ecee89fa44c613e094fcf962fe18cf1 CI <CI@example.com> 1659958355 +1000 rebase: fast-forward
cb7e56856ecee89fa44c613e094fcf962fe18cf1 9c68b57ac7b652fbebc5e93a9a1b72014400c269 CI <CI@example.com> 1659958355 +1000 rebase (continue) (fixup): # This is a combination of 2 commits.
9c68b57ac7b652fbebc5e93a9a1b72014400c269 f4316f7a6df3fe5b7e8da1b2c8767ed1e825dc05 CI <CI@example.com> 1659958355 +1000 rebase (continue) (squash): commit 02
f4316f7a6df3fe5b7e8da1b2c8767ed1e825dc05 f4316f7a6df3fe5b7e8da1b2c8767ed1e825dc05 CI <CI@example.com> 1659958355 +1000 rebase (continue) (finish): returning to refs/heads/master

View File

@ -0,0 +1,6 @@
0000000000000000000000000000000000000000 a1a6f7bda6aeaa08ec75f590845780fde90d901c CI <CI@example.com> 1659958354 +1000 commit (initial): commit 01
a1a6f7bda6aeaa08ec75f590845780fde90d901c cb7e56856ecee89fa44c613e094fcf962fe18cf1 CI <CI@example.com> 1659958354 +1000 commit: commit 02
cb7e56856ecee89fa44c613e094fcf962fe18cf1 6741ab4fe22a3d36b6c64397fc4295dbae1ba71d CI <CI@example.com> 1659958354 +1000 commit: commit 03
6741ab4fe22a3d36b6c64397fc4295dbae1ba71d 84b1ae9d83049341897c9388afffdc9049c3317f CI <CI@example.com> 1659958354 +1000 commit: commit 04
84b1ae9d83049341897c9388afffdc9049c3317f aa2585aff7d2278341ca816f187e623503d7c4fb CI <CI@example.com> 1659958354 +1000 commit: commit 05
aa2585aff7d2278341ca816f187e623503d7c4fb f4316f7a6df3fe5b7e8da1b2c8767ed1e825dc05 CI <CI@example.com> 1659958355 +1000 rebase (continue) (finish): refs/heads/master onto a1a6f7bda6aeaa08ec75f590845780fde90d901c

View File

@ -0,0 +1,3 @@
x�ÎM
Â0†a×9Åì™ÉŸºê1’ô
Ö–ÁãÛ…pûò,ÞºÌó£“h<ô Ä©H ©ÚRòä…½æ„æTØEÆÞ¬Àš5oxuªåŒSˆ¨@Ò–½¯QX}«M£m�T›˜üî÷e£a¤Ë0ÞðÉóúÄ©.ó•$Õ�\ðtf6{ݧ:þä?OìÌ�;ß

View File

@ -0,0 +1,3 @@
xŤÎM
Â0@a×9ĹěI2?m@DčĘcL’
Ö–ÁăŰ…pűřŻ,óüč’úfČU…[LąřLŁęčsEő±´&K#dnÝŞ›˝:Č@A35‹Q±˘d)B�†V(&®Y-dBuúî÷e�éçévµŹÎëÓNe™/„Sâ™ŕĽ÷nŻűT·?ůĎ�'÷Ěu;Ť

View File

@ -0,0 +1,2 @@
x…ŽÁJÄ0†=ç)z–IÚ´‰Č˛°§˝ű“fbĂ6mi"úřF© ^„9}óý3˙¸¦ ´Ş{(;3pďť ÚŚĘ9Íä;‰ť%áµŰą2%Y‰Ťv^
�¤> ÎSOL„†ÇAmŃtz0<[ôĺ(č­Lë×<_oţ ´Í|×tŮkkµiuŹEĄµTát}č˘�—)f¨CPł..TâşŔ@}�z,ź~Yeb�ą+Hś3˝ň“8*ńmóďqžÁ1ä{Ü6ö5ÔüXŘŠOz d×

View File

@ -0,0 +1,4 @@
x�ÎA
1 …a×=E÷‚4“¦m@D˜•Çh3)
Ö†
ß.<€Û�ÿÁ“µµG·ÀáÐwU«pLJ9׊‘¦²¨–€$BT!*ÃÀly×W·ÉÈÊKBç=$ŽÂ˜Ò˜×Ex  B¬&¿û}Ýí|³çùvÕOnÛSO²¶‹…@Ì”�¼=‚sÎ §ºþ™ÿzëÈ|²);=

View File

@ -0,0 +1 @@
f4316f7a6df3fe5b7e8da1b2c8767ed1e825dc05

View File

@ -0,0 +1 @@
file01 content

View File

@ -0,0 +1 @@
file02 content

View File

@ -0,0 +1 @@
file03 content

View File

@ -0,0 +1 @@
file05 content

View File

@ -465,6 +465,14 @@ func (v *View) Cursor() (x, y int) {
return v.cx, v.cy
}
func (v *View) CursorX() int {
return v.cx
}
func (v *View) CursorY() int {
return v.cy
}
// SetOrigin sets the origin position of the view's internal buffer,
// so the buffer starts to be printed from this point, which means that
// it is linked with the origin point of view. It can be used to
@ -1235,6 +1243,13 @@ func (v *View) SelectedLineIdx() int {
return seletedLineIdx
}
// expected to only be used in tests
func (v *View) SelectedLine() string {
line := v.lines[v.SelectedLineIdx()]
str := lineType(line).String()
return strings.Replace(str, "\x00", " ", -1)
}
func (v *View) SelectedPoint() (int, int) {
cx, cy := v.Cursor()
ox, oy := v.Origin()