summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2023-06-03 15:56:34 +1000
committerGitHub <noreply@github.com>2023-06-03 15:56:34 +1000
commit042ab2f99a91414f48a00627f2362eca2d86c220 (patch)
treefe518727b52fdeb5f6e63283902d8204f0c0485b
parente98935f83ea13c6a780d98ed4ac4e7bd07850901 (diff)
parenta9ae5063c2e6dd7d0dd3f0e6617c4abadd08fe92 (diff)
Merge pull request #2704 from jesseduffield/int-matchers
-rw-r--r--pkg/commands/oscommands/copy.go22
-rw-r--r--pkg/gui/extras_panel.go6
-rw-r--r--pkg/integration/components/alert_driver.go4
-rw-r--r--pkg/integration/components/assertion_helper.go2
-rw-r--r--pkg/integration/components/commit_message_panel_driver.go6
-rw-r--r--pkg/integration/components/common.go2
-rw-r--r--pkg/integration/components/confirmation_driver.go4
-rw-r--r--pkg/integration/components/file_system.go2
-rw-r--r--pkg/integration/components/int_matcher.go58
-rw-r--r--pkg/integration/components/matcher.go118
-rw-r--r--pkg/integration/components/menu_driver.go8
-rw-r--r--pkg/integration/components/prompt_driver.go10
-rw-r--r--pkg/integration/components/search_driver.go2
-rw-r--r--pkg/integration/components/test_driver.go4
-rw-r--r--pkg/integration/components/text_matcher.go116
-rw-r--r--pkg/integration/components/view_driver.go26
-rw-r--r--pkg/integration/tests/config/remote_named_star.go4
-rw-r--r--pkg/integration/tests/reflog/patch.go6
18 files changed, 240 insertions, 160 deletions
diff --git a/pkg/commands/oscommands/copy.go b/pkg/commands/oscommands/copy.go
index f68590280..e68d0cfce 100644
--- a/pkg/commands/oscommands/copy.go
+++ b/pkg/commands/oscommands/copy.go
@@ -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
}
}
}
diff --git a/pkg/gui/extras_panel.go b/pkg/gui/extras_panel.go
index 927250627..4d607f631 100644
--- a/pkg/gui/extras_panel.go
+++ b/pkg/gui/extras_panel.go
@@ -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)
diff --git a/pkg/integration/components/alert_driver.go b/pkg/integration/components/alert_driver.go
index 7e1623041..f0cfaa4eb 100644
--- a/pkg/integration/components/alert_driver.go
+++ b/pkg/integration/components/alert_driver.go
@@ -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
diff --git a/pkg/integration/components/assertion_helper.go b/pkg/integration/components/assertion_helper.go
index 4ae785db2..48cc14741 100644
--- a/pkg/integration/components/assertion_helper.go
+++ b/pkg/integration/components/assertion_helper.go
@@ -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)
diff --git a/pkg/integration/components/commit_message_panel_driver.go b/pkg/integration/components/commit_message_panel_driver.go
index d077761fc..f9089ee2e 100644
--- a/pkg/integration/components/commit_message_panel_driver.go
+++ b/pkg/integration/components/commit_message_panel_driver.go
@@ -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
diff --git a/pkg/integration/components/common.go b/pkg/integration/components/common.go
index fe81438ee..904c73711 100644
--- a/pkg/integration/components/common.go
+++ b/pkg/integration/components/common.go
@@ -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()
diff --git a/pkg/integration/components/confirmation_driver.go b/pkg/integration/components/confirmation_driver.go
index 687523833..aad5cc248 100644
--- a/pkg/integration/components/confirmation_driver.go
+++ b/pkg/integration/components/confirmation_driver.go
@@ -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
diff --git a/pkg/integration/components/file_system.go b/pkg/integration/components/file_system.go
index cdc7413fb..74f179fdc 100644
--- a/pkg/integration/components/file_system.go
+++ b/pkg/integration/components/file_system.go
@@ -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) {
diff --git a/pkg/integration/components/int_matcher.go b/pkg/integration/components/int_matcher.go
new file mode 100644
index 000000000..c80a60c85
--- /dev/null
+++ b/pkg/integration/components/int_matcher.go
@@ -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)
+}
diff --git a/pkg/integration/components/matcher.go b/pkg/integration/components/matcher.go
index a87234654..d01aa92ef 100644
--- a/pkg/integration/components/matcher.go
+++ b/pkg/integration/components/matcher.go
@@ -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)
-}
diff --git a/pkg/integration/components/menu_driver.go b/pkg/integration/components/menu_driver.go
index 4092879da..ac620f5a4 100644
--- a/pkg/integration/components/menu_driver.go
+++ b/pkg/integration/components/menu_driver.go
@@ -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
diff --git a/pkg/integration/components/prompt_driver.go b/pkg/integration/components/prompt_driver.go
index d21d6f95e..023c2f438 100644
--- a/pkg/integration/components/prompt_driver.go
+++ b/pkg/integration/components/prompt_driver.go
@@ -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().
diff --git a/pkg/integration/components/search_driver.go b/pkg/integration/components/search_driver.go
index 66a2fae41..498047cce 100644
--- a/pkg/integration/components/search_driver.go
+++ b/pkg/integration/components/search_driver.go
@@ -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
diff --git a/pkg/integration/components/test_driver.go b/pkg/integration/components/test_driver.go
index c67439b54..8ca3f1f70 100644
--- a/pkg/integration/components/test_driver.go
+++ b/pkg/integration/components/test_driver.go
@@ -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 {
diff --git a/pkg/integration/components/text_matcher.go b/pkg/integration/components/text_matcher.go
new file mode 100644
index 000000000..2d2c0e577
--- /dev/null
+++ b/pkg/integration/components/text_matcher.go
@@ -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)
+}
diff --git a/pkg/integration/components/view_driver.go b/pkg/integration/components/view_driver.go
index 1a5017b37..db7e76134 100644
--- a/pkg/integration/components/view_driver.go
+++ b/pkg/integration/components/view_driver.go
@@ -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 curso