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:
@ -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"
|
||||
|
@ -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()
|
||||
}
|
||||
|
35
pkg/integration/components/matcher.go
Normal file
35
pkg/integration/components/matcher.go
Normal 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
|
||||
}
|
@ -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(),
|
||||
|
@ -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)
|
||||
},
|
||||
})
|
||||
|
Reference in New Issue
Block a user