Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,9 @@ jobs:
restore-keys: |
${{runner.os}}-go-
- name: Test code
# LONG_WAIT_BEFORE_FAIL means that for a given test assertion, we'll wait longer before failing
run: |
go test pkg/integration/clients/*.go
LONG_WAIT_BEFORE_FAIL=true go test pkg/integration/clients/*.go
build:
runs-on: ubuntu-latest
env:
Expand Down
23 changes: 23 additions & 0 deletions pkg/gui/mergeconflicts/state.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package mergeconflicts

import (
"strings"

"github.com/jesseduffield/lazygit/pkg/utils"
)

Expand Down Expand Up @@ -188,9 +190,30 @@ func (s *State) ContentAfterConflictResolve(selection Selection) (bool, string,
func (s *State) GetSelectedLine() int {
conflict := s.currentConflict()
if conflict == nil {
// TODO: see why this is 1 and not 0
return 1
}
selection := s.Selection()
startIndex, _ := selection.bounds(conflict)
return startIndex + 1
}

func (s *State) GetSelectedRange() (int, int) {
conflict := s.currentConflict()
if conflict == nil {
return 0, 0
}
selection := s.Selection()
startIndex, endIndex := selection.bounds(conflict)
return startIndex, endIndex
}

func (s *State) PlainRenderSelected() string {
startIndex, endIndex := s.GetSelectedRange()

content := s.GetContent()

contentLines := utils.SplitLines(content)

return strings.Join(contentLines[startIndex:endIndex+1], "\n")
}
6 changes: 6 additions & 0 deletions pkg/integration/components/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
4 changes: 2 additions & 2 deletions pkg/integration/components/alert_driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
15 changes: 12 additions & 3 deletions pkg/integration/components/assertion_helper.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package components

import (
"os"
"time"

integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
Expand All @@ -11,9 +12,17 @@ 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 retryWaitTimes() []int {
if os.Getenv("LONG_WAIT_BEFORE_FAIL") == "true" {
// CI has limited hardware, may be throttled, runs tests in parallel, etc, so we
// give it more leeway compared to when we're running things locally.
return []int{0, 1, 1, 1, 1, 1, 5, 10, 20, 40, 100, 200, 500, 1000, 2000, 4000}
} else {
return []int{0, 1, 1, 1, 1, 1, 5, 10, 20, 40, 100}
}
}

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)
Expand All @@ -22,7 +31,7 @@ func (self *assertionHelper) matchString(matcher *matcher, context string, getVa

func (self *assertionHelper) assertWithRetries(test func() (bool, string)) {
var message string
for _, waitTime := range retryWaitTimes {
for _, waitTime := range retryWaitTimes() {
time.Sleep(time.Duration(waitTime) * time.Millisecond)

var ok bool
Expand Down
2 changes: 1 addition & 1 deletion pkg/integration/components/commit_message_panel_driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions pkg/integration/components/confirmation_driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion pkg/integration/components/file_system.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
48 changes: 26 additions & 22 deletions pkg/integration/components/matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
}
Expand All @@ -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 {
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -115,47 +115,51 @@ 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
}

// 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)
}
8 changes: 4 additions & 4 deletions pkg/integration/components/menu_driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
10 changes: 5 additions & 5 deletions pkg/integration/components/prompt_driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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().
Expand Down
2 changes: 1 addition & 1 deletion pkg/integration/components/search_driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions pkg/integration/components/shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand Down
Loading