From db011d8e34b9fef0bd01badc4b856ec401831a27 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Fri, 24 Feb 2023 21:42:27 +1100 Subject: [PATCH 1/2] Improve staging panel integration tests --- go.mod | 3 +- go.sum | 12 +- pkg/commands/patch/patch_modifier.go | 8 +- pkg/commands/patch/patch_parser.go | 4 +- pkg/gui/gui_driver.go | 9 ++ pkg/integration/components/matcher.go | 5 + pkg/integration/components/test_driver.go | 13 +-- pkg/integration/components/test_test.go | 4 + pkg/integration/components/viewDriver.go | 109 +++++++++++++++++- pkg/integration/components/views.go | 86 +++++++++----- .../tests/staging/diff_context_change.go | 81 ++++++++++--- .../tests/staging/discard_all_changes.go | 6 +- pkg/integration/tests/staging/stage_hunks.go | 88 ++++++++++++-- pkg/integration/tests/staging/stage_lines.go | 34 ++++-- pkg/integration/tests/staging/stage_ranges.go | 63 +++++++--- pkg/integration/types/types.go | 1 + vendor/github.com/jesseduffield/gocui/view.go | 3 + vendor/modules.txt | 4 +- 18 files changed, 417 insertions(+), 116 deletions(-) diff --git a/go.mod b/go.mod index 8a24e093e..3522a4caf 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/integrii/flaggy v1.4.0 github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d - github.com/jesseduffield/gocui v0.3.1-0.20230219034834-06a1f1e95da5 + github.com/jesseduffield/gocui v0.3.1-0.20230225001450-38a4deaa7f24 github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e @@ -46,7 +46,6 @@ require ( github.com/emirpasic/gods v1.12.0 // indirect github.com/fatih/color v1.9.0 // indirect github.com/gdamore/encoding v1.0.0 // indirect - github.com/gdamore/tcell v1.4.0 // indirect github.com/go-git/gcfg v1.5.0 // indirect github.com/go-git/go-billy/v5 v5.0.0 // indirect github.com/go-logfmt/logfmt v0.5.0 // indirect diff --git a/go.sum b/go.sum index d4c892dd5..6af49803a 100644 --- a/go.sum +++ b/go.sum @@ -34,11 +34,7 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= -github.com/gdamore/tcell v1.4.0 h1:vUnHwJRvcPQa3tzi+0QI4U9JINXYJlOz9yiaiPQ2wMU= -github.com/gdamore/tcell v1.4.0/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0= github.com/gdamore/tcell/v2 v2.4.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU= -github.com/gdamore/tcell/v2 v2.5.4 h1:TGU4tSjD3sCL788vFNeJnTdzpNKIw1H5dgLnJRQVv/k= -github.com/gdamore/tcell/v2 v2.5.4/go.mod h1:dZgRy5v4iMobMEcWNYBtREnDZAT9DYmfqIkrgEMxLyw= github.com/gdamore/tcell/v2 v2.6.0 h1:OKbluoP9VYmJwZwq/iLb4BxwKcwGthaa1YNBJIyCySg= github.com/gdamore/tcell/v2 v2.6.0/go.mod h1:be9omFATkdr0D9qewWW3d+MEvl5dha+Etb5y65J2H8Y= github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= @@ -76,8 +72,8 @@ github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8T github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk= github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE= github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o= -github.com/jesseduffield/gocui v0.3.1-0.20230219034834-06a1f1e95da5 h1:6mrOZa9I1bol14HhVK0tl4w9xvvGKLWPmShPRey1Lxg= -github.com/jesseduffield/gocui v0.3.1-0.20230219034834-06a1f1e95da5/go.mod h1:znJuCDnF2Ph40YZSlBwdX/4GEofnIoWLGdT4mK5zRAU= +github.com/jesseduffield/gocui v0.3.1-0.20230225001450-38a4deaa7f24 h1:1uSYSN8np7ym5IjuGi2hHVp/9GQebLbApfz7vudzaSM= +github.com/jesseduffield/gocui v0.3.1-0.20230225001450-38a4deaa7f24/go.mod h1:znJuCDnF2Ph40YZSlBwdX/4GEofnIoWLGdT4mK5zRAU= github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0= github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo= github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY= @@ -117,7 +113,6 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= @@ -141,7 +136,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -203,7 +197,6 @@ golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -224,7 +217,6 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/pkg/commands/patch/patch_modifier.go b/pkg/commands/patch/patch_modifier.go index 2d060ec18..fe0a896b1 100644 --- a/pkg/commands/patch/patch_modifier.go +++ b/pkg/commands/patch/patch_modifier.go @@ -27,7 +27,9 @@ func GetHunksFromDiff(diff string) []*PatchHunk { var hunkLines []string //nolint:prealloc pastDiffHeader := false - for lineIdx, line := range strings.SplitAfter(diff, "\n") { + lines := strings.SplitAfter(diff, "\n") + + for lineIdx, line := range lines { isHunkHeader := strings.HasPrefix(line, "@@ -") if isHunkHeader { @@ -44,6 +46,10 @@ func GetHunksFromDiff(diff string) []*PatchHunk { continue } + if lineIdx == len(lines)-1 && line == "" { // skip the trailing newline + continue + } + hunkLines = append(hunkLines, line) } diff --git a/pkg/commands/patch/patch_parser.go b/pkg/commands/patch/patch_parser.go index 1fd3c107a..3f191f40a 100644 --- a/pkg/commands/patch/patch_parser.go +++ b/pkg/commands/patch/patch_parser.go @@ -134,7 +134,8 @@ func coloredString(textStyle style.TextStyle, str string, selected bool, include } func parsePatch(patch string) ([]int, []int, []*PatchLine) { - lines := strings.Split(patch, "\n") + // ignore trailing newline. + lines := strings.Split(strings.TrimSuffix(patch, "\n"), "\n") hunkStarts := []int{} stageableLines := []int{} pastFirstHunkHeader := false @@ -179,6 +180,7 @@ func parsePatch(patch string) ([]int, []int, []*PatchLine) { } patchLines[index] = &PatchLine{Kind: lineKind, Content: line} } + return hunkStarts, stageableLines, patchLines } diff --git a/pkg/gui/gui_driver.go b/pkg/gui/gui_driver.go index d9735c2fe..35b6a870d 100644 --- a/pkg/gui/gui_driver.go +++ b/pkg/gui/gui_driver.go @@ -49,6 +49,15 @@ func (self *GuiDriver) CurrentContext() types.Context { return self.gui.c.CurrentContext() } +func (self *GuiDriver) ContextForView(viewName string) types.Context { + context, ok := self.gui.contextForView(viewName) + if !ok { + return nil + } + + return context +} + func (self *GuiDriver) Fail(message string) { currentView := self.gui.g.CurrentView() fullMessage := fmt.Sprintf( diff --git a/pkg/integration/components/matcher.go b/pkg/integration/components/matcher.go index 85a26bbde..ebec084a8 100644 --- a/pkg/integration/components/matcher.go +++ b/pkg/integration/components/matcher.go @@ -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) }, }) diff --git a/pkg/integration/components/test_driver.go b/pkg/integration/components/test_driver.go index 43618f23c..694a59dbf 100644 --- a/pkg/integration/components/test_driver.go +++ b/pkg/integration/components/test_driver.go @@ -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} diff --git a/pkg/integration/components/test_test.go b/pkg/integration/components/test_test.go index 08823f525..c40fc3368 100644 --- a/pkg/integration/components/test_test.go +++ b/pkg/integration/components/test_test.go @@ -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 } diff --git a/pkg/integration/components/viewDriver.go b/pkg/integration/components/viewDriver.go index 04c58fc81..3f0b9726b 100644 --- a/pkg/integration/components/viewDriver.go +++ b/pkg/integration/components/viewDriver.go @@ -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 "" + } + + 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") +} diff --git a/pkg/integration/components/views.go b/pkg/integration/components/views.go index ea5257602..1061d6262 100644 --- a/pkg/integration/components/views.go +++ b/pkg/integration/components/views.go @@ -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") } diff --git a/pkg/integration/tests/staging/diff_context_change.go b/pkg/integration/tests/staging/diff_context_change.go index 8ddf3d37f..a88b99848 100644 --- a/pkg/integration/tests/staging/diff_context_change.go +++ b/pkg/integration/tests/staging/diff_context_change.go @@ -50,31 +50,80 @@ var DiffContextChange = NewIntegrationTest(NewIntegrationTestArgs{ t.Views().Staging(). IsFocused(). - Content(Contains("@@ -1,6 +1,6 @@").DoesNotContain(" 7a")). - SelectedLine(Contains("-3a")). + Press(keys.Main.ToggleSelectHunk). + SelectedLines( + Contains(`@@ -1,6 +1,6 @@`), + Contains(` 1a`), + Contains(` 2a`), + Contains(`-3a`), + Contains(`+3b`), + Contains(` 4a`), + Contains(` 5a`), + Contains(` 6a`), + ). Press(keys.Universal.IncreaseContextInDiffView). - // still on the same line - SelectedLine(Contains("-3a")). - // '7a' is now visible - Content(Contains("@@ -1,7 +1,7 @@").Contains(" 7a")). + SelectedLines( + Contains(`@@ -1,7 +1,7 @@`), + Contains(` 1a`), + Contains(` 2a`), + Contains(`-3a`), + Contains(`+3b`), + Contains(` 4a`), + Contains(` 5a`), + Contains(` 6a`), + Contains(` 7a`), + ). Press(keys.Universal.DecreaseContextInDiffView). - SelectedLine(Contains("-3a")). - Content(Contains("@@ -1,6 +1,6 @@").DoesNotContain(" 7a")). + SelectedLines( + Contains(`@@ -1,6 +1,6 @@`), + Contains(` 1a`), + Contains(` 2a`), + Contains(`-3a`), + Contains(`+3b`), + Contains(` 4a`), + Contains(` 5a`), + Contains(` 6a`), + ). Press(keys.Universal.DecreaseContextInDiffView). - SelectedLine(Contains("-3a")). - Content(Contains("@@ -1,5 +1,5 @@").DoesNotContain(" 6a")). + SelectedLines( + Contains(`@@ -1,5 +1,5 @@`), + Contains(` 1a`), + Contains(` 2a`), + Contains(`-3a`), + Contains(`+3b`), + Contains(` 4a`), + Contains(` 5a`), + ). Press(keys.Universal.DecreaseContextInDiffView). - // arguably we should still be on -3a, but at the moment the logic puts us on on +3b - SelectedLine(Contains("+3b")). - Content(Contains("@@ -2,3 +2,3 @@").DoesNotContain(" 5a")). + SelectedLines( + Contains(`@@ -2,3 +2,3 @@`), + Contains(` 2a`), + Contains(`-3a`), + Contains(`+3b`), + Contains(` 4a`), + ). PressPrimaryAction(). - Content(DoesNotContain("+3b")). Press(keys.Universal.TogglePanel) t.Views().StagingSecondary(). IsFocused(). - Content(Contains("@@ -3,2 +3,3 @@\n 3a\n+3b\n 4a")). + Press(keys.Main.ToggleSelectHunk). + SelectedLines( + Contains(`@@ -2,3 +2,3 @@`), + Contains(` 2a`), + Contains(`-3a`), + Contains(`+3b`), + Contains(` 4a`), + ). Press(keys.Universal.IncreaseContextInDiffView). - Content(Contains("@@ -2,4 +2,5 @@\n 2a\n 3a\n+3b\n 4a\n 5a")) + SelectedLines( + Contains(`@@ -1,5 +1,5 @@`), + Contains(` 1a`), + Contains(` 2a`), + Contains(`-3a`), + Contains(`+3b`), + Contains(` 4a`), + Contains(` 5a`), + ) }, }) diff --git a/pkg/integration/tests/staging/discard_all_changes.go b/pkg/integration/tests/staging/discard_all_changes.go index 7d27e7ec2..48cd0b62d 100644 --- a/pkg/integration/tests/staging/discard_all_changes.go +++ b/pkg/integration/tests/staging/discard_all_changes.go @@ -29,13 +29,13 @@ var DiscardAllChanges = NewIntegrationTest(NewIntegrationTestArgs{ t.Views().Staging(). IsFocused(). - SelectedLine(Contains("+three")). + SelectedLines(Contains("+three")). // discard the line Press(keys.Universal.Remove). Tap(func() { t.Actions().ConfirmDiscardLines() }). - SelectedLine(Contains("+four")). + SelectedLines(Contains("+four")). // discard the other line Press(keys.Universal.Remove). Tap(func() { @@ -49,6 +49,6 @@ var DiscardAllChanges = NewIntegrationTest(NewIntegrationTestArgs{ }). // assert we are still in the staging panel, but now looking at the changes of the other file IsFocused(). - SelectedLine(Contains("+3")) + SelectedLines(Contains("+3")) }, }) diff --git a/pkg/integration/tests/staging/stage_hunks.go b/pkg/integration/tests/staging/stage_hunks.go index b79819108..c2f85661a 100644 --- a/pkg/integration/tests/staging/stage_hunks.go +++ b/pkg/integration/tests/staging/stage_hunks.go @@ -50,42 +50,108 @@ var StageHunks = NewIntegrationTest(NewIntegrationTestArgs{ t.Views().Staging(). IsFocused(). - SelectedLine(Contains("-3a")). + SelectedLines( + Contains("-3a"), + ). Press(keys.Universal.NextBlock). - SelectedLine(Contains("-13a")). + SelectedLines( + Contains("-13a"), + ). Press(keys.Main.ToggleSelectHunk). + SelectedLines( + Contains("@@ -10,6 +10,6 @@"), + Contains(" 10a"), + Contains(" 11a"), + Contains(" 12a"), + Contains("-13a"), + Contains("+13b"), + Contains(" 14a"), + Contains(" 15a"), + Contains(`\ No newline at end of file`), + ). // when in hunk mode, pressing up/down moves us up/down by a hunk SelectPreviousItem(). - SelectedLine(Contains("-3a")). + SelectedLines( + Contains(`@@ -1,6 +1,6 @@`), + Contains(` 1a`), + Contains(` 2a`), + Contains(`-3a`), + Contains(`+3b`), + Contains(` 4a`), + Contains(` 5a`), + Contains(` 6a`), + ). SelectNextItem(). - SelectedLine(Contains("-13a")). + SelectedLines( + Contains("@@ -10,6 +10,6 @@"), + Contains(" 10a"), + Contains(" 11a"), + Contains(" 12a"), + Contains("-13a"), + Contains("+13b"), + Contains(" 14a"), + Contains(" 15a"), + Contains(`\ No newline at end of file`), + ). // stage the second hunk PressPrimaryAction(). - Content(Contains("-3a\n+3b")). + ContainsLines( + Contains("-3a"), + Contains("+3b"), + ). Tap(func() { t.Views().StagingSecondary(). - Content(Contains("-13a\n+13b")) + ContainsLines( + Contains("-13a"), + Contains("+13b"), + ) }). Press(keys.Universal.TogglePanel) t.Views().StagingSecondary(). IsFocused(). - SelectedLine(Contains("-13a")). // after toggling panel, we're back to only having selected a single line + SelectedLines( + Contains("-13a"), + ). PressPrimaryAction(). - SelectedLine(Contains("+13b")). + SelectedLines( + Contains("+13b"), + ). PressPrimaryAction(). IsEmpty() t.Views().Staging(). IsFocused(). - SelectedLine(Contains("-3a")). + SelectedLines( + Contains("-3a"), + ). Press(keys.Main.ToggleSelectHunk). + SelectedLines( + Contains(`@@ -1,6 +1,6 @@`), + Contains(` 1a`), + Contains(` 2a`), + Contains(`-3a`), + Contains(`+3b`), + Contains(` 4a`), + Contains(` 5a`), + Contains(` 6a`), + ). Press(keys.Universal.Remove). Tap(func() { t.Actions().ConfirmDiscardLines() }). - SelectedLine(Contains("-13a")). - Content(DoesNotContain("-3a").DoesNotContain("+3b")) + Content(DoesNotContain("-3a").DoesNotContain("+3b")). + SelectedLines( + Contains("@@ -10,6 +10,6 @@"), + Contains(" 10a"), + Contains(" 11a"), + Contains(" 12a"), + Contains("-13a"), + Contains("+13b"), + Contains(" 14a"), + Contains(" 15a"), + Contains(`\ No newline at end of file`), + ) }, }) diff --git a/pkg/integration/tests/staging/stage_lines.go b/pkg/integration/tests/staging/stage_lines.go index f1777fb36..f517440b8 100644 --- a/pkg/integration/tests/staging/stage_lines.go +++ b/pkg/integration/tests/staging/stage_lines.go @@ -26,16 +26,18 @@ var StageLines = NewIntegrationTest(NewIntegrationTestArgs{ t.Views().Staging(). IsFocused(). - SelectedLine(Contains("+three")). + SelectedLines(Contains("+three")). // stage 'three' PressPrimaryAction(). // 'three' moves over to the staging secondary panel Content(DoesNotContain("+three")). Tap(func() { t.Views().StagingSecondary(). - Content(Contains("+three")) + ContainsLines( + Contains("+three"), + ) }). - SelectedLine(Contains("+four")). + SelectedLines(Contains("+four")). // stage 'four' PressPrimaryAction(). // nothing left in our staging panel @@ -45,15 +47,20 @@ var StageLines = NewIntegrationTest(NewIntegrationTestArgs{ // do the same thing as above, moving the lines back to the staging panel t.Views().StagingSecondary(). IsFocused(). - Content(Contains("+three\n+four")). - SelectedLine(Contains("+three")). + ContainsLines( + Contains("+three"), + Contains("+four"), + ). + SelectedLines(Contains("+three")). PressPrimaryAction(). Content(DoesNotContain("+three")). Tap(func() { t.Views().Staging(). - Content(Contains("+three")) + ContainsLines( + Contains("+three"), + ) }). - SelectedLine(Contains("+four")). + SelectedLines(Contains("+four")). // pressing 'remove' has the same effect as pressing space when in the staging secondary panel Press(keys.Universal.Remove). IsEmpty() @@ -61,8 +68,11 @@ var StageLines = NewIntegrationTest(NewIntegrationTestArgs{ // stage one line and then manually toggle to the staging secondary panel t.Views().Staging(). IsFocused(). - Content(Contains("+three\n+four")). - SelectedLine(Contains("+three")). + ContainsLines( + Contains("+three"), + Contains("+four"), + ). + SelectedLines(Contains("+three")). PressPrimaryAction(). Content(DoesNotContain("+three")). Tap(func() { @@ -77,7 +87,7 @@ var StageLines = NewIntegrationTest(NewIntegrationTestArgs{ Press(keys.Universal.TogglePanel) t.Views().Staging(). - SelectedLine(Contains("+four")). + SelectedLines(Contains("+four")). // discard the line Press(keys.Universal.Remove). Tap(func() { @@ -90,7 +100,9 @@ var StageLines = NewIntegrationTest(NewIntegrationTestArgs{ t.Views().StagingSecondary(). IsFocused(). - Content(Contains("+three\n")). + ContainsLines( + Contains("+three"), + ). // return to file PressEscape() diff --git a/pkg/integration/tests/staging/stage_ranges.go b/pkg/integration/tests/staging/stage_ranges.go index cd6d8021f..ea886ccda 100644 --- a/pkg/integration/tests/staging/stage_ranges.go +++ b/pkg/integration/tests/staging/stage_ranges.go @@ -26,50 +26,81 @@ var StageRanges = NewIntegrationTest(NewIntegrationTestArgs{ t.Views().Staging(). IsFocused(). - SelectedLine(Contains("+three")). + SelectedLines( + Contains("+three"), + ). Press(keys.Main.ToggleDragSelect). - SelectNextItem(). - SelectedLine(Contains("+four")). - SelectNextItem(). - SelectedLine(Contains("+five")). + NavigateToListItem(Contains("+five")). + SelectedLines( + Contains("+three"), + Contains("+four"), + Contains("+five"), + ). // stage the three lines we've just selected PressPrimaryAction(). - Content(Contains(" five\n+six")). + SelectedLines( + Contains("+six"), + ). + ContainsLines( + Contains(" five"), + Contains("+six"), + ). Tap(func() { t.Views().StagingSecondary(). - Content(Contains("+three\n+four\n+five")) + ContainsLines( + Contains("+three"), + Contains("+four"), + Contains("+five"), + ) }). Press(keys.Universal.TogglePanel) t.Views().StagingSecondary(). IsFocused(). - SelectedLine(Contains("+three")). + SelectedLines( + Contains("+three"), + ). Press(keys.Main.ToggleDragSelect). - SelectNextItem(). - SelectedLine(Contains("+four")). - SelectNextItem(). - SelectedLine(Contains("+five")). + NavigateToListItem(Contains("+five")). + SelectedLines( + Contains("+three"), + Contains("+four"), + Contains("+five"), + ). // unstage the three selected lines PressPrimaryAction(). // nothing left in our staging secondary panel IsEmpty(). Tap(func() { t.Views().Staging(). - Content(Contains("+three\n+four\n+five\n+six")) + ContainsLines( + Contains("+three"), + Contains("+four"), + Contains("+five"), + Contains("+six"), + ) }) t.Views().Staging(). IsFocused(). // coincidentally we land at '+four' here. Maybe we should instead land // at '+three'? given it's at the start of the hunk? - SelectedLine(Contains("+four")). + SelectedLines( + Contains("+four"), + ). Press(keys.Main.ToggleDragSelect). SelectNextItem(). - SelectedLine(Contains("+five")). + SelectedLines( + Contains("+four"), + Contains("+five"), + ). Press(keys.Universal.Remove). Tap(func() { t.Actions().ConfirmDiscardLines() }). - Content(Contains("+three\n+six")) + ContainsLines( + Contains("+three"), + Contains("+six"), + ) }, }) diff --git a/pkg/integration/types/types.go b/pkg/integration/types/types.go index 5326054d2..a26ac67af 100644 --- a/pkg/integration/types/types.go +++ b/pkg/integration/types/types.go @@ -20,6 +20,7 @@ type GuiDriver interface { PressKey(string) Keys() config.KeybindingConfig CurrentContext() types.Context + ContextForView(viewName string) types.Context Fail(message string) // These two log methods are for the sake of debugging while testing. There's no need to actually // commit any logging. diff --git a/vendor/github.com/jesseduffield/gocui/view.go b/vendor/github.com/jesseduffield/gocui/view.go index 13826313f..c4df63ad7 100644 --- a/vendor/github.com/jesseduffield/gocui/view.go +++ b/vendor/github.com/jesseduffield/gocui/view.go @@ -1302,6 +1302,9 @@ func (v *View) SelectedLineIdx() int { // expected to only be used in tests func (v *View) SelectedLine() string { + v.writeMutex.Lock() + defer v.writeMutex.Unlock() + if len(v.lines) == 0 { return "" } diff --git a/vendor/modules.txt b/vendor/modules.txt index feb7fcd98..d98f6d982 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -39,8 +39,6 @@ github.com/fsnotify/fsnotify # github.com/gdamore/encoding v1.0.0 ## explicit; go 1.9 github.com/gdamore/encoding -# github.com/gdamore/tcell v1.4.0 -## explicit; go 1.12 # github.com/gdamore/tcell/v2 v2.6.0 ## explicit; go 1.12 github.com/gdamore/tcell/v2 @@ -174,7 +172,7 @@ github.com/jesseduffield/go-git/v5/utils/merkletrie/filesystem github.com/jesseduffield/go-git/v5/utils/merkletrie/index github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame github.com/jesseduffield/go-git/v5/utils/merkletrie/noder -# github.com/jesseduffield/gocui v0.3.1-0.20230219034834-06a1f1e95da5 +# github.com/jesseduffield/gocui v0.3.1-0.20230225001450-38a4deaa7f24 ## explicit; go 1.12 github.com/jesseduffield/gocui # github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 From 037e9572825eb1e506d9f503831622fbc9e6edbf Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sat, 25 Feb 2023 11:39:24 +1100 Subject: [PATCH 2/2] fix PullMergeConflict integration test --- pkg/gui/controllers/helpers/merge_and_rebase_helper.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/gui/controllers/helpers/merge_and_rebase_helper.go b/pkg/gui/controllers/helpers/merge_and_rebase_helper.go index 21c02d6f4..b70f7b2ed 100644 --- a/pkg/gui/controllers/helpers/merge_and_rebase_helper.go +++ b/pkg/gui/controllers/helpers/merge_and_rebase_helper.go @@ -119,6 +119,7 @@ var conflictStrings = []string{ "When you have resolved this problem", "fix conflicts", "Resolve all conflicts manually", + "Merge conflict in file", } func isMergeConflictErr(errStr string) bool {