migrate patch building tests
This commit is contained in:
@@ -38,3 +38,9 @@ func (self *Actions) ConfirmDiscardLines() {
|
||||
Content(Contains("Are you sure you want to delete the selected lines")).
|
||||
Confirm()
|
||||
}
|
||||
|
||||
func (self *Actions) SelectPatchOption(matcher *Matcher) {
|
||||
self.t.GlobalPress(self.t.keys.Universal.CreatePatchOptionsMenu)
|
||||
|
||||
self.t.ExpectPopup().Menu().Title(Equals("Patch Options")).Select(matcher).Confirm()
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ func (self *AlertDriver) getViewDriver() *ViewDriver {
|
||||
}
|
||||
|
||||
// asserts that the alert view has the expected title
|
||||
func (self *AlertDriver) Title(expected *matcher) *AlertDriver {
|
||||
func (self *AlertDriver) Title(expected *Matcher) *AlertDriver {
|
||||
self.getViewDriver().Title(expected)
|
||||
|
||||
self.hasCheckedTitle = true
|
||||
@@ -20,7 +20,7 @@ func (self *AlertDriver) Title(expected *matcher) *AlertDriver {
|
||||
}
|
||||
|
||||
// asserts that the alert view has the expected content
|
||||
func (self *AlertDriver) Content(expected *matcher) *AlertDriver {
|
||||
func (self *AlertDriver) Content(expected *Matcher) *AlertDriver {
|
||||
self.getViewDriver().Content(expected)
|
||||
|
||||
self.hasCheckedContent = true
|
||||
|
||||
@@ -13,7 +13,7 @@ type assertionHelper struct {
|
||||
// milliseconds we'll wait when an assertion fails.
|
||||
var retryWaitTimes = []int{0, 1, 1, 1, 1, 1, 5, 10, 20, 40, 100, 200, 500, 1000, 2000, 4000}
|
||||
|
||||
func (self *assertionHelper) matchString(matcher *matcher, context string, getValue func() string) {
|
||||
func (self *assertionHelper) matchString(matcher *Matcher, context string, getValue func() string) {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
value := getValue()
|
||||
return matcher.context(context).test(value)
|
||||
|
||||
@@ -9,7 +9,7 @@ func (self *CommitMessagePanelDriver) getViewDriver() *ViewDriver {
|
||||
}
|
||||
|
||||
// asserts on the text initially present in the prompt
|
||||
func (self *CommitMessagePanelDriver) InitialText(expected *matcher) *CommitMessagePanelDriver {
|
||||
func (self *CommitMessagePanelDriver) InitialText(expected *Matcher) *CommitMessagePanelDriver {
|
||||
self.getViewDriver().Content(expected)
|
||||
|
||||
return self
|
||||
|
||||
@@ -11,7 +11,7 @@ func (self *ConfirmationDriver) getViewDriver() *ViewDriver {
|
||||
}
|
||||
|
||||
// asserts that the confirmation view has the expected title
|
||||
func (self *ConfirmationDriver) Title(expected *matcher) *ConfirmationDriver {
|
||||
func (self *ConfirmationDriver) Title(expected *Matcher) *ConfirmationDriver {
|
||||
self.getViewDriver().Title(expected)
|
||||
|
||||
self.hasCheckedTitle = true
|
||||
@@ -20,7 +20,7 @@ func (self *ConfirmationDriver) Title(expected *matcher) *ConfirmationDriver {
|
||||
}
|
||||
|
||||
// asserts that the confirmation view has the expected content
|
||||
func (self *ConfirmationDriver) Content(expected *matcher) *ConfirmationDriver {
|
||||
func (self *ConfirmationDriver) Content(expected *Matcher) *ConfirmationDriver {
|
||||
self.getViewDriver().Content(expected)
|
||||
|
||||
self.hasCheckedContent = true
|
||||
|
||||
@@ -26,7 +26,7 @@ func (self *FileSystem) PathNotPresent(path string) {
|
||||
}
|
||||
|
||||
// Asserts that the file at the given path has the given content
|
||||
func (self *FileSystem) FileContent(path string, matcher *matcher) {
|
||||
func (self *FileSystem) FileContent(path string, matcher *Matcher) {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
_, err := os.Stat(path)
|
||||
if os.IsNotExist(err) {
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
// for making assertions on string values
|
||||
type matcher struct {
|
||||
type Matcher struct {
|
||||
rules []matcherRule
|
||||
|
||||
// this is printed when there's an error so that it's clear what the context of the assertion is
|
||||
@@ -24,12 +24,12 @@ type matcherRule struct {
|
||||
testFn func(string) (bool, string)
|
||||
}
|
||||
|
||||
func NewMatcher(name string, testFn func(string) (bool, string)) *matcher {
|
||||
func NewMatcher(name string, testFn func(string) (bool, string)) *Matcher {
|
||||
rules := []matcherRule{{name: name, testFn: testFn}}
|
||||
return &matcher{rules: rules}
|
||||
return &Matcher{rules: rules}
|
||||
}
|
||||
|
||||
func (self *matcher) name() string {
|
||||
func (self *Matcher) name() string {
|
||||
if len(self.rules) == 0 {
|
||||
return "anything"
|
||||
}
|
||||
@@ -40,7 +40,7 @@ func (self *matcher) name() string {
|
||||
)
|
||||
}
|
||||
|
||||
func (self *matcher) test(value string) (bool, string) {
|
||||
func (self *Matcher) test(value string) (bool, string) {
|
||||
for _, rule := range self.rules {
|
||||
ok, message := rule.testFn(value)
|
||||
if ok {
|
||||
@@ -57,7 +57,7 @@ func (self *matcher) test(value string) (bool, string) {
|
||||
return true, ""
|
||||
}
|
||||
|
||||
func (self *matcher) Contains(target string) *matcher {
|
||||
func (self *Matcher) Contains(target string) *Matcher {
|
||||
return self.appendRule(matcherRule{
|
||||
name: fmt.Sprintf("contains '%s'", target),
|
||||
testFn: func(value string) (bool, string) {
|
||||
@@ -71,7 +71,7 @@ func (self *matcher) Contains(target string) *matcher {
|
||||
})
|
||||
}
|
||||
|
||||
func (self *matcher) DoesNotContain(target string) *matcher {
|
||||
func (self *Matcher) DoesNotContain(target string) *Matcher {
|
||||
return self.appendRule(matcherRule{
|
||||
name: fmt.Sprintf("does not contain '%s'", target),
|
||||
testFn: func(value string) (bool, string) {
|
||||
@@ -80,7 +80,7 @@ func (self *matcher) DoesNotContain(target string) *matcher {
|
||||
})
|
||||
}
|
||||
|
||||
func (self *matcher) MatchesRegexp(target string) *matcher {
|
||||
func (self *Matcher) MatchesRegexp(target string) *Matcher {
|
||||
return self.appendRule(matcherRule{
|
||||
name: fmt.Sprintf("matches regular expression '%s'", target),
|
||||
testFn: func(value string) (bool, string) {
|
||||
@@ -93,7 +93,7 @@ func (self *matcher) MatchesRegexp(target string) *matcher {
|
||||
})
|
||||
}
|
||||
|
||||
func (self *matcher) Equals(target string) *matcher {
|
||||
func (self *Matcher) Equals(target string) *Matcher {
|
||||
return self.appendRule(matcherRule{
|
||||
name: fmt.Sprintf("equals '%s'", target),
|
||||
testFn: func(value string) (bool, string) {
|
||||
@@ -106,7 +106,7 @@ 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 {
|
||||
func (self *Matcher) IsSelected() *Matcher {
|
||||
return self.appendRule(matcherRule{
|
||||
name: IS_SELECTED_RULE_NAME,
|
||||
testFn: func(value string) (bool, string) {
|
||||
@@ -115,7 +115,7 @@ func (self *matcher) IsSelected() *matcher {
|
||||
})
|
||||
}
|
||||
|
||||
func (self *matcher) appendRule(rule matcherRule) *matcher {
|
||||
func (self *Matcher) appendRule(rule matcherRule) *Matcher {
|
||||
self.rules = append(self.rules, rule)
|
||||
|
||||
return self
|
||||
@@ -123,39 +123,43 @@ func (self *matcher) appendRule(rule matcherRule) *matcher {
|
||||
|
||||
// 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 {
|
||||
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 })
|
||||
func (self *Matcher) checkIsSelected() (bool, *Matcher) {
|
||||
// copying into a new matcher in case we want to re-use the original later
|
||||
newMatcher := &Matcher{}
|
||||
*newMatcher = *self
|
||||
|
||||
self.rules = lo.Filter(self.rules, func(rule matcherRule, _ int) bool { return rule.name != IS_SELECTED_RULE_NAME })
|
||||
check := lo.ContainsBy(newMatcher.rules, func(rule matcherRule) bool { return rule.name == IS_SELECTED_RULE_NAME })
|
||||
|
||||
return check, self
|
||||
newMatcher.rules = lo.Filter(newMatcher.rules, func(rule matcherRule, _ int) bool { return rule.name != IS_SELECTED_RULE_NAME })
|
||||
|
||||
return check, newMatcher
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return &matcher{}
|
||||
func Anything() *Matcher {
|
||||
return &Matcher{}
|
||||
}
|
||||
|
||||
func Contains(target string) *matcher {
|
||||
func Contains(target string) *Matcher {
|
||||
return Anything().Contains(target)
|
||||
}
|
||||
|
||||
func DoesNotContain(target string) *matcher {
|
||||
func DoesNotContain(target string) *Matcher {
|
||||
return Anything().DoesNotContain(target)
|
||||
}
|
||||
|
||||
func MatchesRegexp(target string) *matcher {
|
||||
func MatchesRegexp(target string) *Matcher {
|
||||
return Anything().MatchesRegexp(target)
|
||||
}
|
||||
|
||||
func Equals(target string) *matcher {
|
||||
func Equals(target string) *Matcher {
|
||||
return Anything().Equals(target)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ func (self *MenuDriver) getViewDriver() *ViewDriver {
|
||||
}
|
||||
|
||||
// asserts that the popup has the expected title
|
||||
func (self *MenuDriver) Title(expected *matcher) *MenuDriver {
|
||||
func (self *MenuDriver) Title(expected *Matcher) *MenuDriver {
|
||||
self.getViewDriver().Title(expected)
|
||||
|
||||
self.hasCheckedTitle = true
|
||||
@@ -30,19 +30,19 @@ func (self *MenuDriver) Cancel() {
|
||||
self.getViewDriver().PressEscape()
|
||||
}
|
||||
|
||||
func (self *MenuDriver) Select(option *matcher) *MenuDriver {
|
||||
func (self *MenuDriver) Select(option *Matcher) *MenuDriver {
|
||||
self.getViewDriver().NavigateToListItem(option)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *MenuDriver) Lines(matchers ...*matcher) *MenuDriver {
|
||||
func (self *MenuDriver) Lines(matchers ...*Matcher) *MenuDriver {
|
||||
self.getViewDriver().Lines(matchers...)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *MenuDriver) TopLines(matchers ...*matcher) *MenuDriver {
|
||||
func (self *MenuDriver) TopLines(matchers ...*Matcher) *MenuDriver {
|
||||
self.getViewDriver().TopLines(matchers...)
|
||||
|
||||
return self
|
||||
|
||||
@@ -10,7 +10,7 @@ func (self *PromptDriver) getViewDriver() *ViewDriver {
|
||||
}
|
||||
|
||||
// asserts that the popup has the expected title
|
||||
func (self *PromptDriver) Title(expected *matcher) *PromptDriver {
|
||||
func (self *PromptDriver) Title(expected *Matcher) *PromptDriver {
|
||||
self.getViewDriver().Title(expected)
|
||||
|
||||
self.hasCheckedTitle = true
|
||||
@@ -19,7 +19,7 @@ func (self *PromptDriver) Title(expected *matcher) *PromptDriver {
|
||||
}
|
||||
|
||||
// asserts on the text initially present in the prompt
|
||||
func (self *PromptDriver) InitialText(expected *matcher) *PromptDriver {
|
||||
func (self *PromptDriver) InitialText(expected *Matcher) *PromptDriver {
|
||||
self.getViewDriver().Content(expected)
|
||||
|
||||
return self
|
||||
@@ -55,13 +55,13 @@ func (self *PromptDriver) checkNecessaryChecksCompleted() {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *PromptDriver) SuggestionLines(matchers ...*matcher) *PromptDriver {
|
||||
func (self *PromptDriver) SuggestionLines(matchers ...*Matcher) *PromptDriver {
|
||||
self.t.Views().Suggestions().Lines(matchers...)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *PromptDriver) SuggestionTopLines(matchers ...*matcher) *PromptDriver {
|
||||
func (self *PromptDriver) SuggestionTopLines(matchers ...*Matcher) *PromptDriver {
|
||||
self.t.Views().Suggestions().TopLines(matchers...)
|
||||
|
||||
return self
|
||||
@@ -75,7 +75,7 @@ func (self *PromptDriver) ConfirmFirstSuggestion() {
|
||||
PressEnter()
|
||||
}
|
||||
|
||||
func (self *PromptDriver) ConfirmSuggestion(matcher *matcher) {
|
||||
func (self *PromptDriver) ConfirmSuggestion(matcher *Matcher) {
|
||||
self.t.press(self.t.keys.Universal.TogglePanel)
|
||||
self.t.Views().Suggestions().
|
||||
IsFocused().
|
||||
|
||||
@@ -12,7 +12,7 @@ func (self *SearchDriver) getViewDriver() *ViewDriver {
|
||||
}
|
||||
|
||||
// asserts on the text initially present in the prompt
|
||||
func (self *SearchDriver) InitialText(expected *matcher) *SearchDriver {
|
||||
func (self *SearchDriver) InitialText(expected *Matcher) *SearchDriver {
|
||||
self.getViewDriver().Content(expected)
|
||||
|
||||
return self
|
||||
|
||||
@@ -136,6 +136,10 @@ func (self *Shell) EmptyCommit(message string) *Shell {
|
||||
return self.RunCommand(fmt.Sprintf("git commit --allow-empty -m \"%s\"", message))
|
||||
}
|
||||
|
||||
func (self *Shell) Revert(ref string) *Shell {
|
||||
return self.RunCommand(fmt.Sprintf("git revert %s", ref))
|
||||
}
|
||||
|
||||
func (self *Shell) CreateLightweightTag(name string, ref string) *Shell {
|
||||
return self.RunCommand(fmt.Sprintf("git tag %s %s", name, ref))
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package components
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/atotto/clipboard"
|
||||
@@ -71,65 +70,6 @@ func (self *TestDriver) Shell() *Shell {
|
||||
return self.shell
|
||||
}
|
||||
|
||||
// this will look for a list item in the current panel and if it finds it, it will
|
||||
// enter the keypresses required to navigate to it.
|
||||
// The test will fail if:
|
||||
// - the user is not in a list item
|
||||
// - no list item is found containing the given text
|
||||
// - multiple list items are found containing the given text in the initial page of items
|
||||
//
|
||||
// NOTE: this currently assumes that ViewBufferLines returns all the lines that can be accessed.
|
||||
// If this changes in future, we'll need to update this code to first attempt to find the item
|
||||
// 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) {
|
||||
currentContext := self.gui.CurrentContext()
|
||||
|
||||
view := currentContext.GetView()
|
||||
|
||||
var matchIndex int
|
||||
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
matchIndex = -1
|
||||
var matches []string
|
||||
lines := view.ViewBufferLines()
|
||||
// first we look for a duplicate on the current screen. We won't bother looking beyond that though.
|
||||
for i, line := range lines {
|
||||
ok, _ := matcher.test(line)
|
||||
if ok {
|
||||
matches = append(matches, line)
|
||||
matchIndex = i
|
||||
}
|
||||
}
|
||||
if len(matches) > 1 {
|
||||
return false, fmt.Sprintf("Found %d matches for `%s`, expected only a single match. Matching lines:\n%s", len(matches), matcher.name(), strings.Join(matches, "\n"))
|
||||
} else if len(matches) == 0 {
|
||||
return false, fmt.Sprintf("Could not find item matching: %s. Lines:\n%s", matcher.name(), strings.Join(lines, "\n"))
|
||||
} else {
|
||||
return true, ""
|
||||
}
|
||||
})
|
||||
|
||||
selectedLineIdx := view.SelectedLineIdx()
|
||||
if selectedLineIdx == matchIndex {
|
||||
self.Views().current().SelectedLine(matcher)
|
||||
return
|
||||
}
|
||||
if selectedLineIdx < matchIndex {
|
||||
for i := selectedLineIdx; i < matchIndex; i++ {
|
||||
self.Views().current().SelectNextItem()
|
||||
}
|
||||
self.Views().current().SelectedLine(matcher)
|
||||
return
|
||||
} else {
|
||||
for i := selectedLineIdx; i > matchIndex; i-- {
|
||||
self.Views().current().SelectPreviousItem()
|
||||
}
|
||||
self.Views().current().SelectedLine(matcher)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// for making assertions on lazygit views
|
||||
func (self *TestDriver) Views() *Views {
|
||||
return &Views{t: self}
|
||||
@@ -140,11 +80,11 @@ func (self *TestDriver) ExpectPopup() *Popup {
|
||||
return &Popup{t: self}
|
||||
}
|
||||
|
||||
func (self *TestDriver) ExpectToast(matcher *matcher) {
|
||||
func (self *TestDriver) ExpectToast(matcher *Matcher) {
|
||||
self.Views().AppStatus().Content(matcher)
|
||||
}
|
||||
|
||||
func (self *TestDriver) ExpectClipboard(matcher *matcher) {
|
||||
func (self *TestDriver) ExpectClipboard(matcher *Matcher) {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
text, err := clipboard.ReadAll()
|
||||
if err != nil {
|
||||
|
||||
@@ -10,45 +10,12 @@ import (
|
||||
|
||||
type ViewDriver struct {
|
||||
// context is prepended to any error messages e.g. 'context: "current view"'
|
||||
context string
|
||||
getView func() *gocui.View
|
||||
t *TestDriver
|
||||
getSelectedLinesFn func() ([]string, error)
|
||||
}
|
||||
|
||||
// asserts that the view has the expected title
|
||||
func (self *ViewDriver) Title(expected *matcher) *ViewDriver {
|
||||
self.t.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 *ViewDriver) TopLines(matchers ...*matcher) *ViewDriver {
|
||||
if len(matchers) < 1 {
|
||||
self.t.fail("TopLines method requires at least one matcher. If you are trying to assert that there are no lines, use .IsEmpty()")
|
||||
}
|
||||
|
||||
self.t.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))
|
||||
})
|
||||
|
||||
return self.assertLines(matchers...)
|
||||
}
|
||||
|
||||
// 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 *ViewDriver) Lines(matchers ...*matcher) *ViewDriver {
|
||||
self.LineCount(len(matchers))
|
||||
|
||||
return self.assertLines(matchers...)
|
||||
context string
|
||||
getView func() *gocui.View
|
||||
t *TestDriver
|
||||
getSelectedLinesFn func() ([]string, error)
|
||||
getSelectedRangeFn func() (int, int, error)
|
||||
getSelectedLineIdxFn func() (int, error)
|
||||
}
|
||||
|
||||
func (self *ViewDriver) getSelectedLines() ([]string, error) {
|
||||
@@ -61,7 +28,114 @@ func (self *ViewDriver) getSelectedLines() ([]string, error) {
|
||||
return self.getSelectedLinesFn()
|
||||
}
|
||||
|
||||
func (self *ViewDriver) SelectedLines(matchers ...*matcher) *ViewDriver {
|
||||
func (self *ViewDriver) getSelectedRange() (int, int, error) {
|
||||
if self.getSelectedRangeFn == nil {
|
||||
view := self.t.gui.View(self.getView().Name())
|
||||
idx := view.SelectedLineIdx()
|
||||
|
||||
return idx, idx, nil
|
||||
}
|
||||
|
||||
return self.getSelectedRangeFn()
|
||||
}
|
||||
|
||||
// even if you have a selected range, there may still be a line within that range
|
||||
// which the cursor points at. This function returns that line index.
|
||||
func (self *ViewDriver) getSelectedLineIdx() (int, error) {
|
||||
if self.getSelectedLineIdxFn == nil {
|
||||
view := self.t.gui.View(self.getView().Name())
|
||||
|
||||
return view.SelectedLineIdx(), nil
|
||||
}
|
||||
|
||||
return self.getSelectedLineIdxFn()
|
||||
}
|
||||
|
||||
// asserts that the view has the expected title
|
||||
func (self *ViewDriver) Title(expected *Matcher) *ViewDriver {
|
||||
self.t.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. One matcher must be passed for each line.
|
||||
// If you only care about the top n lines, use the TopLines method instead.
|
||||
func (self *ViewDriver) Lines(matchers ...*Matcher) *ViewDriver {
|
||||
self.validateMatchersPassed(matchers)
|
||||
self.LineCount(len(matchers))
|
||||
|
||||
return self.assertLines(0, matchers...)
|
||||
}
|
||||
|
||||
// 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 *ViewDriver) TopLines(matchers ...*Matcher) *ViewDriver {
|
||||
self.validateMatchersPassed(matchers)
|
||||
self.validateEnoughLines(matchers)
|
||||
|
||||
return self.assertLines(0, matchers...)
|
||||
}
|
||||
|
||||
func (self *ViewDriver) ContainsLines(matchers ...*Matcher) *ViewDriver {
|
||||
self.validateMatchersPassed(matchers)
|
||||
self.validateEnoughLines(matchers)
|
||||
|
||||
self.t.assertWithRetries(func() (bool, string) {
|
||||
content := self.getView().Buffer()
|
||||
lines := strings.Split(content, "\n")
|
||||
|
||||
startIdx, endIdx, err := self.getSelectedRange()
|
||||
|
||||
for i := 0; i < len(lines)-len(matchers)+1; i++ {
|
||||
matches := true
|
||||
for j, matcher := range matchers {
|
||||
checkIsSelected, matcher := matcher.checkIsSelected() // strip the IsSelected matcher out
|
||||
lineIdx := i + j
|
||||
ok, _ := matcher.test(lines[lineIdx])
|
||||
if !ok {
|
||||
matches = false
|
||||
break
|
||||
}
|
||||
if checkIsSelected {
|
||||
if err != nil {
|
||||
matches = false
|
||||
break
|
||||
}
|
||||
if lineIdx < startIdx || lineIdx > endIdx {
|
||||
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-----\nSelected range: %d-%d",
|
||||
expectedContent,
|
||||
content,
|
||||
startIdx,
|
||||
endIdx,
|
||||
)
|
||||
})
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// asserts on the lines that are selected in the view.
|
||||
func (self *ViewDriver) SelectedLines(matchers ...*Matcher) *ViewDriver {
|
||||
self.validateMatchersPassed(matchers)
|
||||
self.validateEnoughLines(matchers)
|
||||
|
||||
self.t.assertWithRetries(func() (bool, string) {
|
||||
selectedLines, err := self.getSelectedLines()
|
||||
if err != nil {
|
||||
@@ -76,7 +150,12 @@ func (self *ViewDriver) SelectedLines(matchers ...*matcher) *ViewDriver {
|
||||
}
|
||||
|
||||
for i, line := range selectedLines {
|
||||
ok, message := matchers[i].test(line)
|
||||
checkIsSelected, matcher := matchers[i].checkIsSelected()
|
||||
if checkIsSelected {
|
||||
self.t.fail("You cannot use the IsSelected matcher with the SelectedLines method")
|
||||
}
|
||||
|
||||
ok, message := matcher.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)
|
||||
}
|
||||
@@ -88,53 +167,51 @@ func (self *ViewDriver) SelectedLines(matchers ...*matcher) *ViewDriver {
|
||||
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) validateMatchersPassed(matchers []*Matcher) {
|
||||
if len(matchers) < 1 {
|
||||
self.t.fail("'Lines' methods require at least one matcher to be passed as an argument. If you are trying to assert that there are no lines, use .IsEmpty()")
|
||||
}
|
||||
}
|
||||
|
||||
func (self *ViewDriver) assertLines(matchers ...*matcher) *ViewDriver {
|
||||
func (self *ViewDriver) validateEnoughLines(matchers []*Matcher) {
|
||||
self.t.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))
|
||||
})
|
||||
}
|
||||
|
||||
func (self *ViewDriver) assertLines(offset int, matchers ...*Matcher) *ViewDriver {
|
||||
view := self.getView()
|
||||
|
||||
for i, matcher := range matchers {
|
||||
for matcherIndex, matcher := range matchers {
|
||||
lineIdx := matcherIndex + offset
|
||||
checkIsSelected, matcher := matcher.checkIsSelected()
|
||||
|
||||
self.t.matchString(matcher, fmt.Sprintf("Unexpected content in view '%s'.", view.Name()),
|
||||
func() string {
|
||||
return view.BufferLines()[i]
|
||||
return view.BufferLines()[lineIdx]
|
||||
},
|
||||
)
|
||||
|
||||
if checkIsSelected {
|
||||
self.t.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)
|
||||
startIdx, endIdx, err := self.getSelectedRange()
|
||||
if err != nil {
|
||||
return false, err.Error()
|
||||
}
|
||||
|
||||
if lineIdx < startIdx || lineIdx > endIdx {
|
||||
if startIdx == endIdx {
|
||||
return false, fmt.Sprintf("Unexpected selected line index in view '%s'. Expected %d, got %d", view.Name(), lineIdx, startIdx)
|
||||
} else {
|
||||
lines, err := self.getSelectedLines()
|
||||
if err != nil {
|
||||
return false, err.Error()
|
||||
}
|
||||
return false, fmt.Sprintf("Unexpected selected line index in view '%s'. Expected line %d to be in range %d to %d. Selected lines:\n---\n%s\n---\n\nExpected line: '%s'", view.Name(), lineIdx, startIdx, endIdx, strings.Join(lines, "\n"), matcher.name())
|
||||
}
|
||||
}
|
||||
return true, ""
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -143,7 +220,7 @@ func (self *ViewDriver) assertLines(matchers ...*matcher) *ViewDriver {
|
||||
}
|
||||
|
||||
// asserts on the content of the view i.e. the stuff within the view's frame.
|
||||
func (self *ViewDriver) Content(matcher *matcher) *ViewDriver {
|
||||
func (self *ViewDriver) Content(matcher *Matcher) *ViewDriver {
|
||||
self.t.matchString(matcher, fmt.Sprintf("%s: Unexpected content.", self.context),
|
||||
func() string {
|
||||
return self.getView().Buffer()
|
||||
@@ -153,40 +230,27 @@ func (self *ViewDriver) Content(matcher *matcher) *ViewDriver {
|
||||
return self
|
||||
}
|
||||
|
||||
// asserts on the selected line of the view
|
||||
func (self *ViewDriver) SelectedLine(matcher *matcher) *ViewDriver {
|
||||
// asserts on the selected line of the view. If your view has multiple lines selected,
|
||||
// but also has a concept of a cursor position, this will assert on the line that
|
||||
// the cursor is on. Otherwise it will assert on the first line of the selection.
|
||||
func (self *ViewDriver) SelectedLine(matcher *Matcher) *ViewDriver {
|
||||
self.t.assertWithRetries(func() (bool, string) {
|
||||
selectedLines, err := self.getSelectedLines()
|
||||
selectedLineIdx, err := self.getSelectedLineIdx()
|
||||
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(),
|
||||
)
|
||||
viewLines := self.getView().BufferLines()
|
||||
|
||||
if selectedLineIdx >= len(viewLines) {
|
||||
return false, fmt.Sprintf("%s: Expected view to have at least %d lines, but it only has %d", self.context, selectedLineIdx+1, len(viewLines))
|
||||
}
|
||||
|
||||
value := selectedLines[0]
|
||||
value := viewLines[selectedLineIdx]
|
||||
|
||||
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 {
|
||||
selectedLines, err := self.getSelectedLines()
|
||||
if err != nil {
|
||||
self.t.gui.Fail(err.Error())
|
||||
return "<failed to obtain selected line>"
|
||||
}
|
||||
|
||||
return selectedLines[0]
|
||||
},
|
||||
)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
@@ -298,10 +362,63 @@ func (self *ViewDriver) PressEscape() *ViewDriver {
|
||||
return self.Press(self.t.keys.Universal.Return)
|
||||
}
|
||||
|
||||
func (self *ViewDriver) NavigateToListItem(matcher *matcher) *ViewDriver {
|
||||
// this will look for a list item in the current panel and if it finds it, it will
|
||||
// enter the keypresses required to navigate to it.
|
||||
// The test will fail if:
|
||||
// - the user is not in a list item
|
||||
// - no list item is found containing the given text
|
||||
// - multiple list items are found containing the given text in the initial page of items
|
||||
//
|
||||
// NOTE: this currently assumes that BufferLines returns all the lines that can be accessed.
|
||||
// If this changes in future, we'll need to update this code to first attempt to find the item
|
||||
// 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 *ViewDriver) NavigateToListItem(matcher *Matcher) *ViewDriver {
|
||||
self.IsFocused()
|
||||
|
||||
self.t.navigateToListItem(matcher)
|
||||
view := self.getView()
|
||||
|
||||
var matchIndex int
|
||||
|
||||
self.t.assertWithRetries(func() (bool, string) {
|
||||
matchIndex = -1
|
||||
var matches []string
|
||||
lines := view.BufferLines()
|
||||
// first we look for a duplicate on the current screen. We won't bother looking beyond that though.
|
||||
for i, line := range lines {
|
||||
ok, _ := matcher.test(line)
|
||||
if ok {
|
||||
matches = append(matches, line)
|
||||
matchIndex = i
|
||||
}
|
||||
}
|
||||
if len(matches) > 1 {
|
||||
return false, fmt.Sprintf("Found %d matches for `%s`, expected only a single match. Matching lines:\n%s", len(matches), matcher.name(), strings.Join(matches, "\n"))
|
||||
} else if len(matches) == 0 {
|
||||
return false, fmt.Sprintf("Could not find item matching: %s. Lines:\n%s", matcher.name(), strings.Join(lines, "\n"))
|
||||
} else {
|
||||
return true, ""
|
||||
}
|
||||
})
|
||||
|
||||
selectedLineIdx, err := self.getSelectedLineIdx()
|
||||
if err != nil {
|
||||
self.t.fail(err.Error())
|
||||
return self
|
||||
}
|
||||
if selectedLineIdx == matchIndex {
|
||||
self.SelectedLine(matcher)
|
||||
} else if selectedLineIdx < matchIndex {
|
||||
for i := selectedLineIdx; i < matchIndex; i++ {
|
||||
self.SelectNextItem()
|
||||
}
|
||||
self.SelectedLine(matcher)
|
||||
} else {
|
||||
for i := selectedLineIdx; i > matchIndex; i-- {
|
||||
self.SelectPreviousItem()
|
||||
}
|
||||
self.SelectedLine(matcher)
|
||||
}
|
||||
|
||||
return self
|
||||
}
|
||||
@@ -349,8 +466,13 @@ 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 {
|
||||
// This purely exists as a convenience method for those who hate the trailing periods in multi-line method chains
|
||||
func (self *ViewDriver) Self() *ViewDriver {
|
||||
return self
|
||||
}
|
||||
|
||||
func expectedContentFromMatchers(matchers []*Matcher) string {
|
||||
return strings.Join(lo.Map(matchers, func(matcher *Matcher, _ int) string {
|
||||
return matcher.name()
|
||||
}), "\n")
|
||||
}
|
||||
|
||||
@@ -40,34 +40,97 @@ func (self *Views) Secondary() *ViewDriver {
|
||||
}
|
||||
|
||||
func (self *Views) regularView(viewName string) *ViewDriver {
|
||||
return self.newStaticViewDriver(viewName, nil)
|
||||
return self.newStaticViewDriver(viewName, nil, nil, 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
|
||||
})
|
||||
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
|
||||
},
|
||||
func() (int, int, error) {
|
||||
ctx := self.t.gui.ContextForView(viewName).(*context.PatchExplorerContext)
|
||||
state := ctx.GetState()
|
||||
if state == nil {
|
||||
return 0, 0, errors.New("Expected patch explorer to be activated")
|
||||
}
|
||||
startIdx, endIdx := state.SelectedRange()
|
||||
return startIdx, endIdx, nil
|
||||
},
|
||||
func() (int, error) {
|
||||
ctx := self.t.gui.ContextForView(viewName).(*context.PatchExplorerContext)
|
||||
state := ctx.GetState()
|
||||
if state == nil {
|
||||
return 0, errors.New("Expected patch explorer to be activated")
|
||||
}
|
||||
return state.GetSelectedLineIdx(), 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 {
|
||||
func (self *Views) newStaticViewDriver(
|
||||
viewName string,
|
||||
getSelectedLinesFn func() ([]string, error),
|
||||
getSelectedLineRangeFn func() (int, int, error),
|
||||
getSelectedLineIdxFn func() (int, error),
|
||||
) *ViewDriver {
|
||||
return &ViewDriver{
|
||||
context: fmt.Sprintf("%s view", viewName),
|
||||
getView: func() *gocui.View { return self.t.gui.View(viewName) },
|
||||
getSelectedLinesFn: getSelectedLinesFn,
|
||||
t: self.t,
|
||||
context: fmt.Sprintf("%s view", viewName),
|
||||
getView: func() *gocui.View { return self.t.gui.View(viewName) },
|
||||
getSelectedLinesFn: getSelectedLinesFn,
|
||||
getSelectedRangeFn: getSelectedLineRangeFn,
|
||||
getSelectedLineIdxFn: getSelectedLineIdxFn,
|
||||
t: self.t,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Views) MergeConflicts() *ViewDriver {
|
||||
viewName := "mergeConflicts"
|
||||
return self.newStaticViewDriver(
|
||||
viewName,
|
||||
func() ([]string, error) {
|
||||
ctx := self.t.gui.ContextForView(viewName).(*context.MergeConflictsContext)
|
||||
state := ctx.GetState()
|
||||
if state == nil {
|
||||
return nil, errors.New("Expected patch explorer to be activated")
|
||||
}
|
||||
selectedContent := strings.Split(state.PlainRenderSelected(), "\n")
|
||||
|
||||
return selectedContent, nil
|
||||
},
|
||||
func() (int, int, error) {
|
||||
ctx := self.t.gui.ContextForView(viewName).(*context.MergeConflictsContext)
|
||||
state := ctx.GetState()
|
||||
if state == nil {
|
||||
return 0, 0, errors.New("Expected patch explorer to be activated")
|
||||
}
|
||||
startIdx, endIdx := state.GetSelectedRange()
|
||||
return startIdx, endIdx, nil
|
||||
},
|
||||
// there is no concept of a cursor in the merge conflicts panel so we just return the start of the selection
|
||||
func() (int, error) {
|
||||
ctx := self.t.gui.ContextForView(viewName).(*context.MergeConflictsContext)
|
||||
state := ctx.GetState()
|
||||
if state == nil {
|
||||
return 0, errors.New("Expected patch explorer to be activated")
|
||||
}
|
||||
startIdx, _ := state.GetSelectedRange()
|
||||
return startIdx, nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (self *Views) Commits() *ViewDriver {
|
||||
return self.regularView("commits")
|
||||
}
|
||||
@@ -158,10 +221,6 @@ func (self *Views) Suggestions() *ViewDriver {
|
||||
return self.regularView("suggestions")
|
||||
}
|
||||
|
||||
func (self *Views) MergeConflicts() *ViewDriver {
|
||||
return self.regularView("mergeConflicts")
|
||||
}
|
||||
|
||||
func (self *Views) Search() *ViewDriver {
|
||||
return self.regularView("search")
|
||||
}
|
||||
|
||||
64
pkg/integration/tests/patch_building/apply.go
Normal file
64
pkg/integration/tests/patch_building/apply.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package patch_building
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var Apply = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Apply a custom patch",
|
||||
ExtraCmdArgs: "",
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.NewBranch("branch-a")
|
||||
shell.CreateFileAndAdd("file1", "first line\n")
|
||||
shell.Commit("first commit")
|
||||
|
||||
shell.NewBranch("branch-b")
|
||||
shell.UpdateFileAndAdd("file1", "first line\nsecond line\n")
|
||||
shell.Commit("update")
|
||||
|
||||
shell.Checkout("branch-a")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Branches().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("branch-a").IsSelected(),
|
||||
Contains("branch-b"),
|
||||
).
|
||||
Press(keys.Universal.NextItem).
|
||||
PressEnter()
|
||||
|
||||
t.Views().SubCommits().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("update").IsSelected(),
|
||||
Contains("first commit"),
|
||||
).
|
||||
PressEnter()
|
||||
|
||||
t.Views().CommitFiles().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("M file1").IsSelected(),
|
||||
).
|
||||
PressPrimaryAction()
|
||||
|
||||
t.Views().Information().Content(Contains("building patch"))
|
||||
|
||||
t.Views().PatchBuildingSecondary().Content(Contains("second line"))
|
||||
|
||||
t.Actions().SelectPatchOption(MatchesRegexp(`apply patch$`))
|
||||
|
||||
t.Views().Files().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("file1").IsSelected(),
|
||||
)
|
||||
|
||||
t.Views().Main().
|
||||
Content(Contains("second line"))
|
||||
},
|
||||
})
|
||||
49
pkg/integration/tests/patch_building/apply_in_reverse.go
Normal file
49
pkg/integration/tests/patch_building/apply_in_reverse.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package patch_building
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var ApplyInReverse = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Apply a custom patch in reverse",
|
||||
ExtraCmdArgs: "",
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateFileAndAdd("file1", "file1 content\n")
|
||||
shell.CreateFileAndAdd("file2", "file2 content\n")
|
||||
shell.Commit("first commit")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("first commit").IsSelected(),
|
||||
).
|
||||
PressEnter()
|
||||
|
||||
t.Views().CommitFiles().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("file1").IsSelected(),
|
||||
Contains("file2"),
|
||||
).
|
||||
PressPrimaryAction()
|
||||
|
||||
t.Views().Information().Content(Contains("building patch"))
|
||||
|
||||
t.Views().PatchBuildingSecondary().Content(Contains("+file1 content"))
|
||||
|
||||
t.Actions().SelectPatchOption(Contains("apply patch in reverse"))
|
||||
|
||||
t.Views().Files().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("D").Contains("file1").IsSelected(),
|
||||
)
|
||||
|
||||
t.Views().Main().
|
||||
Content(Contains("-file1 content"))
|
||||
},
|
||||
})
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
var CopyPatchToClipboard = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Create a patch from the commits and copy the patch to clipbaord.",
|
||||
ExtraCmdArgs: "",
|
||||
Skip: true,
|
||||
Skip: true, // skipping because CI doesn't have clipboard functionality
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.NewBranch("branch-a")
|
||||
@@ -40,11 +40,7 @@ var CopyPatchToClipboard = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
|
||||
t.Views().Information().Content(Contains("building patch"))
|
||||
|
||||
t.Views().
|
||||
CommitFiles().
|
||||
Press(keys.Universal.CreatePatchOptionsMenu)
|
||||
|
||||
t.ExpectPopup().Menu().Title(Equals("Patch Options")).Select(Contains("copy patch to clipboard")).Confirm()
|
||||
t.Actions().SelectPatchOption(Contains("copy patch to clipboard"))
|
||||
|
||||
t.ExpectToast(Contains("Patch copied to clipboard"))
|
||||
|
||||
|
||||
66
pkg/integration/tests/patch_building/move_to_index.go
Normal file
66
pkg/integration/tests/patch_building/move_to_index.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package patch_building
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var MoveToIndex = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Move a patch from a commit to the index",
|
||||
ExtraCmdArgs: "",
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateFileAndAdd("file1", "file1 content\n")
|
||||
shell.CreateFileAndAdd("file2", "file2 content\n")
|
||||
shell.Commit("first commit")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("first commit").IsSelected(),
|
||||
).
|
||||
PressEnter()
|
||||
|
||||
t.Views().CommitFiles().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("file1").IsSelected(),
|
||||
Contains("file2"),
|
||||
).
|
||||
PressPrimaryAction()
|
||||
|
||||
t.Views().Information().Content(Contains("building patch"))
|
||||
|
||||
t.Views().PatchBuildingSecondary().Content(Contains("+file1 content"))
|
||||
|
||||
t.Actions().SelectPatchOption(Contains("move patch out into index"))
|
||||
|
||||
t.Views().Files().
|
||||
Lines(
|
||||
Contains("A").Contains("file1"),
|
||||
)
|
||||
|
||||
t.Views().CommitFiles().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("file2").IsSelected(),
|
||||
).
|
||||
PressEscape()
|
||||
|
||||
t.Views().Main().
|
||||
Content(Contains("+file2 content"))
|
||||
|
||||
t.Views().Commits().
|
||||
Lines(
|
||||
Contains("first commit").IsSelected(),
|
||||
)
|
||||
|
||||
t.Views().Files().
|
||||
Focus()
|
||||
|
||||
t.Views().Main().
|
||||
Content(Contains("file1 content"))
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,98 @@
|
||||
package patch_building
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var MoveToIndexPartial = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Move a patch from a commit to the index. This is different from the MoveToIndex test in that we're only selecting a partial patch from a file",
|
||||
ExtraCmdArgs: "",
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateFileAndAdd("file1", "first line\nsecond line\nthird line\n")
|
||||
shell.Commit("first commit")
|
||||
|
||||
shell.UpdateFileAndAdd("file1", "first line2\nsecond line\nthird line2\n")
|
||||
shell.Commit("second commit")
|
||||
|
||||
shell.CreateFileAndAdd("file2", "file1 content")
|
||||
shell.Commit("third commit")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("third commit").IsSelected(),
|
||||
Contains("second commit"),
|
||||
Contains("first commit"),
|
||||
).
|
||||
NavigateToListItem(Contains("second commit")).
|
||||
PressEnter()
|
||||
|
||||
t.Views().CommitFiles().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("file1").IsSelected(),
|
||||
).
|
||||
PressEnter()
|
||||
|
||||
t.Views().PatchBuilding().
|
||||
IsFocused().
|
||||
ContainsLines(
|
||||
Contains(`-first line`).IsSelected(),
|
||||
Contains(`+first line2`),
|
||||
Contains(` second line`),
|
||||
Contains(`-third line`),
|
||||
Contains(`+third line2`),
|
||||
).
|
||||
PressPrimaryAction().
|
||||
SelectNextItem().
|
||||
PressPrimaryAction().
|
||||
Tap(func() {
|
||||
t.Views().Information().Content(Contains("building patch"))
|
||||
|
||||
t.Views().PatchBuildingSecondary().
|
||||
ContainsLines(
|
||||
Contains(`-first line`),
|
||||
Contains(`+first line2`),
|
||||
Contains(` second line`),
|
||||
Contains(` third line`),
|
||||
)
|
||||
|
||||
t.Actions().SelectPatchOption(Contains("move patch out into index"))
|
||||
|
||||
t.Views().Files().
|
||||
Lines(
|
||||
Contains("M").Contains("file1"),
|
||||
)
|
||||
})
|
||||
|
||||
// Focus is automatically returned to the commit files panel. Arguably it shouldn't be.
|
||||
t.Views().CommitFiles().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("file1"),
|
||||
)
|
||||
|
||||
t.Views().Main().
|
||||
ContainsLines(
|
||||
Contains(` first line`),
|
||||
Contains(` second line`),
|
||||
Contains(`-third line`),
|
||||
Contains(`+third line2`),
|
||||
)
|
||||
|
||||
t.Views().Files().
|
||||
Focus()
|
||||
|
||||
t.Views().Main().
|
||||
ContainsLines(
|
||||
Contains(`-first line`),
|
||||
Contains(`+first line2`),
|
||||
Contains(` second line`),
|
||||
Contains(` third line2`),
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,89 @@
|
||||
package patch_building
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var MoveToIndexWithConflict = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Move a patch from a commit to the index, causing a conflict",
|
||||
ExtraCmdArgs: "",
|
||||
Skip: true, // Skipping until https://github.com/jesseduffield/lazygit/pull/2471 is merged
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateFileAndAdd("file1", "file1 content")
|
||||
shell.Commit("first commit")
|
||||
|
||||
shell.UpdateFileAndAdd("file1", "file1 content with old changes")
|
||||
shell.Commit("second commit")
|
||||
|
||||
shell.UpdateFileAndAdd("file1", "file1 content with new changes")
|
||||
shell.Commit("third commit")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("third commit").IsSelected(),
|
||||
Contains("second commit"),
|
||||
Contains("first commit"),
|
||||
).
|
||||
SelectNextItem().
|
||||
PressEnter()
|
||||
|
||||
t.Views().CommitFiles().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("file1").IsSelected(),
|
||||
).
|
||||
PressPrimaryAction()
|
||||
|
||||
t.Views().Information().Content(Contains("building patch"))
|
||||
|
||||
t.Actions().SelectPatchOption(Contains("move patch out into index"))
|
||||
|
||||
t.Actions().AcknowledgeConflicts()
|
||||
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("UU").Contains("file1"),
|
||||
).
|
||||
PressPrimaryAction()
|
||||
|
||||
t.Views().MergeConflicts().
|
||||
IsFocused().
|
||||
ContainsLines(
|
||||
Contains("<<<<<<< HEAD").IsSelected(),
|
||||
Contains("file1 content").IsSelected(),
|
||||
Contains("=======").IsSelected(),
|
||||
Contains("file1 content with new changes"),
|
||||
Contains(">>>>>>>"),
|
||||
).
|
||||
PressPrimaryAction()
|
||||
|
||||
t.Actions().ContinueOnConflictsResolved()
|
||||
|
||||
t.ExpectPopup().Alert().
|
||||
Title(Equals("Error")).
|
||||
Content(Contains("Applied patch to 'file1' with conflicts")).
|
||||
Confirm()
|
||||
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("UU").Contains("file1"),
|
||||
).
|
||||
PressEnter()
|
||||
|
||||
t.Views().MergeConflicts().
|
||||
TopLines(
|
||||
Contains("<<<<<<< ours"),
|
||||
Contains("file1 content"),
|
||||
Contains("======="),
|
||||
Contains("file1 content with old changes"),
|
||||
Contains(">>>>>>> theirs"),
|
||||
).
|
||||
IsFocused()
|
||||
},
|
||||
})
|
||||
70
pkg/integration/tests/patch_building/move_to_new_commit.go
Normal file
70
pkg/integration/tests/patch_building/move_to_new_commit.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package patch_building
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var MoveToNewCommit = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Move a patch from a commit to a new commit",
|
||||
ExtraCmdArgs: "",
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateFileAndAdd("file1", "file1 content")
|
||||
shell.Commit("first commit")
|
||||
|
||||
shell.UpdateFileAndAdd("file1", "file1 content with old changes")
|
||||
shell.Commit("second commit")
|
||||
|
||||
shell.UpdateFileAndAdd("file1", "file1 content with new changes")
|
||||
shell.Commit("third commit")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("third commit").IsSelected(),
|
||||
Contains("second commit"),
|
||||
Contains("first commit"),
|
||||
).
|
||||
SelectNextItem().
|
||||
PressEnter()
|
||||
|
||||
t.Views().CommitFiles().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("file1").IsSelected(),
|
||||
).
|
||||
PressPrimaryAction()
|
||||
|
||||
t.Views().Information().Content(Contains("building patch"))
|
||||
|
||||
t.Actions().SelectPatchOption(Contains("move patch into new commit"))
|
||||
|
||||
t.Views().CommitFiles().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("file1").IsSelected(),
|
||||
).
|
||||
PressEscape()
|
||||
|
||||
t.Views().Commits().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("third commit"),
|
||||
Contains(`Split from "second commit"`).IsSelected(),
|
||||
Contains("second commit"),
|
||||
Contains("first commit"),
|
||||
).
|
||||
SelectNextItem().
|
||||
PressEnter()
|
||||
|
||||
// the original commit has no more files in it
|
||||
t.Views().CommitFiles().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("(none)"),
|
||||
)
|
||||
},
|
||||
})
|
||||
57
pkg/integration/tests/patch_building/remove_from_commit.go
Normal file
57
pkg/integration/tests/patch_building/remove_from_commit.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package patch_building
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var RemoveFromCommit = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Remove a custom patch from a commit",
|
||||
ExtraCmdArgs: "",
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateFileAndAdd("file1", "file1 content\n")
|
||||
shell.CreateFileAndAdd("file2", "file2 content\n")
|
||||
shell.Commit("first commit")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("first commit").IsSelected(),
|
||||
).
|
||||
PressEnter()
|
||||
|
||||
t.Views().CommitFiles().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("file1").IsSelected(),
|
||||
Contains("file2"),
|
||||
).
|
||||
PressPrimaryAction()
|
||||
|
||||
t.Views().Information().Content(Contains("building patch"))
|
||||
|
||||
t.Views().PatchBuildingSecondary().Content(Contains("+file1 content"))
|
||||
|
||||
t.Actions().SelectPatchOption(Contains("remove patch from original commit"))
|
||||
|
||||
t.Views().Files().IsEmpty()
|
||||
|
||||
t.Views().CommitFiles().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("file2").IsSelected(),
|
||||
).
|
||||
PressEscape()
|
||||
|
||||
t.Views().Main().
|
||||
Content(Contains("+file2 content"))
|
||||
|
||||
t.Views().Commits().
|
||||
Lines(
|
||||
Contains("first commit").IsSelected(),
|
||||
)
|
||||
},
|
||||
})
|
||||
43
pkg/integration/tests/patch_building/reset_with_escape.go
Normal file
43
pkg/integration/tests/patch_building/reset_with_escape.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package patch_building
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var ResetWithEscape = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Reset a custom patch with the escape keybinding",
|
||||
ExtraCmdArgs: "",
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateFileAndAdd("file1", "file1 content")
|
||||
shell.Commit("first commit")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("first commit").IsSelected(),
|
||||
).
|
||||
PressEnter()
|
||||
|
||||
t.Views().CommitFiles().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("file1").IsSelected(),
|
||||
).
|
||||
PressPrimaryAction().
|
||||
Tap(func() {
|
||||
t.Views().Information().Content(Contains("building patch"))
|
||||
}).
|
||||
PressEscape()
|
||||
|
||||
// hitting escape at the top level will reset the patch
|
||||
t.Views().Commits().
|
||||
IsFocused().
|
||||
PressEscape()
|
||||
|
||||
t.Views().Information().Content(DoesNotContain("building patch"))
|
||||
},
|
||||
})
|
||||
42
pkg/integration/tests/patch_building/select_all_files.go
Normal file
42
pkg/integration/tests/patch_building/select_all_files.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package patch_building
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var SelectAllFiles = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "All all files of a commit to a custom patch with the 'a' keybinding",
|
||||
ExtraCmdArgs: "",
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateFileAndAdd("file1", "file1 content")
|
||||
shell.CreateFileAndAdd("file2", "file2 content")
|
||||
shell.CreateFileAndAdd("file3", "file3 content")
|
||||
shell.Commit("first commit")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("first commit").IsSelected(),
|
||||
).
|
||||
PressEnter()
|
||||
|
||||
t.Views().CommitFiles().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("file1").IsSelected(),
|
||||
Contains("file2"),
|
||||
Contains("file3"),
|
||||
).
|
||||
Press(keys.Files.ToggleStagedAll)
|
||||
|
||||
t.Views().Information().Content(Contains("building patch"))
|
||||
|
||||
t.Views().Secondary().Content(
|
||||
Contains("file1").Contains("file3").Contains("file3"),
|
||||
)
|
||||
},
|
||||
})
|
||||
167
pkg/integration/tests/patch_building/specific_selection.go
Normal file
167
pkg/integration/tests/patch_building/specific_selection.go
Normal file
@@ -0,0 +1,167 @@
|
||||
package patch_building
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var SpecificSelection = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Build a custom patch with a specific selection of lines, adding individual lines, as well as a range and hunk, and adding a file directly",
|
||||
ExtraCmdArgs: "",
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateFileAndAdd("hunk-file", "1a\n1b\n1c\n1d\n1e\n1f\n1g\n1h\n1i\n1j\n1k\n1l\n1m\n1n\n1o\n1p\n1q\n1r\n1s\n1t\n1u\n1v\n1w\n1x\n1y\n1z\n")
|
||||
shell.Commit("first commit")
|
||||
|
||||
// making changes in two separate places for the sake of having two hunks
|
||||
shell.UpdateFileAndAdd("hunk-file", "aa\n1b\ncc\n1d\n1e\n1f\n1g\n1h\n1i\n1j\n1k\n1l\n1m\n1n\n1o\n1p\n1q\n1r\n1s\ntt\nuu\nvv\n1w\n1x\n1y\n1z\n")
|
||||
|
||||
shell.CreateFileAndAdd("line-file", "2a\n2b\n2c\n2d\n2e\n2f\n2g\n2h\n2i\n2j\n2k\n2l\n2m\n2n\n2o\n2p\n2q\n2r\n2s\n2t\n2u\n2v\n2w\n2x\n2y\n2z\n")
|
||||
shell.CreateFileAndAdd("direct-file", "direct file content")
|
||||
shell.Commit("second commit")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("second commit").IsSelected(),
|
||||
Contains("first commit"),
|
||||
).
|
||||
PressEnter()
|
||||
|
||||
t.Views().CommitFiles().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("direct-file").IsSelected(),
|
||||
Contains("hunk-file"),
|
||||
Contains("line-file"),
|
||||
).
|
||||
PressPrimaryAction().
|
||||
Tap(func() {
|
||||
t.Views().Information().Content(Contains("building patch"))
|
||||
|
||||
t.Views().Secondary().Content(Contains("direct file content"))
|
||||
}).
|
||||
NavigateToListItem(Contains("hunk-file")).
|
||||
PressEnter()
|
||||
|
||||
t.Views().PatchBuilding().
|
||||
IsFocused().
|
||||
SelectedLines(
|
||||
Contains("-1a"),
|
||||
).
|
||||
Press(keys.Main.ToggleSelectHunk).
|
||||
SelectedLines(
|
||||
Contains(`@@ -1,6 +1,6 @@`),
|
||||
Contains(`-1a`),
|
||||
Contains(`+aa`),
|
||||
Contains(` 1b`),
|
||||
Contains(`-1c`),
|
||||
Contains(`+cc`),
|
||||
Contains(` 1d`),
|
||||
Contains(` 1e`),
|
||||
Contains(` 1f`),
|
||||
).
|
||||
PressPrimaryAction().
|
||||
// unlike in the staging panel, we don't remove lines from the patch building panel
|
||||
// upon 'adding' them. So the same lines will be selected
|
||||
SelectedLines(
|
||||
Contains(`@@ -1,6 +1,6 @@`),
|
||||
Contains(`-1a`),
|
||||
Contains(`+aa`),
|
||||
Contains(` 1b`),
|
||||
Contains(`-1c`),
|
||||
Contains(`+cc`),
|
||||
Contains(` 1d`),
|
||||
Contains(` 1e`),
|
||||
Contains(` 1f`),
|
||||
).
|
||||
Tap(func() {
|
||||
t.Views().Information().Content(Contains("building patch"))
|
||||
|
||||
t.Views().Secondary().Content(
|
||||
// when we're inside the patch building panel, we only show the patch
|
||||
// in the secondary panel that relates to the selected file
|
||||
DoesNotContain("direct file content").
|
||||
Contains("@@ -1,6 +1,6 @@").
|
||||
Contains(" 1f"),
|
||||
)
|
||||
}).
|
||||
PressEscape()
|
||||
|
||||
t.Views().CommitFiles().
|
||||
IsFocused().
|
||||
NavigateToListItem(Contains("line-file")).
|
||||
PressEnter()
|
||||
|
||||
t.Views().PatchBuilding().
|
||||
IsFocused().
|
||||
// hunk is selected because selection mode persists across files
|
||||
ContainsLines(
|
||||
Contains("@@ -0,0 +1,26 @@").IsSelected(),
|
||||
).
|
||||
Press(keys.Main.ToggleSelectHunk).
|
||||
SelectedLines(
|
||||
Contains("+2a"),
|
||||
).
|
||||
PressPrimaryAction().
|
||||
NavigateToListItem(Contains("+2c")).
|
||||
Press(keys.Main.ToggleDragSelect).
|
||||
NavigateToListItem(Contains("+2e")).
|
||||
PressPrimaryAction().
|
||||
NavigateToListItem(Contains("+2g")).
|
||||
PressPrimaryAction().
|
||||
Tap(func() {
|
||||
t.Views().Information().Content(Contains("building patch"))
|
||||
|
||||
t.Views().Secondary().ContainsLines(
|
||||
Contains("+2a"),
|
||||
Contains("+2c"),
|
||||
Contains("+2d"),
|
||||
Contains("+2e"),
|
||||
Contains("+2g"),
|
||||
)
|
||||
}).
|
||||
PressEscape().
|
||||
Tap(func() {
|
||||
t.Views().Secondary().ContainsLines(
|
||||
// direct-file patch
|
||||
Contains(`diff --git a/direct-file b/direct-file`),
|
||||
Contains(`new file mode 100644`),
|
||||
Contains(`index`),
|
||||
Contains(`--- /dev/null`),
|
||||
Contains(`+++ b/direct-file`),
|
||||
Contains(`@@ -0,0 +1 @@`),
|
||||
Contains(`+direct file content`),
|
||||
Contains(`\ No newline at end of file`),
|
||||
// hunk-file patch
|
||||
Contains(`diff --git a/hunk-file b/hunk-file`),
|
||||
Contains(`index`),
|
||||
Contains(`--- a/hunk-file`),
|
||||
Contains(`+++ b/hunk-file`),
|
||||
Contains(`@@ -1,6 +1,6 @@`),
|
||||
Contains(`-1a`),
|
||||
Contains(`+aa`),
|
||||
Contains(` 1b`),
|
||||
Contains(`-1c`),
|
||||
Contains(`+cc`),
|
||||
Contains(` 1d`),
|
||||
Contains(` 1e`),
|
||||
Contains(` 1f`),
|
||||
// line-file patch
|
||||
Contains(`diff --git a/line-file b/line-file`),
|
||||
Contains(`new file mode 100644`),
|
||||
Contains(`index`),
|
||||
Contains(`--- /dev/null`),
|
||||
Contains(`+++ b/line-file`),
|
||||
Contains(`@@ -0,0 +1,5 @@`),
|
||||
Contains(`+2a`),
|
||||
Contains(`+2c`),
|
||||
Contains(`+2d`),
|
||||
Contains(`+2e`),
|
||||
Contains(`+2g`),
|
||||
)
|
||||
})
|
||||
},
|
||||
})
|
||||
62
pkg/integration/tests/patch_building/start_new_patch.go
Normal file
62
pkg/integration/tests/patch_building/start_new_patch.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package patch_building
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var StartNewPatch = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Attempt to add a file from another commit to a patch, then agree to start a new patch",
|
||||
ExtraCmdArgs: "",
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateFileAndAdd("file1", "file1 content")
|
||||
shell.Commit("first commit")
|
||||
|
||||
shell.CreateFileAndAdd("file2", "file2 content")
|
||||
shell.Commit("second commit")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("second commit").IsSelected(),
|
||||
Contains("first commit"),
|
||||
).
|
||||
PressEnter()
|
||||
|
||||
t.Views().CommitFiles().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("file2").IsSelected(),
|
||||
).
|
||||
PressPrimaryAction().
|
||||
Tap(func() {
|
||||
t.Views().Information().Content(Contains("building patch"))
|
||||
|
||||
t.Views().Secondary().Content(Contains("file2"))
|
||||
}).
|
||||
PressEscape()
|
||||
|
||||
t.Views().Commits().
|
||||
IsFocused().
|
||||
NavigateToListItem(Contains("first commit")).
|
||||
PressEnter()
|
||||
|
||||
t.Views().CommitFiles().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("file1").IsSelected(),
|
||||
).
|
||||
PressPrimaryAction().
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Contains("Discard Patch")).
|
||||
Content(Contains("You can only build a patch from one commit/stash-entry at a time. Discard current patch?")).
|
||||
Confirm()
|
||||
|
||||
t.Views().Secondary().Content(Contains("file1").DoesNotContain("file2"))
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -94,7 +94,18 @@ var tests = []*components.IntegrationTest{
|
||||
interactive_rebase.SwapWithConflict,
|
||||
misc.ConfirmOnQuit,
|
||||
misc.InitialOpen,
|
||||
patch_building.Apply,
|
||||
patch_building.ApplyInReverse,
|
||||
patch_building.CopyPatchToClipboard,
|
||||
patch_building.MoveToIndex,
|
||||
patch_building.MoveToIndexPartial,
|
||||
patch_building.MoveToIndexWithConflict,
|
||||
patch_building.MoveToNewCommit,
|
||||
patch_building.RemoveFromCommit,
|
||||
patch_building.ResetWithEscape,
|
||||
patch_building.SelectAllFiles,
|
||||
patch_building.SpecificSelection,
|
||||
patch_building.StartNewPatch,
|
||||
reflog.Checkout,
|
||||
reflog.CherryPick,
|
||||
reflog.Patch,
|
||||
|
||||
Reference in New Issue
Block a user