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:
@@ -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)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user