mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-01-22 05:29:44 +02:00
Merge pull request #2704 from jesseduffield/int-matchers
This commit is contained in:
commit
042ab2f99a
@ -39,13 +39,13 @@ import (
|
||||
func CopyFile(src, dst string) (err error) {
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
return
|
||||
return //nolint: nakedret
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
out, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return
|
||||
return //nolint: nakedret
|
||||
}
|
||||
defer func() {
|
||||
if e := out.Close(); e != nil {
|
||||
@ -55,21 +55,21 @@ func CopyFile(src, dst string) (err error) {
|
||||
|
||||
_, err = io.Copy(out, in)
|
||||
if err != nil {
|
||||
return
|
||||
return //nolint: nakedret
|
||||
}
|
||||
|
||||
err = out.Sync()
|
||||
if err != nil {
|
||||
return
|
||||
return //nolint: nakedret
|
||||
}
|
||||
|
||||
si, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return
|
||||
return //nolint: nakedret
|
||||
}
|
||||
err = os.Chmod(dst, si.Mode())
|
||||
if err != nil {
|
||||
return
|
||||
return //nolint: nakedret
|
||||
}
|
||||
|
||||
return //nolint: nakedret
|
||||
@ -92,7 +92,7 @@ func CopyDir(src string, dst string) (err error) {
|
||||
|
||||
_, err = os.Stat(dst)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return
|
||||
return //nolint: nakedret
|
||||
}
|
||||
if err == nil {
|
||||
// it exists so let's remove it
|
||||
@ -103,12 +103,12 @@ func CopyDir(src string, dst string) (err error) {
|
||||
|
||||
err = os.MkdirAll(dst, si.Mode())
|
||||
if err != nil {
|
||||
return
|
||||
return //nolint: nakedret
|
||||
}
|
||||
|
||||
entries, err := ioutil.ReadDir(src)
|
||||
if err != nil {
|
||||
return
|
||||
return //nolint: nakedret
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
@ -118,7 +118,7 @@ func CopyDir(src string, dst string) (err error) {
|
||||
if entry.IsDir() {
|
||||
err = CopyDir(srcPath, dstPath)
|
||||
if err != nil {
|
||||
return
|
||||
return //nolint: nakedret
|
||||
}
|
||||
} else {
|
||||
// Skip symlinks.
|
||||
@ -128,7 +128,7 @@ func CopyDir(src string, dst string) (err error) {
|
||||
|
||||
err = CopyFile(srcPath, dstPath)
|
||||
if err != nil {
|
||||
return
|
||||
return //nolint: nakedret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -72,13 +72,13 @@ type prefixWriter struct {
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
func (self *prefixWriter) Write(p []byte) (n int, err error) {
|
||||
func (self *prefixWriter) Write(p []byte) (int, error) {
|
||||
if !self.prefixWritten {
|
||||
self.prefixWritten = true
|
||||
// assuming we can write this prefix in one go
|
||||
_, err = self.writer.Write([]byte(self.prefix))
|
||||
n, err := self.writer.Write([]byte(self.prefix))
|
||||
if err != nil {
|
||||
return
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
return self.writer.Write(p)
|
||||
|
@ -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 *TextMatcher) *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 *TextMatcher) *AlertDriver {
|
||||
self.getViewDriver().Content(expected)
|
||||
|
||||
self.hasCheckedContent = true
|
||||
|
@ -22,7 +22,7 @@ func retryWaitTimes() []int {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *assertionHelper) matchString(matcher *Matcher, context string, getValue func() string) {
|
||||
func (self *assertionHelper) matchString(matcher *TextMatcher, context string, getValue func() string) {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
value := getValue()
|
||||
return matcher.context(context).test(value)
|
||||
|
@ -9,19 +9,19 @@ 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 *TextMatcher) *CommitMessagePanelDriver {
|
||||
return self.Content(expected)
|
||||
}
|
||||
|
||||
// asserts on the current context in the prompt
|
||||
func (self *CommitMessagePanelDriver) Content(expected *Matcher) *CommitMessagePanelDriver {
|
||||
func (self *CommitMessagePanelDriver) Content(expected *TextMatcher) *CommitMessagePanelDriver {
|
||||
self.getViewDriver().Content(expected)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// asserts that the confirmation view has the expected title
|
||||
func (self *CommitMessagePanelDriver) Title(expected *Matcher) *CommitMessagePanelDriver {
|
||||
func (self *CommitMessagePanelDriver) Title(expected *TextMatcher) *CommitMessagePanelDriver {
|
||||
self.getViewDriver().Title(expected)
|
||||
|
||||
return self
|
||||
|
@ -39,7 +39,7 @@ func (self *Common) ConfirmDiscardLines() {
|
||||
Confirm()
|
||||
}
|
||||
|
||||
func (self *Common) SelectPatchOption(matcher *Matcher) {
|
||||
func (self *Common) SelectPatchOption(matcher *TextMatcher) {
|
||||
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 *ConfirmationDriver) getViewDriver() *ViewDriver {
|
||||
}
|
||||
|
||||
// asserts that the confirmation view has the expected title
|
||||
func (self *ConfirmationDriver) Title(expected *Matcher) *ConfirmationDriver {
|
||||
func (self *ConfirmationDriver) Title(expected *TextMatcher) *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 *TextMatcher) *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 *TextMatcher) {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
_, err := os.Stat(path)
|
||||
if os.IsNotExist(err) {
|
||||
|
58
pkg/integration/components/int_matcher.go
Normal file
58
pkg/integration/components/int_matcher.go
Normal file
@ -0,0 +1,58 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type IntMatcher struct {
|
||||
*Matcher[int]
|
||||
}
|
||||
|
||||
func (self *IntMatcher) EqualsInt(target int) *IntMatcher {
|
||||
self.appendRule(matcherRule[int]{
|
||||
name: fmt.Sprintf("equals '%d'", target),
|
||||
testFn: func(value int) (bool, string) {
|
||||
return value == target, fmt.Sprintf("Expected '%d' to equal '%d'", value, target)
|
||||
},
|
||||
})
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *IntMatcher) GreaterThan(target int) *IntMatcher {
|
||||
self.appendRule(matcherRule[int]{
|
||||
name: fmt.Sprintf("greater than '%d'", target),
|
||||
testFn: func(value int) (bool, string) {
|
||||
return value > target, fmt.Sprintf("Expected '%d' to greater than '%d'", value, target)
|
||||
},
|
||||
})
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *IntMatcher) LessThan(target int) *IntMatcher {
|
||||
self.appendRule(matcherRule[int]{
|
||||
name: fmt.Sprintf("less than '%d'", target),
|
||||
testFn: func(value int) (bool, string) {
|
||||
return value < target, fmt.Sprintf("Expected '%d' to less than '%d'", value, target)
|
||||
},
|
||||
})
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func AnyInt() *IntMatcher {
|
||||
return &IntMatcher{Matcher: &Matcher[int]{}}
|
||||
}
|
||||
|
||||
func EqualsInt(target int) *IntMatcher {
|
||||
return AnyInt().EqualsInt(target)
|
||||
}
|
||||
|
||||
func GreaterThan(target int) *IntMatcher {
|
||||
return AnyInt().GreaterThan(target)
|
||||
}
|
||||
|
||||
func LessThan(target int) *IntMatcher {
|
||||
return AnyInt().LessThan(target)
|
||||
}
|
@ -1,46 +1,39 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
// for making assertions on string values
|
||||
type Matcher struct {
|
||||
rules []matcherRule
|
||||
type Matcher[T any] struct {
|
||||
rules []matcherRule[T]
|
||||
|
||||
// this is printed when there's an error so that it's clear what the context of the assertion is
|
||||
prefix string
|
||||
}
|
||||
|
||||
type matcherRule struct {
|
||||
type matcherRule[T any] 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)
|
||||
testFn func(T) (bool, string)
|
||||
}
|
||||
|
||||
func NewMatcher(name string, testFn func(string) (bool, string)) *Matcher {
|
||||
rules := []matcherRule{{name: name, testFn: testFn}}
|
||||
return &Matcher{rules: rules}
|
||||
}
|
||||
|
||||
func (self *Matcher) name() string {
|
||||
func (self *Matcher[T]) name() string {
|
||||
if len(self.rules) == 0 {
|
||||
return "anything"
|
||||
}
|
||||
|
||||
return strings.Join(
|
||||
lo.Map(self.rules, func(rule matcherRule, _ int) string { return rule.name }),
|
||||
lo.Map(self.rules, func(rule matcherRule[T], _ int) string { return rule.name }),
|
||||
", ",
|
||||
)
|
||||
}
|
||||
|
||||
func (self *Matcher) test(value string) (bool, string) {
|
||||
func (self *Matcher[T]) test(value T) (bool, string) {
|
||||
for _, rule := range self.rules {
|
||||
ok, message := rule.testFn(value)
|
||||
if ok {
|
||||
@ -57,65 +50,7 @@ func (self *Matcher) test(value string) (bool, string) {
|
||||
return true, ""
|
||||
}
|
||||
|
||||
func (self *Matcher) Contains(target string) *Matcher {
|
||||
return self.appendRule(matcherRule{
|
||||
name: fmt.Sprintf("contains '%s'", target),
|
||||
testFn: func(value string) (bool, string) {
|
||||
// everything contains the empty string so we unconditionally return true here
|
||||
if target == "" {
|
||||
return true, ""
|
||||
}
|
||||
|
||||
return strings.Contains(value, target), fmt.Sprintf("Expected '%s' to be found in '%s'", target, value)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
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) {
|
||||
return !strings.Contains(value, target), fmt.Sprintf("Expected '%s' to NOT be found in '%s'", target, value)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
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) {
|
||||
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 (self *Matcher) Equals(target string) *Matcher {
|
||||
return self.appendRule(matcherRule{
|
||||
name: fmt.Sprintf("equals '%s'", target),
|
||||
testFn: func(value string) (bool, string) {
|
||||
return target == value, fmt.Sprintf("Expected '%s' to equal '%s'", value, target)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
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 {
|
||||
return self.appendRule(matcherRule{
|
||||
name: IS_SELECTED_RULE_NAME,
|
||||
testFn: func(value string) (bool, string) {
|
||||
panic("Special IsSelected matcher is not supposed to have its testFn method called. This rule should only be used within the .Lines() and .TopLines() method on a ViewAsserter.")
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *Matcher) appendRule(rule matcherRule) *Matcher {
|
||||
func (self *Matcher[T]) appendRule(rule matcherRule[T]) *Matcher[T] {
|
||||
self.rules = append(self.rules, rule)
|
||||
|
||||
return self
|
||||
@ -123,43 +58,8 @@ 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[T]) context(prefix string) *Matcher[T] {
|
||||
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) {
|
||||
// copying into a new matcher in case we want to re-use the original later
|
||||
newMatcher := &Matcher{}
|
||||
*newMatcher = *self
|
||||
|
||||
check := lo.ContainsBy(newMatcher.rules, func(rule matcherRule) bool { return rule.name == IS_SELECTED_RULE_NAME })
|
||||
|
||||
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 Contains(target string) *Matcher {
|
||||
return Anything().Contains(target)
|
||||
}
|
||||
|
||||
func DoesNotContain(target string) *Matcher {
|
||||
return Anything().DoesNotContain(target)
|
||||
}
|
||||
|
||||
func MatchesRegexp(target string) *Matcher {
|
||||
return Anything().MatchesRegexp(target)
|
||||
}
|
||||
|
||||
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 *TextMatcher) *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 *TextMatcher) *MenuDriver {
|
||||
self.getViewDriver().NavigateToLine(option)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *MenuDriver) Lines(matchers ...*Matcher) *MenuDriver {
|
||||
func (self *MenuDriver) Lines(matchers ...*TextMatcher) *MenuDriver {
|
||||
self.getViewDriver().Lines(matchers...)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *MenuDriver) TopLines(matchers ...*Matcher) *MenuDriver {
|
||||
func (self *MenuDriver) TopLines(matchers ...*TextMatcher) *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 *TextMatcher) *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 *TextMatcher) *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 ...*TextMatcher) *PromptDriver {
|
||||
self.t.Views().Suggestions().Lines(matchers...)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *PromptDriver) SuggestionTopLines(matchers ...*Matcher) *PromptDriver {
|
||||
func (self *PromptDriver) SuggestionTopLines(matchers ...*TextMatcher) *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 *TextMatcher) {
|
||||
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 *TextMatcher) *SearchDriver {
|
||||
self.getViewDriver().Content(expected)
|
||||
|
||||
return self
|
||||
|
@ -80,11 +80,11 @@ func (self *TestDriver) ExpectPopup() *Popup {
|
||||
return &Popup{t: self}
|
||||
}
|
||||
|
||||
func (self *TestDriver) ExpectToast(matcher *Matcher) {
|
||||
func (self *TestDriver) ExpectToast(matcher *TextMatcher) {
|
||||
self.Views().AppStatus().Content(matcher)
|
||||
}
|
||||
|
||||
func (self *TestDriver) ExpectClipboard(matcher *Matcher) {
|
||||
func (self *TestDriver) ExpectClipboard(matcher *TextMatcher) {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
text, err := clipboard.ReadAll()
|
||||
if err != nil {
|
||||
|
116
pkg/integration/components/text_matcher.go
Normal file
116
pkg/integration/components/text_matcher.go
Normal file
@ -0,0 +1,116 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type TextMatcher struct {
|
||||
*Matcher[string]
|
||||
}
|
||||
|
||||
func (self *TextMatcher) Contains(target string) *TextMatcher {
|
||||
self.appendRule(matcherRule[string]{
|
||||
name: fmt.Sprintf("contains '%s'", target),
|
||||
testFn: func(value string) (bool, string) {
|
||||
// everything contains the empty string so we unconditionally return true here
|
||||
if target == "" {
|
||||
return true, ""
|
||||
}
|
||||
|
||||
return strings.Contains(value, target), fmt.Sprintf("Expected '%s' to be found in '%s'", target, value)
|
||||
},
|
||||
})
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *TextMatcher) DoesNotContain(target string) *TextMatcher {
|
||||
self.appendRule(matcherRule[string]{
|
||||
name: fmt.Sprintf("does not contain '%s'", target),
|
||||
testFn: func(value string) (bool, string) {
|
||||
return !strings.Contains(value, target), fmt.Sprintf("Expected '%s' to NOT be found in '%s'", target, value)
|
||||
},
|
||||
})
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *TextMatcher) MatchesRegexp(target string) *TextMatcher {
|
||||
self.appendRule(matcherRule[string]{
|
||||
name: fmt.Sprintf("matches regular expression '%s'", target),
|
||||
testFn: 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)
|
||||
},
|
||||
})
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *TextMatcher) Equals(target string) *TextMatcher {
|
||||
self.appendRule(matcherRule[string]{
|
||||
name: fmt.Sprintf("equals '%s'", target),
|
||||
testFn: func(value string) (bool, string) {
|
||||
return target == value, fmt.Sprintf("Expected '%s' to equal '%s'", value, target)
|
||||
},
|
||||
})
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
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 *TextMatcher) IsSelected() *TextMatcher {
|
||||
self.appendRule(matcherRule[string]{
|
||||
name: IS_SELECTED_RULE_NAME,
|
||||
testFn: func(value string) (bool, string) {
|
||||
panic("Special IsSelected matcher is not supposed to have its testFn method called. This rule should only be used within the .Lines() and .TopLines() method on a ViewAsserter.")
|
||||
},
|
||||
})
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// if the matcher has an `IsSelected` rule, it returns true, along with the matcher after that rule has been removed
|
||||
func (self *TextMatcher) checkIsSelected() (bool, *TextMatcher) {
|
||||
// copying into a new matcher in case we want to re-use the original later
|
||||
newMatcher := &TextMatcher{}
|
||||
*newMatcher = *self
|
||||
|
||||
check := lo.ContainsBy(newMatcher.rules, func(rule matcherRule[string]) bool { return rule.name == IS_SELECTED_RULE_NAME })
|
||||
|
||||
newMatcher.rules = lo.Filter(newMatcher.rules, func(rule matcherRule[string], _ 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 AnyString() *TextMatcher {
|
||||
return &TextMatcher{Matcher: &Matcher[string]{}}
|
||||
}
|
||||
|
||||
func Contains(target string) *TextMatcher {
|
||||
return AnyString().Contains(target)
|
||||
}
|
||||
|
||||
func DoesNotContain(target string) *TextMatcher {
|
||||
return AnyString().DoesNotContain(target)
|
||||
}
|
||||
|
||||
func MatchesRegexp(target string) *TextMatcher {
|
||||
return AnyString().MatchesRegexp(target)
|
||||
}
|
||||
|
||||
func Equals(target string) *TextMatcher {
|
||||
return AnyString().Equals(target)
|
||||
}
|
@ -52,7 +52,7 @@ func (self *ViewDriver) getSelectedLineIdx() (int, error) {
|
||||
}
|
||||
|
||||
// asserts that the view has the expected title
|
||||
func (self *ViewDriver) Title(expected *Matcher) *ViewDriver {
|
||||
func (self *ViewDriver) Title(expected *TextMatcher) *ViewDriver {
|
||||
self.t.assertWithRetries(func() (bool, string) {
|
||||
actual := self.getView().Title
|
||||
return expected.context(fmt.Sprintf("%s title", self.context)).test(actual)
|
||||
@ -64,7 +64,7 @@ func (self *ViewDriver) Title(expected *Matcher) *ViewDriver {
|
||||
// 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.
|
||||
// If you only care about a subset of lines, use the ContainsLines method instead.
|
||||
func (self *ViewDriver) Lines(matchers ...*Matcher) *ViewDriver {
|
||||
func (self *ViewDriver) Lines(matchers ...*TextMatcher) *ViewDriver {
|
||||
self.validateMatchersPassed(matchers)
|
||||
self.LineCount(len(matchers))
|
||||
|
||||
@ -75,7 +75,7 @@ func (self *ViewDriver) Lines(matchers ...*Matcher) *ViewDriver {
|
||||
// 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 {
|
||||
func (self *ViewDriver) TopLines(matchers ...*TextMatcher) *ViewDriver {
|
||||
self.validateMatchersPassed(matchers)
|
||||
self.validateEnoughLines(matchers)
|
||||
|
||||
@ -83,7 +83,7 @@ func (self *ViewDriver) TopLines(matchers ...*Matcher) *ViewDriver {
|
||||
}
|
||||
|
||||
// asserts that somewhere in the view there are consequetive lines matching the given matchers.
|
||||
func (self *ViewDriver) ContainsLines(matchers ...*Matcher) *ViewDriver {
|
||||
func (self *ViewDriver) ContainsLines(matchers ...*TextMatcher) *ViewDriver {
|
||||
self.validateMatchersPassed(matchers)
|
||||
self.validateEnoughLines(matchers)
|
||||
|
||||
@ -162,7 +162,7 @@ func (self *ViewDriver) DoesNotContainColoredText(fgColorStr string, text string
|
||||
}
|
||||
|
||||
// asserts on the lines that are selected in the view. Don't use the `IsSelected` matcher with this because it's redundant.
|
||||
func (self *ViewDriver) SelectedLines(matchers ...*Matcher) *ViewDriver {
|
||||
func (self *ViewDriver) SelectedLines(matchers ...*TextMatcher) *ViewDriver {
|
||||
self.validateMatchersPassed(matchers)
|
||||
self.validateEnoughLines(matchers)
|
||||
|
||||
@ -197,13 +197,13 @@ func (self *ViewDriver) SelectedLines(matchers ...*Matcher) *ViewDriver {
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *ViewDriver) validateMatchersPassed(matchers []*Matcher) {
|
||||
func (self *ViewDriver) validateMatchersPassed(matchers []*TextMatcher) {
|
||||
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) validateEnoughLines(matchers []*Matcher) {
|
||||
func (self *ViewDriver) validateEnoughLines(matchers []*TextMatcher) {
|
||||
view := self.getView()
|
||||
|
||||
self.t.assertWithRetries(func() (bool, string) {
|
||||
@ -212,7 +212,7 @@ func (self *ViewDriver) validateEnoughLines(matchers []*Matcher) {
|
||||
})
|
||||
}
|
||||
|
||||
func (self *ViewDriver) assertLines(offset int, matchers ...*Matcher) *ViewDriver {
|
||||
func (self *ViewDriver) assertLines(offset int, matchers ...*TextMatcher) *ViewDriver {
|
||||
view := self.getView()
|
||||
|
||||
for matcherIndex, matcher := range matchers {
|
||||
@ -252,7 +252,7 @@ func (self *ViewDriver) assertLines(offset int, matchers ...*Matcher) *ViewDrive
|
||||
}
|
||||
|
||||
// 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 *TextMatcher) *ViewDriver {
|
||||
self.t.matchString(matcher, fmt.Sprintf("%s: Unexpected content.", self.context),
|
||||
func() string {
|
||||
return self.getView().Buffer()
|
||||
@ -265,7 +265,7 @@ func (self *ViewDriver) Content(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 {
|
||||
func (self *ViewDriver) SelectedLine(matcher *TextMatcher) *ViewDriver {
|
||||
self.t.assertWithRetries(func() (bool, string) {
|
||||
selectedLineIdx, err := self.getSelectedLineIdx()
|
||||
if err != nil {
|
||||
@ -410,7 +410,7 @@ func (self *ViewDriver) PressEscape() *ViewDriver {
|
||||
// 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) NavigateToLine(matcher *Matcher) *ViewDriver {
|
||||
func (self *ViewDriver) NavigateToLine(matcher *TextMatcher) *ViewDriver {
|
||||
self.IsFocused()
|
||||
|
||||
view := self.getView()
|
||||
@ -510,8 +510,8 @@ func (self *ViewDriver) Self() *ViewDriver {
|
||||
return self
|
||||
}
|
||||
|
||||
func expectedContentFromMatchers(matchers []*Matcher) string {
|
||||
return strings.Join(lo.Map(matchers, func(matcher *Matcher, _ int) string {
|
||||
func expectedContentFromMatchers(matchers []*TextMatcher) string {
|
||||
return strings.Join(lo.Map(matchers, func(matcher *TextMatcher, _ int) string {
|
||||
return matcher.name()
|
||||
}), "\n")
|
||||
}
|
||||
|
@ -19,8 +19,8 @@ var RemoteNamedStar = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
// here we're just asserting that we haven't panicked upon starting lazygit
|
||||
t.Views().Commits().
|
||||
Lines(
|
||||
Anything(),
|
||||
Anything(),
|
||||
AnyString(),
|
||||
AnyString(),
|
||||
)
|
||||
},
|
||||
})
|
||||
|
@ -28,6 +28,12 @@ var Patch = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Contains("commit (initial): one"),
|
||||
).
|
||||
SelectNextItem().
|
||||
Lines(
|
||||
Contains("reset: moving to HEAD^^"),
|
||||
Contains("commit: three").IsSelected(),
|
||||
Contains("commit: two"),
|
||||
Contains("commit (initial): one"),
|
||||
).
|
||||
PressEnter()
|
||||
|
||||
t.Views().SubCommits().
|
||||
|
Loading…
x
Reference in New Issue
Block a user