1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-12-01 22:52:01 +02:00

Improve staging panel integration tests

This commit is contained in:
Jesse Duffield
2023-02-24 21:42:27 +11:00
parent 752526c880
commit db011d8e34
18 changed files with 417 additions and 116 deletions

View File

@@ -61,6 +61,11 @@ 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)
},
})

View File

@@ -7,7 +7,6 @@ import (
"github.com/atotto/clipboard"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/types"
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
)
@@ -84,9 +83,7 @@ func (self *TestDriver) Shell() *Shell {
// 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 *TestDriver) navigateToListItem(matcher *matcher) {
self.inListContext()
currentContext := self.gui.CurrentContext().(types.IListContext)
currentContext := self.gui.CurrentContext()
view := currentContext.GetView()
@@ -133,14 +130,6 @@ func (self *TestDriver) navigateToListItem(matcher *matcher) {
}
}
func (self *TestDriver) inListContext() {
self.assertWithRetries(func() (bool, string) {
currentContext := self.gui.CurrentContext()
_, ok := currentContext.(types.IListContext)
return ok, fmt.Sprintf("Expected current context to be a list context, but got %s", currentContext.GetKey())
})
}
// for making assertions on lazygit views
func (self *TestDriver) Views() *Views {
return &Views{t: self}

View File

@@ -30,6 +30,10 @@ func (self *fakeGuiDriver) CurrentContext() types.Context {
return nil
}
func (self *fakeGuiDriver) ContextForView(viewName string) types.Context {
return nil
}
func (self *fakeGuiDriver) Fail(message string) {
self.failureMessage = message
}

View File

@@ -10,9 +10,10 @@ import (
type ViewDriver struct {
// context is prepended to any error messages e.g. 'context: "current view"'
context string
getView func() *gocui.View
t *TestDriver
context string
getView func() *gocui.View
t *TestDriver
getSelectedLinesFn func() ([]string, error)
}
// asserts that the view has the expected title
@@ -50,6 +51,74 @@ func (self *ViewDriver) Lines(matchers ...*matcher) *ViewDriver {
return self.assertLines(matchers...)
}
func (self *ViewDriver) getSelectedLines() ([]string, error) {
if self.getSelectedLinesFn == nil {
view := self.t.gui.View(self.getView().Name())
return []string{view.SelectedLine()}, nil
}
return self.getSelectedLinesFn()
}
func (self *ViewDriver) SelectedLines(matchers ...*matcher) *ViewDriver {
self.t.assertWithRetries(func() (bool, string) {
selectedLines, err := self.getSelectedLines()
if err != nil {
return false, err.Error()
}
selectedContent := strings.Join(selectedLines, "\n")
expectedContent := expectedContentFromMatchers(matchers)
if len(selectedLines) != len(matchers) {
return false, fmt.Sprintf("Expected the following to be selected:\n-----\n%s\n-----\nBut got:\n-----\n%s\n-----", expectedContent, selectedContent)
}
for i, line := range selectedLines {
ok, message := matchers[i].test(line)
if !ok {
return false, fmt.Sprintf("Error: %s. Expected the following to be selected:\n-----\n%s\n-----\nBut got:\n-----\n%s\n-----", message, expectedContent, selectedContent)
}
}
return true, ""
})
return self
}
func (self *ViewDriver) ContainsLines(matchers ...*matcher) *ViewDriver {
self.t.assertWithRetries(func() (bool, string) {
content := self.getView().Buffer()
lines := strings.Split(content, "\n")
for i := 0; i < len(lines)-len(matchers)+1; i++ {
matches := true
for j, matcher := range matchers {
ok, _ := matcher.test(lines[i+j])
if !ok {
matches = false
break
}
}
if matches {
return true, ""
}
}
expectedContent := expectedContentFromMatchers(matchers)
return false, fmt.Sprintf(
"Expected the following to be contained in the staging panel:\n-----\n%s\n-----\nBut got:\n-----\n%s\n-----",
expectedContent,
content,
)
})
return self
}
func (self *ViewDriver) assertLines(matchers ...*matcher) *ViewDriver {
view := self.getView()
@@ -86,9 +155,35 @@ func (self *ViewDriver) Content(matcher *matcher) *ViewDriver {
// asserts on the selected line of the view
func (self *ViewDriver) SelectedLine(matcher *matcher) *ViewDriver {
self.t.assertWithRetries(func() (bool, string) {
selectedLines, err := self.getSelectedLines()
if err != nil {
return false, err.Error()
}
if len(selectedLines) == 0 {
return false, "No line selected. Expected exactly one line to be selected"
} else if len(selectedLines) > 1 {
return false, fmt.Sprintf(
"Multiple lines selected. Expected only a single line to be selected. Selected lines:\n---\n%s\n---\n\nExpected line: %s",
strings.Join(selectedLines, "\n"),
matcher.name(),
)
}
value := selectedLines[0]
return matcher.context(fmt.Sprintf("%s: Unexpected selected line.", self.context)).test(value)
})
self.t.matchString(matcher, fmt.Sprintf("%s: Unexpected selected line.", self.context),
func() string {
return self.getView().SelectedLine()
selectedLines, err := self.getSelectedLines()
if err != nil {
self.t.gui.Fail(err.Error())
return "<failed to obtain selected line>"
}
return selectedLines[0]
},
)
@@ -253,3 +348,9 @@ func (self *ViewDriver) Tap(f func()) *ViewDriver {
return self
}
func expectedContentFromMatchers(matchers []*matcher) string {
return strings.Join(lo.Map(matchers, func(matcher *matcher, _ int) string {
return matcher.name()
}), "\n")
}

View File

@@ -2,8 +2,11 @@ package components
import (
"fmt"
"strings"
"github.com/go-errors/errors"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context"
)
type Views struct {
@@ -36,98 +39,129 @@ func (self *Views) Secondary() *ViewDriver {
}
}
func (self *Views) byName(viewName string) *ViewDriver {
func (self *Views) regularView(viewName string) *ViewDriver {
return self.newStaticViewDriver(viewName, nil)
}
func (self *Views) patchExplorerViewByName(viewName string) *ViewDriver {
return self.newStaticViewDriver(viewName, func() ([]string, error) {
ctx := self.t.gui.ContextForView(viewName).(*context.PatchExplorerContext)
state := ctx.GetState()
if state == nil {
return nil, errors.New("Expected patch explorer to be activated")
}
selectedContent := state.PlainRenderSelected()
// the above method returns a string with a trailing newline so we need to remove that before splitting
selectedLines := strings.Split(strings.TrimSuffix(selectedContent, "\n"), "\n")
return selectedLines, nil
})
}
// 'static' because it'll always refer to the same view, as opposed to the 'main' view which could actually be
// one of several views, or the 'current' view which depends on focus.
func (self *Views) newStaticViewDriver(viewName string, getSelectedLinesFn func() ([]string, error)) *ViewDriver {
return &ViewDriver{
context: fmt.Sprintf("%s view", viewName),
getView: func() *gocui.View { return self.t.gui.View(viewName) },
t: self.t,
context: fmt.Sprintf("%s view", viewName),
getView: func() *gocui.View { return self.t.gui.View(viewName) },
getSelectedLinesFn: getSelectedLinesFn,
t: self.t,
}
}
func (self *Views) Commits() *ViewDriver {
return self.byName("commits")
return self.regularView("commits")
}
func (self *Views) Files() *ViewDriver {
return self.byName("files")
return self.regularView("files")
}
func (self *Views) Status() *ViewDriver {
return self.byName("status")
return self.regularView("status")
}
func (self *Views) Submodules() *ViewDriver {
return self.byName("submodules")
return self.regularView("submodules")
}
func (self *Views) Information() *ViewDriver {
return self.byName("information")
return self.regularView("information")
}
func (self *Views) AppStatus() *ViewDriver {
return self.byName("appStatus")
return self.regularView("appStatus")
}
func (self *Views) Branches() *ViewDriver {
return self.byName("localBranches")
return self.regularView("localBranches")
}
func (self *Views) Remotes() *ViewDriver {
return self.byName("remotes")
return self.regularView("remotes")
}
func (self *Views) RemoteBranches() *ViewDriver {
return self.byName("remoteBranches")
return self.regularView("remoteBranches")
}
func (self *Views) Tags() *ViewDriver {
return self.byName("tags")
return self.regularView("tags")
}
func (self *Views) ReflogCommits() *ViewDriver {
return self.byName("reflogCommits")
return self.regularView("reflogCommits")
}
func (self *Views) SubCommits() *ViewDriver {
return self.byName("subCommits")
return self.regularView("subCommits")
}
func (self *Views) CommitFiles() *ViewDriver {
return self.byName("commitFiles")
return self.regularView("commitFiles")
}
func (self *Views) Stash() *ViewDriver {
return self.byName("stash")
return self.regularView("stash")
}
func (self *Views) Staging() *ViewDriver {
return self.byName("staging")
return self.patchExplorerViewByName("staging")
}
func (self *Views) StagingSecondary() *ViewDriver {
return self.byName("stagingSecondary")
return self.patchExplorerViewByName("stagingSecondary")
}
func (self *Views) PatchBuilding() *ViewDriver {
return self.patchExplorerViewByName("patchBuilding")
}
func (self *Views) PatchBuildingSecondary() *ViewDriver {
// this is not a patch explorer view because you can't actually focus it: it
// just renders content
return self.regularView("patchBuildingSecondary")
}
func (self *Views) Menu() *ViewDriver {
return self.byName("menu")
return self.regularView("menu")
}
func (self *Views) Confirmation() *ViewDriver {
return self.byName("confirmation")
return self.regularView("confirmation")
}
func (self *Views) CommitMessage() *ViewDriver {
return self.byName("commitMessage")
return self.regularView("commitMessage")
}
func (self *Views) Suggestions() *ViewDriver {
return self.byName("suggestions")
return self.regularView("suggestions")
}
func (self *Views) MergeConflicts() *ViewDriver {
return self.byName("mergeConflicts")
return self.regularView("mergeConflicts")
}
func (self *Views) Search() *ViewDriver {
return self.byName("search")
return self.regularView("search")
}