1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-07-30 03:23:08 +03:00

add helper functions for popups in tests

This commit is contained in:
Jesse Duffield
2022-12-24 17:48:57 +11:00
parent aedfce2845
commit b623ecf898
40 changed files with 380 additions and 402 deletions

View File

@ -21,57 +21,44 @@ func NewAssert(gui integrationTypes.GuiDriver) *Assert {
return &Assert{gui: gui}
}
// for making assertions on string values
type matcher struct {
testFn func(string) (bool, string)
prefix string
}
func (self *matcher) test(value string) (bool, string) {
ok, message := self.testFn(value)
if ok {
return true, ""
}
if self.prefix != "" {
return false, self.prefix + " " + message
}
return false, message
}
func (self *matcher) context(prefix string) *matcher {
self.prefix = prefix
return self
}
func Contains(target string) *matcher {
return &matcher{testFn: func(value string) (bool, string) {
return strings.Contains(value, target), fmt.Sprintf("Expected '%s' to be found in '%s'", target, value)
}}
return NewMatcher(
fmt.Sprintf("contains '%s'", target),
func(value string) (bool, string) {
return strings.Contains(value, target), fmt.Sprintf("Expected '%s' to be found in '%s'", target, value)
},
)
}
func NotContains(target string) *matcher {
return &matcher{testFn: func(value string) (bool, string) {
return !strings.Contains(value, target), fmt.Sprintf("Expected '%s' to NOT be found in '%s'", target, value)
}}
return NewMatcher(
fmt.Sprintf("does not contain '%s'", target),
func(value string) (bool, string) {
return !strings.Contains(value, target), fmt.Sprintf("Expected '%s' to NOT be found in '%s'", target, value)
},
)
}
func MatchesRegexp(regexStr string) *matcher {
return &matcher{testFn: func(value string) (bool, string) {
matched, err := regexp.MatchString(regexStr, value)
if err != nil {
return false, fmt.Sprintf("Unexpected error parsing regular expression '%s': %s", regexStr, err.Error())
}
return matched, fmt.Sprintf("Expected '%s' to match regular expression '%s'", value, regexStr)
}}
func MatchesRegexp(target string) *matcher {
return NewMatcher(
fmt.Sprintf("matches regular expression '%s'", target),
func(value string) (bool, string) {
matched, err := regexp.MatchString(target, value)
if err != nil {
return false, fmt.Sprintf("Unexpected error parsing regular expression '%s': %s", target, err.Error())
}
return matched, fmt.Sprintf("Expected '%s' to match regular expression '%s'", value, target)
},
)
}
func Equals(target string) *matcher {
return &matcher{testFn: func(value string) (bool, string) {
return target == value, fmt.Sprintf("Expected '%s' to equal '%s'", value, target)
}}
return NewMatcher(
fmt.Sprintf("equals '%s'", target),
func(value string) (bool, string) {
return target == value, fmt.Sprintf("Expected '%s' to equal '%s'", value, target)
},
)
}
func (self *Assert) WorkingTreeFileCount(expectedCount int) {
@ -186,6 +173,13 @@ func (self *Assert) InAlert() {
})
}
func (self *Assert) InCommitMessagePanel() {
self.assertWithRetries(func() (bool, string) {
currentView := self.gui.CurrentContext().GetView()
return currentView.Name() == "commitMessage", "Expected commit message panel to be focused"
})
}
func (self *Assert) InMenu() {
self.assertWithRetries(func() (bool, string) {
return self.gui.CurrentContext().GetView().Name() == "menu", "Expected popup menu to be focused"

View File

@ -28,87 +28,81 @@ func NewInput(gui integrationTypes.GuiDriver, keys config.KeybindingConfig, asse
// 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
func (self *Input) PressKeys(keyStrs ...string) {
func (self *Input) Press(keyStrs ...string) {
for _, keyStr := range keyStrs {
self.pressKey(keyStr)
self.press(keyStr)
}
}
func (self *Input) pressKey(keyStr string) {
func (self *Input) press(keyStr string) {
self.Wait(self.pushKeyDelay)
self.gui.PressKey(keyStr)
}
func (self *Input) SwitchToStatusWindow() {
self.pressKey(self.keys.Universal.JumpToBlock[0])
self.press(self.keys.Universal.JumpToBlock[0])
self.assert.CurrentWindowName("status")
}
func (self *Input) SwitchToFilesWindow() {
self.pressKey(self.keys.Universal.JumpToBlock[1])
self.press(self.keys.Universal.JumpToBlock[1])
self.assert.CurrentWindowName("files")
}
func (self *Input) SwitchToBranchesWindow() {
self.pressKey(self.keys.Universal.JumpToBlock[2])
self.press(self.keys.Universal.JumpToBlock[2])
self.assert.CurrentWindowName("localBranches")
}
func (self *Input) SwitchToCommitsWindow() {
self.pressKey(self.keys.Universal.JumpToBlock[3])
self.press(self.keys.Universal.JumpToBlock[3])
self.assert.CurrentWindowName("commits")
}
func (self *Input) SwitchToStashWindow() {
self.pressKey(self.keys.Universal.JumpToBlock[4])
self.press(self.keys.Universal.JumpToBlock[4])
self.assert.CurrentWindowName("stash")
}
func (self *Input) Type(content string) {
for _, char := range content {
self.pressKey(string(char))
self.press(string(char))
}
}
// i.e. pressing enter
func (self *Input) Confirm() {
self.pressKey(self.keys.Universal.Confirm)
}
func (self *Input) ProceedWhenAsked(matcher *matcher) {
self.assert.InConfirm()
self.assert.CurrentViewContent(matcher)
self.Confirm()
self.press(self.keys.Universal.Confirm)
}
// i.e. same as Confirm
func (self *Input) Enter() {
self.pressKey(self.keys.Universal.Confirm)
self.press(self.keys.Universal.Confirm)
}
// i.e. pressing escape
func (self *Input) Cancel() {
self.pressKey(self.keys.Universal.Return)
self.press(self.keys.Universal.Return)
}
// i.e. pressing space
func (self *Input) PrimaryAction() {
self.pressKey(self.keys.Universal.Select)
self.press(self.keys.Universal.Select)
}
// i.e. pressing down arrow
func (self *Input) NextItem() {
self.pressKey(self.keys.Universal.NextItem)
self.press(self.keys.Universal.NextItem)
}
// i.e. pressing up arrow
func (self *Input) PreviousItem() {
self.pressKey(self.keys.Universal.PrevItem)
self.press(self.keys.Universal.PrevItem)
}
func (self *Input) ContinueMerge() {
self.PressKeys(self.keys.Universal.CreateRebaseOptionsMenu)
self.Press(self.keys.Universal.CreateRebaseOptionsMenu)
self.assert.SelectedLine(Contains("continue"))
self.Confirm()
}
@ -141,7 +135,7 @@ func (self *Input) Log(message string) {
// 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 *Input) NavigateToListItemContainingText(text string) {
func (self *Input) NavigateToListItem(matcher *matcher) {
self.assert.InListContext()
currentContext := self.gui.CurrentContext().(types.IListContext)
@ -151,19 +145,20 @@ func (self *Input) NavigateToListItemContainingText(text string) {
var matchIndex int
self.assert.assertWithRetries(func() (bool, string) {
matchCount := 0
matchIndex = -1
var matches []string
// first we look for a duplicate on the current screen. We won't bother looking beyond that though.
for i, line := range view.ViewBufferLines() {
if strings.Contains(line, text) {
matchCount++
ok, _ := matcher.test(line)
if ok {
matches = append(matches, line)
matchIndex = i
}
}
if matchCount > 1 {
return false, fmt.Sprintf("Found %d matches for %s, expected only a single match", matchCount, text)
} else if matchCount == 0 {
return false, fmt.Sprintf("Could not find item containing text: %s", text)
if len(matches) > 1 {
return false, fmt.Sprintf("Found %d matches for `%s`, expected only a single match. Lines:\n%s", len(matches), matcher.name, strings.Join(matches, "\n"))
} else if len(matches) == 0 {
return false, fmt.Sprintf("Could not find item matching: %s", matcher.name)
} else {
return true, ""
}
@ -171,20 +166,67 @@ func (self *Input) NavigateToListItemContainingText(text string) {
selectedLineIdx := view.SelectedLineIdx()
if selectedLineIdx == matchIndex {
self.assert.SelectedLine(Contains(text))
self.assert.SelectedLine(matcher)
return
}
if selectedLineIdx < matchIndex {
for i := selectedLineIdx; i < matchIndex; i++ {
self.NextItem()
}
self.assert.SelectedLine(Contains(text))
self.assert.SelectedLine(matcher)
return
} else {
for i := selectedLineIdx; i > matchIndex; i-- {
self.PreviousItem()
}
self.assert.SelectedLine(Contains(text))
self.assert.SelectedLine(matcher)
return
}
}
func (self *Input) AcceptConfirmation(title *matcher, content *matcher) {
self.assert.InConfirm()
self.assert.CurrentViewTitle(title)
self.assert.CurrentViewContent(content)
self.Confirm()
}
func (self *Input) DenyConfirmation(title *matcher, content *matcher) {
self.assert.InConfirm()
self.assert.CurrentViewTitle(title)
self.assert.CurrentViewContent(content)
self.Cancel()
}
func (self *Input) Prompt(title *matcher, textToType string) {
self.assert.InPrompt()
self.assert.CurrentViewTitle(title)
self.Type(textToType)
self.Confirm()
}
// type some text into a prompt, then switch to the suggestions panel and expect the first
// item to match the given matcher, then confirm that item.
func (self *Input) Typeahead(title *matcher, textToType string, expectedFirstOption *matcher) {
self.assert.InPrompt()
self.assert.CurrentViewTitle(title)
self.Type(textToType)
self.Press(self.keys.Universal.TogglePanel)
self.assert.CurrentViewName("suggestions")
self.assert.SelectedLine(expectedFirstOption)
self.Confirm()
}
func (self *Input) Menu(title *matcher, optionToSelect *matcher) {
self.assert.InMenu()
self.assert.CurrentViewTitle(title)
self.NavigateToListItem(optionToSelect)
self.Confirm()
}
func (self *Input) Alert(title *matcher, content *matcher) {
self.assert.InListContext()
self.assert.CurrentViewTitle(title)
self.assert.CurrentViewContent(content)
self.Confirm()
}

View File

@ -0,0 +1,35 @@
package components
// for making assertions on string values
type matcher struct {
// e.g. "contains 'foo'"
name string
// returns a bool that says whether the test passed and if it returns false, it
// also returns a string of the error message
testFn func(string) (bool, string)
// this is printed when there's an error so that it's clear what the context of the assertion is
prefix string
}
func NewMatcher(name string, testFn func(string) (bool, string)) *matcher {
return &matcher{name: name, testFn: testFn}
}
func (self *matcher) test(value string) (bool, string) {
ok, message := self.testFn(value)
if ok {
return true, ""
}
if self.prefix != "" {
return false, self.prefix + " " + message
}
return false, message
}
func (self *matcher) context(prefix string) *matcher {
self.prefix = prefix
return self
}

View File

@ -115,6 +115,10 @@ func prepareTestDir(
}
func buildLazygit() error {
// // TODO: remove this line!
// // skipping this because I'm not making changes to the app code atm.
// return nil
osCommand := oscommands.NewDummyOSCommand()
return osCommand.Cmd.New(fmt.Sprintf(
"go build -o %s pkg/integration/clients/injector/main.go", tempLazygitPath(),

View File

@ -64,8 +64,8 @@ func TestAssertionFailure(t *testing.T) {
test := NewIntegrationTest(NewIntegrationTestArgs{
Description: unitTestDescription,
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
input.PressKeys("a")
input.PressKeys("b")
input.Press("a")
input.Press("b")
assert.CommitCount(2)
},
})
@ -91,8 +91,8 @@ func TestSuccess(t *testing.T) {
test := NewIntegrationTest(NewIntegrationTestArgs{
Description: unitTestDescription,
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
input.PressKeys("a")
input.PressKeys("b")
input.Press("a")
input.Press("b")
assert.CommitCount(0)
},
})