From dd34adb36c072b7bfd34991fdb117d7959efa113 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sat, 3 Jun 2023 15:12:31 +1000 Subject: [PATCH 1/3] Support matchers on integers in integration tests --- pkg/integration/components/alert_driver.go | 4 +- .../components/assertion_helper.go | 2 +- .../components/commit_message_panel_driver.go | 6 +- pkg/integration/components/common.go | 2 +- .../components/confirmation_driver.go | 4 +- pkg/integration/components/file_system.go | 2 +- pkg/integration/components/int_matcher.go | 58 +++++++++ pkg/integration/components/matcher.go | 118 ++---------------- pkg/integration/components/menu_driver.go | 8 +- pkg/integration/components/prompt_driver.go | 10 +- pkg/integration/components/search_driver.go | 2 +- pkg/integration/components/test_driver.go | 4 +- pkg/integration/components/text_matcher.go | 116 +++++++++++++++++ pkg/integration/components/view_driver.go | 26 ++-- .../tests/config/remote_named_star.go | 4 +- 15 files changed, 220 insertions(+), 146 deletions(-) create mode 100644 pkg/integration/components/int_matcher.go create mode 100644 pkg/integration/components/text_matcher.go 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 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") } diff --git a/pkg/integration/tests/config/remote_named_star.go b/pkg/integration/tests/config/remote_named_star.go index 94ff80b41..15bb45f2e 100644 --- a/pkg/integration/tests/config/remote_named_star.go +++ b/pkg/integration/tests/config/remote_named_star.go @@ -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(), ) }, }) From 1932c2366b1cc8bfca634274a1e265ce795e79da Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sat, 3 Jun 2023 15:49:38 +1000 Subject: [PATCH 2/3] Appease linter --- pkg/commands/oscommands/copy.go | 22 +++++++++++----------- pkg/gui/extras_panel.go | 6 +++--- 2 files changed, 14 insertions(+), 14 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) From a9ae5063c2e6dd7d0dd3f0e6617c4abadd08fe92 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sat, 3 Jun 2023 15:50:20 +1000 Subject: [PATCH 3/3] Fix flakey test Whenever we perform an action in a test, we should assert on the result before doing the next action. This prevents issues where the test moves too fast for our code. It would be nice to not have to do this, but for now that's the situation --- pkg/integration/tests/reflog/patch.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/integration/tests/reflog/patch.go b/pkg/integration/tests/reflog/patch.go index 7cbdcba83..415fb18ee 100644 --- a/pkg/integration/tests/reflog/patch.go +++ b/pkg/integration/tests/reflog/patch.go @@ -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().