mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-10-08 22:52:12 +02:00
migrate patch building tests
This commit is contained in:
@@ -38,3 +38,9 @@ func (self *Actions) ConfirmDiscardLines() {
|
||||
Content(Contains("Are you sure you want to delete the selected lines")).
|
||||
Confirm()
|
||||
}
|
||||
|
||||
func (self *Actions) SelectPatchOption(matcher *Matcher) {
|
||||
self.t.GlobalPress(self.t.keys.Universal.CreatePatchOptionsMenu)
|
||||
|
||||
self.t.ExpectPopup().Menu().Title(Equals("Patch Options")).Select(matcher).Confirm()
|
||||
}
|
||||
|
@@ -11,7 +11,7 @@ func (self *AlertDriver) getViewDriver() *ViewDriver {
|
||||
}
|
||||
|
||||
// asserts that the alert view has the expected title
|
||||
func (self *AlertDriver) Title(expected *matcher) *AlertDriver {
|
||||
func (self *AlertDriver) Title(expected *Matcher) *AlertDriver {
|
||||
self.getViewDriver().Title(expected)
|
||||
|
||||
self.hasCheckedTitle = true
|
||||
@@ -20,7 +20,7 @@ func (self *AlertDriver) Title(expected *matcher) *AlertDriver {
|
||||
}
|
||||
|
||||
// asserts that the alert view has the expected content
|
||||
func (self *AlertDriver) Content(expected *matcher) *AlertDriver {
|
||||
func (self *AlertDriver) Content(expected *Matcher) *AlertDriver {
|
||||
self.getViewDriver().Content(expected)
|
||||
|
||||
self.hasCheckedContent = true
|
||||
|
@@ -13,7 +13,7 @@ type assertionHelper struct {
|
||||
// milliseconds we'll wait when an assertion fails.
|
||||
var retryWaitTimes = []int{0, 1, 1, 1, 1, 1, 5, 10, 20, 40, 100, 200, 500, 1000, 2000, 4000}
|
||||
|
||||
func (self *assertionHelper) matchString(matcher *matcher, context string, getValue func() string) {
|
||||
func (self *assertionHelper) matchString(matcher *Matcher, context string, getValue func() string) {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
value := getValue()
|
||||
return matcher.context(context).test(value)
|
||||
|
@@ -9,7 +9,7 @@ func (self *CommitMessagePanelDriver) getViewDriver() *ViewDriver {
|
||||
}
|
||||
|
||||
// asserts on the text initially present in the prompt
|
||||
func (self *CommitMessagePanelDriver) InitialText(expected *matcher) *CommitMessagePanelDriver {
|
||||
func (self *CommitMessagePanelDriver) InitialText(expected *Matcher) *CommitMessagePanelDriver {
|
||||
self.getViewDriver().Content(expected)
|
||||
|
||||
return self
|
||||
|
@@ -11,7 +11,7 @@ func (self *ConfirmationDriver) getViewDriver() *ViewDriver {
|
||||
}
|
||||
|
||||
// asserts that the confirmation view has the expected title
|
||||
func (self *ConfirmationDriver) Title(expected *matcher) *ConfirmationDriver {
|
||||
func (self *ConfirmationDriver) Title(expected *Matcher) *ConfirmationDriver {
|
||||
self.getViewDriver().Title(expected)
|
||||
|
||||
self.hasCheckedTitle = true
|
||||
@@ -20,7 +20,7 @@ func (self *ConfirmationDriver) Title(expected *matcher) *ConfirmationDriver {
|
||||
}
|
||||
|
||||
// asserts that the confirmation view has the expected content
|
||||
func (self *ConfirmationDriver) Content(expected *matcher) *ConfirmationDriver {
|
||||
func (self *ConfirmationDriver) Content(expected *Matcher) *ConfirmationDriver {
|
||||
self.getViewDriver().Content(expected)
|
||||
|
||||
self.hasCheckedContent = true
|
||||
|
@@ -26,7 +26,7 @@ func (self *FileSystem) PathNotPresent(path string) {
|
||||
}
|
||||
|
||||
// Asserts that the file at the given path has the given content
|
||||
func (self *FileSystem) FileContent(path string, matcher *matcher) {
|
||||
func (self *FileSystem) FileContent(path string, matcher *Matcher) {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
_, err := os.Stat(path)
|
||||
if os.IsNotExist(err) {
|
||||
|
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
// for making assertions on string values
|
||||
type matcher struct {
|
||||
type Matcher struct {
|
||||
rules []matcherRule
|
||||
|
||||
// this is printed when there's an error so that it's clear what the context of the assertion is
|
||||
@@ -24,12 +24,12 @@ type matcherRule struct {
|
||||
testFn func(string) (bool, string)
|
||||
}
|
||||
|
||||
func NewMatcher(name string, testFn func(string) (bool, string)) *matcher {
|
||||
func NewMatcher(name string, testFn func(string) (bool, string)) *Matcher {
|
||||
rules := []matcherRule{{name: name, testFn: testFn}}
|
||||
return &matcher{rules: rules}
|
||||
return &Matcher{rules: rules}
|
||||
}
|
||||
|
||||
func (self *matcher) name() string {
|
||||
func (self *Matcher) name() string {
|
||||
if len(self.rules) == 0 {
|
||||
return "anything"
|
||||
}
|
||||
@@ -40,7 +40,7 @@ func (self *matcher) name() string {
|
||||
)
|
||||
}
|
||||
|
||||
func (self *matcher) test(value string) (bool, string) {
|
||||
func (self *Matcher) test(value string) (bool, string) {
|
||||
for _, rule := range self.rules {
|
||||
ok, message := rule.testFn(value)
|
||||
if ok {
|
||||
@@ -57,7 +57,7 @@ func (self *matcher) test(value string) (bool, string) {
|
||||
return true, ""
|
||||
}
|
||||
|
||||
func (self *matcher) Contains(target string) *matcher {
|
||||
func (self *Matcher) Contains(target string) *Matcher {
|
||||
return self.appendRule(matcherRule{
|
||||
name: fmt.Sprintf("contains '%s'", target),
|
||||
testFn: func(value string) (bool, string) {
|
||||
@@ -71,7 +71,7 @@ func (self *matcher) Contains(target string) *matcher {
|
||||
})
|
||||
}
|
||||
|
||||
func (self *matcher) DoesNotContain(target string) *matcher {
|
||||
func (self *Matcher) DoesNotContain(target string) *Matcher {
|
||||
return self.appendRule(matcherRule{
|
||||
name: fmt.Sprintf("does not contain '%s'", target),
|
||||
testFn: func(value string) (bool, string) {
|
||||
@@ -80,7 +80,7 @@ func (self *matcher) DoesNotContain(target string) *matcher {
|
||||
})
|
||||
}
|
||||
|
||||
func (self *matcher) MatchesRegexp(target string) *matcher {
|
||||
func (self *Matcher) MatchesRegexp(target string) *Matcher {
|
||||
return self.appendRule(matcherRule{
|
||||
name: fmt.Sprintf("matches regular expression '%s'", target),
|
||||
testFn: func(value string) (bool, string) {
|
||||
@@ -93,7 +93,7 @@ func (self *matcher) MatchesRegexp(target string) *matcher {
|
||||
})
|
||||
}
|
||||
|
||||
func (self *matcher) Equals(target string) *matcher {
|
||||
func (self *Matcher) Equals(target string) *Matcher {
|
||||
return self.appendRule(matcherRule{
|
||||
name: fmt.Sprintf("equals '%s'", target),
|
||||
testFn: func(value string) (bool, string) {
|
||||
@@ -106,7 +106,7 @@ const IS_SELECTED_RULE_NAME = "is selected"
|
||||
|
||||
// special rule that is only to be used in the TopLines and Lines methods, as a way of
|
||||
// asserting that a given line is selected.
|
||||
func (self *matcher) IsSelected() *matcher {
|
||||
func (self *Matcher) IsSelected() *Matcher {
|
||||
return self.appendRule(matcherRule{
|
||||
name: IS_SELECTED_RULE_NAME,
|
||||
testFn: func(value string) (bool, string) {
|
||||
@@ -115,7 +115,7 @@ func (self *matcher) IsSelected() *matcher {
|
||||
})
|
||||
}
|
||||
|
||||
func (self *matcher) appendRule(rule matcherRule) *matcher {
|
||||
func (self *Matcher) appendRule(rule matcherRule) *Matcher {
|
||||
self.rules = append(self.rules, rule)
|
||||
|
||||
return self
|
||||
@@ -123,39 +123,43 @@ func (self *matcher) appendRule(rule matcherRule) *matcher {
|
||||
|
||||
// adds context so that if the matcher test(s) fails, we understand what we were trying to test.
|
||||
// E.g. prefix: "Unexpected content in view 'files'."
|
||||
func (self *matcher) context(prefix string) *matcher {
|
||||
func (self *Matcher) context(prefix string) *Matcher {
|
||||
self.prefix = prefix
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// if the matcher has an `IsSelected` rule, it returns true, along with the matcher after that rule has been removed
|
||||
func (self *matcher) checkIsSelected() (bool, *matcher) {
|
||||
check := lo.ContainsBy(self.rules, func(rule matcherRule) bool { return rule.name == IS_SELECTED_RULE_NAME })
|
||||
func (self *Matcher) checkIsSelected() (bool, *Matcher) {
|
||||
// copying into a new matcher in case we want to re-use the original later
|
||||
newMatcher := &Matcher{}
|
||||
*newMatcher = *self
|
||||
|
||||
self.rules = lo.Filter(self.rules, func(rule matcherRule, _ int) bool { return rule.name != IS_SELECTED_RULE_NAME })
|
||||
check := lo.ContainsBy(newMatcher.rules, func(rule matcherRule) bool { return rule.name == IS_SELECTED_RULE_NAME })
|
||||
|
||||
return check, self
|
||||
newMatcher.rules = lo.Filter(newMatcher.rules, func(rule matcherRule, _ int) bool { return rule.name != IS_SELECTED_RULE_NAME })
|
||||
|
||||
return check, newMatcher
|
||||
}
|
||||
|
||||
// this matcher has no rules meaning it always passes the test. Use this
|
||||
// when you don't care what value you're dealing with.
|
||||
func Anything() *matcher {
|
||||
return &matcher{}
|
||||
func Anything() *Matcher {
|
||||
return &Matcher{}
|
||||
}
|
||||
|
||||
func Contains(target string) *matcher {
|
||||
func Contains(target string) *Matcher {
|
||||
return Anything().Contains(target)
|
||||
}
|
||||
|
||||
func DoesNotContain(target string) *matcher {
|
||||
func DoesNotContain(target string) *Matcher {
|
||||
return Anything().DoesNotContain(target)
|
||||
}
|
||||
|
||||
func MatchesRegexp(target string) *matcher {
|
||||
func MatchesRegexp(target string) *Matcher {
|
||||
return Anything().MatchesRegexp(target)
|
||||
}
|
||||
|
||||
func Equals(target string) *matcher {
|
||||
func Equals(target string) *Matcher {
|
||||
return Anything().Equals(target)
|
||||
}
|
||||
|
@@ -10,7 +10,7 @@ func (self *MenuDriver) getViewDriver() *ViewDriver {
|
||||
}
|
||||
|
||||
// asserts that the popup has the expected title
|
||||
func (self *MenuDriver) Title(expected *matcher) *MenuDriver {
|
||||
func (self *MenuDriver) Title(expected *Matcher) *MenuDriver {
|
||||
self.getViewDriver().Title(expected)
|
||||
|
||||
self.hasCheckedTitle = true
|
||||
@@ -30,19 +30,19 @@ func (self *MenuDriver) Cancel() {
|
||||
self.getViewDriver().PressEscape()
|
||||
}
|
||||
|
||||
func (self *MenuDriver) Select(option *matcher) *MenuDriver {
|
||||
func (self *MenuDriver) Select(option *Matcher) *MenuDriver {
|
||||
self.getViewDriver().NavigateToListItem(option)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *MenuDriver) Lines(matchers ...*matcher) *MenuDriver {
|
||||
func (self *MenuDriver) Lines(matchers ...*Matcher) *MenuDriver {
|
||||
self.getViewDriver().Lines(matchers...)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *MenuDriver) TopLines(matchers ...*matcher) *MenuDriver {
|
||||
func (self *MenuDriver) TopLines(matchers ...*Matcher) *MenuDriver {
|
||||
self.getViewDriver().TopLines(matchers...)
|
||||
|
||||
return self
|
||||
|
@@ -10,7 +10,7 @@ func (self *PromptDriver) getViewDriver() *ViewDriver {
|
||||
}
|
||||
|
||||
// asserts that the popup has the expected title
|
||||
func (self *PromptDriver) Title(expected *matcher) *PromptDriver {
|
||||
func (self *PromptDriver) Title(expected *Matcher) *PromptDriver {
|
||||
self.getViewDriver().Title(expected)
|
||||
|
||||
self.hasCheckedTitle = true
|
||||
@@ -19,7 +19,7 @@ func (self *PromptDriver) Title(expected *matcher) *PromptDriver {
|
||||
}
|
||||
|
||||
// asserts on the text initially present in the prompt
|
||||
func (self *PromptDriver) InitialText(expected *matcher) *PromptDriver {
|
||||
func (self *PromptDriver) InitialText(expected *Matcher) *PromptDriver {
|
||||
self.getViewDriver().Content(expected)
|
||||
|
||||
return self
|
||||
@@ -55,13 +55,13 @@ func (self *PromptDriver) checkNecessaryChecksCompleted() {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *PromptDriver) SuggestionLines(matchers ...*matcher) *PromptDriver {
|
||||
func (self *PromptDriver) SuggestionLines(matchers ...*Matcher) *PromptDriver {
|
||||
self.t.Views().Suggestions().Lines(matchers...)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *PromptDriver) SuggestionTopLines(matchers ...*matcher) *PromptDriver {
|
||||
func (self *PromptDriver) SuggestionTopLines(matchers ...*Matcher) *PromptDriver {
|
||||
self.t.Views().Suggestions().TopLines(matchers...)
|
||||
|
||||
return self
|
||||
@@ -75,7 +75,7 @@ func (self *PromptDriver) ConfirmFirstSuggestion() {
|
||||
PressEnter()
|
||||
}
|
||||
|
||||
func (self *PromptDriver) ConfirmSuggestion(matcher *matcher) {
|
||||
func (self *PromptDriver) ConfirmSuggestion(matcher *Matcher) {
|
||||
self.t.press(self.t.keys.Universal.TogglePanel)
|
||||
self.t.Views().Suggestions().
|
||||
IsFocused().
|
||||
|
@@ -12,7 +12,7 @@ func (self *SearchDriver) getViewDriver() *ViewDriver {
|
||||
}
|
||||
|
||||
// asserts on the text initially present in the prompt
|
||||
func (self *SearchDriver) InitialText(expected *matcher) *SearchDriver {
|
||||
func (self *SearchDriver) InitialText(expected *Matcher) *SearchDriver {
|
||||
self.getViewDriver().Content(expected)
|
||||
|
||||
return self
|
||||
|
@@ -136,6 +136,10 @@ func (self *Shell) EmptyCommit(message string) *Shell {
|
||||
return self.RunCommand(fmt.Sprintf("git commit --allow-empty -m \"%s\"", message))
|
||||
}
|
||||
|
||||
func (self *Shell) Revert(ref string) *Shell {
|
||||
return self.RunCommand(fmt.Sprintf("git revert %s", ref))
|
||||
}
|
||||
|
||||
func (self *Shell) CreateLightweightTag(name string, ref string) *Shell {
|
||||
return self.RunCommand(fmt.Sprintf("git tag %s %s", name, ref))
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@ package components
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/atotto/clipboard"
|
||||
@@ -71,65 +70,6 @@ func (self *TestDriver) Shell() *Shell {
|
||||
return self.shell
|
||||
}
|
||||
|
||||
// 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
|
||||
//
|
||||
// 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 *TestDriver) navigateToListItem(matcher *matcher) {
|
||||
currentContext := self.gui.CurrentContext()
|
||||
|
||||
view := currentContext.GetView()
|
||||
|
||||
var matchIndex int
|
||||
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
matchIndex = -1
|
||||
var matches []string
|
||||
lines := view.ViewBufferLines()
|
||||
// first we look for a duplicate on the current screen. We won't bother looking beyond that though.
|
||||
for i, line := range lines {
|
||||
ok, _ := matcher.test(line)
|
||||
if ok {
|
||||
matches = append(matches, line)
|
||||
matchIndex = i
|
||||
}
|
||||
}
|
||||
if len(matches) > 1 {
|
||||
return false, fmt.Sprintf("Found %d matches for `%s`, expected only a single match. Matching 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. Lines:\n%s", matcher.name(), strings.Join(lines, "\n"))
|
||||
} else {
|
||||
return true, ""
|
||||
}
|
||||
})
|
||||
|
||||
selectedLineIdx := view.SelectedLineIdx()
|
||||
if selectedLineIdx == matchIndex {
|
||||
self.Views().current().SelectedLine(matcher)
|
||||
return
|
||||
}
|
||||
if selectedLineIdx < matchIndex {
|
||||
for i := selectedLineIdx; i < matchIndex; i++ {
|
||||
self.Views().current().SelectNextItem()
|
||||
}
|
||||
self.Views().current().SelectedLine(matcher)
|
||||
return
|
||||
} else {
|
||||
for i := selectedLineIdx; i > matchIndex; i-- {
|
||||
self.Views().current().SelectPreviousItem()
|
||||
}
|
||||
self.Views().current().SelectedLine(matcher)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// for making assertions on lazygit views
|
||||
func (self *TestDriver) Views() *Views {
|
||||
return &Views{t: self}
|
||||
@@ -140,11 +80,11 @@ func (self *TestDriver) ExpectPopup() *Popup {
|
||||
return &Popup{t: self}
|
||||
}
|
||||
|
||||
func (self *TestDriver) ExpectToast(matcher *matcher) {
|
||||
func (self *TestDriver) ExpectToast(matcher *Matcher) {
|
||||
self.Views().AppStatus().Content(matcher)
|
||||
}
|
||||
|
||||
func (self *TestDriver) ExpectClipboard(matcher *matcher) {
|
||||
func (self *TestDriver) ExpectClipboard(matcher *Matcher) {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
text, err := clipboard.ReadAll()
|
||||
if err != nil {
|
||||
|
@@ -10,45 +10,12 @@ import (
|
||||
|
||||
type ViewDriver struct {
|
||||
// context is prepended to any error messages e.g. 'context: "current view"'
|
||||
context string
|
||||
getView func() *gocui.View
|
||||
t *TestDriver
|
||||
getSelectedLinesFn func() ([]string, error)
|
||||
}
|
||||
|
||||
// asserts that the view has the expected title
|
||||
func (self *ViewDriver) Title(expected *matcher) *ViewDriver {
|
||||
self.t.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 *ViewDriver) TopLines(matchers ...*matcher) *ViewDriver {
|
||||
if len(matchers) < 1 {
|
||||
self.t.fail("TopLines method requires at least one matcher. If you are trying to assert that there are no lines, use .IsEmpty()")
|
||||
}
|
||||
|
||||
self.t.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))
|
||||
})
|
||||
|
||||
return self.assertLines(matchers...)
|
||||
}
|
||||
|
||||
// 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 *ViewDriver) Lines(matchers ...*matcher) *ViewDriver {
|
||||
self.LineCount(len(matchers))
|
||||
|
||||
return self.assertLines(matchers...)
|
||||
context string
|
||||
getView func() *gocui.View
|
||||
t *TestDriver
|
||||
getSelectedLinesFn func() ([]string, error)
|
||||
getSelectedRangeFn func() (int, int, error)
|
||||
getSelectedLineIdxFn func() (int, error)
|
||||
}
|
||||
|
||||
func (self *ViewDriver) getSelectedLines() ([]string, error) {
|
||||
@@ -61,7 +28,114 @@ func (self *ViewDriver) getSelectedLines() ([]string, error) {
|
||||
return self.getSelectedLinesFn()
|
||||
}
|
||||
|
||||
func (self *ViewDriver) SelectedLines(matchers ...*matcher) *ViewDriver {
|
||||
func (self *ViewDriver) getSelectedRange() (int, int, error) {
|
||||
if self.getSelectedRangeFn == nil {
|
||||
view := self.t.gui.View(self.getView().Name())
|
||||
idx := view.SelectedLineIdx()
|
||||
|
||||
return idx, idx, nil
|
||||
}
|
||||
|
||||
return self.getSelectedRangeFn()
|
||||
}
|
||||
|
||||
// even if you have a selected range, there may still be a line within that range
|
||||
// which the cursor points at. This function returns that line index.
|
||||
func (self *ViewDriver) getSelectedLineIdx() (int, error) {
|
||||
if self.getSelectedLineIdxFn == nil {
|
||||
view := self.t.gui.View(self.getView().Name())
|
||||
|
||||
return view.SelectedLineIdx(), nil
|
||||
}
|
||||
|
||||
return self.getSelectedLineIdxFn()
|
||||
}
|
||||
|
||||
// asserts that the view has the expected title
|
||||
func (self *ViewDriver) Title(expected *Matcher) *ViewDriver {
|
||||
self.t.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. One matcher must be passed for each line.
|
||||
// If you only care about the top n lines, use the TopLines method instead.
|
||||
func (self *ViewDriver) Lines(matchers ...*Matcher) *ViewDriver {
|
||||
self.validateMatchersPassed(matchers)
|
||||
self.LineCount(len(matchers))
|
||||
|
||||
return self.assertLines(0, matchers...)
|
||||
}
|
||||
|
||||
// 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 *ViewDriver) TopLines(matchers ...*Matcher) *ViewDriver {
|
||||
self.validateMatchersPassed(matchers)
|
||||
self.validateEnoughLines(matchers)
|
||||
|
||||
return self.assertLines(0, matchers...)
|
||||
}
|
||||
|
||||
func (self *ViewDriver) ContainsLines(matchers ...*Matcher) *ViewDriver {
|
||||
self.validateMatchersPassed(matchers)
|
||||
self.validateEnoughLines(matchers)
|
||||
|
||||
self.t.assertWithRetries(func() (bool, string) {
|
||||
content := self.getView().Buffer()
|
||||
lines := strings.Split(content, "\n")
|
||||
|
||||
startIdx, endIdx, err := self.getSelectedRange()
|
||||
|
||||
for i := 0; i < len(lines)-len(matchers)+1; i++ {
|
||||
matches := true
|
||||
for j, matcher := range matchers {
|
||||
checkIsSelected, matcher := matcher.checkIsSelected() // strip the IsSelected matcher out
|
||||
lineIdx := i + j
|
||||
ok, _ := matcher.test(lines[lineIdx])
|
||||
if !ok {
|
||||
matches = false
|
||||
break
|
||||
}
|
||||
if checkIsSelected {
|
||||
if err != nil {
|
||||
matches = false
|
||||
break
|
||||
}
|
||||
if lineIdx < startIdx || lineIdx > endIdx {
|
||||
matches = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if matches {
|
||||
return true, ""
|
||||
}
|
||||
}
|
||||
|
||||
expectedContent := expectedContentFromMatchers(matchers)
|
||||
|
||||
return false, fmt.Sprintf(
|
||||
"Expected the following to be contained in the staging panel:\n-----\n%s\n-----\nBut got:\n-----\n%s\n-----\nSelected range: %d-%d",
|
||||
expectedContent,
|
||||
content,
|
||||
startIdx,
|
||||
endIdx,
|
||||
)
|
||||
})
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// asserts on the lines that are selected in the view.
|
||||
func (self *ViewDriver) SelectedLines(matchers ...*Matcher) *ViewDriver {
|
||||
self.validateMatchersPassed(matchers)
|
||||
self.validateEnoughLines(matchers)
|
||||
|
||||
self.t.assertWithRetries(func() (bool, string) {
|
||||
selectedLines, err := self.getSelectedLines()
|
||||
if err != nil {
|
||||
@@ -76,7 +150,12 @@ func (self *ViewDriver) SelectedLines(matchers ...*matcher) *ViewDriver {
|
||||
}
|
||||
|
||||
for i, line := range selectedLines {
|
||||
ok, message := matchers[i].test(line)
|
||||
checkIsSelected, matcher := matchers[i].checkIsSelected()
|
||||
if checkIsSelected {
|
||||
self.t.fail("You cannot use the IsSelected matcher with the SelectedLines method")
|
||||
}
|
||||
|
||||
ok, message := matcher.test(line)
|
||||
if !ok {
|
||||
return false, fmt.Sprintf("Error: %s. Expected the following to be selected:\n-----\n%s\n-----\nBut got:\n-----\n%s\n-----", message, expectedContent, selectedContent)
|
||||
}
|
||||
@@ -88,53 +167,51 @@ func (self *ViewDriver) SelectedLines(matchers ...*matcher) *ViewDriver {
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *ViewDriver) ContainsLines(matchers ...*matcher) *ViewDriver {
|
||||
self.t.assertWithRetries(func() (bool, string) {
|
||||
content := self.getView().Buffer()
|
||||
lines := strings.Split(content, "\n")
|
||||
|
||||
for i := 0; i < len(lines)-len(matchers)+1; i++ {
|
||||
matches := true
|
||||
for j, matcher := range matchers {
|
||||
ok, _ := matcher.test(lines[i+j])
|
||||
if !ok {
|
||||
matches = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if matches {
|
||||
return true, ""
|
||||
}
|
||||
}
|
||||
|
||||
expectedContent := expectedContentFromMatchers(matchers)
|
||||
|
||||
return false, fmt.Sprintf(
|
||||
"Expected the following to be contained in the staging panel:\n-----\n%s\n-----\nBut got:\n-----\n%s\n-----",
|
||||
expectedContent,
|
||||
content,
|
||||
)
|
||||
})
|
||||
|
||||
return self
|
||||
func (self *ViewDriver) validateMatchersPassed(matchers []*Matcher) {
|
||||
if len(matchers) < 1 {
|
||||
self.t.fail("'Lines' methods require at least one matcher to be passed as an argument. If you are trying to assert that there are no lines, use .IsEmpty()")
|
||||
}
|
||||
}
|
||||
|
||||
func (self *ViewDriver) assertLines(matchers ...*matcher) *ViewDriver {
|
||||
func (self *ViewDriver) validateEnoughLines(matchers []*Matcher) {
|
||||
self.t.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))
|
||||
})
|
||||
}
|
||||
|
||||
func (self *ViewDriver) assertLines(offset int, matchers ...*Matcher) *ViewDriver {
|
||||
view := self.getView()
|
||||
|
||||
for i, matcher := range matchers {
|
||||
for matcherIndex, matcher := range matchers {
|
||||
lineIdx := matcherIndex + offset
|
||||
checkIsSelected, matcher := matcher.checkIsSelected()
|
||||
|
||||
self.t.matchString(matcher, fmt.Sprintf("Unexpected content in view '%s'.", view.Name()),
|
||||
func() string {
|
||||
return view.BufferLines()[i]
|
||||
return view.BufferLines()[lineIdx]
|
||||
},
|
||||
)
|
||||
|
||||
if checkIsSelected {
|
||||
self.t.assertWithRetries(func() (bool, string) {
|
||||
lineIdx := view.SelectedLineIdx()
|
||||
return lineIdx == i, fmt.Sprintf("Unexpected selected line index in view '%s'. Expected %d, got %d", view.Name(), i, lineIdx)
|
||||
startIdx, endIdx, err := self.getSelectedRange()
|
||||
if err != nil {
|
||||
return false, err.Error()
|
||||
}
|
||||
|
||||
if lineIdx < startIdx || lineIdx > endIdx {
|
||||
if startIdx == endIdx {
|
||||
return false, fmt.Sprintf("Unexpected selected line index in view '%s'. Expected %d, got %d", view.Name(), lineIdx, startIdx)
|
||||
} else {
|
||||
lines, err := self.getSelectedLines()
|
||||
if err != nil {
|
||||
return false, err.Error()
|
||||
}
|
||||
return false, fmt.Sprintf("Unexpected selected line index in view '%s'. Expected line %d to be in range %d to %d. Selected lines:\n---\n%s\n---\n\nExpected line: '%s'", view.Name(), lineIdx, startIdx, endIdx, strings.Join(lines, "\n"), matcher.name())
|
||||
}
|
||||
}
|
||||
return true, ""
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -143,7 +220,7 @@ func (self *ViewDriver) assertLines(matchers ...*matcher) *ViewDriver {
|
||||
}
|
||||
|
||||
// asserts on the content of the view i.e. the stuff within the view's frame.
|
||||
func (self *ViewDriver) Content(matcher *matcher) *ViewDriver {
|
||||
func (self *ViewDriver) Content(matcher *Matcher) *ViewDriver {
|
||||
self.t.matchString(matcher, fmt.Sprintf("%s: Unexpected content.", self.context),
|
||||
func() string {
|
||||
return self.getView().Buffer()
|
||||
@@ -153,40 +230,27 @@ func (self *ViewDriver) Content(matcher *matcher) *ViewDriver {
|
||||
return self
|
||||
}
|
||||
|
||||
// asserts on the selected line of the view
|
||||
func (self *ViewDriver) SelectedLine(matcher *matcher) *ViewDriver {
|
||||
// asserts on the selected line of the view. If your view has multiple lines selected,
|
||||
// but also has a concept of a cursor position, this will assert on the line that
|
||||
// the cursor is on. Otherwise it will assert on the first line of the selection.
|
||||
func (self *ViewDriver) SelectedLine(matcher *Matcher) *ViewDriver {
|
||||
self.t.assertWithRetries(func() (bool, string) {
|
||||
selectedLines, err := self.getSelectedLines()
|
||||
selectedLineIdx, err := self.getSelectedLineIdx()
|
||||
if err != nil {
|
||||
return false, err.Error()
|
||||
}
|
||||
|
||||
if len(selectedLines) == 0 {
|
||||
return false, "No line selected. Expected exactly one line to be selected"
|
||||
} else if len(selectedLines) > 1 {
|
||||
return false, fmt.Sprintf(
|
||||
"Multiple lines selected. Expected only a single line to be selected. Selected lines:\n---\n%s\n---\n\nExpected line: %s",
|
||||
strings.Join(selectedLines, "\n"),
|
||||
matcher.name(),
|
||||
)
|
||||
viewLines := self.getView().BufferLines()
|
||||
|
||||
if selectedLineIdx >= len(viewLines) {
|
||||
return false, fmt.Sprintf("%s: Expected view to have at least %d lines, but it only has %d", self.context, selectedLineIdx+1, len(viewLines))
|
||||
}
|
||||
|
||||
value := selectedLines[0]
|
||||
value := viewLines[selectedLineIdx]
|
||||
|
||||
return matcher.context(fmt.Sprintf("%s: Unexpected selected line.", self.context)).test(value)
|
||||
})
|
||||
|
||||
self.t.matchString(matcher, fmt.Sprintf("%s: Unexpected selected line.", self.context),
|
||||
func() string {
|
||||
selectedLines, err := self.getSelectedLines()
|
||||
if err != nil {
|
||||
self.t.gui.Fail(err.Error())
|
||||
return "<failed to obtain selected line>"
|
||||
}
|
||||
|
||||
return selectedLines[0]
|
||||
},
|
||||
)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
@@ -298,10 +362,63 @@ func (self *ViewDriver) PressEscape() *ViewDriver {
|
||||
return self.Press(self.t.keys.Universal.Return)
|
||||
}
|
||||
|
||||
func (self *ViewDriver) NavigateToListItem(matcher *matcher) *ViewDriver {
|
||||
// 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
|
||||
//
|
||||
// NOTE: this currently assumes that BufferLines 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 *ViewDriver) NavigateToListItem(matcher *Matcher) *ViewDriver {
|
||||
self.IsFocused()
|
||||
|
||||
self.t.navigateToListItem(matcher)
|
||||
view := self.getView()
|
||||
|
||||
var matchIndex int
|
||||
|
||||
self.t.assertWithRetries(func() (bool, string) {
|
||||
matchIndex = -1
|
||||
var matches []string
|
||||
lines := view.BufferLines()
|
||||
// first we look for a duplicate on the current screen. We won't bother looking beyond that though.
|
||||
for i, line := range lines {
|
||||
ok, _ := matcher.test(line)
|
||||
if ok {
|
||||
matches = append(matches, line)
|
||||
matchIndex = i
|
||||
}
|
||||
}
|
||||
if len(matches) > 1 {
|
||||
return false, fmt.Sprintf("Found %d matches for `%s`, expected only a single match. Matching 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. Lines:\n%s", matcher.name(), strings.Join(lines, "\n"))
|
||||
} else {
|
||||
return true, ""
|
||||
}
|
||||
})
|
||||
|
||||
selectedLineIdx, err := self.getSelectedLineIdx()
|
||||
if err != nil {
|
||||
self.t.fail(err.Error())
|
||||
return self
|
||||
}
|
||||
if selectedLineIdx == matchIndex {
|
||||
self.SelectedLine(matcher)
|
||||
} else if selectedLineIdx < matchIndex {
|
||||
for i := selectedLineIdx; i < matchIndex; i++ {
|
||||
self.SelectNextItem()
|
||||
}
|
||||
self.SelectedLine(matcher)
|
||||
} else {
|
||||
for i := selectedLineIdx; i > matchIndex; i-- {
|
||||
self.SelectPreviousItem()
|
||||
}
|
||||
self.SelectedLine(matcher)
|
||||
}
|
||||
|
||||
return self
|
||||
}
|
||||
@@ -349,8 +466,13 @@ func (self *ViewDriver) Tap(f func()) *ViewDriver {
|
||||
return self
|
||||
}
|
||||
|
||||
func expectedContentFromMatchers(matchers []*matcher) string {
|
||||
return strings.Join(lo.Map(matchers, func(matcher *matcher, _ int) string {
|
||||
// This purely exists as a convenience method for those who hate the trailing periods in multi-line method chains
|
||||
func (self *ViewDriver) Self() *ViewDriver {
|
||||
return self
|
||||
}
|
||||
|
||||
func expectedContentFromMatchers(matchers []*Matcher) string {
|
||||
return strings.Join(lo.Map(matchers, func(matcher *Matcher, _ int) string {
|
||||
return matcher.name()
|
||||
}), "\n")
|
||||
}
|
||||
|
@@ -40,34 +40,97 @@ func (self *Views) Secondary() *ViewDriver {
|
||||
}
|
||||
|
||||
func (self *Views) regularView(viewName string) *ViewDriver {
|
||||
return self.newStaticViewDriver(viewName, nil)
|
||||
return self.newStaticViewDriver(viewName, nil, nil, nil)
|
||||
}
|
||||
|
||||
func (self *Views) patchExplorerViewByName(viewName string) *ViewDriver {
|
||||
return self.newStaticViewDriver(viewName, func() ([]string, error) {
|
||||
ctx := self.t.gui.ContextForView(viewName).(*context.PatchExplorerContext)
|
||||
state := ctx.GetState()
|
||||
if state == nil {
|
||||
return nil, errors.New("Expected patch explorer to be activated")
|
||||
}
|
||||
selectedContent := state.PlainRenderSelected()
|
||||
// the above method returns a string with a trailing newline so we need to remove that before splitting
|
||||
selectedLines := strings.Split(strings.TrimSuffix(selectedContent, "\n"), "\n")
|
||||
return selectedLines, nil
|
||||
})
|
||||
return self.newStaticViewDriver(
|
||||
viewName,
|
||||
func() ([]string, error) {
|
||||
ctx := self.t.gui.ContextForView(viewName).(*context.PatchExplorerContext)
|
||||
state := ctx.GetState()
|
||||
if state == nil {
|
||||
return nil, errors.New("Expected patch explorer to be activated")
|
||||
}
|
||||
selectedContent := state.PlainRenderSelected()
|
||||
// the above method returns a string with a trailing newline so we need to remove that before splitting
|
||||
selectedLines := strings.Split(strings.TrimSuffix(selectedContent, "\n"), "\n")
|
||||
return selectedLines, nil
|
||||
},
|
||||
func() (int, int, error) {
|
||||
ctx := self.t.gui.ContextForView(viewName).(*context.PatchExplorerContext)
|
||||
state := ctx.GetState()
|
||||
if state == nil {
|
||||
return 0, 0, errors.New("Expected patch explorer to be activated")
|
||||
}
|
||||
startIdx, endIdx := state.SelectedRange()
|
||||
return startIdx, endIdx, nil
|
||||
},
|
||||
func() (int, error) {
|
||||
ctx := self.t.gui.ContextForView(viewName).(*context.PatchExplorerContext)
|
||||
state := ctx.GetState()
|
||||
if state == nil {
|
||||
return 0, errors.New("Expected patch explorer to be activated")
|
||||
}
|
||||
return state.GetSelectedLineIdx(), nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// 'static' because it'll always refer to the same view, as opposed to the 'main' view which could actually be
|
||||
// one of several views, or the 'current' view which depends on focus.
|
||||
func (self *Views) newStaticViewDriver(viewName string, getSelectedLinesFn func() ([]string, error)) *ViewDriver {
|
||||
func (self *Views) newStaticViewDriver(
|
||||
viewName string,
|
||||
getSelectedLinesFn func() ([]string, error),
|
||||
getSelectedLineRangeFn func() (int, int, error),
|
||||
getSelectedLineIdxFn func() (int, error),
|
||||
) *ViewDriver {
|
||||
return &ViewDriver{
|
||||
context: fmt.Sprintf("%s view", viewName),
|
||||
getView: func() *gocui.View { return self.t.gui.View(viewName) },
|
||||
getSelectedLinesFn: getSelectedLinesFn,
|
||||
t: self.t,
|
||||
context: fmt.Sprintf("%s view", viewName),
|
||||
getView: func() *gocui.View { return self.t.gui.View(viewName) },
|
||||
getSelectedLinesFn: getSelectedLinesFn,
|
||||
getSelectedRangeFn: getSelectedLineRangeFn,
|
||||
getSelectedLineIdxFn: getSelectedLineIdxFn,
|
||||
t: self.t,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Views) MergeConflicts() *ViewDriver {
|
||||
viewName := "mergeConflicts"
|
||||
return self.newStaticViewDriver(
|
||||
viewName,
|
||||
func() ([]string, error) {
|
||||
ctx := self.t.gui.ContextForView(viewName).(*context.MergeConflictsContext)
|
||||
state := ctx.GetState()
|
||||
if state == nil {
|
||||
return nil, errors.New("Expected patch explorer to be activated")
|
||||
}
|
||||
selectedContent := strings.Split(state.PlainRenderSelected(), "\n")
|
||||
|
||||
return selectedContent, nil
|
||||
},
|
||||
func() (int, int, error) {
|
||||
ctx := self.t.gui.ContextForView(viewName).(*context.MergeConflictsContext)
|
||||
state := ctx.GetState()
|
||||
if state == nil {
|
||||
return 0, 0, errors.New("Expected patch explorer to be activated")
|
||||
}
|
||||
startIdx, endIdx := state.GetSelectedRange()
|
||||
return startIdx, endIdx, nil
|
||||
},
|
||||
// there is no concept of a cursor in the merge conflicts panel so we just return the start of the selection
|
||||
func() (int, error) {
|
||||
ctx := self.t.gui.ContextForView(viewName).(*context.MergeConflictsContext)
|
||||
state := ctx.GetState()
|
||||
if state == nil {
|
||||
return 0, errors.New("Expected patch explorer to be activated")
|
||||
}
|
||||
startIdx, _ := state.GetSelectedRange()
|
||||
return startIdx, nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (self *Views) Commits() *ViewDriver {
|
||||
return self.regularView("commits")
|
||||
}
|
||||
@@ -158,10 +221,6 @@ func (self *Views) Suggestions() *ViewDriver {
|
||||
return self.regularView("suggestions")
|
||||
}
|
||||
|
||||
func (self *Views) MergeConflicts() *ViewDriver {
|
||||
return self.regularView("mergeConflicts")
|
||||
}
|
||||
|
||||
func (self *Views) Search() *ViewDriver {
|
||||
return self.regularView("search")
|
||||
}
|
||||
|
Reference in New Issue
Block a user