mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-07-31 14:24:25 +03:00
cleaner test assertions
This commit is contained in:
@ -3,10 +3,9 @@ package components
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
|
||||
)
|
||||
@ -21,46 +20,6 @@ func NewAssert(gui integrationTypes.GuiDriver) *Assert {
|
||||
return &Assert{gui: gui}
|
||||
}
|
||||
|
||||
func Contains(target string) *matcher {
|
||||
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 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(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 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) {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
actualCount := len(self.gui.Model().Files)
|
||||
@ -114,13 +73,6 @@ func (self *Assert) HeadCommitMessage(matcher *matcher) {
|
||||
)
|
||||
}
|
||||
|
||||
func (self *Assert) CurrentViewName(expectedViewName string) {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
actual := self.gui.CurrentContext().GetView().Name()
|
||||
return actual == expectedViewName, fmt.Sprintf("Expected current view name to be '%s', but got '%s'", expectedViewName, actual)
|
||||
})
|
||||
}
|
||||
|
||||
func (self *Assert) CurrentWindowName(expectedWindowName string) {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
actual := self.gui.CurrentContext().GetView().Name()
|
||||
@ -143,21 +95,6 @@ func (self *Assert) InListContext() {
|
||||
})
|
||||
}
|
||||
|
||||
func (self *Assert) CurrentLine(matcher *matcher) {
|
||||
self.matchString(matcher, "Unexpected selected line.",
|
||||
func() string {
|
||||
return self.gui.CurrentContext().GetView().SelectedLine()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (self *Assert) CurrentLineIdx(expected int) {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
actual := self.gui.CurrentContext().GetView().SelectedLineIdx()
|
||||
return expected == actual, fmt.Sprintf("Expected selected line index to be %d, got %d", expected, actual)
|
||||
})
|
||||
}
|
||||
|
||||
func (self *Assert) InPrompt() {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
currentView := self.gui.CurrentContext().GetView()
|
||||
@ -200,87 +137,6 @@ func (self *Assert) NotInPopup() {
|
||||
})
|
||||
}
|
||||
|
||||
func (self *Assert) CurrentViewTitle(matcher *matcher) {
|
||||
self.matchString(matcher, "Unexpected current view title.",
|
||||
func() string {
|
||||
return self.gui.CurrentContext().GetView().Title
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (self *Assert) ViewContent(viewName string, matcher *matcher) {
|
||||
self.matchString(matcher, fmt.Sprintf("Unexpected content in view '%s'.", viewName),
|
||||
func() string {
|
||||
return self.gui.View(viewName).Buffer()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// asserts that the given view has lines matching the given matchers.
|
||||
func (self *Assert) ViewLines(viewName string, matchers ...*matcher) {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
lines := self.gui.View(viewName).BufferLines()
|
||||
return len(lines) == len(matchers), fmt.Sprintf("unexpected number of lines in view. Expected %d, got %d", len(matchers), len(lines))
|
||||
})
|
||||
|
||||
for i, matcher := range matchers {
|
||||
self.matchString(matcher, fmt.Sprintf("Unexpected content in view '%s'.", viewName),
|
||||
func() string {
|
||||
return self.gui.View(viewName).BufferLines()[i]
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Assert) CurrentViewLines(matchers ...*matcher) {
|
||||
self.ViewLines(self.gui.CurrentContext().GetView().Name(), matchers...)
|
||||
}
|
||||
|
||||
// asserts that the given view has lines matching the given matchers. So if three matchers
|
||||
// are passed, we only check the first three lines of the view.
|
||||
func (self *Assert) ViewTopLines(viewName string, matchers ...*matcher) {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
lines := self.gui.View(viewName).BufferLines()
|
||||
return len(lines) >= len(matchers), fmt.Sprintf("unexpected number of lines in view. Expected at least %d, got %d", len(matchers), len(lines))
|
||||
})
|
||||
|
||||
for i, matcher := range matchers {
|
||||
self.matchString(matcher, fmt.Sprintf("Unexpected content in view '%s'.", viewName),
|
||||
func() string {
|
||||
return self.gui.View(viewName).BufferLines()[i]
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Assert) CurrentViewTopLines(matchers ...*matcher) {
|
||||
self.ViewTopLines(self.gui.CurrentContext().GetView().Name(), matchers...)
|
||||
}
|
||||
|
||||
func (self *Assert) CurrentViewContent(matcher *matcher) {
|
||||
self.matchString(matcher, "Unexpected content in current view.",
|
||||
func() string {
|
||||
return self.gui.CurrentContext().GetView().Buffer()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (self *Assert) MainViewContent(matcher *matcher) {
|
||||
self.matchString(matcher, "Unexpected main view content.",
|
||||
func() string {
|
||||
return self.gui.MainView().Buffer()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (self *Assert) SecondaryViewContent(matcher *matcher) {
|
||||
self.matchString(matcher, "Unexpected secondary view title.",
|
||||
func() string {
|
||||
return self.gui.SecondaryView().Buffer()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (self *Assert) matchString(matcher *matcher, context string, getValue func() string) {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
value := getValue()
|
||||
@ -325,3 +181,35 @@ func (self *Assert) FileSystemPathNotPresent(path string) {
|
||||
return os.IsNotExist(err), fmt.Sprintf("Expected path '%s' to not exist, but it does", path)
|
||||
})
|
||||
}
|
||||
|
||||
func (self *Assert) CurrentView() *ViewAsserter {
|
||||
return &ViewAsserter{
|
||||
context: "current view",
|
||||
getView: func() *gocui.View { return self.gui.CurrentContext().GetView() },
|
||||
assert: self,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Assert) View(viewName string) *ViewAsserter {
|
||||
return &ViewAsserter{
|
||||
context: fmt.Sprintf("%s view", viewName),
|
||||
getView: func() *gocui.View { return self.gui.View(viewName) },
|
||||
assert: self,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Assert) MainView() *ViewAsserter {
|
||||
return &ViewAsserter{
|
||||
context: "main view",
|
||||
getView: func() *gocui.View { return self.gui.MainView() },
|
||||
assert: self,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Assert) SecondaryView() *ViewAsserter {
|
||||
return &ViewAsserter{
|
||||
context: "secondary view",
|
||||
getView: func() *gocui.View { return self.gui.SecondaryView() },
|
||||
assert: self,
|
||||
}
|
||||
}
|
||||
|
@ -103,7 +103,7 @@ func (self *Input) PreviousItem() {
|
||||
|
||||
func (self *Input) ContinueMerge() {
|
||||
self.Press(self.keys.Universal.CreateRebaseOptionsMenu)
|
||||
self.assert.CurrentLine(Contains("continue"))
|
||||
self.assert.CurrentView().SelectedLine(Contains("continue"))
|
||||
self.Confirm()
|
||||
}
|
||||
|
||||
@ -166,41 +166,41 @@ func (self *Input) NavigateToListItem(matcher *matcher) {
|
||||
|
||||
selectedLineIdx := view.SelectedLineIdx()
|
||||
if selectedLineIdx == matchIndex {
|
||||
self.assert.CurrentLine(matcher)
|
||||
self.assert.CurrentView().SelectedLine(matcher)
|
||||
return
|
||||
}
|
||||
if selectedLineIdx < matchIndex {
|
||||
for i := selectedLineIdx; i < matchIndex; i++ {
|
||||
self.NextItem()
|
||||
}
|
||||
self.assert.CurrentLine(matcher)
|
||||
self.assert.CurrentView().SelectedLine(matcher)
|
||||
return
|
||||
} else {
|
||||
for i := selectedLineIdx; i > matchIndex; i-- {
|
||||
self.PreviousItem()
|
||||
}
|
||||
self.assert.CurrentLine(matcher)
|
||||
self.assert.CurrentView().SelectedLine(matcher)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Input) AcceptConfirmation(title *matcher, content *matcher) {
|
||||
self.assert.InConfirm()
|
||||
self.assert.CurrentViewTitle(title)
|
||||
self.assert.CurrentViewContent(content)
|
||||
self.assert.CurrentView().Title(title)
|
||||
self.assert.CurrentView().Content(content)
|
||||
self.Confirm()
|
||||
}
|
||||
|
||||
func (self *Input) DenyConfirmation(title *matcher, content *matcher) {
|
||||
self.assert.InConfirm()
|
||||
self.assert.CurrentViewTitle(title)
|
||||
self.assert.CurrentViewContent(content)
|
||||
self.assert.CurrentView().Title(title)
|
||||
self.assert.CurrentView().Content(content)
|
||||
self.Cancel()
|
||||
}
|
||||
|
||||
func (self *Input) Prompt(title *matcher, textToType string) {
|
||||
self.assert.InPrompt()
|
||||
self.assert.CurrentViewTitle(title)
|
||||
self.assert.CurrentView().Title(title)
|
||||
self.Type(textToType)
|
||||
self.Confirm()
|
||||
}
|
||||
@ -209,24 +209,24 @@ func (self *Input) Prompt(title *matcher, textToType string) {
|
||||
// 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.assert.CurrentView().Title(title)
|
||||
self.Type(textToType)
|
||||
self.Press(self.keys.Universal.TogglePanel)
|
||||
self.assert.CurrentViewName("suggestions")
|
||||
self.assert.CurrentLine(expectedFirstOption)
|
||||
self.assert.CurrentView().Name("suggestions")
|
||||
self.assert.CurrentView().SelectedLine(expectedFirstOption)
|
||||
self.Confirm()
|
||||
}
|
||||
|
||||
func (self *Input) Menu(title *matcher, optionToSelect *matcher) {
|
||||
self.assert.InMenu()
|
||||
self.assert.CurrentViewTitle(title)
|
||||
self.assert.CurrentView().Title(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.assert.CurrentView().Title(title)
|
||||
self.assert.CurrentView().Content(content)
|
||||
self.Confirm()
|
||||
}
|
||||
|
@ -1,5 +1,11 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// for making assertions on string values
|
||||
type matcher struct {
|
||||
// e.g. "contains 'foo'"
|
||||
@ -33,3 +39,43 @@ func (self *matcher) context(prefix string) *matcher {
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func Contains(target string) *matcher {
|
||||
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 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(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 NewMatcher(
|
||||
fmt.Sprintf("equals '%s'", target),
|
||||
func(value string) (bool, string) {
|
||||
return target == value, fmt.Sprintf("Expected '%s' to equal '%s'", value, target)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
111
pkg/integration/components/view_asserter.go
Normal file
111
pkg/integration/components/view_asserter.go
Normal file
@ -0,0 +1,111 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
type ViewAsserter struct {
|
||||
// context is prepended to any error messages e.g. 'context: "current view"'
|
||||
context string
|
||||
getView func() *gocui.View
|
||||
assert *Assert
|
||||
}
|
||||
|
||||
// asserts that the view has the expected name. This is typically used in tandem with the CurrentView method i.e.;
|
||||
// assert.CurrentView().Name("commits") to assert that the current view is the commits view.
|
||||
func (self *ViewAsserter) Name(expected string) *ViewAsserter {
|
||||
self.assert.assertWithRetries(func() (bool, string) {
|
||||
actual := self.getView().Name()
|
||||
return actual == expected, fmt.Sprintf("%s: Expected view name to be '%s', but got '%s'", self.context, expected, actual)
|
||||
})
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// asserts that the view has the expected title
|
||||
func (self *ViewAsserter) Title(expected *matcher) *ViewAsserter {
|
||||
self.assert.assertWithRetries(func() (bool, string) {
|
||||
actual := self.getView().Title
|
||||
return expected.context(fmt.Sprintf("%s title", self.context)).test(actual)
|
||||
})
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// asserts that the view has lines matching the given matchers. So if three matchers
|
||||
// are passed, we only check the first three lines of the view.
|
||||
// This method is convenient when you have a list of commits but you only want to
|
||||
// assert on the first couple of commits.
|
||||
func (self *ViewAsserter) TopLines(matchers ...*matcher) *ViewAsserter {
|
||||
self.assert.assertWithRetries(func() (bool, string) {
|
||||
lines := self.getView().BufferLines()
|
||||
return len(lines) >= len(matchers), fmt.Sprintf("unexpected number of lines in view. Expected at least %d, got %d", len(matchers), len(lines))
|
||||
})
|
||||
|
||||
view := self.getView()
|
||||
|
||||
for i, matcher := range matchers {
|
||||
self.assert.matchString(matcher, fmt.Sprintf("Unexpected content in view '%s'.", view.Name()),
|
||||
func() string {
|
||||
return view.BufferLines()[i]
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// asserts that the view has lines matching the given matchers. One matcher must be passed for each line.
|
||||
// If you only care about the top n lines, use the TopLines method instead.
|
||||
func (self *ViewAsserter) Lines(matchers ...*matcher) *ViewAsserter {
|
||||
self.assert.assertWithRetries(func() (bool, string) {
|
||||
lines := self.getView().BufferLines()
|
||||
return len(lines) == len(matchers), fmt.Sprintf("unexpected number of lines in view. Expected %d, got %d", len(matchers), len(lines))
|
||||
})
|
||||
|
||||
view := self.getView()
|
||||
|
||||
for i, matcher := range matchers {
|
||||
self.assert.matchString(matcher, fmt.Sprintf("Unexpected content in view '%s'.", view.Name()),
|
||||
func() string {
|
||||
return view.BufferLines()[i]
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// asserts on the content of the view i.e. the stuff within the view's frame.
|
||||
func (self *ViewAsserter) Content(matcher *matcher) *ViewAsserter {
|
||||
self.assert.matchString(matcher, fmt.Sprintf("%s: Unexpected content.", self.context),
|
||||
func() string {
|
||||
return self.getView().Buffer()
|
||||
},
|
||||
)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// asserts on the selected line of the view
|
||||
func (self *ViewAsserter) SelectedLine(matcher *matcher) *ViewAsserter {
|
||||
self.assert.matchString(matcher, fmt.Sprintf("%s: Unexpected selected line.", self.context),
|
||||
func() string {
|
||||
return self.getView().SelectedLine()
|
||||
},
|
||||
)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// asserts on the index of the selected line. 0 is the first index, representing the line at the top of the view.
|
||||
func (self *ViewAsserter) SelectedLineIdx(expected int) *ViewAsserter {
|
||||
self.assert.assertWithRetries(func() (bool, string) {
|
||||
actual := self.getView().SelectedLineIdx()
|
||||
return expected == actual, fmt.Sprintf("%s: Expected selected line index to be %d, got %d", self.context, expected, actual)
|
||||
})
|
||||
|
||||
return self
|
||||
}
|
Reference in New Issue
Block a user