diff options
author | Jesse Duffield <jessedduffield@gmail.com> | 2022-12-26 11:12:56 +1100 |
---|---|---|
committer | Jesse Duffield <jessedduffield@gmail.com> | 2022-12-26 12:20:13 +1100 |
commit | 9a6f21ce429fedd949fdf9853c6ebf4892f858fd (patch) | |
tree | d8ee0c4ad1e993319caf53e0554186f6103fb1d7 | |
parent | fa0414777fcf266a05b777e2e8b7d487fd56f376 (diff) |
cleaner test assertions
34 files changed, 442 insertions, 378 deletions
diff --git a/pkg/integration/components/assert.go b/pkg/integration/components/assert.go index c81e25fab..ac6b99f0e 100644 --- a/pkg/integration/components/assert.go +++ b/pkg/integration/components/assert.go @@ -3,10 +3,9 @@ package components import ( "fmt" "os" - "regexp" - "strings" "time" + "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/gui/types" integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" ) @@ -21,46 +20,6 @@ func NewAssert(gui integrationTypes.GuiDriver) *Assert { return &Assert{gui: gui} } -func Contains(target string) *matcher { - return NewMatcher( - fmt.Sprintf("contains '%s'", target), - func(value string) (bool, string) { - return strings.Contains(value, target), fmt.Sprintf("Expected '%s' to be found in '%s'", target, value) - }, - ) -} - -func NotContains(target string) *matcher { - return NewMatcher( - fmt.Sprintf("does not contain '%s'", target), - func(value string) (bool, string) { - return !strings.Contains(value, target), fmt.Sprintf("Expected '%s' to NOT be found in '%s'", target, value) - }, - ) -} - -func MatchesRegexp(target string) *matcher { - return NewMatcher( - fmt.Sprintf("matches regular expression '%s'", target), - 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 Equals(target string) *matcher { - return NewMatcher( - fmt.Sprintf("equals '%s'", target), - func(value string) (bool, string) { - return target == value, fmt.Sprintf("Expected '%s' to equal '%s'", value, target) - }, - ) -} - func (self *Assert) WorkingTreeFileCount(expectedCount int) { self.assertWithRetries(func() (bool, string) { actualCount := len(self.gui.Model().Files) @@ -114,13 +73,6 @@ func (self *Assert) HeadCommitMessage(matcher *matcher) { ) } -func (self *Assert) CurrentViewName(expectedViewName string) { - self.assertWithRetries(func() (bool, string) { - actual := self.gui.CurrentContext().GetView().Name() - return actual == expectedViewName, fmt.Sprintf("Expected current view name to be '%s', but got '%s'", expectedViewName, actual) - }) -} - func (self *Assert) CurrentWindowName(expectedWindowName string) { self.assertWithRetries(func() (bool, string) { actual := self.gui.CurrentContext().GetView().Name() @@ -143,21 +95,6 @@ func (self *Assert) InListContext() { }) } -func (self *Assert) CurrentLine(matcher *matcher) { - self.matchString(matcher, "Unexpected selected line.", - func() string { - return self.gui.CurrentContext().GetView().SelectedLine() - }, - ) -} - -func (self *Assert) CurrentLineIdx(expected int) { - self.assertWithRetries(func() (bool, string) { - actual := self.gui.CurrentContext().GetView().SelectedLineIdx() - return expected == actual, fmt.Sprintf("Expected selected line index to be %d, got %d", expected, actual) - }) -} - func (self *Assert) InPrompt() { self.assertWithRetries(func() (bool, string) { currentView := self.gui.CurrentContext().GetView() @@ -200,87 +137,6 @@ func (self *Assert) NotInPopup() { }) } -func (self *Assert) CurrentViewTitle(matcher *matcher) { - self.matchString(matcher, "Unexpected current view title.", - func() string { - return self.gui.CurrentContext().GetView().Title - }, - ) -} - -func (self *Assert) ViewContent(viewName string, matcher *matcher) { - self.matchString(matcher, fmt.Sprintf("Unexpected content in view '%s'.", viewName), - func() string { - return self.gui.View(viewName).Buffer() - }, - ) -} - -// asserts that the given view has lines matching the given matchers. -func (self *Assert) ViewLines(viewName string, matchers ...*matcher) { - self.assertWithRetries(func() (bool, string) { - lines := self.gui.View(viewName).BufferLines() - return len(lines) == len(matchers), fmt.Sprintf("unexpected number of lines in view. Expected %d, got %d", len(matchers), len(lines)) - }) - - for i, matcher := range matchers { - self.matchString(matcher, fmt.Sprintf("Unexpected content in view '%s'.", viewName), - func() string { - return self.gui.View(viewName).BufferLines()[i] - }, - ) - } -} - -func (self *Assert) CurrentViewLines(matchers ...*matcher) { - self.ViewLines(self.gui.CurrentContext().GetView().Name(), matchers...) -} - -// asserts that the given view has lines matching the given matchers. So if three matchers -// are passed, we only check the first three lines of the view. -func (self *Assert) ViewTopLines(viewName string, matchers ...*matcher) { - self.assertWithRetries(func() (bool, string) { - lines := self.gui.View(viewName).BufferLines() - return len(lines) >= len(matchers), fmt.Sprintf("unexpected number of lines in view. Expected at least %d, got %d", len(matchers), len(lines)) - }) - - for i, matcher := range matchers { - self.matchString(matcher, fmt.Sprintf("Unexpected content in view '%s'.", viewName), - func() string { - return self.gui.View(viewName).BufferLines()[i] - }, - ) - } -} - -func (self *Assert) CurrentViewTopLines(matchers ...*matcher) { - self.ViewTopLines(self.gui.CurrentContext().GetView().Name(), matchers...) -} - -func (self *Assert) CurrentViewContent(matcher *matcher) { - self.matchString(matcher, "Unexpected content in current view.", - func() string { - return self.gui.CurrentContext().GetView().Buffer() - }, - ) -} - -func (self *Assert) MainViewContent(matcher *matcher) { - self.matchString(matcher, "Unexpected main view content.", - func() string { - return self.gui.MainView().Buffer() - }, - ) -} - -func (self *Assert) SecondaryViewContent(matcher *matcher) { - self.matchString(matcher, "Unexpected secondary view title.", - func() string { - return self.gui.SecondaryView().Buffer() - }, - ) -} - func (self *Assert) matchString(matcher *matcher, context string, getValue func() string) { self.assertWithRetries(func() (bool, string) { value := getValue() @@ -325,3 +181,35 @@ func (self *Assert) FileSystemPathNotPresent(path string) { return os.IsNotExist(err), fmt.Sprintf("Expected path '%s' to not exist, but it does", path) }) } + +func (self *Assert) CurrentView() *ViewAsserter { + return &ViewAsserter{ + context: "current view", + getView: func() *gocui.View { return self.gui.CurrentContext().GetView() }, + assert: self, + } +} + +func (self *Assert) View(viewName string) *ViewAsserter { + return &ViewAsserter{ + context: fmt.Sprintf("%s view", viewName), + getView: func() *gocui.View { return self.gui.View(viewName) }, + assert: self, + } +} + +func (self *Assert) MainView() *ViewAsserter { + return &ViewAsserter{ + context: "main view", + getView: func() *gocui.View { return self.gui.MainView() }, + assert: self, + } +} + +func (self *Assert) SecondaryView() *ViewAsserter { + return &ViewAsserter{ + context: "secondary view", + getView: func() *gocui.View { return self.gui.SecondaryView() }, + assert: self, + } +} diff --git a/pkg/integration/components/input.go b/pkg/integration/components/input.go index 5905e2087..03aacd758 100644 --- a/pkg/integration/components/input.go +++ b/pkg/integration/components/input.go @@ -103,7 +103,7 @@ func (self *Input) PreviousItem() { func (self *Input) ContinueMerge() { self.Press(self.keys.Universal.CreateRebaseOptionsMenu) - self.assert.CurrentLine(Contains("continue")) + self.assert.CurrentView().SelectedLine(Contains("continue")) self.Confirm() } @@ -166,41 +166,41 @@ func (self *Input) NavigateToListItem(matcher *matcher) { selectedLineIdx := view.SelectedLineIdx() if selectedLineIdx == matchIndex { - self.assert.CurrentLine(matcher) + self.assert.CurrentView().SelectedLine(matcher) return } if selectedLineIdx < matchIndex { for i := selectedLineIdx; i < matchIndex; i++ { self.NextItem() } - self.assert.CurrentLine(matcher) + self.assert.CurrentView().SelectedLine(matcher) return } else { for i := selectedLineIdx; i > matchIndex; i-- { self.PreviousItem() } - self.assert.CurrentLine(matcher) + self.assert.CurrentView().SelectedLine(matcher) return } } func (self *Input) AcceptConfirmation(title *matcher, content *matcher) { self.assert.InConfirm() - self.assert.CurrentViewTitle(title) - self.assert.CurrentViewContent(content) + self.assert.CurrentView().Title(title) + self.assert.CurrentView().Content(content) self.Confirm() } func (self *Input) DenyConfirmation(title *matcher, content *matcher) { self.assert.InConfirm() - self.assert.CurrentViewTitle(title) - self.assert.CurrentViewContent(content) + self.assert.CurrentView().Title(title) + self.assert.CurrentView().Content(content) self.Cancel() } func (self *Input) Prompt(title *matcher, textToType string) { self.assert.InPrompt() - self.assert.CurrentViewTitle(title) + self.assert.CurrentView().Title(title) self.Type(textToType) self.Confirm() } @@ -209,24 +209,24 @@ func (self *Input) Prompt(title *matcher, textToType string) { // item to match the given matcher, then confirm that item. func (self *Input) Typeahead(title *matcher, textToType string, expectedFirstOption *matcher) { self.assert.InPrompt() - self.assert.CurrentViewTitle(title) + self.assert.CurrentView().Title(title) self.Type(textToType) self.Press(self.keys.Universal.TogglePanel) - self.assert.CurrentViewName("suggestions") - self.assert.CurrentLine(expectedFirstOption) + self.assert.CurrentView().Name("suggestions") + self.assert.CurrentView().SelectedLine(expectedFirstOption) self.Confirm() } func (self *Input) Menu(title *matcher, optionToSelect *matcher) { self.assert.InMenu() - self.assert.CurrentViewTitle(title) + self.assert.CurrentView().Title(title) self.NavigateToListItem(optionToSelect) self.Confirm() } func (self *Input) Alert(title *matcher, content *matcher) { self.assert.InListContext() - self.assert.CurrentViewTitle(title) - self.assert.CurrentViewContent(content) + self.assert.CurrentView().Title(title) + self.assert.CurrentView().Content(content) self.Confirm() } diff --git a/pkg/integration/components/matcher.go b/pkg/integration/components/matcher.go index 02b10b714..c8d47933c 100644 --- a/pkg/integration/components/matcher.go +++ b/pkg/integration/components/matcher.go @@ -1,5 +1,11 @@ package components +import ( + "fmt" + "regexp" + "strings" +) + // for making assertions on string values type matcher struct { // e.g. "contains 'foo'" @@ -33,3 +39,43 @@ func (self *matcher) context(prefix string) *matcher { return self } + +func Contains(target string) *matcher { + return NewMatcher( + fmt.Sprintf("contains '%s'", target), + func(value string) (bool, string) { + return strings.Contains(value, target), fmt.Sprintf("Expected '%s' to be found in '%s'", target, value) + }, + ) +} + +func NotContains(target string) *matcher { + return NewMatcher( + fmt.Sprintf("does not contain '%s'", target), + func(value string) (bool, string) { + return !strings.Contains(value, target), fmt.Sprintf("Expected '%s' to NOT be found in '%s'", target, value) + }, + ) +} + +func MatchesRegexp(target string) *matcher { + return NewMatcher( + fmt.Sprintf("matches regular expression '%s'", target), + 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 Equals(target string) *matcher { + return NewMatcher( + fmt.Sprintf("equals '%s'", target), + func(value string) (bool, string) { + return target == value, fmt.Sprintf("Expected '%s' to equal '%s'", value, target) + }, + ) +} diff --git a/pkg/integration/components/view_asserter.go b/pkg/integration/components/view_asserter.go new file mode 100644 index 000000000..65535a598 --- /dev/null +++ b/pkg/integration/components/view_asserter.go @@ -0,0 +1,111 @@ +package components + +import ( + "fmt" + + "github.com/jesseduffield/gocui" +) + +type ViewAsserter struct { + // context is prepended to any error messages e.g. 'context: "current view"' + context string + getView func() *gocui.View + assert *Assert +} + +// asserts that the view has the expected name. This is typically used in tandem with the CurrentView method i.e.; +// assert.CurrentView().Name("commits") to assert that the current view is the commits view. +func (self *ViewAsserter) Name(expected string) *ViewAsserter { + self.assert.assertWithRetries(func() (bool, string) { + actual := self.getView().Name() + return actual == expected, fmt.Sprintf("%s: Expected view name to be '%s', but got '%s'", self.context, expected, actual) + }) + + return self +} + +// asserts that the view has the expected title +func (self *ViewAsserter) Title(expected *matcher) *ViewAsserter { + self.assert.assertWithRetries(func() (bool, string) { + actual := self.getView().Title + return expected.context(fmt.Sprintf("%s title", self.context)).test(actual) + }) + + return self +} + +// asserts that the view has lines matching the given matchers. So if three matchers +// are passed, we only check the first three lines of the view. +// This method is convenient when you have a list of commits but you only want to +// assert on the first couple of commits. +func (self *ViewAsserter) TopLines(matchers ...*matcher) *ViewAsserter { + self.assert.assertWithRetries(func() (bool, string) { + lines := self.getView().BufferLines() + return len(lines) >= len(matchers), fmt.Sprintf("unexpected number of lines in view. Expected at least %d, got %d", len(matchers), len(lines)) + }) + + 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 +} + +// asserts that the view has lines matching the given matchers. One matcher must be passed for each line. +// If you only care about the top n lines, use the TopLines method instead. +func (self *ViewAsserter) Lines(matchers ...*matcher) *ViewAsserter { + self.assert.assertWithRetries(func() (bool, string) { + lines := self.getView().BufferLines() + return len(lines) == len(matchers), fmt.Sprintf("unexpected number of lines in view. Expected %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 +} + +// asserts on the content of the view i.e. the stuff within the view's frame. +func (self *ViewAsserter) Content(matcher *matcher) *ViewAsserter { + self.assert.matchString(matcher, fmt.Sprintf("%s: Unexpected content.", self.context), + func() string { + return self.getView().Buffer() + }, + ) + + return self +} + +// asserts on the selected line of the view +func (self *ViewAsserter) SelectedLine(matcher *matcher) *ViewAsserter { + self.assert.matchString(matcher, fmt.Sprintf("%s: Unexpected selected line.", self.context), + func() string { + return self.getView().SelectedLine() + }, + ) + + return self +} + +// asserts on the index of the selected line. 0 is the first index, representing the line at the top of the view. +func (self *ViewAsserter) SelectedLineIdx(expected int) *ViewAsserter { + self.assert.assertWithRetries(func() (bool, string) { + actual := self.getView().SelectedLineIdx() + return expected == actual, fmt.Sprintf("%s: Expected selected line index to be %d, got %d", self.context, expected, actual) + }) + + return self +} diff --git a/pkg/integration/tests/bisect/basic.go b/pkg/integration/tests/bisect/basic.go index eb44fa2ab..58fcb8896 100644 --- a/pkg/integration/tests/bisect/basic.go +++ b/pkg/integration/tests/bisect/basic.go @@ -34,39 +34,40 @@ var Basic = NewIntegrationTest(NewIntegrationTestArgs{ input.SwitchToCommitsWindow() - assert.CurrentLine(Contains("commit 10")) + assert.CurrentView().SelectedLine(Contains("commit 10")) input.NavigateToListItem(Contains("commit 09")) markCommitAsBad() - assert.ViewContent("information", Contains("bisecting")) + assert.View("information").Content(Contains("bisecting")) - assert.CurrentViewName("commits") - assert.CurrentLine(Contains("<-- bad")) + assert.CurrentView().Name("commits") + assert.CurrentView().SelectedLine(Contains("<-- bad")) input.NavigateToListItem(Contains("commit 02")) markCommitAsGood() // lazygit will land us in the commit between our good and bad commits. - assert.CurrentViewName("commits") - assert.CurrentLine(Contains("commit 05")) - assert.CurrentLine(Contains("<-- current")) + assert.CurrentView(). + Name("commits"). + SelectedLine(Contains("commit 05")). + SelectedLine(Contains("<-- current")) markCommitAsBad() - assert.CurrentViewName("commits") - assert.CurrentLine(Contains("commit 04")) - assert.CurrentLine(Contains("<-- current")) + assert.CurrentView(). + Name("commits"). + SelectedLine(Contains("commit 04")). + SelectedLine(Contains("<-- current")) markCommitAsGood() // commit 5 is the culprit because we marked 4 as good and 5 as bad. input.Alert(Equals("Bisect complete"), MatchesRegexp("(?s)commit 05.*Do you want to reset")) - assert.CurrentViewName("commits") - assert.CurrentViewContent(Contains("commit 04")) - assert.ViewContent("information", NotContains("bisecting")) + assert.CurrentView().Name("commits").Content(Contains("commit 04")) + assert.View("information").Content(NotContains("bisecting")) }, }) diff --git a/pkg/integration/tests/bisect/from_other_branch.go b/pkg/integration/tests/bisect/from_other_branch.go index 8c00f9d31..a523a4e3e 100644 --- a/pkg/integration/tests/bisect/from_other_branch.go +++ b/pkg/integration/tests/bisect/from_other_branch.go @@ -24,13 +24,13 @@ var FromOtherBranch = NewIntegrationTest(NewIntegrationTestArgs{ assert *Assert, keys config.KeybindingConfig, ) { - assert.ViewContent("information", Contains("bisecting")) + assert.View("information").Content(Contains("bisecting")) assert.AtLeastOneCommit() input.SwitchToCommitsWindow() - assert.ViewTopLines("commits", + assert.CurrentView().Name("commits").TopLines( MatchesRegexp(`<-- bad.*commit 08`), MatchesRegexp(`<-- current.*commit 07`), MatchesRegexp(`\?.*commit 06`), @@ -44,11 +44,10 @@ var FromOtherBranch = NewIntegrationTest(NewIntegrationTestArgs{ input.Alert(Equals("Bisect complete"), MatchesRegexp(`(?s)commit 08.*Do you want to reset`)) - assert.ViewContent("information", NotContains("bisecting")) + assert.View("information").Content(NotContains("bisecting")) // back in master branch which just had the one commit - assert.CurrentViewName("commits") - assert.CurrentViewLines( + assert.CurrentView().Name("commits").Lines( Contains("only commit on master"), ) }, diff --git a/pkg/integration/tests/branch/checkout_by_name.go b/pkg/integration/tests/branch/checkout_by_name.go index 652a1c58e..d8af26ee9 100644 --- a/pkg/integration/tests/branch/checkout_by_name.go +++ b/pkg/integration/tests/branch/checkout_by_name.go @@ -19,9 +19,8 @@ var CheckoutByName = NewIntegrationTest(NewIntegrationTestArgs{ }, Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) { input.SwitchToBranchesWindow() - assert.CurrentViewName("localBranches") - assert.CurrentViewLines( + assert.CurrentView().Name("localBranches").Lines( Contains("master"), Contains("@"), ) @@ -33,13 +32,12 @@ var CheckoutByName = NewIntegrationTest(NewIntegrationTestArgs{ input.Alert(Equals("Branch not found"), Equals("Branch not found. Create a new branch named new-branch?")) - assert.CurrentViewName("localBranches") - assert.CurrentViewLines( - MatchesRegexp(`\*.*new-branch`), - Contains("master"), - Contains("@"), - ) - - assert.CurrentLine(Contains("new-branch")) + assert.CurrentView().Name("localBranches"). + Lines( + MatchesRegexp(`\*.*new-branch`), + Contains("master"), + Contains("@"), + ). + SelectedLine(Contains("new-branch")) }, }) diff --git a/pkg/integration/tests/branch/delete.go b/pkg/integration/tests/branch/delete.go index 55e9b6aa5..a873ebaef 100644 --- a/pkg/integration/tests/branch/delete.go +++ b/pkg/integration/tests/branch/delete.go @@ -18,9 +18,8 @@ var Delete = NewIntegrationTest(NewIntegrationTestArgs{ }, Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) { input.SwitchToBranchesWindow() - assert.CurrentViewName("localBranches") - assert.CurrentViewLines( + assert.CurrentView().Name("localBranches").Lines( MatchesRegexp(`\*.*branch-two`), MatchesRegexp(`branch-one`), MatchesRegexp(`master`), @@ -34,11 +33,11 @@ var Delete = NewIntegrationTest(NewIntegrationTestArgs{ input.Press(keys.Universal.Remove) input.AcceptConfirmation(Equals("Delete Branch"), Contains("Are you sure you want to delete the branch 'branch-one'?")) - assert.CurrentViewName("localBranches") - assert.CurrentViewLines( - MatchesRegexp(`\*.*branch-two`), - MatchesRegexp(`master`), - ) - assert.CurrentLineIdx(1) + assert.CurrentView().Name("localBranches"). + Lines( + MatchesRegexp(`\*.*branch-two`), + MatchesRegexp(`master`), + ). + SelectedLineIdx(1) }, }) |