diff --git a/pkg/integration/components/matcher.go b/pkg/integration/components/matcher.go index 9885b7d5e..0bbb1109b 100644 --- a/pkg/integration/components/matcher.go +++ b/pkg/integration/components/matcher.go @@ -41,11 +41,6 @@ func (self *matcher) name() string { } func (self *matcher) test(value string) (bool, string) { - // if there are no rules, then we pass the test by default - if len(self.rules) == 0 { - return true, "" - } - for _, rule := range self.rules { ok, message := rule.testFn(value) if ok { @@ -63,33 +58,25 @@ func (self *matcher) test(value string) (bool, string) { } func (self *matcher) Contains(target string) *matcher { - rule := matcherRule{ + return self.appendRule(matcherRule{ name: fmt.Sprintf("contains '%s'", target), testFn: func(value string) (bool, string) { return strings.Contains(value, target), fmt.Sprintf("Expected '%s' to be found in '%s'", target, value) }, - } - - self.rules = append(self.rules, rule) - - return self + }) } func (self *matcher) DoesNotContain(target string) *matcher { - rule := matcherRule{ + 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) }, - } - - self.rules = append(self.rules, rule) - - return self + }) } func (self *matcher) MatchesRegexp(target string) *matcher { - rule := matcherRule{ + return self.appendRule(matcherRule{ name: fmt.Sprintf("matches regular expression '%s'", target), testFn: func(value string) (bool, string) { matched, err := regexp.MatchString(target, value) @@ -98,32 +85,54 @@ func (self *matcher) MatchesRegexp(target string) *matcher { } return matched, fmt.Sprintf("Expected '%s' to match regular expression '%s'", value, target) }, - } - - self.rules = append(self.rules, rule) - - return self + }) } func (self *matcher) Equals(target string) *matcher { - rule := matcherRule{ + 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 { self.rules = append(self.rules, rule) return self } +// 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 { 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 }) + + self.rules = lo.Filter(self.rules, func(rule matcherRule, _ int) bool { return rule.name != IS_SELECTED_RULE_NAME }) + + return check, self +} + // 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 { diff --git a/pkg/integration/components/view_asserter.go b/pkg/integration/components/view_asserter.go index 65535a598..73f315993 100644 --- a/pkg/integration/components/view_asserter.go +++ b/pkg/integration/components/view_asserter.go @@ -44,17 +44,7 @@ func (self *ViewAsserter) TopLines(matchers ...*matcher) *ViewAsserter { return len(lines) >= len(matchers), fmt.Sprintf("unexpected number of lines in view. Expected at least %d, got %d", len(matchers), len(lines)) }) - view := self.getView() - - for i, matcher := range matchers { - self.assert.matchString(matcher, fmt.Sprintf("Unexpected content in view '%s'.", view.Name()), - func() string { - return view.BufferLines()[i] - }, - ) - } - - return self + return self.assertLines(matchers...) } // asserts that the view has lines matching the given matchers. One matcher must be passed for each line. @@ -65,14 +55,27 @@ func (self *ViewAsserter) Lines(matchers ...*matcher) *ViewAsserter { return len(lines) == len(matchers), fmt.Sprintf("unexpected number of lines in view. Expected %d, got %d", len(matchers), len(lines)) }) + return self.assertLines(matchers...) +} + +func (self *ViewAsserter) assertLines(matchers ...*matcher) *ViewAsserter { view := self.getView() for i, matcher := range matchers { + checkIsSelected, matcher := matcher.checkIsSelected() + self.assert.matchString(matcher, fmt.Sprintf("Unexpected content in view '%s'.", view.Name()), func() string { return view.BufferLines()[i] }, ) + + if checkIsSelected { + self.assert.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) + }) + } } return self diff --git a/pkg/integration/tests/bisect/basic.go b/pkg/integration/tests/bisect/basic.go index c6ad88c5b..45d66fa18 100644 --- a/pkg/integration/tests/bisect/basic.go +++ b/pkg/integration/tests/bisect/basic.go @@ -42,8 +42,7 @@ var Basic = NewIntegrationTest(NewIntegrationTestArgs{ assert.View("information").Content(Contains("bisecting")) - assert.CurrentView().Name("commits") - assert.CurrentView().SelectedLine(Contains("<-- bad")) + assert.CurrentView().Name("commits").SelectedLine(Contains("<-- bad")) input.NavigateToListItem(Contains("commit 02")) diff --git a/pkg/integration/tests/branch/delete.go b/pkg/integration/tests/branch/delete.go index c90458546..01bd4edda 100644 --- a/pkg/integration/tests/branch/delete.go +++ b/pkg/integration/tests/branch/delete.go @@ -36,8 +36,7 @@ var Delete = NewIntegrationTest(NewIntegrationTestArgs{ assert.CurrentView().Name("localBranches"). Lines( MatchesRegexp(`\*.*branch-two`), - MatchesRegexp(`master`), - ). - SelectedLineIdx(1) + MatchesRegexp(`master`).IsSelected(), + ) }, }) diff --git a/pkg/integration/tests/branch/rebase_and_drop.go b/pkg/integration/tests/branch/rebase_and_drop.go index 479d240d9..2ef65c3aa 100644 --- a/pkg/integration/tests/branch/rebase_and_drop.go +++ b/pkg/integration/tests/branch/rebase_and_drop.go @@ -27,7 +27,7 @@ var RebaseAndDrop = NewIntegrationTest(NewIntegrationTestArgs{ ) assert.View("commits").TopLines( - Contains("to keep"), + Contains("to keep").IsSelected(), Contains("to remove"), Contains("first change"), Contains("original"), @@ -44,22 +44,29 @@ var RebaseAndDrop = NewIntegrationTest(NewIntegrationTestArgs{ assert.CurrentView(). Name("files"). - SelectedLine(Contains("file")) + SelectedLine(MatchesRegexp("UU.*file")) input.SwitchToCommitsView() assert.CurrentView(). TopLines( - MatchesRegexp(`pick.*to keep`), + MatchesRegexp(`pick.*to keep`).IsSelected(), MatchesRegexp(`pick.*to remove`), MatchesRegexp("YOU ARE HERE.*second-change-branch unrelated change"), MatchesRegexp("second change"), MatchesRegexp("original"), - ). - SelectedLineIdx(0) + ) input.NextItem() input.Press(keys.Universal.Remove) - assert.CurrentView().SelectedLine(MatchesRegexp(`drop.*to remove`)) + + assert.CurrentView(). + TopLines( + MatchesRegexp(`pick.*to keep`), + MatchesRegexp(`drop.*to remove`).IsSelected(), + MatchesRegexp("YOU ARE HERE.*second-change-branch unrelated change"), + MatchesRegexp("second change"), + MatchesRegexp("original"), + ) input.SwitchToFilesView() @@ -76,7 +83,7 @@ var RebaseAndDrop = NewIntegrationTest(NewIntegrationTestArgs{ assert.View("commits").TopLines( Contains("to keep"), - Contains("second-change-branch unrelated change"), + Contains("second-change-branch unrelated change").IsSelected(), Contains("second change"), Contains("original"), ) diff --git a/pkg/integration/tests/commit/revert.go b/pkg/integration/tests/commit/revert.go index e23091baf..a9bd6373a 100644 --- a/pkg/integration/tests/commit/revert.go +++ b/pkg/integration/tests/commit/revert.go @@ -29,10 +29,9 @@ var Revert = NewIntegrationTest(NewIntegrationTestArgs{ assert.CurrentView().Name("commits"). Lines( - Contains("Revert \"first commit\""), + Contains("Revert \"first commit\"").IsSelected(), Contains("first commit"), - ). - SelectedLineIdx(0) + ) assert.MainView().Content(Contains("-myfile content")) assert.FileSystemPathNotPresent("myfile") diff --git a/pkg/integration/tests/diff/diff_commits.go b/pkg/integration/tests/diff/diff_commits.go index 920686c2c..efa44d818 100644 --- a/pkg/integration/tests/diff/diff_commits.go +++ b/pkg/integration/tests/diff/diff_commits.go @@ -48,8 +48,7 @@ var DiffCommits = NewIntegrationTest(NewIntegrationTestArgs{ input.Enter() - assert.CurrentView().Name("commitFiles") - assert.CurrentView().SelectedLine(Contains("file1")) + assert.CurrentView().Name("commitFiles").SelectedLine(Contains("file1")) assert.MainView().Content(Contains("+second line\n+third line")) }, })