1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2024-12-04 10:34:55 +02:00

Merge pull request #2114 from jesseduffield/more-test-examples

This commit is contained in:
Jesse Duffield 2022-08-14 21:37:09 +10:00 committed by GitHub
commit a95d3e26b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
105 changed files with 503 additions and 175 deletions

View File

@ -13,7 +13,7 @@ Usage:
See https://github.com/jesseduffield/lazygit/tree/master/pkg/integration/README.md See https://github.com/jesseduffield/lazygit/tree/master/pkg/integration/README.md
CLI mode: CLI mode:
> go run cmd/integration_test/main.go cli <test1> <test2> ... > go run cmd/integration_test/main.go cli [--slow] <test1> <test2> ...
If you pass no test names, it runs all tests If you pass no test names, it runs all tests
Accepted environment variables: Accepted environment variables:
KEY_PRESS_DELAY (e.g. 200): the number of milliseconds to wait between keypresses KEY_PRESS_DELAY (e.g. 200): the number of milliseconds to wait between keypresses
@ -40,7 +40,14 @@ func main() {
case "help": case "help":
fmt.Println(usage) fmt.Println(usage)
case "cli": case "cli":
clients.RunCLI(os.Args[2:]) testNames := os.Args[2:]
slow := false
// get the next arg if it's --slow
if len(os.Args) > 2 && (os.Args[2] == "--slow" || os.Args[2] == "-slow") {
testNames = os.Args[3:]
slow = true
}
clients.RunCLI(testNames, slow)
case "tui": case "tui":
clients.RunTUI() clients.RunTUI()
default: default:

View File

@ -314,7 +314,9 @@ type CustomCommand struct {
} }
type CustomCommandPrompt struct { type CustomCommandPrompt struct {
Type string `yaml:"type"` // one of 'input', 'menu', or 'confirm' // one of 'input', 'menu', 'confirm', or 'menuFromCommand'
Type string `yaml:"type"`
Title string `yaml:"title"` Title string `yaml:"title"`
// this only apply to input prompts // this only apply to input prompts

View File

@ -71,3 +71,11 @@ func (self *GuiDriver) LogUI(message string) {
func (self *GuiDriver) CheckedOutRef() *models.Branch { func (self *GuiDriver) CheckedOutRef() *models.Branch {
return self.gui.helpers.Refs.GetCheckedOutRef() return self.gui.helpers.Refs.GetCheckedOutRef()
} }
func (self *GuiDriver) MainView() *gocui.View {
return self.gui.mainView()
}
func (self *GuiDriver) SecondaryView() *gocui.View {
return self.gui.secondaryView()
}

View File

@ -37,7 +37,7 @@ If you find yourself doing something frequently in a test, consider making it a
There are three ways to invoke a test: There are three ways to invoke a test:
1. go run cmd/integration_test/main.go cli [<testname>...] 1. go run cmd/integration_test/main.go cli [--slow] [<testname or testpath>...]
2. go run cmd/integration_test/main.go tui 2. go run cmd/integration_test/main.go tui
3. go test pkg/integration/clients/go_test.go 3. go test pkg/integration/clients/go_test.go
@ -47,7 +47,7 @@ The third, the go-test command, intended only for use in CI, to be run along wit
The name of a test is based on its path, so the name of the test at `pkg/integration/tests/commit/new_branch.go` is commit/new_branch. So to run it with our test runner you would run `go run cmd/integration_test/main.go cli commit/new_branch`. The name of a test is based on its path, so the name of the test at `pkg/integration/tests/commit/new_branch.go` is commit/new_branch. So to run it with our test runner you would run `go run cmd/integration_test/main.go cli commit/new_branch`.
You can pass the KEY_PRESS_DELAY env var to the test runner in order to set a delay in milliseconds between keypresses, which helps for watching a test at a realistic speed to understand what it's doing. Or in the tui you can press 't' to run the test with a pre-set delay. You can pass the KEY_PRESS_DELAY env var to the test runner in order to set a delay in milliseconds between keypresses, which helps for watching a test at a realistic speed to understand what it's doing. Or you can pass the '--slow' flag which sets a pre-set 'slow' key delay. In the tui you can press 't' to run the test in slow mode.
### Snapshots ### Snapshots

View File

@ -4,8 +4,11 @@ import (
"log" "log"
"os" "os"
"os/exec" "os/exec"
"regexp"
"strconv" "strconv"
"strings"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/integration/components" "github.com/jesseduffield/lazygit/pkg/integration/components"
"github.com/jesseduffield/lazygit/pkg/integration/tests" "github.com/jesseduffield/lazygit/pkg/integration/tests"
) )
@ -20,14 +23,19 @@ import (
// If invoked directly, you can specify tests to run by passing their names as positional arguments // If invoked directly, you can specify tests to run by passing their names as positional arguments
func RunCLI(testNames []string) { func RunCLI(testNames []string, slow bool) {
keyPressDelay := tryConvert(os.Getenv("KEY_PRESS_DELAY"), 0)
if slow {
keyPressDelay = SLOW_KEY_PRESS_DELAY
}
err := components.RunTests( err := components.RunTests(
getTestsToRun(testNames), getTestsToRun(testNames),
log.Printf, log.Printf,
runCmdInTerminal, runCmdInTerminal,
runAndPrintError, runAndPrintError,
getModeFromEnv(), getModeFromEnv(),
tryConvert(os.Getenv("KEY_PRESS_DELAY"), 0), keyPressDelay,
) )
if err != nil { if err != nil {
log.Print(err.Error()) log.Print(err.Error())
@ -36,21 +44,30 @@ func RunCLI(testNames []string) {
func runAndPrintError(test *components.IntegrationTest, f func() error) { func runAndPrintError(test *components.IntegrationTest, f func() error) {
if err := f(); err != nil { if err := f(); err != nil {
log.Print(err.Error()) log.Fatalf(err.Error())
} }
} }
func getTestsToRun(testNames []string) []*components.IntegrationTest { func getTestsToRun(testNames []string) []*components.IntegrationTest {
allIntegrationTests := tests.GetTests()
var testsToRun []*components.IntegrationTest var testsToRun []*components.IntegrationTest
if len(testNames) == 0 { if len(testNames) == 0 {
return tests.Tests return allIntegrationTests
} }
testNames = slices.Map(testNames, func(name string) string {
// allowing full test paths to be passed for convenience
return strings.TrimSuffix(
regexp.MustCompile(`.*pkg/integration/tests/`).ReplaceAllString(name, ""),
".go",
)
})
outer: outer:
for _, testName := range testNames { for _, testName := range testNames {
// check if our given test name actually exists // check if our given test name actually exists
for _, test := range tests.Tests { for _, test := range allIntegrationTests {
if test.Name() == testName { if test.Name() == testName {
testsToRun = append(testsToRun, test) testsToRun = append(testsToRun, test)
continue outer continue outer

View File

@ -29,7 +29,7 @@ func TestIntegration(t *testing.T) {
testNumber := 0 testNumber := 0
err := components.RunTests( err := components.RunTests(
tests.Tests, tests.GetTests(),
t.Logf, t.Logf,
runCmdHeadless, runCmdHeadless,
func(test *components.IntegrationTest, f func() error) { func(test *components.IntegrationTest, f func() error) {

View File

@ -52,7 +52,8 @@ func getIntegrationTest() integrationTypes.IntegrationTest {
)) ))
} }
for _, candidateTest := range tests.Tests { allTests := tests.GetTests()
for _, candidateTest := range allTests {
if candidateTest.Name() == integrationTestName { if candidateTest.Name() == integrationTestName {
return candidateTest return candidateTest
} }

View File

@ -19,6 +19,8 @@ import (
// This program lets you run integration tests from a TUI. See pkg/integration/README.md for more info. // This program lets you run integration tests from a TUI. See pkg/integration/README.md for more info.
var SLOW_KEY_PRESS_DELAY = 300
func RunTUI() { func RunTUI() {
rootDir := utils.GetLazygitRootDirectory() rootDir := utils.GetLazygitRootDirectory()
testDir := filepath.Join(rootDir, "test", "integration") testDir := filepath.Join(rootDir, "test", "integration")
@ -106,7 +108,7 @@ func RunTUI() {
return nil return nil
} }
suspendAndRunTest(currentTest, components.ASK_TO_UPDATE_SNAPSHOT, 200) suspendAndRunTest(currentTest, components.ASK_TO_UPDATE_SNAPSHOT, SLOW_KEY_PRESS_DELAY)
return nil return nil
}); err != nil { }); err != nil {
@ -168,7 +170,7 @@ func RunTUI() {
return err return err
} }
app.filteredTests = tests.Tests app.filteredTests = app.allTests
app.renderTests() app.renderTests()
app.editorView.TextArea.Clear() app.editorView.TextArea.Clear()
app.editorView.Clear() app.editorView.Clear()
@ -204,6 +206,7 @@ func RunTUI() {
} }
type app struct { type app struct {
allTests []*components.IntegrationTest
filteredTests []*components.IntegrationTest filteredTests []*components.IntegrationTest
itemIdx int itemIdx int
testDir string testDir string
@ -214,7 +217,7 @@ type app struct {
} }
func newApp(testDir string) *app { func newApp(testDir string) *app {
return &app{testDir: testDir} return &app{testDir: testDir, allTests: tests.GetTests()}
} }
func (self *app) getCurrentTest() *components.IntegrationTest { func (self *app) getCurrentTest() *components.IntegrationTest {
@ -226,7 +229,7 @@ func (self *app) getCurrentTest() *components.IntegrationTest {
} }
func (self *app) loadTests() { func (self *app) loadTests() {
self.filteredTests = tests.Tests self.filteredTests = self.allTests
self.adjustCursor() self.adjustCursor()
} }
@ -237,9 +240,9 @@ func (self *app) adjustCursor() {
func (self *app) filterWithString(needle string) { func (self *app) filterWithString(needle string) {
if needle == "" { if needle == "" {
self.filteredTests = tests.Tests self.filteredTests = self.allTests
} else { } else {
self.filteredTests = slices.Filter(tests.Tests, func(test *components.IntegrationTest) bool { self.filteredTests = slices.Filter(self.allTests, func(test *components.IntegrationTest) bool {
return strings.Contains(test.Name(), needle) return strings.Contains(test.Name(), needle)
}) })
} }

View File

@ -19,6 +19,43 @@ func NewAssert(gui integrationTypes.GuiDriver) *Assert {
return &Assert{gui: gui} return &Assert{gui: gui}
} }
// for making assertions on string values
type matcher struct {
testFn func(string) (bool, string)
prefix string
}
func (self *matcher) test(value string) (bool, string) {
ok, message := self.testFn(value)
if ok {
return true, ""
}
if self.prefix != "" {
return false, self.prefix + " " + message
}
return false, message
}
func (self *matcher) context(prefix string) *matcher {
self.prefix = prefix
return self
}
func Contains(target string) *matcher {
return &matcher{testFn: func(value string) (bool, string) {
return strings.Contains(value, target), fmt.Sprintf("Expected '%s' to contain '%s'", value, target)
}}
}
func Equals(target string) *matcher {
return &matcher{testFn: func(value string) (bool, string) {
return target == value, fmt.Sprintf("Expected '%s' to equal '%s'", value, target)
}}
}
func (self *Assert) WorkingTreeFileCount(expectedCount int) { func (self *Assert) WorkingTreeFileCount(expectedCount int) {
self.assertWithRetries(func() (bool, string) { self.assertWithRetries(func() (bool, string) {
actualCount := len(self.gui.Model().Files) actualCount := len(self.gui.Model().Files)
@ -41,22 +78,16 @@ func (self *Assert) CommitCount(expectedCount int) {
}) })
} }
func (self *Assert) HeadCommitMessage(expectedMessage string) { func (self *Assert) MatchHeadCommitMessage(matcher *matcher) {
self.assertWithRetries(func() (bool, string) { self.assertWithRetries(func() (bool, string) {
if len(self.gui.Model().Commits) == 0 { return len(self.gui.Model().Commits) > 0, "Expected at least one commit to be present"
return false, "Expected at least one commit to be present"
}
headCommit := self.gui.Model().Commits[0]
if headCommit.Name != expectedMessage {
return false, fmt.Sprintf(
"Expected commit message to be '%s', but got '%s'",
expectedMessage, headCommit.Name,
)
}
return true, ""
}) })
self.matchString(matcher, "Unexpected commit message.",
func() string {
return self.gui.Model().Commits[0].Name
},
)
} }
func (self *Assert) CurrentViewName(expectedViewName string) { func (self *Assert) CurrentViewName(expectedViewName string) {
@ -81,10 +112,70 @@ func (self *Assert) InListContext() {
}) })
} }
func (self *Assert) SelectedLineContains(text string) { func (self *Assert) MatchSelectedLine(matcher *matcher) {
self.matchString(matcher, "Unexpected selected line.",
func() string {
return self.gui.CurrentContext().GetView().SelectedLine()
},
)
}
func (self *Assert) InPrompt() {
self.assertWithRetries(func() (bool, string) { self.assertWithRetries(func() (bool, string) {
line := self.gui.CurrentContext().GetView().SelectedLine() currentView := self.gui.CurrentContext().GetView()
return strings.Contains(line, text), fmt.Sprintf("Expected selected line to contain '%s', but got '%s'", text, line) return currentView.Name() == "confirmation" && currentView.Editable, "Expected prompt popup to be focused"
})
}
func (self *Assert) InConfirm() {
self.assertWithRetries(func() (bool, string) {
currentView := self.gui.CurrentContext().GetView()
return currentView.Name() == "confirmation" && !currentView.Editable, "Expected confirmation popup to be focused"
})
}
func (self *Assert) InAlert() {
// basically the same thing as a confirmation popup with the current implementation
self.assertWithRetries(func() (bool, string) {
currentView := self.gui.CurrentContext().GetView()
return currentView.Name() == "confirmation" && !currentView.Editable, "Expected alert popup to be focused"
})
}
func (self *Assert) InMenu() {
self.assertWithRetries(func() (bool, string) {
return self.gui.CurrentContext().GetView().Name() == "menu", "Expected popup menu to be focused"
})
}
func (self *Assert) MatchCurrentViewTitle(matcher *matcher) {
self.matchString(matcher, "Unexpected current view title.",
func() string {
return self.gui.CurrentContext().GetView().Title
},
)
}
func (self *Assert) MatchMainViewContent(matcher *matcher) {
self.matchString(matcher, "Unexpected main view content.",
func() string {
return self.gui.MainView().Buffer()
},
)
}
func (self *Assert) MatchSecondaryViewContent(matcher *matcher) {
self.matchString(matcher, "Unexpected secondary view title.",
func() string {
return self.gui.SecondaryView().Buffer()
},
)
}
func (self *Assert) matchString(matcher *matcher, context string, getValue func() string) {
self.assertWithRetries(func() (bool, string) {
value := getValue()
return matcher.context(context).test(value)
}) })
} }

View File

@ -77,7 +77,7 @@ func (self *Input) Cancel() {
} }
// i.e. pressing space // i.e. pressing space
func (self *Input) Select() { func (self *Input) PrimaryAction() {
self.pressKey(self.keys.Universal.Select) self.pressKey(self.keys.Universal.Select)
} }
@ -93,7 +93,7 @@ func (self *Input) PreviousItem() {
func (self *Input) ContinueMerge() { func (self *Input) ContinueMerge() {
self.PressKeys(self.keys.Universal.CreateRebaseOptionsMenu) self.PressKeys(self.keys.Universal.CreateRebaseOptionsMenu)
self.assert.SelectedLineContains("continue") self.assert.MatchSelectedLine(Contains("continue"))
self.Confirm() self.Confirm()
} }

View File

@ -53,7 +53,7 @@ func NewIntegrationTest(args NewIntegrationTestArgs) *IntegrationTest {
if args.Description != unitTestDescription { if args.Description != unitTestDescription {
// this panics if we're in a unit test for our integration tests, // this panics if we're in a unit test for our integration tests,
// so we're using "test test" as a sentinel value // so we're using "test test" as a sentinel value
name = testNameFromFilePath() name = testNameFromCurrentFilePath()
} }
return &IntegrationTest{ return &IntegrationTest{
@ -106,8 +106,12 @@ func (self *IntegrationTest) Run(gui integrationTypes.GuiDriver) {
} }
} }
func testNameFromFilePath() string { func testNameFromCurrentFilePath() string {
path := utils.FilePath(3) path := utils.FilePath(3)
return TestNameFromFilePath(path)
}
func TestNameFromFilePath(path string) string {
name := strings.Split(path, "integration/tests/")[1] name := strings.Split(path, "integration/tests/")[1]
return name[:len(name)-len(".go")] return name[:len(name)-len(".go")]

View File

@ -3,6 +3,7 @@ package components
import ( import (
"testing" "testing"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
@ -47,6 +48,14 @@ func (self *fakeGuiDriver) CheckedOutRef() *models.Branch {
return nil return nil
} }
func (self *fakeGuiDriver) MainView() *gocui.View {
return nil
}
func (self *fakeGuiDriver) SecondaryView() *gocui.View {
return nil
}
func TestAssertionFailure(t *testing.T) { func TestAssertionFailure(t *testing.T) {
test := NewIntegrationTest(NewIntegrationTestArgs{ test := NewIntegrationTest(NewIntegrationTestArgs{
Description: unitTestDescription, Description: unitTestDescription,

View File

@ -2,24 +2,24 @@ package commit
import ( import (
"github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/integration/components" . "github.com/jesseduffield/lazygit/pkg/integration/components"
) )
var Commit = components.NewIntegrationTest(components.NewIntegrationTestArgs{ var Commit = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Staging a couple files and committing", Description: "Staging a couple files and committing",
ExtraCmdArgs: "", ExtraCmdArgs: "",
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *components.Shell) { SetupRepo: func(shell *Shell) {
shell.CreateFile("myfile", "myfile content") shell.CreateFile("myfile", "myfile content")
shell.CreateFile("myfile2", "myfile2 content") shell.CreateFile("myfile2", "myfile2 content")
}, },
Run: func(shell *components.Shell, input *components.Input, assert *components.Assert, keys config.KeybindingConfig) { Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
assert.CommitCount(0) assert.CommitCount(0)
input.Select() input.PrimaryAction()
input.NextItem() input.NextItem()
input.Select() input.PrimaryAction()
input.PressKeys(keys.Files.CommitChanges) input.PressKeys(keys.Files.CommitChanges)
commitMessage := "my commit message" commitMessage := "my commit message"
@ -27,6 +27,6 @@ var Commit = components.NewIntegrationTest(components.NewIntegrationTestArgs{
input.Confirm() input.Confirm()
assert.CommitCount(1) assert.CommitCount(1)
assert.HeadCommitMessage(commitMessage) assert.MatchHeadCommitMessage(Equals(commitMessage))
}, },
}) })

View File

@ -2,21 +2,21 @@ package commit
import ( import (
"github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/integration/components" . "github.com/jesseduffield/lazygit/pkg/integration/components"
) )
var NewBranch = components.NewIntegrationTest(components.NewIntegrationTestArgs{ var NewBranch = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Creating a new branch from a commit", Description: "Creating a new branch from a commit",
ExtraCmdArgs: "", ExtraCmdArgs: "",
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *components.Shell) { SetupRepo: func(shell *Shell) {
shell. shell.
EmptyCommit("commit 1"). EmptyCommit("commit 1").
EmptyCommit("commit 2"). EmptyCommit("commit 2").
EmptyCommit("commit 3") EmptyCommit("commit 3")
}, },
Run: func(shell *components.Shell, input *components.Input, assert *components.Assert, keys config.KeybindingConfig) { Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
assert.CommitCount(3) assert.CommitCount(3)
input.SwitchToCommitsWindow() input.SwitchToCommitsWindow()
@ -32,7 +32,7 @@ var NewBranch = components.NewIntegrationTest(components.NewIntegrationTestArgs{
input.Confirm() input.Confirm()
assert.CommitCount(2) assert.CommitCount(2)
assert.HeadCommitMessage("commit 2") assert.MatchHeadCommitMessage(Contains("commit 2"))
assert.CurrentBranchName(branchName) assert.CurrentBranchName(branchName)
}, },
}) })

View File

@ -0,0 +1,36 @@
package custom_commands
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var Basic = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Using a custom command to create a new file",
ExtraCmdArgs: "",
Skip: false,
SetupRepo: func(shell *Shell) {
shell.EmptyCommit("blah")
},
SetupConfig: func(cfg *config.AppConfig) {
cfg.UserConfig.CustomCommands = []config.CustomCommand{
{
Key: "a",
Context: "files",
Command: "touch myfile",
},
}
},
Run: func(
shell *Shell,
input *Input,
assert *Assert,
keys config.KeybindingConfig,
) {
assert.WorkingTreeFileCount(0)
input.PressKeys("a")
assert.WorkingTreeFileCount(1)
assert.MatchSelectedLine(Contains("myfile"))
},
})

View File

@ -0,0 +1,74 @@
package custom_commands
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
// NOTE: we're getting a weird offset in the popup prompt for some reason. Not sure what's behind that.
var MenuFromCommand = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Using menuFromCommand prompt type",
ExtraCmdArgs: "",
Skip: false,
SetupRepo: func(shell *Shell) {
shell.
EmptyCommit("foo").
EmptyCommit("bar").
EmptyCommit("baz").
NewBranch("feature/foo")
},
SetupConfig: func(cfg *config.AppConfig) {
cfg.UserConfig.CustomCommands = []config.CustomCommand{
{
Key: "a",
Context: "localBranches",
Command: `echo "{{index .PromptResponses 0}} {{index .PromptResponses 1}} {{ .SelectedLocalBranch.Name }}" > output.txt`,
Prompts: []config.CustomCommandPrompt{
{
Type: "menuFromCommand",
Title: "Choose commit message",
Command: `git log --oneline --pretty=%B`,
Filter: `(?P<commit_message>.*)`,
ValueFormat: `{{ .commit_message }}`,
LabelFormat: `{{ .commit_message | yellow }}`,
},
{
Type: "input",
Title: "Description",
InitialValue: `{{ if .SelectedLocalBranch.Name }}Branch: #{{ .SelectedLocalBranch.Name }}{{end}}`,
},
},
},
}
},
Run: func(
shell *Shell,
input *Input,
assert *Assert,
keys config.KeybindingConfig,
) {
assert.WorkingTreeFileCount(0)
input.SwitchToBranchesWindow()
input.PressKeys("a")
assert.InMenu()
assert.MatchCurrentViewTitle(Equals("Choose commit message"))
assert.MatchSelectedLine(Equals("baz"))
input.NextItem()
assert.MatchSelectedLine(Equals("bar"))
input.Confirm()
assert.InPrompt()
assert.MatchCurrentViewTitle(Equals("Description"))
input.Type(" my branch")
input.Confirm()
input.SwitchToFilesWindow()
assert.WorkingTreeFileCount(1)
assert.MatchSelectedLine(Contains("output.txt"))
assert.MatchMainViewContent(Contains("bar Branch: #feature/foo my branch feature/foo"))
},
})

View File

@ -0,0 +1,86 @@
package custom_commands
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var MultiplePrompts = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Using a custom command with multiple prompts",
ExtraCmdArgs: "",
Skip: false,
SetupRepo: func(shell *Shell) {
shell.EmptyCommit("blah")
},
SetupConfig: func(cfg *config.AppConfig) {
cfg.UserConfig.CustomCommands = []config.CustomCommand{
{
Key: "a",
Context: "files",
Command: `echo "{{index .PromptResponses 1}}" > {{index .PromptResponses 0}}`,
Prompts: []config.CustomCommandPrompt{
{
Type: "input",
Title: "Enter a file name",
},
{
Type: "menu",
Title: "Choose file content",
Options: []config.CustomCommandMenuOption{
{
Name: "foo",
Description: "Foo",
Value: "FOO",
},
{
Name: "bar",
Description: "Bar",
Value: "BAR",
},
{
Name: "baz",
Description: "Baz",
Value: "BAZ",
},
},
},
{
Type: "confirm",
Title: "Are you sure?",
Body: "Are you REALLY sure you want to make this file? Up to you buddy.",
},
},
},
}
},
Run: func(
shell *Shell,
input *Input,
assert *Assert,
keys config.KeybindingConfig,
) {
assert.WorkingTreeFileCount(0)
input.PressKeys("a")
assert.InPrompt()
assert.MatchCurrentViewTitle(Equals("Enter a file name"))
input.Type("myfile")
input.Confirm()
assert.InMenu()
assert.MatchCurrentViewTitle(Equals("Choose file content"))
assert.MatchSelectedLine(Contains("foo"))
input.NextItem()
assert.MatchSelectedLine(Contains("bar"))
input.Confirm()
assert.InConfirm()
assert.MatchCurrentViewTitle(Equals("Are you sure?"))
input.Confirm()
assert.WorkingTreeFileCount(1)
assert.MatchSelectedLine(Contains("myfile"))
assert.MatchMainViewContent(Contains("BAR"))
},
})

View File

@ -2,37 +2,37 @@ package interactive_rebase
import ( import (
"github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/integration/components" . "github.com/jesseduffield/lazygit/pkg/integration/components"
) )
var One = components.NewIntegrationTest(components.NewIntegrationTestArgs{ var One = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Begins an interactive rebase, then fixups, drops, and squashes some commits", Description: "Begins an interactive rebase, then fixups, drops, and squashes some commits",
ExtraCmdArgs: "", ExtraCmdArgs: "",
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *components.Shell) { SetupRepo: func(shell *Shell) {
shell. shell.
CreateNCommits(5) // these will appears at commit 05, 04, 04, down to 01 CreateNCommits(5) // these will appears at commit 05, 04, 04, down to 01
}, },
Run: func(shell *components.Shell, input *components.Input, assert *components.Assert, keys config.KeybindingConfig) { Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
input.SwitchToCommitsWindow() input.SwitchToCommitsWindow()
assert.CurrentViewName("commits") assert.CurrentViewName("commits")
input.NavigateToListItemContainingText("commit 02") input.NavigateToListItemContainingText("commit 02")
input.PressKeys(keys.Universal.Edit) input.PressKeys(keys.Universal.Edit)
assert.SelectedLineContains("YOU ARE HERE") assert.MatchSelectedLine(Contains("YOU ARE HERE"))
input.PreviousItem() input.PreviousItem()
input.PressKeys(keys.Commits.MarkCommitAsFixup) input.PressKeys(keys.Commits.MarkCommitAsFixup)
assert.SelectedLineContains("fixup") assert.MatchSelectedLine(Contains("fixup"))
input.PreviousItem() input.PreviousItem()
input.PressKeys(keys.Universal.Remove) input.PressKeys(keys.Universal.Remove)
assert.SelectedLineContains("drop") assert.MatchSelectedLine(Contains("drop"))
input.PreviousItem() input.PreviousItem()
input.PressKeys(keys.Commits.SquashDown) input.PressKeys(keys.Commits.SquashDown)
assert.SelectedLineContains("squash") assert.MatchSelectedLine(Contains("squash"))
input.ContinueRebase() input.ContinueRebase()

View File

@ -1,18 +1,74 @@
package tests package tests
import ( import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/jesseduffield/generics/set"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/integration/components" "github.com/jesseduffield/lazygit/pkg/integration/components"
"github.com/jesseduffield/lazygit/pkg/integration/tests/branch" "github.com/jesseduffield/lazygit/pkg/integration/tests/branch"
"github.com/jesseduffield/lazygit/pkg/integration/tests/commit" "github.com/jesseduffield/lazygit/pkg/integration/tests/commit"
"github.com/jesseduffield/lazygit/pkg/integration/tests/custom_commands"
"github.com/jesseduffield/lazygit/pkg/integration/tests/interactive_rebase" "github.com/jesseduffield/lazygit/pkg/integration/tests/interactive_rebase"
"github.com/jesseduffield/lazygit/pkg/utils"
) )
// Here is where we lists the actual tests that will run. When you create a new test, // Here is where we lists the actual tests that will run. When you create a new test,
// be sure to add it to this list. // be sure to add it to this list.
var Tests = []*components.IntegrationTest{ var tests = []*components.IntegrationTest{
commit.Commit, commit.Commit,
commit.NewBranch, commit.NewBranch,
branch.Suggestions, branch.Suggestions,
interactive_rebase.One, interactive_rebase.One,
custom_commands.Basic,
custom_commands.MultiplePrompts,
custom_commands.MenuFromCommand,
}
func GetTests() []*components.IntegrationTest {
// first we ensure that each test in this directory has actually been added to the above list.
testCount := 0
testNamesSet := set.NewFromSlice(slices.Map(
tests,
func(test *components.IntegrationTest) string {
return test.Name()
},
))
missingTestNames := []string{}
if err := filepath.Walk(filepath.Join(utils.GetLazygitRootDirectory(), "pkg/integration/tests"), func(path string, info os.FileInfo, err error) error {
if !info.IsDir() && strings.HasSuffix(path, ".go") {
// ignoring this current file
if filepath.Base(path) == "tests.go" {
return nil
}
nameFromPath := components.TestNameFromFilePath(path)
if !testNamesSet.Includes(nameFromPath) {
missingTestNames = append(missingTestNames, nameFromPath)
}
testCount++
}
return nil
}); err != nil {
panic(fmt.Sprintf("failed to walk tests: %v", err))
}
if len(missingTestNames) > 0 {
panic(fmt.Sprintf("The following tests are missing from the list of tests: %s. You need to add them to `pkg/integration/tests/tests.go`.", strings.Join(missingTestNames, ", ")))
}
if testCount > len(tests) {
panic("you have not added all of the tests to the tests list in `pkg/integration/tests/tests.go`")
} else if testCount < len(tests) {
panic("There are more tests in `pkg/integration/tests/tests.go` than there are test files in the tests directory. Ensure that you only have one test per file and you haven't included the same test twice in the tests list.")
}
return tests
} }

View File

@ -1,6 +1,7 @@
package types package types
import ( import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
@ -28,4 +29,9 @@ type GuiDriver interface {
// logs in the actual UI (in the commands panel) // logs in the actual UI (in the commands panel)
LogUI(message string) LogUI(message string)
CheckedOutRef() *models.Branch CheckedOutRef() *models.Branch
// the view that appears to the right of the side panel
MainView() *gocui.View
// the other view that sometimes appears to the right of the side panel
// e.g. when we're showing both staged and unstaged changes
SecondaryView() *gocui.View
} }

View File

@ -1,18 +0,0 @@
disableStartupPopups: true
customCommands:
- key : 'N'
description: 'Add file'
command: "echo {{index .PromptResponses 0}} > {{index .PromptResponses 1}}"
context: 'files'
prompts:
- type: 'input'
title: 'File name:'
- type: 'input'
title: 'File content:'
gui:
theme:
activeBorderColor:
- green
- bold
SelectedRangeBgcolor:
- reverse

View File

@ -1 +0,0 @@
0000000000000000000000000000000000000000 15bdb2c31c825116ad5af06ee25517d90b24f13b CI <CI@example.com> 1617684452 +1000 commit (initial): test

View File

@ -1 +0,0 @@
0000000000000000000000000000000000000000 15bdb2c31c825116ad5af06ee25517d90b24f13b CI <CI@example.com> 1617684452 +1000 commit (initial): test

View File

@ -1 +0,0 @@
15bdb2c31c825116ad5af06ee25517d90b24f13b

View File

@ -1 +0,0 @@
myfile

View File

@ -1 +0,0 @@
{"KeyEvents":[{"Timestamp":837,"Mod":0,"Key":256,"Ch":78},{"Timestamp":1622,"Mod":0,"Key":256,"Ch":109},{"Timestamp":1798,"Mod":0,"Key":256,"Ch":121},{"Timestamp":1918,"Mod":0,"Key":256,"Ch":102},{"Timestamp":2006,"Mod":0,"Key":256,"Ch":105},{"Timestamp":2078,"Mod":0,"Key":256,"Ch":108},{"Timestamp":2174,"Mod":0,"Key":256,"Ch":101},{"Timestamp":2431,"Mod":0,"Key":13,"Ch":13},{"Timestamp":3246,"Mod":0,"Key":256,"Ch":98},{"Timestamp":3294,"Mod":0,"Key":256,"Ch":108},{"Timestamp":3398,"Mod":0,"Key":256,"Ch":97},{"Timestamp":3462,"Mod":0,"Key":256,"Ch":104},{"Timestamp":3735,"Mod":0,"Key":13,"Ch":13},{"Timestamp":4206,"Mod":0,"Key":256,"Ch":32},{"Timestamp":4421,"Mod":0,"Key":256,"Ch":99},{"Timestamp":4646,"Mod":0,"Key":256,"Ch":116},{"Timestamp":4726,"Mod":0,"Key":256,"Ch":101},{"Timestamp":4886,"Mod":0,"Key":256,"Ch":115},{"Timestamp":4918,"Mod":0,"Key":256,"Ch":116},{"Timestamp":5190,"Mod":0,"Key":13,"Ch":13},{"Timestamp":5550,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]}

View File

@ -1,10 +0,0 @@
#!/bin/sh
set -e
cd $1
git init
git config user.email "CI@example.com"
git config user.name "CI"

View File

@ -1 +0,0 @@
{ "description": "Invoke a custom command that creates a file, and then stage and commit that file", "speed": 5 }

View File

@ -1,31 +0,0 @@
disableStartupPopups: true
customCommands:
- key: 'N'
description: 'Add file'
context: 'localBranches'
command: 'echo "{{index .PromptResponses 0}} {{index .PromptResponses 1}} {{index .PromptResponses 2}} {{ .SelectedLocalBranch.Name }}" > output.txt'
loadingText: 'Running custom command...'
prompts:
- type: 'menuFromCommand'
title: 'Title'
command: 'git log --oneline --pretty=%B'
filter: '(?P<commit_message>.*)'
valueFormat: '{{ .commit_message }}'
labelFormat: '{{ .commit_message | yellow }}'
- type: 'input'
title: 'Description'
initialValue: "{{ if .SelectedLocalBranch.Name }}Branch: #{{ .SelectedLocalBranch.Name }}{{end}}"
- type: 'menu'
title: 'yes or no'
options:
- name: 'no'
value: 'false'
- name: 'yes'
value: 'true'
gui:
theme:
activeBorderColor:
- green
- bold
SelectedRangeBgcolor:
- reverse

View File

@ -1,5 +0,0 @@
0000000000000000000000000000000000000000 ab38b1ca116f77648925d952e731f419db360cdb CI <CI@example.com> 1642201096 +1100 commit (initial): myfile1
ab38b1ca116f77648925d952e731f419db360cdb 4fdfedfd9d406506be8b02f5b863dbc08d43cc9f CI <CI@example.com> 1642201096 +1100 commit: myfile2
4fdfedfd9d406506be8b02f5b863dbc08d43cc9f 7dd93a4be3d27d40fbe791d6d77e0d2fedc4d785 CI <CI@example.com> 1642201096 +1100 commit: myfile3
7dd93a4be3d27d40fbe791d6d77e0d2fedc4d785 f708d3e3819470a69f6c8562ff1e68eef02f8cac CI <CI@example.com> 1642201096 +1100 commit: myfile4
f708d3e3819470a69f6c8562ff1e68eef02f8cac 5428838691c97ac192c8b8e1c3f573d8541a94b6 CI <CI@example.com> 1642201104 +1100 commit: test

View File

@ -1,5 +0,0 @@
0000000000000000000000000000000000000000 ab38b1ca116f77648925d952e731f419db360cdb CI <CI@example.com> 1642201096 +1100 commit (initial): myfile1
ab38b1ca116f77648925d952e731f419db360cdb 4fdfedfd9d406506be8b02f5b863dbc08d43cc9f CI <CI@example.com> 1642201096 +1100 commit: myfile2
4fdfedfd9d406506be8b02f5b863dbc08d43cc9f 7dd93a4be3d27d40fbe791d6d77e0d2fedc4d785 CI <CI@example.com> 1642201096 +1100 commit: myfile3
7dd93a4be3d27d40fbe791d6d77e0d2fedc4d785 f708d3e3819470a69f6c8562ff1e68eef02f8cac CI <CI@example.com> 1642201096 +1100 commit: myfile4
f708d3e3819470a69f6c8562ff1e68eef02f8cac 5428838691c97ac192c8b8e1c3f573d8541a94b6 CI <CI@example.com> 1642201104 +1100 commit: test

View File

@ -1,2 +0,0 @@
x�ÍA
ƒ0@Ñ®sŠÙÊL:#)¸ò1™PÁ!")ØÛ×#tûyðS5[Ë¥íª€*©`”¹ë5df¥ 9��T:žùž…KLï⧽ëãÏqzém[õ–ª @ÂÞ#a/p%Btg='MÿäξeY•Ü.,,·

View File

@ -1,2 +0,0 @@
x�ЮA
Т0@Qз9Eі�Ь$�I"BW=F��`СиR"шээм~от�ЕЕЅ[LtъЛЊu�1№�*в�ЬaШ>АFvфCЩ!ЁйђЎЏnЃHђ�fѕтЂдYcBa�QA\U)$q&Пћcнэ8йы8нѕ�лідKYлЭ"�s��и�ЬQ�ЉЎrгОuy*�3н9�

View File

@ -1 +0,0 @@
5428838691c97ac192c8b8e1c3f573d8541a94b6

View File

@ -1 +0,0 @@
myfile2 Branch: #master haha true master

View File

@ -1 +0,0 @@
{"KeyEvents":[{"Timestamp":623,"Mod":0,"Key":259,"Ch":0},{"Timestamp":1369,"Mod":0,"Key":256,"Ch":78},{"Timestamp":1904,"Mod":0,"Key":258,"Ch":0},{"Timestamp":2033,"Mod":0,"Key":258,"Ch":0},{"Timestamp":2328,"Mod":0,"Key":13,"Ch":13},{"Timestamp":2848,"Mod":0,"Key":256,"Ch":32},{"Timestamp":3296,"Mod":0,"Key":256,"Ch":97},{"Timestamp":3616,"Mod":0,"Key":127,"Ch":127},{"Timestamp":3824,"Mod":0,"Key":256,"Ch":104},{"Timestamp":3879,"Mod":0,"Key":256,"Ch":97},{"Timestamp":3927,"Mod":0,"Key":256,"Ch":104},{"Timestamp":4000,"Mod":0,"Key":256,"Ch":97},{"Timestamp":4239,"Mod":0,"Key":13,"Ch":13},{"Timestamp":4809,"Mod":0,"Key":258,"Ch":0},{"Timestamp":5024,"Mod":0,"Key":13,"Ch":13},{"Timestamp":5824,"Mod":0,"Key":260,"Ch":0},{"Timestamp":6079,"Mod":0,"Key":256,"Ch":32},{"Timestamp":6376,"Mod":0,"Key":256,"Ch":99},{"Timestamp":6591,"Mod":0,"Key":256,"Ch":116},{"Timestamp":6640,"Mod":0,"Key":256,"Ch":101},{"Timestamp":6816,"Mod":0,"Key":256,"Ch":115},{"Timestamp":6856,"Mod":0,"Key":256,"Ch":116},{"Timestamp":7136,"Mod":0,"Key":13,"Ch":13},{"Timestamp":7487,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":36}]}

View File

@ -1,23 +0,0 @@
#!/bin/sh
set -e
cd $1
git init
git config user.email "CI@example.com"
git config user.name "CI"
echo test1 > myfile1
git add .
git commit -am "myfile1"
echo test2 > myfile2
git add .
git commit -am "myfile2"
echo test3 > myfile3
git add .
git commit -am "myfile3"
echo test4 > myfile4
git add .
git commit -am "myfile4"

View File

@ -1,4 +0,0 @@
{
"description": "Invoke a custom command that creates a file, and then stage and commit that file. In this case we're using a more customised flow",
"speed": 5
}

View File

@ -0,0 +1 @@
0000000000000000000000000000000000000000 fe47c0cf0521f8864cd0531ddf35d2f741c14abf CI <CI@example.com> 1660476851 +1000 commit (initial): blah

View File

@ -0,0 +1 @@
0000000000000000000000000000000000000000 fe47c0cf0521f8864cd0531ddf35d2f741c14abf CI <CI@example.com> 1660476851 +1000 commit (initial): blah

View File

@ -0,0 +1 @@
fe47c0cf0521f8864cd0531ddf35d2f741c14abf

View File

@ -0,0 +1 @@
ref: refs/heads/feature/foo

View File

@ -0,0 +1,4 @@
0000000000000000000000000000000000000000 d50975554a574b9c66e109927fdb4edfb6bbadb3 CI <CI@example.com> 1660476303 +1000 commit (initial): foo
d50975554a574b9c66e109927fdb4edfb6bbadb3 af550d3777f20bf024ad55c9c796e7e85ef32ccb CI <CI@example.com> 1660476303 +1000 commit: bar
af550d3777f20bf024ad55c9c796e7e85ef32ccb 16919871d6b442beac07e1573c557ca433cff356 CI <CI@example.com> 1660476303 +1000 commit: baz
16919871d6b442beac07e1573c557ca433cff356 16919871d6b442beac07e1573c557ca433cff356 CI <CI@example.com> 1660476303 +1000 checkout: moving from master to feature/foo

View File

@ -0,0 +1 @@
0000000000000000000000000000000000000000 16919871d6b442beac07e1573c557ca433cff356 CI <CI@example.com> 1660476303 +1000 branch: Created from HEAD

View File

@ -0,0 +1,3 @@
0000000000000000000000000000000000000000 d50975554a574b9c66e109927fdb4edfb6bbadb3 CI <CI@example.com> 1660476303 +1000 commit (initial): foo
d50975554a574b9c66e109927fdb4edfb6bbadb3 af550d3777f20bf024ad55c9c796e7e85ef32ccb CI <CI@example.com> 1660476303 +1000 commit: bar
af550d3777f20bf024ad55c9c796e7e85ef32ccb 16919871d6b442beac07e1573c557ca433cff356 CI <CI@example.com> 1660476303 +1000 commit: baz

View File

@ -0,0 +1,3 @@
x�ֽA
ֲ0@Q׳9ֵל™₪׃1)BW=F&� `˜R"פרצn?~±ײ�<…K�U�$†q-L¡«₪��:’װ¸r
1VQ¥„הע·¿l‡y�ַ¼<ץָm�ט­X›ְ3#�yְ®�YֿI׳?¹«fמ~�+T

View File

@ -0,0 +1 @@
16919871d6b442beac07e1573c557ca433cff356

View File

@ -0,0 +1 @@
16919871d6b442beac07e1573c557ca433cff356

View File

@ -0,0 +1 @@
bar Branch: #feature/foo my branch feature/foo

View File

@ -0,0 +1,10 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[user]
email = CI@example.com
name = CI

View File

@ -0,0 +1 @@
Unnamed repository; edit this file 'description' to name the repository.

View File

@ -0,0 +1,7 @@
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
.DS_Store

View File

@ -0,0 +1 @@
0000000000000000000000000000000000000000 b97a1d7c0e8dceef724220008962f8512a974ff0 CI <CI@example.com> 1660476863 +1000 commit (initial): blah

Some files were not shown because too many files have changed in this diff Show More