mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-07-15 01:34:26 +02:00
Merge pull request #2334 from jesseduffield/more-test-refactoring
This commit is contained in:
@ -49,10 +49,6 @@ func (self *GuiDriver) CurrentContext() types.Context {
|
||||
return self.gui.c.CurrentContext()
|
||||
}
|
||||
|
||||
func (self *GuiDriver) Model() *types.Model {
|
||||
return self.gui.State.Model
|
||||
}
|
||||
|
||||
func (self *GuiDriver) Fail(message string) {
|
||||
self.gui.g.Close()
|
||||
// need to give the gui time to close
|
||||
|
@ -24,18 +24,13 @@ In the setup step, we prepare a repo with shell commands, for example, creating
|
||||
|
||||
### Run step
|
||||
|
||||
The run step has four arguments passed in:
|
||||
The run step has two arguments passed in:
|
||||
|
||||
1. `shell`
|
||||
2. `input`
|
||||
3. `assert`
|
||||
4. `keys`
|
||||
1. `t` (the test driver)
|
||||
2. `keys`
|
||||
|
||||
`shell` we've already seen in the setup step. The reason it's passed into the run step is that we may want to emulate background events. For example, the user modifying a file outside of lazygit.
|
||||
|
||||
`input` is for driving the gui by pressing certain keys, selecting list items, etc.
|
||||
|
||||
`assert` is for asserting on the state of the lazygit session. When you call a method on `assert`, the assert struct will wait for the assertion to hold true and then continue (failing the test after a timeout). For this reason, assertions have two purposes: one is to ensure the test fails as soon as something unexpected happens, but another is to allow lazygit to process a keypress before you follow up with more keypresses. If you input a bunch of keypresses too quickly lazygit might get confused.
|
||||
`t` is for driving the gui by pressing certain keys, selecting list items, etc.
|
||||
`keys` is for use when getting the test to press a particular key e.g. `t.Views().Commits().Focus().PressKey(keys.Universal.Confirm)`
|
||||
|
||||
### Tips
|
||||
|
||||
@ -43,33 +38,11 @@ The run step has four arguments passed in:
|
||||
|
||||
Try to do as much setup work as possible in your setup step. For example, if all you're testing is that the user is able to resolve merge conflicts, create the merge conflicts in the setup step. On the other hand, if you're testing to see that lazygit can warn the user about merge conflicts after an attempted merge, it's fine to wait until the run step to actually create the conflicts. If the run step is focused on the thing you're trying to test, the test will run faster and its intent will be clearer.
|
||||
|
||||
#### Assert after input
|
||||
|
||||
Use assertions to ensure that lazygit has processed all your keybindings so far. Each time you press a key, something should happen on the screen, so you should assert that that thing has happened. This means we won't get into trouble from keys being entered two quickly because at each stage we ensure the key has been processed. This also makes tests more readable because they help explain what we expect to be happening on-screen. For example:
|
||||
|
||||
```go
|
||||
input.Press(keys.Files.CommitChanges)
|
||||
assert.InCommitMessagePanel()
|
||||
```
|
||||
|
||||
Note that there are some `input` methods that have assertions baked in, such as the `SwitchToView` methods.
|
||||
|
||||
#### Create helper functions for (very) frequently used test logic
|
||||
|
||||
If you find yourself doing something frequently in a test, consider making it a method in one of the helper arguments. For example, instead of calling `input.PressKey(keys.Universal.Confirm)` in 100 places, it's better to have a method `input.Confirm()`. This is not to say that everything should be made into a method on the input struct: just things that are particularly common in tests.
|
||||
If you find yourself doing something frequently in a test, consider making it a method in one of the helper arguments. For example, instead of calling `t.PressKey(keys.Universal.Confirm)` in 100 places, it's better to have a method `t.Confirm()`. This is not to say that everything should be made into a helper method: just things that are particularly common in tests.
|
||||
|
||||
Also, given how often we need to select a menu item or type into a prompt panel, there are some helper functions for that. For example:
|
||||
|
||||
```go
|
||||
// asserts that a prompt opens with the title 'Enter a file name', and then types 'my file' and confirms
|
||||
input.Prompt(Equals("Enter a file name"), "my file")
|
||||
|
||||
// asserts that a menu opens with the title: 'Choose file content', and then selects the option which contains 'bar'
|
||||
input.Menu(Equals("Choose file content"), Contains("bar"))
|
||||
|
||||
// asserts a confirmation appears with the title 'Are you sure?' and the content 'Are you REALLY sure' and then confirms
|
||||
input.AcceptConfirmation(Equals("Are you sure?"), Equals("Are you REALLY sure?"))
|
||||
```
|
||||
Also, given how often we need to select a menu item or type into a prompt panel, there are some helper functions for that. See `ExpectConfirmation` for an example.
|
||||
|
||||
## Running tests
|
||||
|
||||
|
19
pkg/integration/components/actions.go
Normal file
19
pkg/integration/components/actions.go
Normal file
@ -0,0 +1,19 @@
|
||||
package components
|
||||
|
||||
// for running common actions
|
||||
type Actions struct {
|
||||
t *TestDriver
|
||||
}
|
||||
|
||||
func (self *Actions) ContinueMerge() {
|
||||
self.t.Views().current().Press(self.t.keys.Universal.CreateRebaseOptionsMenu)
|
||||
|
||||
self.t.ExpectPopup().Menu().
|
||||
Title(Equals("Rebase Options")).
|
||||
Select(Contains("continue")).
|
||||
Confirm()
|
||||
}
|
||||
|
||||
func (self *Actions) ContinueRebase() {
|
||||
self.ContinueMerge()
|
||||
}
|
47
pkg/integration/components/alert_asserter.go
Normal file
47
pkg/integration/components/alert_asserter.go
Normal file
@ -0,0 +1,47 @@
|
||||
package components
|
||||
|
||||
type AlertAsserter struct {
|
||||
t *TestDriver
|
||||
hasCheckedTitle bool
|
||||
hasCheckedContent bool
|
||||
}
|
||||
|
||||
func (self *AlertAsserter) getViewAsserter() *View {
|
||||
return self.t.Views().Confirmation()
|
||||
}
|
||||
|
||||
// asserts that the alert view has the expected title
|
||||
func (self *AlertAsserter) Title(expected *matcher) *AlertAsserter {
|
||||
self.getViewAsserter().Title(expected)
|
||||
|
||||
self.hasCheckedTitle = true
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// asserts that the alert view has the expected content
|
||||
func (self *AlertAsserter) Content(expected *matcher) *AlertAsserter {
|
||||
self.getViewAsserter().Content(expected)
|
||||
|
||||
self.hasCheckedContent = true
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *AlertAsserter) Confirm() {
|
||||
self.checkNecessaryChecksCompleted()
|
||||
|
||||
self.getViewAsserter().PressEnter()
|
||||
}
|
||||
|
||||
func (self *AlertAsserter) Cancel() {
|
||||
self.checkNecessaryChecksCompleted()
|
||||
|
||||
self.getViewAsserter().PressEscape()
|
||||
}
|
||||
|
||||
func (self *AlertAsserter) checkNecessaryChecksCompleted() {
|
||||
if !self.hasCheckedContent || !self.hasCheckedTitle {
|
||||
self.t.Fail("You must both check the content and title of a confirmation popup by calling Title()/Content() before calling Confirm()/Cancel().")
|
||||
}
|
||||
}
|
@ -1,215 +0,0 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
|
||||
)
|
||||
|
||||
// through this struct we assert on the state of the lazygit gui
|
||||
|
||||
type Assert struct {
|
||||
gui integrationTypes.GuiDriver
|
||||
}
|
||||
|
||||
func NewAssert(gui integrationTypes.GuiDriver) *Assert {
|
||||
return &Assert{gui: gui}
|
||||
}
|
||||
|
||||
func (self *Assert) WorkingTreeFileCount(expectedCount int) {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
actualCount := len(self.gui.Model().Files)
|
||||
|
||||
return actualCount == expectedCount, fmt.Sprintf(
|
||||
"Expected %d changed working tree files, but got %d",
|
||||
expectedCount, actualCount,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func (self *Assert) CommitCount(expectedCount int) {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
actualCount := len(self.gui.Model().Commits)
|
||||
|
||||
return actualCount == expectedCount, fmt.Sprintf(
|
||||
"Expected %d commits present, but got %d",
|
||||
expectedCount, actualCount,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func (self *Assert) StashCount(expectedCount int) {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
actualCount := len(self.gui.Model().StashEntries)
|
||||
|
||||
return actualCount == expectedCount, fmt.Sprintf(
|
||||
"Expected %d stash entries, but got %d",
|
||||
expectedCount, actualCount,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func (self *Assert) AtLeastOneCommit() {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
actualCount := len(self.gui.Model().Commits)
|
||||
|
||||
return actualCount > 0, "Expected at least one commit present"
|
||||
})
|
||||
}
|
||||
|
||||
func (self *Assert) HeadCommitMessage(matcher *matcher) {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
return len(self.gui.Model().Commits) > 0, "Expected at least one commit to be present"
|
||||
})
|
||||
|
||||
self.matchString(matcher, "Unexpected commit message.",
|
||||
func() string {
|
||||
return self.gui.Model().Commits[0].Name
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (self *Assert) CurrentWindowName(expectedWindowName string) {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
actual := self.gui.CurrentContext().GetView().Name()
|
||||
return actual == expectedWindowName, fmt.Sprintf("Expected current window name to be '%s', but got '%s'", expectedWindowName, actual)
|
||||
})
|
||||
}
|
||||
|
||||
func (self *Assert) CurrentBranchName(expectedViewName string) {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
actual := self.gui.CheckedOutRef().Name
|
||||
return actual == expectedViewName, fmt.Sprintf("Expected current branch name to be '%s', but got '%s'", expectedViewName, actual)
|
||||
})
|
||||
}
|
||||
|
||||
func (self *Assert) InListContext() {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
currentContext := self.gui.CurrentContext()
|
||||
_, ok := currentContext.(types.IListContext)
|
||||
return ok, fmt.Sprintf("Expected current context to be a list context, but got %s", currentContext.GetKey())
|
||||
})
|
||||
}
|
||||
|
||||
func (self *Assert) InPrompt() {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
currentView := self.gui.CurrentContext().GetView()
|
||||
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) InCommitMessagePanel() {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
currentView := self.gui.CurrentContext().GetView()
|
||||
return currentView.Name() == "commitMessage", "Expected commit message panel 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) NotInPopup() {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
currentViewName := self.gui.CurrentContext().GetView().Name()
|
||||
return currentViewName != "menu" && currentViewName != "confirmation" && currentViewName != "commitMessage", "Expected popup not to be focused"
|
||||
})
|
||||
}
|
||||
|
||||
func (self *Assert) matchString(matcher *matcher, context string, getValue func() string) {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
value := getValue()
|
||||
return matcher.context(context).test(value)
|
||||
})
|
||||
}
|
||||
|
||||
func (self *Assert) assertWithRetries(test func() (bool, string)) {
|
||||
waitTimes := []int{0, 1, 1, 1, 1, 1, 5, 10, 20, 40, 100, 200, 500, 1000, 2000, 4000}
|
||||
|
||||
var message string
|
||||
for _, waitTime := range waitTimes {
|
||||
time.Sleep(time.Duration(waitTime) * time.Millisecond)
|
||||
|
||||
var ok bool
|
||||
ok, message = test()
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
self.Fail(message)
|
||||
}
|
||||
|
||||
// for when you just want to fail the test yourself
|
||||
func (self *Assert) Fail(message string) {
|
||||
self.gui.Fail(message)
|
||||
}
|
||||
|
||||
// This does _not_ check the files panel, it actually checks the filesystem
|
||||
func (self *Assert) FileSystemPathPresent(path string) {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
_, err := os.Stat(path)
|
||||
return err == nil, fmt.Sprintf("Expected path '%s' to exist, but it does not", path)
|
||||
})
|
||||
}
|
||||
|
||||
// This does _not_ check the files panel, it actually checks the filesystem
|
||||
func (self *Assert) FileSystemPathNotPresent(path string) {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
_, err := os.Stat(path)
|
||||
return os.IsNotExist(err), fmt.Sprintf("Expected path '%s' to not exist, but it does", path)
|
||||
})
|
||||
}
|
||||
|
||||
func (self *Assert) CurrentView() *ViewAsserter {
|
||||
return &ViewAsserter{
|
||||
context: "current view",
|
||||
getView: func() *gocui.View { return self.gui.CurrentContext().GetView() },
|
||||
assert: self,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Assert) View(viewName string) *ViewAsserter {
|
||||
return &ViewAsserter{
|
||||
context: fmt.Sprintf("%s view", viewName),
|
||||
getView: func() *gocui.View { return self.gui.View(viewName) },
|
||||
assert: self,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Assert) MainView() *ViewAsserter {
|
||||
return &ViewAsserter{
|
||||
context: "main view",
|
||||
getView: func() *gocui.View { return self.gui.MainView() },
|
||||
assert: self,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Assert) SecondaryView() *ViewAsserter {
|
||||
return &ViewAsserter{
|
||||
context: "secondary view",
|
||||
getView: func() *gocui.View { return self.gui.SecondaryView() },
|
||||
assert: self,
|
||||
}
|
||||
}
|
40
pkg/integration/components/assertion_helper.go
Normal file
40
pkg/integration/components/assertion_helper.go
Normal file
@ -0,0 +1,40 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
|
||||
)
|
||||
|
||||
type assertionHelper struct {
|
||||
gui integrationTypes.GuiDriver
|
||||
}
|
||||
|
||||
// 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) {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
value := getValue()
|
||||
return matcher.context(context).test(value)
|
||||
})
|
||||
}
|
||||
|
||||
func (self *assertionHelper) assertWithRetries(test func() (bool, string)) {
|
||||
var message string
|
||||
for _, waitTime := range retryWaitTimes {
|
||||
time.Sleep(time.Duration(waitTime) * time.Millisecond)
|
||||
|
||||
var ok bool
|
||||
ok, message = test()
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
self.fail(message)
|
||||
}
|
||||
|
||||
func (self *assertionHelper) fail(message string) {
|
||||
self.gui.Fail(message)
|
||||
}
|
40
pkg/integration/components/commit_message_panel_asserter.go
Normal file
40
pkg/integration/components/commit_message_panel_asserter.go
Normal file
@ -0,0 +1,40 @@
|
||||
package components
|
||||
|
||||
type CommitMessagePanelAsserter struct {
|
||||
t *TestDriver
|
||||
}
|
||||
|
||||
func (self *CommitMessagePanelAsserter) getViewAsserter() *View {
|
||||
return self.t.Views().CommitMessage()
|
||||
}
|
||||
|
||||
// asserts on the text initially present in the prompt
|
||||
func (self *CommitMessagePanelAsserter) InitialText(expected *matcher) *CommitMessagePanelAsserter {
|
||||
self.getViewAsserter().Content(expected)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *CommitMessagePanelAsserter) Type(value string) *CommitMessagePanelAsserter {
|
||||
self.t.typeContent(value)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *CommitMessagePanelAsserter) AddNewline() *CommitMessagePanelAsserter {
|
||||
self.t.press(self.t.keys.Universal.AppendNewline)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *CommitMessagePanelAsserter) Clear() *CommitMessagePanelAsserter {
|
||||
panic("Clear method not yet implemented!")
|
||||
}
|
||||
|
||||
func (self *CommitMessagePanelAsserter) Confirm() {
|
||||
self.getViewAsserter().PressEnter()
|
||||
}
|
||||
|
||||
func (self *CommitMessagePanelAsserter) Cancel() {
|
||||
self.getViewAsserter().PressEscape()
|
||||
}
|
47
pkg/integration/components/confirmation_asserter.go
Normal file
47
pkg/integration/components/confirmation_asserter.go
Normal file
@ -0,0 +1,47 @@
|
||||
package components
|
||||
|
||||
type ConfirmationAsserter struct {
|
||||
t *TestDriver
|
||||
hasCheckedTitle bool
|
||||
hasCheckedContent bool
|
||||
}
|
||||
|
||||
func (self *ConfirmationAsserter) getViewAsserter() *View {
|
||||
return self.t.Views().Confirmation()
|
||||
}
|
||||
|
||||
// asserts that the confirmation view has the expected title
|
||||
func (self *ConfirmationAsserter) Title(expected *matcher) *ConfirmationAsserter {
|
||||
self.getViewAsserter().Title(expected)
|
||||
|
||||
self.hasCheckedTitle = true
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// asserts that the confirmation view has the expected content
|
||||
func (self *ConfirmationAsserter) Content(expected *matcher) *ConfirmationAsserter {
|
||||
self.getViewAsserter().Content(expected)
|
||||
|
||||
self.hasCheckedContent = true
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *ConfirmationAsserter) Confirm() {
|
||||
self.checkNecessaryChecksCompleted()
|
||||
|
||||
self.getViewAsserter().PressEnter()
|
||||
}
|
||||
|
||||
func (self *ConfirmationAsserter) Cancel() {
|
||||
self.checkNecessaryChecksCompleted()
|
||||
|
||||
self.getViewAsserter().PressEscape()
|
||||
}
|
||||
|
||||
func (self *ConfirmationAsserter) checkNecessaryChecksCompleted() {
|
||||
if !self.hasCheckedContent || !self.hasCheckedTitle {
|
||||
self.t.Fail("You must both check the content and title of a confirmation popup by calling Title()/Content() before calling Confirm()/Cancel().")
|
||||
}
|
||||
}
|
26
pkg/integration/components/file_system.go
Normal file
26
pkg/integration/components/file_system.go
Normal file
@ -0,0 +1,26 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
type FileSystem struct {
|
||||
*assertionHelper
|
||||
}
|
||||
|
||||
// This does _not_ check the files panel, it actually checks the filesystem
|
||||
func (self *FileSystem) PathPresent(path string) {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
_, err := os.Stat(path)
|
||||
return err == nil, fmt.Sprintf("Expected path '%s' to exist, but it does not", path)
|
||||
})
|
||||
}
|
||||
|
||||
// This does _not_ check the files panel, it actually checks the filesystem
|
||||
func (self *FileSystem) PathNotPresent(path string) {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
_, err := os.Stat(path)
|
||||
return os.IsNotExist(err), fmt.Sprintf("Expected path '%s' to not exist, but it does", path)
|
||||
})
|
||||
}
|
28
pkg/integration/components/git.go
Normal file
28
pkg/integration/components/git.go
Normal file
@ -0,0 +1,28 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Git struct {
|
||||
*assertionHelper
|
||||
shell *Shell
|
||||
}
|
||||
|
||||
func (self *Git) CurrentBranchName(expectedName string) *Git {
|
||||
return self.assert("git rev-parse --abbrev-ref HEAD", expectedName)
|
||||
}
|
||||
|
||||
func (self *Git) assert(cmdStr string, expected string) *Git {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
output, err := self.shell.runCommandWithOutput(cmdStr)
|
||||
if err != nil {
|
||||
return false, fmt.Sprintf("Unexpected error running command: `%s`. Error: %s", cmdStr, err.Error())
|
||||
}
|
||||
actual := strings.TrimSpace(output)
|
||||
return actual == expected, fmt.Sprintf("Expected current branch name to be '%s', but got '%s'", expected, actual)
|
||||
})
|
||||
|
||||
return self
|
||||
}
|
@ -1,263 +0,0 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
|
||||
)
|
||||
|
||||
type Input struct {
|
||||
gui integrationTypes.GuiDriver
|
||||
keys config.KeybindingConfig
|
||||
assert *Assert
|
||||
pushKeyDelay int
|
||||
}
|
||||
|
||||
func NewInput(gui integrationTypes.GuiDriver, keys config.KeybindingConfig, assert *Assert, pushKeyDelay int) *Input {
|
||||
return &Input{
|
||||
gui: gui,
|
||||
keys: keys,
|
||||
assert: assert,
|
||||
pushKeyDelay: pushKeyDelay,
|
||||
}
|
||||
}
|
||||
|
||||
// key is something like 'w' or '<space>'. It's best not to pass a direct value,
|
||||
// but instead to go through the default user config to get a more meaningful key name
|
||||
func (self *Input) Press(keyStrs ...string) {
|
||||
for _, keyStr := range keyStrs {
|
||||
self.press(keyStr)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Input) press(keyStr string) {
|
||||
self.Wait(self.pushKeyDelay)
|
||||
|
||||
self.gui.PressKey(keyStr)
|
||||
}
|
||||
|
||||
func (self *Input) SwitchToStatusWindow() {
|
||||
self.press(self.keys.Universal.JumpToBlock[0])
|
||||
self.assert.CurrentWindowName("status")
|
||||
}
|
||||
|
||||
// switch to status window and assert that the status view is on top
|
||||
func (self *Input) SwitchToStatusView() {
|
||||
self.SwitchToStatusWindow()
|
||||
self.assert.CurrentView().Name("status")
|
||||
}
|
||||
|
||||
func (self *Input) SwitchToFilesWindow() {
|
||||
self.press(self.keys.Universal.JumpToBlock[1])
|
||||
self.assert.CurrentWindowName("files")
|
||||
}
|
||||
|
||||
// switch to files window and assert that the files view is on top
|
||||
func (self *Input) SwitchToFilesView() {
|
||||
self.SwitchToFilesWindow()
|
||||
self.assert.CurrentView().Name("files")
|
||||
}
|
||||
|
||||
func (self *Input) SwitchToBranchesWindow() {
|
||||
self.press(self.keys.Universal.JumpToBlock[2])
|
||||
self.assert.CurrentWindowName("localBranches")
|
||||
}
|
||||
|
||||
// switch to branches window and assert that the branches view is on top
|
||||
func (self *Input) SwitchToBranchesView() {
|
||||
self.SwitchToBranchesWindow()
|
||||
self.assert.CurrentView().Name("localBranches")
|
||||
}
|
||||
|
||||
func (self *Input) SwitchToCommitsWindow() {
|
||||
self.press(self.keys.Universal.JumpToBlock[3])
|
||||
self.assert.CurrentWindowName("commits")
|
||||
}
|
||||
|
||||
// switch to commits window and assert that the commits view is on top
|
||||
func (self *Input) SwitchToCommitsView() {
|
||||
self.SwitchToCommitsWindow()
|
||||
self.assert.CurrentView().Name("commits")
|
||||
}
|
||||
|
||||
func (self *Input) SwitchToStashWindow() {
|
||||
self.press(self.keys.Universal.JumpToBlock[4])
|
||||
self.assert.CurrentWindowName("stash")
|
||||
}
|
||||
|
||||
// switch to stash window and assert that the stash view is on top
|
||||
func (self *Input) SwitchToStashView() {
|
||||
self.SwitchToStashWindow()
|
||||
self.assert.CurrentView().Name("stash")
|
||||
}
|
||||
|
||||
func (self *Input) Type(content string) {
|
||||
for _, char := range content {
|
||||
self.press(string(char))
|
||||
}
|
||||
}
|
||||
|
||||
// i.e. pressing enter
|
||||
func (self *Input) Confirm() {
|
||||
self.press(self.keys.Universal.Confirm)
|
||||
}
|
||||
|
||||
// i.e. same as Confirm
|
||||
func (self *Input) Enter() {
|
||||
self.press(self.keys.Universal.Confirm)
|
||||
}
|
||||
|
||||
// i.e. pressing escape
|
||||
func (self *Input) Cancel() {
|
||||
self.press(self.keys.Universal.Return)
|
||||
}
|
||||
|
||||
// i.e. pressing space
|
||||
func (self *Input) PrimaryAction() {
|
||||
self.press(self.keys.Universal.Select)
|
||||
}
|
||||
|
||||
// i.e. pressing down arrow
|
||||
func (self *Input) NextItem() {
|
||||
self.press(self.keys.Universal.NextItem)
|
||||
}
|
||||
|
||||
// i.e. pressing up arrow
|
||||
func (self *Input) PreviousItem() {
|
||||
self.press(self.keys.Universal.PrevItem)
|
||||
}
|
||||
|
||||
func (self *Input) ContinueMerge() {
|
||||
self.Press(self.keys.Universal.CreateRebaseOptionsMenu)
|
||||
self.assert.CurrentView().SelectedLine(Contains("continue"))
|
||||
self.Confirm()
|
||||
}
|
||||
|
||||
func (self *Input) ContinueRebase() {
|
||||
self.ContinueMerge()
|
||||
}
|
||||
|
||||
// for when you want to allow lazygit to process something before continuing
|
||||
func (self *Input) Wait(milliseconds int) {
|
||||
time.Sleep(time.Duration(milliseconds) * time.Millisecond)
|
||||
}
|
||||
|
||||
func (self *Input) LogUI(message string) {
|
||||
self.gui.LogUI(message)
|
||||
}
|
||||
|
||||
func (self *Input) Log(message string) {
|
||||
self.gui.LogUI(message)
|
||||
}
|
||||
|
||||
// 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 *Input) NavigateToListItem(matcher *matcher) {
|
||||
self.assert.InListContext()
|
||||
|
||||
currentContext := self.gui.CurrentContext().(types.IListContext)
|
||||
|
||||
view := currentContext.GetView()
|
||||
|
||||
var matchIndex int
|
||||
|
||||
self.assert.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.assert.CurrentView().SelectedLine(matcher)
|
||||
return
|
||||
}
|
||||
if selectedLineIdx < matchIndex {
|
||||
for i := selectedLineIdx; i < matchIndex; i++ {
|
||||
self.NextItem()
|
||||
}
|
||||
self.assert.CurrentView().SelectedLine(matcher)
|
||||
return
|
||||
} else {
|
||||
for i := selectedLineIdx; i > matchIndex; i-- {
|
||||
self.PreviousItem()
|
||||
}
|
||||
self.assert.CurrentView().SelectedLine(matcher)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Input) AcceptConfirmation(title *matcher, content *matcher) {
|
||||
self.assert.InConfirm()
|
||||
self.assert.CurrentView().Title(title)
|
||||
self.assert.CurrentView().Content(content)
|
||||
self.Confirm()
|
||||
}
|
||||
|
||||
func (self *Input) DenyConfirmation(title *matcher, content *matcher) {
|
||||
self.assert.InConfirm()
|
||||
self.assert.CurrentView().Title(title)
|
||||
self.assert.CurrentView().Content(content)
|
||||
self.Cancel()
|
||||
}
|
||||
|
||||
func (self *Input) Prompt(title *matcher, textToType string) {
|
||||
self.assert.InPrompt()
|
||||
self.assert.CurrentView().Title(title)
|
||||
self.Type(textToType)
|
||||
self.Confirm()
|
||||
}
|
||||
|
||||
// type some text into a prompt, then switch to the suggestions panel and expect the first
|
||||
// item to match the given matcher, then confirm that item.
|
||||
func (self *Input) Typeahead(title *matcher, textToType string, expectedFirstOption *matcher) {
|
||||
self.assert.InPrompt()
|
||||
self.assert.CurrentView().Title(title)
|
||||
self.Type(textToType)
|
||||
self.Press(self.keys.Universal.TogglePanel)
|
||||
self.assert.CurrentView().Name("suggestions")
|
||||
self.assert.CurrentView().SelectedLine(expectedFirstOption)
|
||||
self.Confirm()
|
||||
}
|
||||
|
||||
func (self *Input) Menu(title *matcher, optionToSelect *matcher) {
|
||||
self.assert.InMenu()
|
||||
self.assert.CurrentView().Title(title)
|
||||
self.NavigateToListItem(optionToSelect)
|
||||
self.Confirm()
|
||||
}
|
||||
|
||||
func (self *Input) Alert(title *matcher, content *matcher) {
|
||||
self.assert.InListContext()
|
||||
self.assert.CurrentView().Title(title)
|
||||
self.assert.CurrentView().Content(content)
|
||||
self.Confirm()
|
||||
}
|
43
pkg/integration/components/menu_asserter.go
Normal file
43
pkg/integration/components/menu_asserter.go
Normal file
@ -0,0 +1,43 @@
|
||||
package components
|
||||
|
||||
type MenuAsserter struct {
|
||||
t *TestDriver
|
||||
hasCheckedTitle bool
|
||||
}
|
||||
|
||||
func (self *MenuAsserter) getViewAsserter() *View {
|
||||
return self.t.Views().Menu()
|
||||
}
|
||||
|
||||
// asserts that the popup has the expected title
|
||||
func (self *MenuAsserter) Title(expected *matcher) *MenuAsserter {
|
||||
self.getViewAsserter().Title(expected)
|
||||
|
||||
self.hasCheckedTitle = true
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *MenuAsserter) Confirm() {
|
||||
self.checkNecessaryChecksCompleted()
|
||||
|
||||
self.getViewAsserter().PressEnter()
|
||||
}
|
||||
|
||||
func (self *MenuAsserter) Cancel() {
|
||||
self.checkNecessaryChecksCompleted()
|
||||
|
||||
self.getViewAsserter().PressEscape()
|
||||
}
|
||||
|
||||
func (self *MenuAsserter) Select(option *matcher) *MenuAsserter {
|
||||
self.getViewAsserter().NavigateToListItem(option)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *MenuAsserter) checkNecessaryChecksCompleted() {
|
||||
if !self.hasCheckedTitle {
|
||||
self.t.Fail("You must check the title of a menu popup by calling Title() before calling Confirm()/Cancel().")
|
||||
}
|
||||
}
|
70
pkg/integration/components/popup.go
Normal file
70
pkg/integration/components/popup.go
Normal file
@ -0,0 +1,70 @@
|
||||
package components
|
||||
|
||||
type Popup struct {
|
||||
t *TestDriver
|
||||
}
|
||||
|
||||
func (self *Popup) Confirmation() *ConfirmationAsserter {
|
||||
self.inConfirm()
|
||||
|
||||
return &ConfirmationAsserter{t: self.t}
|
||||
}
|
||||
|
||||
func (self *Popup) inConfirm() {
|
||||
self.t.assertWithRetries(func() (bool, string) {
|
||||
currentView := self.t.gui.CurrentContext().GetView()
|
||||
return currentView.Name() == "confirmation" && !currentView.Editable, "Expected confirmation popup to be focused"
|
||||
})
|
||||
}
|
||||
|
||||
func (self *Popup) Prompt() *PromptAsserter {
|
||||
self.inPrompt()
|
||||
|
||||
return &PromptAsserter{t: self.t}
|
||||
}
|
||||
|
||||
func (self *Popup) inPrompt() {
|
||||
self.t.assertWithRetries(func() (bool, string) {
|
||||
currentView := self.t.gui.CurrentContext().GetView()
|
||||
return currentView.Name() == "confirmation" && currentView.Editable, "Expected prompt popup to be focused"
|
||||
})
|
||||
}
|
||||
|
||||
func (self *Popup) Alert() *AlertAsserter {
|
||||
self.inAlert()
|
||||
|
||||
return &AlertAsserter{t: self.t}
|
||||
}
|
||||
|
||||
func (self *Popup) inAlert() {
|
||||
// basically the same thing as a confirmation popup with the current implementation
|
||||
self.t.assertWithRetries(func() (bool, string) {
|
||||
currentView := self.t.gui.CurrentContext().GetView()
|
||||
return currentView.Name() == "confirmation" && !currentView.Editable, "Expected alert popup to be focused"
|
||||
})
|
||||
}
|
||||
|
||||
func (self *Popup) Menu() *MenuAsserter {
|
||||
self.inMenu()
|
||||
|
||||
return &MenuAsserter{t: self.t}
|
||||
}
|
||||
|
||||
func (self *Popup) inMenu() {
|
||||
self.t.assertWithRetries(func() (bool, string) {
|
||||
return self.t.gui.CurrentContext().GetView().Name() == "menu", "Expected popup menu to be focused"
|
||||
})
|
||||
}
|
||||
|
||||
func (self *Popup) CommitMessagePanel() *CommitMessagePanelAsserter {
|
||||
self.inCommitMessagePanel()
|
||||
|
||||
return &CommitMessagePanelAsserter{t: self.t}
|
||||
}
|
||||
|
||||
func (self *Popup) inCommitMessagePanel() {
|
||||
self.t.assertWithRetries(func() (bool, string) {
|
||||
currentView := self.t.gui.CurrentContext().GetView()
|
||||
return currentView.Name() == "commitMessage", "Expected commit message panel to be focused"
|
||||
})
|
||||
}
|
82
pkg/integration/components/prompt_asserter.go
Normal file
82
pkg/integration/components/prompt_asserter.go
Normal file
@ -0,0 +1,82 @@
|
||||
package components
|
||||
|
||||
type PromptAsserter struct {
|
||||
t *TestDriver
|
||||
hasCheckedTitle bool
|
||||
}
|
||||
|
||||
func (self *PromptAsserter) getViewAsserter() *View {
|
||||
return self.t.Views().Confirmation()
|
||||
}
|
||||
|
||||
// asserts that the popup has the expected title
|
||||
func (self *PromptAsserter) Title(expected *matcher) *PromptAsserter {
|
||||
self.getViewAsserter().Title(expected)
|
||||
|
||||
self.hasCheckedTitle = true
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// asserts on the text initially present in the prompt
|
||||
func (self *PromptAsserter) InitialText(expected *matcher) *PromptAsserter {
|
||||
self.getViewAsserter().Content(expected)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *PromptAsserter) Type(value string) *PromptAsserter {
|
||||
self.t.typeContent(value)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *PromptAsserter) Clear() *PromptAsserter {
|
||||
panic("Clear method not yet implemented!")
|
||||
}
|
||||
|
||||
func (self *PromptAsserter) Confirm() {
|
||||
self.checkNecessaryChecksCompleted()
|
||||
|
||||
self.getViewAsserter().PressEnter()
|
||||
}
|
||||
|
||||
func (self *PromptAsserter) Cancel() {
|
||||
self.checkNecessaryChecksCompleted()
|
||||
|
||||
self.getViewAsserter().PressEscape()
|
||||
}
|
||||
|
||||
func (self *PromptAsserter) checkNecessaryChecksCompleted() {
|
||||
if !self.hasCheckedTitle {
|
||||
self.t.Fail("You must check the title of a prompt popup by calling Title() before calling Confirm()/Cancel().")
|
||||
}
|
||||
}
|
||||
|
||||
func (self *PromptAsserter) SuggestionLines(matchers ...*matcher) *PromptAsserter {
|
||||
self.t.Views().Suggestions().Lines(matchers...)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *PromptAsserter) SuggestionTopLines(matchers ...*matcher) *PromptAsserter {
|
||||
self.t.Views().Suggestions().TopLines(matchers...)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *PromptAsserter) ConfirmFirstSuggestion() {
|
||||
self.t.press(self.t.keys.Universal.TogglePanel)
|
||||
self.t.Views().Suggestions().
|
||||
IsFocused().
|
||||
SelectedLineIdx(0).
|
||||
PressEnter()
|
||||
}
|
||||
|
||||
func (self *PromptAsserter) ConfirmSuggestion(matcher *matcher) {
|
||||
self.t.press(self.t.keys.Universal.TogglePanel)
|
||||
self.t.Views().Suggestions().
|
||||
IsFocused().
|
||||
NavigateToListItem(matcher).
|
||||
PressEnter()
|
||||
}
|
@ -126,7 +126,7 @@ func buildLazygit() error {
|
||||
}
|
||||
|
||||
func createFixture(test *IntegrationTest, paths Paths) error {
|
||||
shell := NewShell(paths.ActualRepo())
|
||||
shell := NewShell(paths.ActualRepo(), func(errorMsg string) { panic(errorMsg) })
|
||||
shell.RunCommand("git init -b master")
|
||||
shell.RunCommand(`git config user.email "CI@example.com"`)
|
||||
shell.RunCommand(`git config user.name "CI"`)
|
||||
|
@ -15,119 +15,133 @@ import (
|
||||
type Shell struct {
|
||||
// working directory the shell is invoked in
|
||||
dir string
|
||||
// when running the shell outside the gui we can directly panic on failure,
|
||||
// but inside the gui we need to close the gui before panicking
|
||||
fail func(string)
|
||||
}
|
||||
|
||||
func NewShell(dir string) *Shell {
|
||||
return &Shell{dir: dir}
|
||||
func NewShell(dir string, fail func(string)) *Shell {
|
||||
return &Shell{dir: dir, fail: fail}
|
||||
}
|
||||
|
||||
func (s *Shell) RunCommand(cmdStr string) *Shell {
|
||||
func (self *Shell) RunCommand(cmdStr string) *Shell {
|
||||
args := str.ToArgv(cmdStr)
|
||||
cmd := secureexec.Command(args[0], args[1:]...)
|
||||
cmd.Env = os.Environ()
|
||||
cmd.Dir = s.dir
|
||||
cmd.Dir = self.dir
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("error running command: %s\n%s", cmdStr, string(output)))
|
||||
self.fail(fmt.Sprintf("error running command: %s\n%s", cmdStr, string(output)))
|
||||
}
|
||||
|
||||
return s
|
||||
return self
|
||||
}
|
||||
|
||||
func (s *Shell) RunShellCommand(cmdStr string) *Shell {
|
||||
func (self *Shell) runCommandWithOutput(cmdStr string) (string, error) {
|
||||
args := str.ToArgv(cmdStr)
|
||||
cmd := secureexec.Command(args[0], args[1:]...)
|
||||
cmd.Env = os.Environ()
|
||||
cmd.Dir = self.dir
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
|
||||
return string(output), err
|
||||
}
|
||||
|
||||
func (self *Shell) RunShellCommand(cmdStr string) *Shell {
|
||||
cmd := secureexec.Command("sh", "-c", cmdStr)
|
||||
cmd.Env = os.Environ()
|
||||
cmd.Dir = s.dir
|
||||
cmd.Dir = self.dir
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("error running shell command: %s\n%s", cmdStr, string(output)))
|
||||
self.fail(fmt.Sprintf("error running shell command: %s\n%s", cmdStr, string(output)))
|
||||
}
|
||||
|
||||
return s
|
||||
return self
|
||||
}
|
||||
|
||||
func (s *Shell) RunShellCommandExpectError(cmdStr string) *Shell {
|
||||
func (self *Shell) RunShellCommandExpectError(cmdStr string) *Shell {
|
||||
cmd := secureexec.Command("sh", "-c", cmdStr)
|
||||
cmd.Env = os.Environ()
|
||||
cmd.Dir = s.dir
|
||||
cmd.Dir = self.dir
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err == nil {
|
||||
panic(fmt.Sprintf("Expected error running shell command: %s\n%s", cmdStr, string(output)))
|
||||
self.fail(fmt.Sprintf("Expected error running shell command: %s\n%s", cmdStr, string(output)))
|
||||
}
|
||||
|
||||
return s
|
||||
return self
|
||||
}
|
||||
|
||||
func (s *Shell) CreateFile(path string, content string) *Shell {
|
||||
fullPath := filepath.Join(s.dir, path)
|
||||
func (self *Shell) CreateFile(path string, content string) *Shell {
|
||||
fullPath := filepath.Join(self.dir, path)
|
||||
err := os.WriteFile(fullPath, []byte(content), 0o644)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("error creating file: %s\n%s", fullPath, err))
|
||||
self.fail(fmt.Sprintf("error creating file: %s\n%s", fullPath, err))
|
||||
}
|
||||
|
||||
return s
|
||||
return self
|
||||
}
|
||||
|
||||
func (s *Shell) CreateDir(path string) *Shell {
|
||||
fullPath := filepath.Join(s.dir, path)
|
||||
func (self *Shell) CreateDir(path string) *Shell {
|
||||
fullPath := filepath.Join(self.dir, path)
|
||||
if err := os.MkdirAll(fullPath, 0o755); err != nil {
|
||||
panic(fmt.Sprintf("error creating directory: %s\n%s", fullPath, err))
|
||||
self.fail(fmt.Sprintf("error creating directory: %s\n%s", fullPath, err))
|
||||
}
|
||||
|
||||
return s
|
||||
return self
|
||||
}
|
||||
|
||||
func (s *Shell) UpdateFile(path string, content string) *Shell {
|
||||
fullPath := filepath.Join(s.dir, path)
|
||||
func (self *Shell) UpdateFile(path string, content string) *Shell {
|
||||
fullPath := filepath.Join(self.dir, path)
|
||||
err := os.WriteFile(fullPath, []byte(content), 0o644)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("error updating file: %s\n%s", fullPath, err))
|
||||
self.fail(fmt.Sprintf("error updating file: %s\n%s", fullPath, err))
|
||||
}
|
||||
|
||||
return s
|
||||
return self
|
||||
}
|
||||
|
||||
func (s *Shell) NewBranch(name string) *Shell {
|
||||
return s.RunCommand("git checkout -b " + name)
|
||||
func (self *Shell) NewBranch(name string) *Shell {
|
||||
return self.RunCommand("git checkout -b " + name)
|
||||
}
|
||||
|
||||
func (s *Shell) Checkout(name string) *Shell {
|
||||
return s.RunCommand("git checkout " + name)
|
||||
func (self *Shell) Checkout(name string) *Shell {
|
||||
return self.RunCommand("git checkout " + name)
|
||||
}
|
||||
|
||||
func (s *Shell) Merge(name string) *Shell {
|
||||
return s.RunCommand("git merge --commit --no-ff " + name)
|
||||
func (self *Shell) Merge(name string) *Shell {
|
||||
return self.RunCommand("git merge --commit --no-ff " + name)
|
||||
}
|
||||
|
||||
func (s *Shell) GitAdd(path string) *Shell {
|
||||
return s.RunCommand(fmt.Sprintf("git add \"%s\"", path))
|
||||
func (self *Shell) GitAdd(path string) *Shell {
|
||||
return self.RunCommand(fmt.Sprintf("git add \"%s\"", path))
|
||||
}
|
||||
|
||||
func (s *Shell) GitAddAll() *Shell {
|
||||
return s.RunCommand("git add -A")
|
||||
func (self *Shell) GitAddAll() *Shell {
|
||||
return self.RunCommand("git add -A")
|
||||
}
|
||||
|
||||
func (s *Shell) Commit(message string) *Shell {
|
||||
return s.RunCommand(fmt.Sprintf("git commit -m \"%s\"", message))
|
||||
func (self *Shell) Commit(message string) *Shell {
|
||||
return self.RunCommand(fmt.Sprintf("git commit -m \"%s\"", message))
|
||||
}
|
||||
|
||||
func (s *Shell) EmptyCommit(message string) *Shell {
|
||||
return s.RunCommand(fmt.Sprintf("git commit --allow-empty -m \"%s\"", message))
|
||||
func (self *Shell) EmptyCommit(message string) *Shell {
|
||||
return self.RunCommand(fmt.Sprintf("git commit --allow-empty -m \"%s\"", message))
|
||||
}
|
||||
|
||||
// convenience method for creating a file and adding it
|
||||
func (s *Shell) CreateFileAndAdd(fileName string, fileContents string) *Shell {
|
||||
return s.
|
||||
func (self *Shell) CreateFileAndAdd(fileName string, fileContents string) *Shell {
|
||||
return self.
|
||||
CreateFile(fileName, fileContents).
|
||||
GitAdd(fileName)
|
||||
}
|
||||
|
||||
// convenience method for updating a file and adding it
|
||||
func (s *Shell) UpdateFileAndAdd(fileName string, fileContents string) *Shell {
|
||||
return s.
|
||||
func (self *Shell) UpdateFileAndAdd(fileName string, fileContents string) *Shell {
|
||||
return self.
|
||||
UpdateFile(fileName, fileContents).
|
||||
GitAdd(fileName)
|
||||
}
|
||||
@ -135,24 +149,24 @@ func (s *Shell) UpdateFileAndAdd(fileName string, fileContents string) *Shell {
|
||||
// creates commits 01, 02, 03, ..., n with a new file in each
|
||||
// The reason for padding with zeroes is so that it's easier to do string
|
||||
// matches on the commit messages when there are many of them
|
||||
func (s *Shell) CreateNCommits(n int) *Shell {
|
||||
func (self *Shell) CreateNCommits(n int) *Shell {
|
||||
for i := 1; i <= n; i++ {
|
||||
s.CreateFileAndAdd(
|
||||
self.CreateFileAndAdd(
|
||||
fmt.Sprintf("file%02d.txt", i),
|
||||
fmt.Sprintf("file%02d content", i),
|
||||
).
|
||||
Commit(fmt.Sprintf("commit %02d", i))
|
||||
}
|
||||
|
||||
return s
|
||||
return self
|
||||
}
|
||||
|
||||
func (s *Shell) StashWithMessage(message string) *Shell {
|
||||
s.RunCommand(fmt.Sprintf(`git stash -m "%s"`, message))
|
||||
return s
|
||||
func (self *Shell) StashWithMessage(message string) *Shell {
|
||||
self.RunCommand(fmt.Sprintf(`git stash -m "%s"`, message))
|
||||
return self
|
||||
}
|
||||
|
||||
func (s *Shell) SetConfig(key string, value string) *Shell {
|
||||
s.RunCommand(fmt.Sprintf(`git config --local "%s" %s`, key, value))
|
||||
return s
|
||||
func (self *Shell) SetConfig(key string, value string) *Shell {
|
||||
self.RunCommand(fmt.Sprintf(`git config --local "%s" %s`, key, value))
|
||||
return self
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/env"
|
||||
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
@ -24,9 +25,7 @@ type IntegrationTest struct {
|
||||
setupRepo func(shell *Shell)
|
||||
setupConfig func(config *config.AppConfig)
|
||||
run func(
|
||||
shell *Shell,
|
||||
input *Input,
|
||||
assert *Assert,
|
||||
testDriver *TestDriver,
|
||||
keys config.KeybindingConfig,
|
||||
)
|
||||
}
|
||||
@ -41,7 +40,7 @@ type NewIntegrationTestArgs struct {
|
||||
// takes a config and mutates. The mutated context will end up being passed to the gui
|
||||
SetupConfig func(config *config.AppConfig)
|
||||
// runs the test
|
||||
Run func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig)
|
||||
Run func(t *TestDriver, keys config.KeybindingConfig)
|
||||
// additional args passed to lazygit
|
||||
ExtraCmdArgs string
|
||||
// for when a test is flakey
|
||||
@ -91,18 +90,20 @@ func (self *IntegrationTest) SetupRepo(shell *Shell) {
|
||||
self.setupRepo(shell)
|
||||
}
|
||||
|
||||
// I want access to all contexts, the model, the ability to press a key, the ability to log,
|
||||
func (self *IntegrationTest) Run(gui integrationTypes.GuiDriver) {
|
||||
shell := NewShell("/tmp/lazygit-test")
|
||||
assert := NewAssert(gui)
|
||||
keys := gui.Keys()
|
||||
input := NewInput(gui, keys, assert, KeyPressDelay())
|
||||
// we pass the --pass arg to lazygit when running an integration test, and that
|
||||
// ends up stored in the following env var
|
||||
repoPath := env.GetGitWorkTreeEnv()
|
||||
|
||||
self.run(shell, input, assert, keys)
|
||||
shell := NewShell(repoPath, func(errorMsg string) { gui.Fail(errorMsg) })
|
||||
keys := gui.Keys()
|
||||
testDriver := NewTestDriver(gui, shell, keys, KeyPressDelay())
|
||||
|
||||
self.run(testDriver, keys)
|
||||
|
||||
if KeyPressDelay() > 0 {
|
||||
// the dev would want to see the final state if they're running in slow mode
|
||||
input.Wait(2000)
|
||||
testDriver.Wait(2000)
|
||||
}
|
||||
}
|
||||
|
||||
|
160
pkg/integration/components/test_driver.go
Normal file
160
pkg/integration/components/test_driver.go
Normal file
@ -0,0 +1,160 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
|
||||
)
|
||||
|
||||
type TestDriver struct {
|
||||
gui integrationTypes.GuiDriver
|
||||
keys config.KeybindingConfig
|
||||
pushKeyDelay int
|
||||
*assertionHelper
|
||||
shell *Shell
|
||||
}
|
||||
|
||||
func NewTestDriver(gui integrationTypes.GuiDriver, shell *Shell, keys config.KeybindingConfig, pushKeyDelay int) *TestDriver {
|
||||
return &TestDriver{
|
||||
gui: gui,
|
||||
keys: keys,
|
||||
pushKeyDelay: pushKeyDelay,
|
||||
assertionHelper: &assertionHelper{gui: gui},
|
||||
shell: shell,
|
||||
}
|
||||
}
|
||||
|
||||
// key is something like 'w' or '<space>'. It's best not to pass a direct value,
|
||||
// but instead to go through the default user config to get a more meaningful key name
|
||||
func (self *TestDriver) press(keyStr string) {
|
||||
self.Wait(self.pushKeyDelay)
|
||||
|
||||
self.gui.PressKey(keyStr)
|
||||
}
|
||||
|
||||
func (self *TestDriver) typeContent(content string) {
|
||||
for _, char := range content {
|
||||
self.press(string(char))
|
||||
}
|
||||
}
|
||||
|
||||
func (self *TestDriver) Actions() *Actions {
|
||||
return &Actions{t: self}
|
||||
}
|
||||
|
||||
// for when you want to allow lazygit to process something before continuing
|
||||
func (self *TestDriver) Wait(milliseconds int) {
|
||||
time.Sleep(time.Duration(milliseconds) * time.Millisecond)
|
||||
}
|
||||
|
||||
func (self *TestDriver) LogUI(message string) {
|
||||
self.gui.LogUI(message)
|
||||
}
|
||||
|
||||
func (self *TestDriver) Log(message string) {
|
||||
self.gui.LogUI(message)
|
||||
}
|
||||
|
||||
// allows the user to run shell commands during the test to emulate background activity
|
||||
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) {
|
||||
self.inListContext()
|
||||
|
||||
currentContext := self.gui.CurrentContext().(types.IListContext)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
func (self *TestDriver) inListContext() {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
currentContext := self.gui.CurrentContext()
|
||||
_, ok := currentContext.(types.IListContext)
|
||||
return ok, fmt.Sprintf("Expected current context to be a list context, but got %s", currentContext.GetKey())
|
||||
})
|
||||
}
|
||||
|
||||
// for making assertions on lazygit views
|
||||
func (self *TestDriver) Views() *Views {
|
||||
return &Views{t: self}
|
||||
}
|
||||
|
||||
// for interacting with popups
|
||||
func (self *TestDriver) ExpectPopup() *Popup {
|
||||
return &Popup{t: self}
|
||||
}
|
||||
|
||||
// for making assertions through git itself
|
||||
func (self *TestDriver) Git() *Git {
|
||||
return &Git{assertionHelper: self.assertionHelper, shell: self.shell}
|
||||
}
|
||||
|
||||
// for making assertions on the file system
|
||||
func (self *TestDriver) FileSystem() *FileSystem {
|
||||
return &FileSystem{assertionHelper: self.assertionHelper}
|
||||
}
|
||||
|
||||
// for when you just want to fail the test yourself.
|
||||
// This runs callbacks to ensure we render the error after closing the gui.
|
||||
func (self *TestDriver) Fail(message string) {
|
||||
self.assertionHelper.fail(message)
|
||||
}
|
@ -30,10 +30,6 @@ func (self *fakeGuiDriver) CurrentContext() types.Context {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *fakeGuiDriver) Model() *types.Model {
|
||||
return &types.Model{Commits: []*models.Commit{}}
|
||||
}
|
||||
|
||||
func (self *fakeGuiDriver) Fail(message string) {
|
||||
self.failureMessage = message
|
||||
}
|
||||
@ -60,26 +56,11 @@ func (self *fakeGuiDriver) View(viewName string) *gocui.View {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestAssertionFailure(t *testing.T) {
|
||||
test := NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: unitTestDescription,
|
||||
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
|
||||
input.Press("a")
|
||||
input.Press("b")
|
||||
assert.CommitCount(2)
|
||||
},
|
||||
})
|
||||
driver := &fakeGuiDriver{}
|
||||
test.Run(driver)
|
||||
assert.EqualValues(t, []string{"a", "b"}, driver.pressedKeys)
|
||||
assert.Equal(t, "Expected 2 commits present, but got 0", driver.failureMessage)
|
||||
}
|
||||
|
||||
func TestManualFailure(t *testing.T) {
|
||||
test := NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: unitTestDescription,
|
||||
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
|
||||
assert.Fail("blah")
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Fail("blah")
|
||||
},
|
||||
})
|
||||
driver := &fakeGuiDriver{}
|
||||
@ -90,10 +71,9 @@ func TestManualFailure(t *testing.T) {
|
||||
func TestSuccess(t *testing.T) {
|
||||
test := NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: unitTestDescription,
|
||||
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
|
||||
input.Press("a")
|
||||
input.Press("b")
|
||||
assert.CommitCount(0)
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.press("a")
|
||||
t.press("b")
|
||||
},
|
||||
})
|
||||
driver := &fakeGuiDriver{}
|
||||
|
230
pkg/integration/components/view.go
Normal file
230
pkg/integration/components/view.go
Normal file
@ -0,0 +1,230 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
type View struct {
|
||||
// context is prepended to any error messages e.g. 'context: "current view"'
|
||||
context string
|
||||
getView func() *gocui.View
|
||||
t *TestDriver
|
||||
}
|
||||
|
||||
// asserts that the view has the expected title
|
||||
func (self *View) Title(expected *matcher) *View {
|
||||
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 *View) TopLines(matchers ...*matcher) *View {
|
||||
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 *View) Lines(matchers ...*matcher) *View {
|
||||
self.LineCount(len(matchers))
|
||||
|
||||
return self.assertLines(matchers...)
|
||||
}
|
||||
|
||||
func (self *View) assertLines(matchers ...*matcher) *View {
|
||||
view := self.getView()
|
||||
|
||||
for i, matcher := range matchers {
|
||||
checkIsSelected, matcher := matcher.checkIsSelected()
|
||||
|
||||
self.t.matchString(matcher, fmt.Sprintf("Unexpected content in view '%s'.", view.Name()),
|
||||
func() string {
|
||||
return view.BufferLines()[i]
|
||||
},
|
||||
)
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// asserts on the content of the view i.e. the stuff within the view's frame.
|
||||
func (self *View) Content(matcher *matcher) *View {
|
||||
self.t.matchString(matcher, fmt.Sprintf("%s: Unexpected content.", self.context),
|
||||
func() string {
|
||||
return self.getView().Buffer()
|
||||
},
|
||||
)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// asserts on the selected line of the view
|
||||
func (self *View) SelectedLine(matcher *matcher) *View {
|
||||
self.t.matchString(matcher, fmt.Sprintf("%s: Unexpected selected line.", self.context),
|
||||
func() string {
|
||||
return self.getView().SelectedLine()
|
||||
},
|
||||
)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// asserts on the index of the selected line. 0 is the first index, representing the line at the top of the view.
|
||||
func (self *View) SelectedLineIdx(expected int) *View {
|
||||
self.t.assertWithRetries(func() (bool, string) {
|
||||
actual := self.getView().SelectedLineIdx()
|
||||
return expected == actual, fmt.Sprintf("%s: Expected selected line index to be %d, got %d", self.context, expected, actual)
|
||||
})
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// focus the view (assumes the view is a side-view that can be focused via a keybinding)
|
||||
func (self *View) Focus() *View {
|
||||
// we can easily change focus by switching to the view's window, but this assumes that the desired view
|
||||
// is at the top of that window. So for now we'll switch to the window then assert that the desired
|
||||
// view is on top (i.e. that it's the current view).
|
||||
// If we want to support other views e.g. the tags view, we'll need to add more logic here.
|
||||
viewName := self.getView().Name()
|
||||
|
||||
// using a map rather than a slice because we might add other views which share a window index later
|
||||
windowIndexMap := map[string]int{
|
||||
"status": 0,
|
||||
"files": 1,
|
||||
"localBranches": 2,
|
||||
"commits": 3,
|
||||
"stash": 4,
|
||||
}
|
||||
|
||||
index, ok := windowIndexMap[viewName]
|
||||
if !ok {
|
||||
self.t.fail(fmt.Sprintf("Cannot focus view %s: Focus() method not implemented", viewName))
|
||||
}
|
||||
|
||||
self.t.press(self.t.keys.Universal.JumpToBlock[index])
|
||||
|
||||
// assert that we land in the expected view
|
||||
self.IsFocused()
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// asserts that the view is focused
|
||||
func (self *View) IsFocused() *View {
|
||||
self.t.assertWithRetries(func() (bool, string) {
|
||||
expected := self.getView().Name()
|
||||
actual := self.t.gui.CurrentContext().GetView().Name()
|
||||
return actual == expected, fmt.Sprintf("%s: Unexpected view focused. Expected %s, got %s", self.context, expected, actual)
|
||||
})
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *View) Press(keyStr string) *View {
|
||||
self.IsFocused()
|
||||
|
||||
self.t.press(keyStr)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// i.e. pressing down arrow
|
||||
func (self *View) SelectNextItem() *View {
|
||||
return self.Press(self.t.keys.Universal.NextItem)
|
||||
}
|
||||
|
||||
// i.e. pressing up arrow
|
||||
func (self *View) SelectPreviousItem() *View {
|
||||
return self.Press(self.t.keys.Universal.PrevItem)
|
||||
}
|
||||
|
||||
// i.e. pressing space
|
||||
func (self *View) PressPrimaryAction() *View {
|
||||
return self.Press(self.t.keys.Universal.Select)
|
||||
}
|
||||
|
||||
// i.e. pressing space
|
||||
func (self *View) PressEnter() *View {
|
||||
return self.Press(self.t.keys.Universal.Confirm)
|
||||
}
|
||||
|
||||
// i.e. pressing escape
|
||||
func (self *View) PressEscape() *View {
|
||||
return self.Press(self.t.keys.Universal.Return)
|
||||
}
|
||||
|
||||
func (self *View) NavigateToListItem(matcher *matcher) *View {
|
||||
self.IsFocused()
|
||||
|
||||
self.t.navigateToListItem(matcher)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// returns true if the view is a list view and it contains no items
|
||||
func (self *View) IsEmpty() *View {
|
||||
self.t.assertWithRetries(func() (bool, string) {
|
||||
actual := strings.TrimSpace(self.getView().Buffer())
|
||||
return actual == "", fmt.Sprintf("%s: Unexpected content in view: expected no content. Content: %s", self.context, actual)
|
||||
})
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *View) LineCount(expectedCount int) *View {
|
||||
if expectedCount == 0 {
|
||||
return self.IsEmpty()
|
||||
}
|
||||
|
||||
self.t.assertWithRetries(func() (bool, string) {
|
||||
lines := self.getView().BufferLines()
|
||||
return len(lines) == expectedCount, fmt.Sprintf("unexpected number of lines in view. Expected %d, got %d", expectedCount, len(lines))
|
||||
})
|
||||
|
||||
self.t.assertWithRetries(func() (bool, string) {
|
||||
lines := self.getView().BufferLines()
|
||||
|
||||
// if the view has a single blank line (often the case) we want to treat that as having no lines
|
||||
if len(lines) == 1 && expectedCount == 1 {
|
||||
actual := strings.TrimSpace(self.getView().Buffer())
|
||||
return actual != "", "unexpected number of lines in view. Expected 1, got 0"
|
||||
}
|
||||
|
||||
return len(lines) == expectedCount, fmt.Sprintf("unexpected number of lines in view. Expected %d, got %d", expectedCount, len(lines))
|
||||
})
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// for when you want to make some assertion unrelated to the current view
|
||||
// without breaking the method chain
|
||||
func (self *View) Tap(f func()) *View {
|
||||
f()
|
||||
|
||||
return self
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
type ViewAsserter struct {
|
||||
// context is prepended to any error messages e.g. 'context: "current view"'
|
||||
context string
|
||||
getView func() *gocui.View
|
||||
assert *Assert
|
||||
}
|
||||
|
||||
// asserts that the view has the expected name. This is typically used in tandem with the CurrentView method i.e.;
|
||||
// assert.CurrentView().Name("commits") to assert that the current view is the commits view.
|
||||
func (self *ViewAsserter) Name(expected string) *ViewAsserter {
|
||||
self.assert.assertWithRetries(func() (bool, string) {
|
||||
actual := self.getView().Name()
|
||||
return actual == expected, fmt.Sprintf("%s: Expected view name to be '%s', but got '%s'", self.context, expected, actual)
|
||||
})
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// asserts that the view has the expected title
|
||||
func (self *ViewAsserter) Title(expected *matcher) *ViewAsserter {
|
||||
self.assert.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 *ViewAsserter) TopLines(matchers ...*matcher) *ViewAsserter {
|
||||
self.assert.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 *ViewAsserter) Lines(matchers ...*matcher) *ViewAsserter {
|
||||
self.assert.assertWithRetries(func() (bool, string) {
|
||||
lines := self.getView().BufferLines()
|
||||
return len(lines) == len(matchers), fmt.Sprintf("unexpected number of lines in view. Expected %d, got %d", len(matchers), len(lines))
|
||||
})
|
||||
|
||||
return self.assertLines(matchers...)
|
||||
}
|
||||
|
||||
func (self *ViewAsserter) assertLines(matchers ...*matcher) *ViewAsserter {
|
||||
view := self.getView()
|
||||
|
||||
for i, matcher := range matchers {
|
||||
checkIsSelected, matcher := matcher.checkIsSelected()
|
||||
|
||||
self.assert.matchString(matcher, fmt.Sprintf("Unexpected content in view '%s'.", view.Name()),
|
||||
func() string {
|
||||
return view.BufferLines()[i]
|
||||
},
|
||||
)
|
||||
|
||||
if checkIsSelected {
|
||||
self.assert.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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// asserts on the content of the view i.e. the stuff within the view's frame.
|
||||
func (self *ViewAsserter) Content(matcher *matcher) *ViewAsserter {
|
||||
self.assert.matchString(matcher, fmt.Sprintf("%s: Unexpected content.", self.context),
|
||||
func() string {
|
||||
return self.getView().Buffer()
|
||||
},
|
||||
)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// asserts on the selected line of the view
|
||||
func (self *ViewAsserter) SelectedLine(matcher *matcher) *ViewAsserter {
|
||||
self.assert.matchString(matcher, fmt.Sprintf("%s: Unexpected selected line.", self.context),
|
||||
func() string {
|
||||
return self.getView().SelectedLine()
|
||||
},
|
||||
)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// asserts on the index of the selected line. 0 is the first index, representing the line at the top of the view.
|
||||
func (self *ViewAsserter) SelectedLineIdx(expected int) *ViewAsserter {
|
||||
self.assert.assertWithRetries(func() (bool, string) {
|
||||
actual := self.getView().SelectedLineIdx()
|
||||
return expected == actual, fmt.Sprintf("%s: Expected selected line index to be %d, got %d", self.context, expected, actual)
|
||||
})
|
||||
|
||||
return self
|
||||
}
|
121
pkg/integration/components/views.go
Normal file
121
pkg/integration/components/views.go
Normal file
@ -0,0 +1,121 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
type Views struct {
|
||||
t *TestDriver
|
||||
}
|
||||
|
||||
// not exporting this because I want the test to always be explicit about what
|
||||
// view it's dealing with.
|
||||
func (self *Views) current() *View {
|
||||
return &View{
|
||||
context: "current view",
|
||||
getView: func() *gocui.View { return self.t.gui.CurrentContext().GetView() },
|
||||
t: self.t,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Views) Main() *View {
|
||||
return &View{
|
||||
context: "main view",
|
||||
getView: func() *gocui.View { return self.t.gui.MainView() },
|
||||
t: self.t,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Views) Secondary() *View {
|
||||
return &View{
|
||||
context: "secondary view",
|
||||
getView: func() *gocui.View { return self.t.gui.SecondaryView() },
|
||||
t: self.t,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Views) byName(viewName string) *View {
|
||||
return &View{
|
||||
context: fmt.Sprintf("%s view", viewName),
|
||||
getView: func() *gocui.View { return self.t.gui.View(viewName) },
|
||||
t: self.t,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Views) Commits() *View {
|
||||
return self.byName("commits")
|
||||
}
|
||||
|
||||
func (self *Views) Files() *View {
|
||||
return self.byName("files")
|
||||
}
|
||||
|
||||
func (self *Views) Status() *View {
|
||||
return self.byName("status")
|
||||
}
|
||||
|
||||
func (self *Views) Submodules() *View {
|
||||
return self.byName("submodules")
|
||||
}
|
||||
|
||||
func (self *Views) Information() *View {
|
||||
return self.byName("information")
|
||||
}
|
||||
|
||||
func (self *Views) Branches() *View {
|
||||
return self.byName("localBranches")
|
||||
}
|
||||
|
||||
func (self *Views) RemoteBranches() *View {
|
||||
return self.byName("remoteBranches")
|
||||
}
|
||||
|
||||
func (self *Views) Tags() *View {
|
||||
return self.byName("tags")
|
||||
}
|
||||
|
||||
func (self *Views) ReflogCommits() *View {
|
||||
return self.byName("reflogCommits")
|
||||
}
|
||||
|
||||
func (self *Views) SubCommits() *View {
|
||||
return self.byName("subCommits")
|
||||
}
|
||||
|
||||
func (self *Views) CommitFiles() *View {
|
||||
return self.byName("commitFiles")
|
||||
}
|
||||
|
||||
func (self *Views) Stash() *View {
|
||||
return self.byName("stash")
|
||||
}
|
||||
|
||||
func (self *Views) Staging() *View {
|
||||
return self.byName("staging")
|
||||
}
|
||||
|
||||
func (self *Views) StagingSecondary() *View {
|
||||
return self.byName("stagingSecondary")
|
||||
}
|
||||
|
||||
func (self *Views) Menu() *View {
|
||||
return self.byName("menu")
|
||||
}
|
||||
|
||||
func (self *Views) Confirmation() *View {
|
||||
return self.byName("confirmation")
|
||||
}
|
||||
|
||||
func (self *Views) CommitMessage() *View {
|
||||
return self.byName("commitMessage")
|
||||
}
|
||||
|
||||
func (self *Views) Suggestions() *View {
|
||||
return self.byName("suggestions")
|
||||
}
|
||||
|
||||
func (self *Views) MergeConflicts() *View {
|
||||
return self.byName("mergeConflicts")
|
||||
}
|
@ -14,57 +14,46 @@ var Basic = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
CreateNCommits(10)
|
||||
},
|
||||
SetupConfig: func(cfg *config.AppConfig) {},
|
||||
Run: func(
|
||||
shell *Shell,
|
||||
input *Input,
|
||||
assert *Assert,
|
||||
keys config.KeybindingConfig,
|
||||
) {
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
markCommitAsBad := func() {
|
||||
input.Press(keys.Commits.ViewBisectOptions)
|
||||
input.Menu(Equals("Bisect"), MatchesRegexp(`mark .* as bad`))
|
||||
t.Views().Commits().
|
||||
Press(keys.Commits.ViewBisectOptions)
|
||||
|
||||
t.ExpectPopup().Menu().Title(Equals("Bisect")).Select(MatchesRegexp(`mark .* as bad`)).Confirm()
|
||||
}
|
||||
|
||||
markCommitAsGood := func() {
|
||||
input.Press(keys.Commits.ViewBisectOptions)
|
||||
input.Menu(Equals("Bisect"), MatchesRegexp(`mark .* as good`))
|
||||
t.Views().Commits().
|
||||
Press(keys.Commits.ViewBisectOptions)
|
||||
|
||||
t.ExpectPopup().Menu().Title(Equals("Bisect")).Select(MatchesRegexp(`mark .* as good`)).Confirm()
|
||||
}
|
||||
|
||||
assert.AtLeastOneCommit()
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
SelectedLine(Contains("commit 10")).
|
||||
NavigateToListItem(Contains("commit 09")).
|
||||
Tap(func() {
|
||||
markCommitAsBad()
|
||||
|
||||
input.SwitchToCommitsView()
|
||||
t.Views().Information().Content(Contains("bisecting"))
|
||||
}).
|
||||
SelectedLine(Contains("<-- bad")).
|
||||
NavigateToListItem(Contains("commit 02")).
|
||||
Tap(markCommitAsGood).
|
||||
// lazygit will land us in the commit between our good and bad commits.
|
||||
SelectedLine(Contains("commit 05").Contains("<-- current")).
|
||||
Tap(markCommitAsBad).
|
||||
SelectedLine(Contains("commit 04").Contains("<-- current")).
|
||||
Tap(func() {
|
||||
markCommitAsGood()
|
||||
|
||||
assert.CurrentView().SelectedLine(Contains("commit 10"))
|
||||
// commit 5 is the culprit because we marked 4 as good and 5 as bad.
|
||||
t.ExpectPopup().Alert().Title(Equals("Bisect complete")).Content(MatchesRegexp("(?s)commit 05.*Do you want to reset")).Confirm()
|
||||
}).
|
||||
IsFocused().
|
||||
Content(Contains("commit 04"))
|
||||
|
||||
input.NavigateToListItem(Contains("commit 09"))
|
||||
|
||||
markCommitAsBad()
|
||||
|
||||
assert.View("information").Content(Contains("bisecting"))
|
||||
|
||||
assert.CurrentView().Name("commits").SelectedLine(Contains("<-- bad"))
|
||||
|
||||
input.NavigateToListItem(Contains("commit 02"))
|
||||
|
||||
markCommitAsGood()
|
||||
|
||||
// lazygit will land us in the commit between our good and bad commits.
|
||||
assert.CurrentView().
|
||||
Name("commits").
|
||||
SelectedLine(Contains("commit 05").Contains("<-- current"))
|
||||
|
||||
markCommitAsBad()
|
||||
|
||||
assert.CurrentView().
|
||||
Name("commits").
|
||||
SelectedLine(Contains("commit 04").Contains("<-- current"))
|
||||
|
||||
markCommitAsGood()
|
||||
|
||||
// commit 5 is the culprit because we marked 4 as good and 5 as bad.
|
||||
input.Alert(Equals("Bisect complete"), MatchesRegexp("(?s)commit 05.*Do you want to reset"))
|
||||
|
||||
assert.CurrentView().Name("commits").Content(Contains("commit 04"))
|
||||
assert.View("information").Content(DoesNotContain("bisecting"))
|
||||
t.Views().Information().Content(DoesNotContain("bisecting"))
|
||||
},
|
||||
})
|
||||
|
@ -18,37 +18,29 @@ var FromOtherBranch = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
RunCommand("git bisect start other~2 other~5")
|
||||
},
|
||||
SetupConfig: func(cfg *config.AppConfig) {},
|
||||
Run: func(
|
||||
shell *Shell,
|
||||
input *Input,
|
||||
assert *Assert,
|
||||
keys config.KeybindingConfig,
|
||||
) {
|
||||
assert.View("information").Content(Contains("bisecting"))
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Information().Content(Contains("bisecting"))
|
||||
|
||||
assert.AtLeastOneCommit()
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
TopLines(
|
||||
MatchesRegexp(`<-- bad.*commit 08`),
|
||||
MatchesRegexp(`<-- current.*commit 07`),
|
||||
MatchesRegexp(`\?.*commit 06`),
|
||||
MatchesRegexp(`<-- good.*commit 05`),
|
||||
).
|
||||
SelectNextItem().
|
||||
Press(keys.Commits.ViewBisectOptions).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Menu().Title(Equals("Bisect")).Select(MatchesRegexp(`mark .* as good`)).Confirm()
|
||||
|
||||
input.SwitchToCommitsView()
|
||||
t.ExpectPopup().Alert().Title(Equals("Bisect complete")).Content(MatchesRegexp("(?s)commit 08.*Do you want to reset")).Confirm()
|
||||
|
||||
assert.CurrentView().TopLines(
|
||||
MatchesRegexp(`<-- bad.*commit 08`),
|
||||
MatchesRegexp(`<-- current.*commit 07`),
|
||||
MatchesRegexp(`\?.*commit 06`),
|
||||
MatchesRegexp(`<-- good.*commit 05`),
|
||||
)
|
||||
|
||||
input.NextItem()
|
||||
|
||||
input.Press(keys.Commits.ViewBisectOptions)
|
||||
input.Menu(Equals("Bisect"), MatchesRegexp(`mark .* as good`))
|
||||
|
||||
input.Alert(Equals("Bisect complete"), MatchesRegexp(`(?s)commit 08.*Do you want to reset`))
|
||||
|
||||
assert.View("information").Content(DoesNotContain("bisecting"))
|
||||
|
||||
// back in master branch which just had the one commit
|
||||
assert.CurrentView().Name("commits").Lines(
|
||||
Contains("only commit on master"),
|
||||
)
|
||||
t.Views().Information().Content(DoesNotContain("bisecting"))
|
||||
}).
|
||||
// back in master branch which just had the one commit
|
||||
Lines(
|
||||
Contains("only commit on master"),
|
||||
)
|
||||
},
|
||||
})
|
||||
|
@ -17,27 +17,24 @@ var CheckoutByName = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Checkout("master").
|
||||
EmptyCommit("blah")
|
||||
},
|
||||
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
|
||||
input.SwitchToBranchesView()
|
||||
|
||||
assert.CurrentView().Lines(
|
||||
Contains("master"),
|
||||
Contains("@"),
|
||||
)
|
||||
input.NextItem()
|
||||
|
||||
input.Press(keys.Branches.CheckoutBranchByName)
|
||||
|
||||
input.Prompt(Equals("Branch name:"), "new-branch")
|
||||
|
||||
input.Alert(Equals("Branch not found"), Equals("Branch not found. Create a new branch named new-branch?"))
|
||||
|
||||
assert.CurrentView().Name("localBranches").
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Branches().
|
||||
Focus().
|
||||
Lines(
|
||||
MatchesRegexp(`\*.*new-branch`),
|
||||
Contains("master"),
|
||||
Contains("master").IsSelected(),
|
||||
Contains("@"),
|
||||
).
|
||||
SelectedLine(Contains("new-branch"))
|
||||
SelectNextItem().
|
||||
Press(keys.Branches.CheckoutBranchByName).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Prompt().Title(Equals("Branch name:")).Type("new-branch").Confirm()
|
||||
|
||||
t.ExpectPopup().Alert().Title(Equals("Branch not found")).Content(Equals("Branch not found. Create a new branch named new-branch?")).Confirm()
|
||||
}).
|
||||
Lines(
|
||||
MatchesRegexp(`\*.*new-branch`).IsSelected(),
|
||||
Contains("master"),
|
||||
Contains("@"),
|
||||
)
|
||||
},
|
||||
})
|
||||
|
@ -16,24 +16,26 @@ var Delete = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
NewBranch("branch-one").
|
||||
NewBranch("branch-two")
|
||||
},
|
||||
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
|
||||
input.SwitchToBranchesView()
|
||||
|
||||
assert.CurrentView().Lines(
|
||||
MatchesRegexp(`\*.*branch-two`),
|
||||
MatchesRegexp(`branch-one`),
|
||||
MatchesRegexp(`master`),
|
||||
)
|
||||
|
||||
input.Press(keys.Universal.Remove)
|
||||
input.Alert(Equals("Error"), Contains("You cannot delete the checked out branch!"))
|
||||
|
||||
input.NextItem()
|
||||
|
||||
input.Press(keys.Universal.Remove)
|
||||
input.AcceptConfirmation(Equals("Delete Branch"), Contains("Are you sure you want to delete the branch 'branch-one'?"))
|
||||
|
||||
assert.CurrentView().Name("localBranches").
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Branches().
|
||||
Focus().
|
||||
Lines(
|
||||
MatchesRegexp(`\*.*branch-two`).IsSelected(),
|
||||
MatchesRegexp(`branch-one`),
|
||||
MatchesRegexp(`master`),
|
||||
).
|
||||
Press(keys.Universal.Remove).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Alert().Title(Equals("Error")).Content(Contains("You cannot delete the checked out branch!")).Confirm()
|
||||
}).
|
||||
SelectNextItem().
|
||||
Press(keys.Universal.Remove).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Equals("Delete Branch")).
|
||||
Content(Contains("Are you sure you want to delete the branch 'branch-one'?")).
|
||||
Confirm()
|
||||
}).
|
||||
Lines(
|
||||
MatchesRegexp(`\*.*branch-two`),
|
||||
MatchesRegexp(`master`).IsSelected(),
|
||||
|
@ -14,42 +14,51 @@ var Rebase = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shared.MergeConflictsSetup(shell)
|
||||
},
|
||||
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
|
||||
input.SwitchToBranchesView()
|
||||
|
||||
assert.View("localBranches").Lines(
|
||||
Contains("first-change-branch"),
|
||||
Contains("second-change-branch"),
|
||||
Contains("original-branch"),
|
||||
)
|
||||
|
||||
assert.View("commits").TopLines(
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().TopLines(
|
||||
Contains("first change"),
|
||||
Contains("original"),
|
||||
)
|
||||
|
||||
input.NextItem()
|
||||
input.Press(keys.Branches.RebaseBranch)
|
||||
t.Views().Branches().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("first-change-branch"),
|
||||
Contains("second-change-branch"),
|
||||
Contains("original-branch"),
|
||||
).
|
||||
SelectNextItem().
|
||||
Press(keys.Branches.RebaseBranch)
|
||||
|
||||
input.AcceptConfirmation(Equals("Rebasing"), Contains("Are you sure you want to rebase 'first-change-branch' on top of 'second-change-branch'?"))
|
||||
input.AcceptConfirmation(Equals("Auto-merge failed"), Contains("Conflicts!"))
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Equals("Rebasing")).
|
||||
Content(Contains("Are you sure you want to rebase 'first-change-branch' on top of 'second-change-branch'?")).
|
||||
Confirm()
|
||||
|
||||
assert.CurrentView().Name("files").SelectedLine(Contains("file"))
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Equals("Auto-merge failed")).
|
||||
Content(Contains("Conflicts!")).
|
||||
Confirm()
|
||||
|
||||
// not using Confirm() convenience method because I suspect we might change this
|
||||
// keybinding to something more bespoke
|
||||
input.Press(keys.Universal.Confirm)
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
SelectedLine(Contains("file")).
|
||||
PressEnter()
|
||||
|
||||
assert.CurrentView().Name("mergeConflicts")
|
||||
input.PrimaryAction()
|
||||
t.Views().MergeConflicts().
|
||||
IsFocused().
|
||||
PressPrimaryAction()
|
||||
|
||||
assert.View("information").Content(Contains("rebasing"))
|
||||
t.Views().Information().Content(Contains("rebasing"))
|
||||
|
||||
input.AcceptConfirmation(Equals("continue"), Contains("all merge conflicts resolved. Continue?"))
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Equals("continue")).
|
||||
Content(Contains("all merge conflicts resolved. Continue?")).
|
||||
Confirm()
|
||||
|
||||
assert.View("information").Content(DoesNotContain("rebasing"))
|
||||
t.Views().Information().Content(DoesNotContain("rebasing"))
|
||||
|
||||
assert.View("commits").TopLines(
|
||||
t.Views().Commits().TopLines(
|
||||
Contains("second-change-branch unrelated change"),
|
||||
Contains("second change"),
|
||||
Contains("original"),
|
||||
|
@ -17,49 +17,51 @@ var RebaseAndDrop = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
shell.EmptyCommit("to remove")
|
||||
shell.EmptyCommit("to keep")
|
||||
},
|
||||
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
|
||||
input.SwitchToBranchesView()
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
TopLines(
|
||||
Contains("to keep"),
|
||||
Contains("to remove"),
|
||||
Contains("first change"),
|
||||
Contains("original"),
|
||||
)
|
||||
|
||||
assert.CurrentView().Lines(
|
||||
Contains("first-change-branch"),
|
||||
Contains("second-change-branch"),
|
||||
Contains("original-branch"),
|
||||
)
|
||||
t.Views().Branches().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("first-change-branch").IsSelected(),
|
||||
Contains("second-change-branch"),
|
||||
Contains("original-branch"),
|
||||
).
|
||||
SelectNextItem().
|
||||
Press(keys.Branches.RebaseBranch)
|
||||
|
||||
assert.View("commits").TopLines(
|
||||
Contains("to keep").IsSelected(),
|
||||
Contains("to remove"),
|
||||
Contains("first change"),
|
||||
Contains("original"),
|
||||
)
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Equals("Rebasing")).
|
||||
Content(Contains("Are you sure you want to rebase 'first-change-branch' on top of 'second-change-branch'?")).
|
||||
Confirm()
|
||||
|
||||
input.NextItem()
|
||||
input.Press(keys.Branches.RebaseBranch)
|
||||
t.Views().Information().Content(Contains("rebasing"))
|
||||
|
||||
input.AcceptConfirmation(Equals("Rebasing"), Contains("Are you sure you want to rebase 'first-change-branch' on top of 'second-change-branch'?"))
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Equals("Auto-merge failed")).
|
||||
Content(Contains("Conflicts!")).
|
||||
Confirm()
|
||||
|
||||
assert.View("information").Content(Contains("rebasing"))
|
||||
|
||||
input.AcceptConfirmation(Equals("Auto-merge failed"), Contains("Conflicts!"))
|
||||
|
||||
assert.CurrentView().
|
||||
Name("files").
|
||||
t.Views().Files().IsFocused().
|
||||
SelectedLine(MatchesRegexp("UU.*file"))
|
||||
|
||||
input.SwitchToCommitsView()
|
||||
assert.CurrentView().
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
TopLines(
|
||||
MatchesRegexp(`pick.*to keep`).IsSelected(),
|
||||
MatchesRegexp(`pick.*to remove`),
|
||||
MatchesRegexp("YOU ARE HERE.*second-change-branch unrelated change"),
|
||||
MatchesRegexp("second change"),
|
||||
MatchesRegexp("original"),
|
||||
)
|
||||
|
||||
input.NextItem()
|
||||
input.Press(keys.Universal.Remove)
|
||||
|
||||
assert.CurrentView().
|
||||
).
|
||||
SelectNextItem().
|
||||
Press(keys.Universal.Remove).
|
||||
TopLines(
|
||||
MatchesRegexp(`pick.*to keep`),
|
||||
MatchesRegexp(`drop.*to remove`).IsSelected(),
|
||||
@ -68,20 +70,22 @@ var RebaseAndDrop = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
MatchesRegexp("original"),
|
||||
)
|
||||
|
||||
input.SwitchToFilesView()
|
||||
t.Views().Files().
|
||||
Focus().
|
||||
PressEnter()
|
||||
|
||||
// not using Confirm() convenience method because I suspect we might change this
|
||||
// keybinding to something more bespoke
|
||||
input.Press(keys.Universal.Confirm)
|
||||
t.Views().MergeConflicts().
|
||||
IsFocused().
|
||||
PressPrimaryAction()
|
||||
|
||||
assert.CurrentView().Name("mergeConflicts")
|
||||
input.PrimaryAction()
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Equals("continue")).
|
||||
Content(Contains("all merge conflicts resolved. Continue?")).
|
||||
Confirm()
|
||||
|
||||
input.AcceptConfirmation(Equals("continue"), Contains("all merge conflicts resolved. Continue?"))
|
||||
t.Views().Information().Content(DoesNotContain("rebasing"))
|
||||
|
||||
assert.View("information").Content(DoesNotContain("rebasing"))
|
||||
|
||||
assert.View("commits").TopLines(
|
||||
t.Views().Commits().TopLines(
|
||||
Contains("to keep"),
|
||||
Contains("second-change-branch unrelated change").IsSelected(),
|
||||
Contains("second change"),
|
||||
|
@ -20,32 +20,31 @@ var Reset = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
shell.Checkout("current-branch")
|
||||
shell.EmptyCommit("current-branch commit")
|
||||
},
|
||||
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
|
||||
assert.View("commits").Lines(
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().Lines(
|
||||
Contains("current-branch commit"),
|
||||
Contains("root commit"),
|
||||
)
|
||||
|
||||
input.SwitchToBranchesView()
|
||||
t.Views().Branches().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("current-branch").IsSelected(),
|
||||
Contains("other-branch"),
|
||||
).
|
||||
SelectNextItem().
|
||||
Press(keys.Commits.ViewResetOptions)
|
||||
|
||||
assert.CurrentView().Lines(
|
||||
Contains("current-branch"),
|
||||
Contains("other-branch"),
|
||||
)
|
||||
input.NextItem()
|
||||
|
||||
input.Press(keys.Commits.ViewResetOptions)
|
||||
|
||||
input.Menu(Contains("reset to other-branch"), Contains("hard reset"))
|
||||
|
||||
// ensure that we've returned from the menu before continuing
|
||||
assert.CurrentView().Name("localBranches")
|
||||
t.ExpectPopup().Menu().
|
||||
Title(Contains("reset to other-branch")).
|
||||
Select(Contains("hard reset")).
|
||||
Confirm()
|
||||
|
||||
// assert that we now have the expected commits in the commit panel
|
||||
input.SwitchToCommitsView()
|
||||
assert.CurrentView().Lines(
|
||||
Contains("other-branch commit"),
|
||||
Contains("root commit"),
|
||||
)
|
||||
t.Views().Commits().
|
||||
Lines(
|
||||
Contains("other-branch commit"),
|
||||
Contains("root commit"),
|
||||
)
|
||||
},
|
||||
})
|
||||
|
@ -20,15 +20,19 @@ var Suggestions = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
NewBranch("other-new-branch-2").
|
||||
NewBranch("other-new-branch-3")
|
||||
},
|
||||
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
|
||||
input.SwitchToBranchesView()
|
||||
|
||||
input.Press(keys.Branches.CheckoutBranchByName)
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Branches().
|
||||
Focus().
|
||||
Press(keys.Branches.CheckoutBranchByName)
|
||||
|
||||
// we expect the first suggestion to be the branch we want because it most
|
||||
// closely matches what we typed in
|
||||
input.Typeahead(Equals("Branch name:"), "branch-to", Contains("branch-to-checkout"))
|
||||
t.ExpectPopup().Prompt().
|
||||
Title(Equals("Branch name:")).
|
||||
Type("branch-to").
|
||||
SuggestionTopLines(Contains("branch-to-checkout")).
|
||||
ConfirmFirstSuggestion()
|
||||
|
||||
assert.CurrentBranchName("branch-to-checkout")
|
||||
t.Git().CurrentBranchName("branch-to-checkout")
|
||||
},
|
||||
})
|
||||
|
@ -23,53 +23,62 @@ var CherryPick = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
EmptyCommit("four").
|
||||
Checkout("first-branch")
|
||||
},
|
||||
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
|
||||
input.SwitchToBranchesView()
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Branches().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("first-branch"),
|
||||
Contains("second-branch"),
|
||||
Contains("master"),
|
||||
).
|
||||
SelectNextItem().
|
||||
PressEnter()
|
||||
|
||||
assert.CurrentView().Lines(
|
||||
Contains("first-branch"),
|
||||
Contains("second-branch"),
|
||||
Contains("master"),
|
||||
)
|
||||
t.Views().SubCommits().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("four").IsSelected(),
|
||||
Contains("three"),
|
||||
Contains("base"),
|
||||
).
|
||||
// copy commits 'four' and 'three'
|
||||
Press(keys.Commits.CherryPickCopy).
|
||||
Tap(func() {
|
||||
t.Views().Information().Content(Contains("1 commit copied"))
|
||||
}).
|
||||
SelectNextItem().
|
||||
Press(keys.Commits.CherryPickCopy)
|
||||
|
||||
input.NextItem()
|
||||
t.Views().Information().Content(Contains("2 commits copied"))
|
||||
|
||||
input.Enter()
|
||||
|
||||
assert.CurrentView().Name("subCommits").Lines(
|
||||
Contains("four"),
|
||||
Contains("three"),
|
||||
Contains("base"),
|
||||
)
|
||||
|
||||
// copy commits 'four' and 'three'
|
||||
input.Press(keys.Commits.CherryPickCopy)
|
||||
assert.View("information").Content(Contains("1 commit copied"))
|
||||
input.NextItem()
|
||||
input.Press(keys.Commits.CherryPickCopy)
|
||||
assert.View("information").Content(Contains("2 commits copied"))
|
||||
|
||||
input.SwitchToCommitsView()
|
||||
|
||||
assert.CurrentView().Lines(
|
||||
Contains("two"),
|
||||
Contains("one"),
|
||||
Contains("base"),
|
||||
)
|
||||
|
||||
input.Press(keys.Commits.PasteCommits)
|
||||
input.Alert(Equals("Cherry-Pick"), Contains("Are you sure you want to cherry-pick the copied commits onto this branch?"))
|
||||
|
||||
assert.CurrentView().Name("commits").Lines(
|
||||
Contains("four"),
|
||||
Contains("three"),
|
||||
Contains("two"),
|
||||
Contains("one"),
|
||||
Contains("base"),
|
||||
)
|
||||
|
||||
assert.View("information").Content(Contains("2 commits copied"))
|
||||
input.Press(keys.Universal.Return)
|
||||
assert.View("information").Content(DoesNotContain("commits copied"))
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("two").IsSelected(),
|
||||
Contains("one"),
|
||||
Contains("base"),
|
||||
).
|
||||
Press(keys.Commits.PasteCommits).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Alert().
|
||||
Title(Equals("Cherry-Pick")).
|
||||
Content(Contains("Are you sure you want to cherry-pick the copied commits onto this branch?")).
|
||||
Confirm()
|
||||
}).
|
||||
Lines(
|
||||
Contains("four"),
|
||||
Contains("three"),
|
||||
Contains("two"),
|
||||
Contains("one"),
|
||||
Contains("base"),
|
||||
).
|
||||
Tap(func() {
|
||||
// we need to manually exit out of cherry pick mode
|
||||
t.Views().Information().Content(Contains("2 commits copied"))
|
||||
}).
|
||||
PressEscape().
|
||||
Tap(func() {
|
||||
t.Views().Information().Content(DoesNotContain("commits copied"))
|
||||
})
|
||||
},
|
||||
})
|
||||
|
@ -14,75 +14,85 @@ var CherryPickConflicts = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shared.MergeConflictsSetup(shell)
|
||||
},
|
||||
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
|
||||
input.SwitchToBranchesView()
|
||||
assert.CurrentView().Lines(
|
||||
Contains("first-change-branch"),
|
||||
Contains("second-change-branch"),
|
||||
Contains("original-branch"),
|
||||
)
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Branches().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("first-change-branch"),
|
||||
Contains("second-change-branch"),
|
||||
Contains("original-branch"),
|
||||
).
|
||||
SelectNextItem().
|
||||
PressEnter()
|
||||
|
||||
input.NextItem()
|
||||
t.Views().SubCommits().
|
||||
IsFocused().
|
||||
TopLines(
|
||||
Contains("second-change-branch unrelated change"),
|
||||
Contains("second change"),
|
||||
).
|
||||
Press(keys.Commits.CherryPickCopy).
|
||||
Tap(func() {
|
||||
t.Views().Information().Content(Contains("1 commit copied"))
|
||||
}).
|
||||
SelectNextItem().
|
||||
Press(keys.Commits.CherryPickCopy)
|
||||
|
||||
input.Enter()
|
||||
t.Views().Information().Content(Contains("2 commits copied"))
|
||||
|
||||
assert.CurrentView().Name("subCommits").TopLines(
|
||||
Contains("second-change-branch unrelated change"),
|
||||
Contains("second change"),
|
||||
)
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
TopLines(
|
||||
Contains("first change"),
|
||||
).
|
||||
Press(keys.Commits.PasteCommits)
|
||||
|
||||
input.Press(keys.Commits.CherryPickCopy)
|
||||
assert.View("information").Content(Contains("1 commit copied"))
|
||||
t.ExpectPopup().Alert().Title(Equals("Cherry-Pick")).Content(Contains("Are you sure you want to cherry-pick the copied commits onto this branch?")).Confirm()
|
||||
|
||||
input.NextItem()
|
||||
input.Press(keys.Commits.CherryPickCopy)
|
||||
assert.View("information").Content(Contains("2 commits copied"))
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Equals("Auto-merge failed")).
|
||||
Content(Contains("Conflicts!")).
|
||||
Confirm()
|
||||
|
||||
input.SwitchToCommitsView()
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
SelectedLine(Contains("file")).
|
||||
PressEnter()
|
||||
|
||||
assert.CurrentView().TopLines(
|
||||
Contains("first change"),
|
||||
)
|
||||
t.Views().MergeConflicts().
|
||||
IsFocused().
|
||||
// picking 'Second change'
|
||||
SelectNextItem().
|
||||
PressPrimaryAction()
|
||||
|
||||
input.Press(keys.Commits.PasteCommits)
|
||||
input.Alert(Equals("Cherry-Pick"), Contains("Are you sure you want to cherry-pick the copied commits onto this branch?"))
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Equals("continue")).
|
||||
Content(Contains("all merge conflicts resolved. Continue?")).
|
||||
Confirm()
|
||||
|
||||
input.AcceptConfirmation(Equals("Auto-merge failed"), Contains("Conflicts!"))
|
||||
t.Views().Files().IsEmpty()
|
||||
|
||||
assert.CurrentView().Name("files")
|
||||
assert.CurrentView().SelectedLine(Contains("file"))
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
TopLines(
|
||||
Contains("second-change-branch unrelated change"),
|
||||
Contains("second change"),
|
||||
Contains("first change"),
|
||||
).
|
||||
SelectNextItem().
|
||||
Tap(func() {
|
||||
// because we picked 'Second change' when resolving the conflict,
|
||||
// we now see this commit as having replaced First Change with Second Change,
|
||||
// as opposed to replacing 'Original' with 'Second change'
|
||||
t.Views().Main().
|
||||
Content(Contains("-First Change")).
|
||||
Content(Contains("+Second Change"))
|
||||
|
||||
// not using Confirm() convenience method because I suspect we might change this
|
||||
// keybinding to something more bespoke
|
||||
input.Press(keys.Universal.Confirm)
|
||||
|
||||
assert.CurrentView().Name("mergeConflicts")
|
||||
// picking 'Second change'
|
||||
input.NextItem()
|
||||
input.PrimaryAction()
|
||||
|
||||
input.AcceptConfirmation(Equals("continue"), Contains("all merge conflicts resolved. Continue?"))
|
||||
|
||||
assert.CurrentView().Name("files")
|
||||
assert.WorkingTreeFileCount(0)
|
||||
|
||||
input.SwitchToCommitsView()
|
||||
|
||||
assert.CurrentView().TopLines(
|
||||
Contains("second-change-branch unrelated change"),
|
||||
Contains("second change"),
|
||||
Contains("first change"),
|
||||
)
|
||||
input.NextItem()
|
||||
// because we picked 'Second change' when resolving the conflict,
|
||||
// we now see this commit as having replaced First Change with Second Change,
|
||||
// as opposed to replacing 'Original' with 'Second change'
|
||||
assert.MainView().
|
||||
Content(Contains("-First Change")).
|
||||
Content(Contains("+Second Change"))
|
||||
|
||||
assert.View("information").Content(Contains("2 commits copied"))
|
||||
input.Press(keys.Universal.Return)
|
||||
assert.View("information").Content(DoesNotContain("commits copied"))
|
||||
t.Views().Information().Content(Contains("2 commits copied"))
|
||||
}).
|
||||
PressEscape().
|
||||
Tap(func() {
|
||||
t.Views().Information().Content(DoesNotContain("commits copied"))
|
||||
})
|
||||
},
|
||||
})
|
||||
|
@ -14,20 +14,24 @@ var Commit = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
shell.CreateFile("myfile", "myfile content")
|
||||
shell.CreateFile("myfile2", "myfile2 content")
|
||||
},
|
||||
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
|
||||
assert.CommitCount(0)
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
IsEmpty()
|
||||
|
||||
input.PrimaryAction()
|
||||
input.NextItem()
|
||||
input.PrimaryAction()
|
||||
input.Press(keys.Files.CommitChanges)
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
PressPrimaryAction(). // stage file
|
||||
SelectNextItem().
|
||||
PressPrimaryAction(). // stage other file
|
||||
Press(keys.Files.CommitChanges)
|
||||
|
||||
assert.InCommitMessagePanel()
|
||||
commitMessage := "my commit message"
|
||||
input.Type(commitMessage)
|
||||
input.Confirm()
|
||||
|
||||
assert.CommitCount(1)
|
||||
assert.HeadCommitMessage(Equals(commitMessage))
|
||||
t.ExpectPopup().CommitMessagePanel().Type(commitMessage).Confirm()
|
||||
|
||||
t.Views().Commits().
|
||||
Lines(
|
||||
Contains(commitMessage),
|
||||
)
|
||||
},
|
||||
})
|
||||
|
@ -13,23 +13,23 @@ var CommitMultiline = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateFile("myfile", "myfile content")
|
||||
},
|
||||
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
|
||||
assert.CommitCount(0)
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
IsEmpty()
|
||||
|
||||
input.PrimaryAction()
|
||||
input.Press(keys.Files.CommitChanges)
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
PressPrimaryAction().
|
||||
Press(keys.Files.CommitChanges)
|
||||
|
||||
assert.InCommitMessagePanel()
|
||||
input.Type("first line")
|
||||
input.Press(keys.Universal.AppendNewline)
|
||||
input.Press(keys.Universal.AppendNewline)
|
||||
input.Type("third line")
|
||||
input.Confirm()
|
||||
t.ExpectPopup().CommitMessagePanel().Type("first line").AddNewline().AddNewline().Type("third line").Confirm()
|
||||
|
||||
assert.CommitCount(1)
|
||||
assert.HeadCommitMessage(Equals("first line"))
|
||||
t.Views().Commits().
|
||||
Lines(
|
||||
Contains("first line"),
|
||||
)
|
||||
|
||||
input.SwitchToCommitsView()
|
||||
assert.MainView().Content(MatchesRegexp("first line\n\\s*\n\\s*third line"))
|
||||
t.Views().Commits().Focus()
|
||||
t.Views().Main().Content(MatchesRegexp("first line\n\\s*\n\\s*third line"))
|
||||
},
|
||||
})
|
||||
|
@ -16,27 +16,25 @@ var NewBranch = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
EmptyCommit("commit 2").
|
||||
EmptyCommit("commit 3")
|
||||
},
|
||||
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
|
||||
assert.CommitCount(3)
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("commit 3").IsSelected(),
|
||||
Contains("commit 2"),
|
||||
Contains("commit 1"),
|
||||
).
|
||||
SelectNextItem().
|
||||
Press(keys.Universal.New).
|
||||
Tap(func() {
|
||||
branchName := "my-branch-name"
|
||||
t.ExpectPopup().Prompt().Title(Contains("New Branch Name")).Type(branchName).Confirm()
|
||||
|
||||
input.SwitchToCommitsView()
|
||||
assert.CurrentView().Lines(
|
||||
Contains("commit 3"),
|
||||
Contains("commit 2"),
|
||||
Contains("commit 1"),
|
||||
)
|
||||
input.NextItem()
|
||||
|
||||
input.Press(keys.Universal.New)
|
||||
|
||||
branchName := "my-branch-name"
|
||||
input.Prompt(Contains("New Branch Name"), branchName)
|
||||
|
||||
assert.CurrentBranchName(branchName)
|
||||
|
||||
assert.View("commits").Lines(
|
||||
Contains("commit 2"),
|
||||
Contains("commit 1"),
|
||||
)
|
||||
t.Git().CurrentBranchName(branchName)
|
||||
}).
|
||||
Lines(
|
||||
Contains("commit 2"),
|
||||
Contains("commit 1"),
|
||||
)
|
||||
},
|
||||
})
|
||||
|
@ -15,25 +15,25 @@ var Revert = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
shell.GitAddAll()
|
||||
shell.Commit("first commit")
|
||||
},
|
||||
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
|
||||
assert.CommitCount(1)
|
||||
|
||||
input.SwitchToCommitsView()
|
||||
|
||||
assert.CurrentView().Lines(
|
||||
Contains("first commit"),
|
||||
)
|
||||
|
||||
input.Press(keys.Commits.RevertCommit)
|
||||
input.AcceptConfirmation(Equals("Revert commit"), MatchesRegexp(`Are you sure you want to revert \w+?`))
|
||||
|
||||
assert.CurrentView().Name("commits").
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("first commit"),
|
||||
).
|
||||
Press(keys.Commits.RevertCommit).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Equals("Revert commit")).
|
||||
Content(MatchesRegexp(`Are you sure you want to revert \w+?`)).
|
||||
Confirm()
|
||||
}).
|
||||
Lines(
|
||||
Contains("Revert \"first commit\"").IsSelected(),
|
||||
Contains("first commit"),
|
||||
)
|
||||
|
||||
assert.MainView().Content(Contains("-myfile content"))
|
||||
assert.FileSystemPathNotPresent("myfile")
|
||||
t.Views().Main().Content(Contains("-myfile content"))
|
||||
t.FileSystem().PathNotPresent("myfile")
|
||||
},
|
||||
})
|
||||
|
@ -15,38 +15,45 @@ var Staged = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
CreateFile("myfile", "myfile content\nwith a second line").
|
||||
CreateFile("myfile2", "myfile2 content")
|
||||
},
|
||||
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
|
||||
assert.CommitCount(0)
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
IsEmpty()
|
||||
|
||||
assert.CurrentView().Name("files")
|
||||
assert.CurrentView().SelectedLine(Contains("myfile"))
|
||||
// stage the file
|
||||
input.PrimaryAction()
|
||||
input.Enter()
|
||||
assert.CurrentView().Name("stagingSecondary")
|
||||
// we start with both lines having been staged
|
||||
assert.View("stagingSecondary").Content(Contains("+myfile content"))
|
||||
assert.View("stagingSecondary").Content(Contains("+with a second line"))
|
||||
assert.View("staging").Content(DoesNotContain("+myfile content"))
|
||||
assert.View("staging").Content(DoesNotContain("+with a second line"))
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
SelectedLine(Contains("myfile")).
|
||||
PressPrimaryAction(). // stage the file
|
||||
PressEnter()
|
||||
|
||||
// unstage the selected line
|
||||
input.PrimaryAction()
|
||||
t.Views().StagingSecondary().
|
||||
IsFocused().
|
||||
Tap(func() {
|
||||
// we start with both lines having been staged
|
||||
t.Views().StagingSecondary().Content(Contains("+myfile content"))
|
||||
t.Views().StagingSecondary().Content(Contains("+with a second line"))
|
||||
t.Views().Staging().Content(DoesNotContain("+myfile content"))
|
||||
t.Views().Staging().Content(DoesNotContain("+with a second line"))
|
||||
}).
|
||||
// unstage the selected line
|
||||
PressPrimaryAction().
|
||||
Tap(func() {
|
||||
// the line should have been moved to the main view
|
||||
t.Views().StagingSecondary().Content(DoesNotContain("+myfile content"))
|
||||
t.Views().StagingSecondary().Content(Contains("+with a second line"))
|
||||
t.Views().Staging().Content(Contains("+myfile content"))
|
||||
t.Views().Staging().Content(DoesNotContain("+with a second line"))
|
||||
}).
|
||||
Press(keys.Files.CommitChanges)
|
||||
|
||||
// the line should have been moved to the main view
|
||||
assert.View("stagingSecondary").Content(DoesNotContain("+myfile content"))
|
||||
assert.View("stagingSecondary").Content(Contains("+with a second line"))
|
||||
assert.View("staging").Content(Contains("+myfile content"))
|
||||
assert.View("staging").Content(DoesNotContain("+with a second line"))
|
||||
|
||||
input.Press(keys.Files.CommitChanges)
|
||||
commitMessage := "my commit message"
|
||||
input.Type(commitMessage)
|
||||
input.Confirm()
|
||||
t.ExpectPopup().CommitMessagePanel().Type(commitMessage).Confirm()
|
||||
|
||||
assert.CommitCount(1)
|
||||
assert.HeadCommitMessage(Equals(commitMessage))
|
||||
assert.CurrentWindowName("stagingSecondary")
|
||||
t.Views().Commits().
|
||||
Lines(
|
||||
Contains(commitMessage),
|
||||
)
|
||||
|
||||
t.Views().StagingSecondary().IsFocused()
|
||||
|
||||
// TODO: assert that the staging panel has been refreshed (it currently does not get correctly refreshed)
|
||||
},
|
||||
|
@ -15,40 +15,45 @@ var StagedWithoutHooks = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
CreateFile("myfile", "myfile content\nwith a second line").
|
||||
CreateFile("myfile2", "myfile2 content")
|
||||
},
|
||||
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
|
||||
assert.CommitCount(0)
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
IsEmpty()
|
||||
|
||||
// stage the file
|
||||
assert.CurrentView().Name("files")
|
||||
assert.CurrentView().SelectedLine(Contains("myfile"))
|
||||
input.PrimaryAction()
|
||||
input.Enter()
|
||||
assert.CurrentView().Name("stagingSecondary")
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
SelectedLine(Contains("myfile")).
|
||||
PressPrimaryAction().
|
||||
PressEnter()
|
||||
|
||||
// we start with both lines having been staged
|
||||
assert.View("stagingSecondary").Content(
|
||||
t.Views().StagingSecondary().Content(
|
||||
Contains("+myfile content").Contains("+with a second line"),
|
||||
)
|
||||
assert.View("staging").Content(
|
||||
t.Views().Staging().Content(
|
||||
DoesNotContain("+myfile content").DoesNotContain("+with a second line"),
|
||||
)
|
||||
|
||||
// unstage the selected line
|
||||
input.PrimaryAction()
|
||||
t.Views().StagingSecondary().
|
||||
IsFocused().
|
||||
PressPrimaryAction().
|
||||
Tap(func() {
|
||||
// the line should have been moved to the main view
|
||||
t.Views().Staging().Content(Contains("+myfile content").DoesNotContain("+with a second line"))
|
||||
}).
|
||||
Content(DoesNotContain("+myfile content").Contains("+with a second line")).
|
||||
Press(keys.Files.CommitChangesWithoutHook)
|
||||
|
||||
// the line should have been moved to the main view
|
||||
assert.View("stagingSecondary").Content(DoesNotContain("+myfile content").Contains("+with a second line"))
|
||||
assert.View("staging").Content(Contains("+myfile content").DoesNotContain("+with a second line"))
|
||||
|
||||
input.Press(keys.Files.CommitChangesWithoutHook)
|
||||
assert.InCommitMessagePanel()
|
||||
assert.CurrentView().Content(Contains("WIP"))
|
||||
commitMessage := ": my commit message"
|
||||
input.Type(commitMessage)
|
||||
input.Confirm()
|
||||
t.ExpectPopup().CommitMessagePanel().InitialText(Contains("WIP")).Type(commitMessage).Confirm()
|
||||
|
||||
assert.CommitCount(1)
|
||||
assert.HeadCommitMessage(Equals("WIP" + commitMessage))
|
||||
assert.CurrentView().Name("stagingSecondary")
|
||||
t.Views().Commits().
|
||||
Lines(
|
||||
Contains("WIP" + commitMessage),
|
||||
)
|
||||
|
||||
t.Views().StagingSecondary().IsFocused()
|
||||
|
||||
// TODO: assert that the staging panel has been refreshed (it currently does not get correctly refreshed)
|
||||
},
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
// TODO: find out why we can't use assert.SelectedLine() on the staging/stagingSecondary views.
|
||||
// TODO: find out why we can't use .SelectedLine() on the staging/stagingSecondary views.
|
||||
|
||||
var Unstaged = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Staging a couple files, going in the unstaged files menu, staging a line and committing",
|
||||
@ -17,27 +17,37 @@ var Unstaged = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
CreateFile("myfile", "myfile content\nwith a second line").
|
||||
CreateFile("myfile2", "myfile2 content")
|
||||
},
|
||||
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
|
||||
assert.CommitCount(0)
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
IsEmpty()
|
||||
|
||||
assert.CurrentView().Name("files").SelectedLine(Contains("myfile"))
|
||||
input.Enter()
|
||||
assert.CurrentView().Name("staging")
|
||||
assert.View("stagingSecondary").Content(DoesNotContain("+myfile content"))
|
||||
// stage the first line
|
||||
input.PrimaryAction()
|
||||
assert.View("staging").Content(DoesNotContain("+myfile content"))
|
||||
assert.View("stagingSecondary").Content(Contains("+myfile content"))
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
SelectedLine(Contains("myfile")).
|
||||
PressEnter()
|
||||
|
||||
t.Views().Staging().
|
||||
IsFocused().
|
||||
Tap(func() {
|
||||
t.Views().StagingSecondary().Content(DoesNotContain("+myfile content"))
|
||||
}).
|
||||
// stage the first line
|
||||
PressPrimaryAction().
|
||||
Tap(func() {
|
||||
t.Views().Staging().Content(DoesNotContain("+myfile content"))
|
||||
t.Views().StagingSecondary().Content(Contains("+myfile content"))
|
||||
}).
|
||||
Press(keys.Files.CommitChanges)
|
||||
|
||||
input.Press(keys.Files.CommitChanges)
|
||||
assert.InCommitMessagePanel()
|
||||
commitMessage := "my commit message"
|
||||
input.Type(commitMessage)
|
||||
input.Confirm()
|
||||
t.ExpectPopup().CommitMessagePanel().Type(commitMessage).Confirm()
|
||||
|
||||
assert.CommitCount(1)
|
||||
assert.HeadCommitMessage(Equals(commitMessage))
|
||||
assert.CurrentWindowName("staging")
|
||||
t.Views().Commits().
|
||||
Lines(
|
||||
Contains(commitMessage),
|
||||
)
|
||||
|
||||
t.Views().Staging().IsFocused()
|
||||
|
||||
// TODO: assert that the staging panel has been refreshed (it currently does not get correctly refreshed)
|
||||
},
|
||||
|
@ -15,13 +15,12 @@ var RemoteNamedStar = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
CreateNCommits(2)
|
||||
},
|
||||
SetupConfig: func(cfg *config.AppConfig) {},
|
||||
Run: func(
|
||||
shell *Shell,
|
||||
input *Input,
|
||||
assert *Assert,
|
||||
keys config.KeybindingConfig,
|
||||
) {
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
// here we're just asserting that we haven't panicked upon starting lazygit
|
||||
assert.AtLeastOneCommit()
|
||||
t.Views().Commits().
|
||||
Lines(
|
||||
Anything(),
|
||||
Anything(),
|
||||
)
|
||||
},
|
||||
})
|
||||
|
@ -21,18 +21,13 @@ var Basic = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
},
|
||||
}
|
||||
},
|
||||
Run: func(
|
||||
shell *Shell,
|
||||
input *Input,
|
||||
assert *Assert,
|
||||
keys config.KeybindingConfig,
|
||||
) {
|
||||
assert.WorkingTreeFileCount(0)
|
||||
|
||||
input.Press("a")
|
||||
|
||||
assert.View("files").Lines(
|
||||
Contains("myfile"),
|
||||
)
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Files().
|
||||
IsEmpty().
|
||||
IsFocused().
|
||||
Press("a").
|
||||
Lines(
|
||||
Contains("myfile"),
|
||||
)
|
||||
},
|
||||
})
|
||||
|
@ -55,24 +55,26 @@ var FormPrompts = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
},
|
||||
}
|
||||
},
|
||||
Run: func(
|
||||
shell *Shell,
|
||||
input *Input,
|
||||
assert *Assert,
|
||||
keys config.KeybindingConfig,
|
||||
) {
|
||||
assert.WorkingTreeFileCount(0)
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Files().
|
||||
IsEmpty().
|
||||
IsFocused().
|
||||
Press("a")
|
||||
|
||||
input.Press("a")
|
||||
t.ExpectPopup().Prompt().Title(Equals("Enter a file name")).Type("my file").Confirm()
|
||||
|
||||
input.Prompt(Equals("Enter a file name"), "my file")
|
||||
t.ExpectPopup().Menu().Title(Equals("Choose file content")).Select(Contains("bar")).Confirm()
|
||||
|
||||
input.Menu(Equals("Choose file content"), Contains("bar"))
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Equals("Are you sure?")).
|
||||
Content(Equals("Are you REALLY sure you want to make this file? Up to you buddy.")).
|
||||
Confirm()
|
||||
|
||||
input.AcceptConfirmation(Equals("Are you sure?"), Equals("Are you REALLY sure you want to make this file? Up to you buddy."))
|
||||
t.Views().Files().
|
||||
Lines(
|
||||
Contains("my file").IsSelected(),
|
||||
)
|
||||
|
||||
assert.WorkingTreeFileCount(1)
|
||||
assert.CurrentView().SelectedLine(Contains("my file"))
|
||||
assert.MainView().Content(Contains(`"BAR"`))
|
||||
t.Views().Main().Content(Contains(`"BAR"`))
|
||||
},
|
||||
})
|
||||
|
@ -42,25 +42,24 @@ var MenuFromCommand = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
},
|
||||
}
|
||||
},
|
||||
Run: func(
|
||||
shell *Shell,
|
||||
input *Input,
|
||||
assert *Assert,
|
||||
keys config.KeybindingConfig,
|
||||
) {
|
||||
assert.WorkingTreeFileCount(0)
|
||||
input.SwitchToBranchesView()
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Files().
|
||||
IsEmpty()
|
||||
|
||||
input.Press("a")
|
||||
t.Views().Branches().
|
||||
Focus().
|
||||
Press("a")
|
||||
|
||||
input.Menu(Equals("Choose commit message"), Contains("bar"))
|
||||
t.ExpectPopup().Menu().Title(Equals("Choose commit message")).Select(Contains("bar")).Confirm()
|
||||
|
||||
input.Prompt(Equals("Description"), " my branch")
|
||||
t.ExpectPopup().Prompt().Title(Equals("Description")).Type(" my branch").Confirm()
|
||||
|
||||
input.SwitchToFilesView()
|
||||
t.Views().Files().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("output.txt").IsSelected(),
|
||||
)
|
||||
|
||||
assert.WorkingTreeFileCount(1)
|
||||
assert.CurrentView().SelectedLine(Contains("output.txt"))
|
||||
assert.MainView().Content(Contains("bar Branch: #feature/foo my branch feature/foo"))
|
||||
t.Views().Main().Content(Contains("bar Branch: #feature/foo my branch feature/foo"))
|
||||
},
|
||||
})
|
||||
|
@ -41,27 +41,20 @@ var MenuFromCommandsOutput = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
},
|
||||
}
|
||||
},
|
||||
Run: func(
|
||||
shell *Shell,
|
||||
input *Input,
|
||||
assert *Assert,
|
||||
keys config.KeybindingConfig,
|
||||
) {
|
||||
assert.CurrentBranchName("feature/bar")
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Git().CurrentBranchName("feature/bar")
|
||||
|
||||
assert.WorkingTreeFileCount(0)
|
||||
input.SwitchToBranchesView()
|
||||
t.Views().Branches().
|
||||
Focus().
|
||||
Press("a")
|
||||
|
||||
input.Press("a")
|
||||
|
||||
assert.InPrompt()
|
||||
assert.CurrentView().
|
||||
t.ExpectPopup().Prompt().
|
||||
Title(Equals("Which git command do you want to run?")).
|
||||
SelectedLine(Equals("branch"))
|
||||
input.Confirm()
|
||||
InitialText(Equals("branch")).
|
||||
Confirm()
|
||||
|
||||
input.Menu(Equals("Branch:"), Equals("master"))
|
||||
t.ExpectPopup().Menu().Title(Equals("Branch:")).Select(Equals("master")).Confirm()
|
||||
|
||||
assert.CurrentBranchName("master")
|
||||
t.Git().CurrentBranchName("master")
|
||||
},
|
||||
})
|
||||
|
@ -53,24 +53,27 @@ var MultiplePrompts = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
},
|
||||
}
|
||||
},
|
||||
Run: func(
|
||||
shell *Shell,
|
||||
input *Input,
|
||||
assert *Assert,
|
||||
keys config.KeybindingConfig,
|
||||
) {
|
||||
assert.WorkingTreeFileCount(0)
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Files().
|
||||
IsEmpty().
|
||||
IsFocused().
|
||||
Press("a")
|
||||
|
||||
input.Press("a")
|
||||
t.ExpectPopup().Prompt().Title(Equals("Enter a file name")).Type("myfile").Confirm()
|
||||
|
||||
input.Prompt(Equals("Enter a file name"), "myfile")
|
||||
t.ExpectPopup().Menu().Title(Equals("Choose file content")).Select(Contains("bar")).Confirm()
|
||||
|
||||
input.Menu(Equals("Choose file content"), Contains("bar"))
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Equals("Are you sure?")).
|
||||
Content(Equals("Are you REALLY sure you want to make this file? Up to you buddy.")).
|
||||
Confirm()
|
||||
|
||||
input.AcceptConfirmation(Equals("Are you sure?"), Equals("Are you REALLY sure you want to make this file? Up to you buddy."))
|
||||
t.Views().Files().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("myfile").IsSelected(),
|
||||
)
|
||||
|
||||
assert.WorkingTreeFileCount(1)
|
||||
assert.CurrentView().SelectedLine(Contains("myfile"))
|
||||
assert.MainView().Content(Contains("BAR"))
|
||||
t.Views().Main().Content(Contains("BAR"))
|
||||
},
|
||||
})
|
||||
|
@ -21,38 +21,53 @@ var Diff = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
|
||||
shell.Checkout("branch-a")
|
||||
},
|
||||
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
|
||||
input.SwitchToBranchesView()
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Branches().
|
||||
Focus().
|
||||
TopLines(
|
||||
Contains("branch-a"),
|
||||
Contains("branch-b"),
|
||||
).
|
||||
Press(keys.Universal.DiffingMenu)
|
||||
|
||||
assert.CurrentView().TopLines(
|
||||
Contains("branch-a"),
|
||||
Contains("branch-b"),
|
||||
)
|
||||
input.Press(keys.Universal.DiffingMenu)
|
||||
input.Menu(Equals("Diffing"), Contains(`diff branch-a`))
|
||||
t.ExpectPopup().Menu().Title(Equals("Diffing")).Select(Contains(`diff branch-a`)).Confirm()
|
||||
|
||||
assert.CurrentView().Name("localBranches")
|
||||
t.Views().Branches().
|
||||
IsFocused().
|
||||
Tap(func() {
|
||||
t.Views().Information().Content(Contains("showing output for: git diff branch-a branch-a"))
|
||||
}).
|
||||
SelectNextItem().
|
||||
Tap(func() {
|
||||
t.Views().Information().Content(Contains("showing output for: git diff branch-a branch-b"))
|
||||
t.Views().Main().Content(Contains("+second line"))
|
||||
}).
|
||||
PressEnter()
|
||||
|
||||
assert.View("information").Content(Contains("showing output for: git diff branch-a branch-a"))
|
||||
input.NextItem()
|
||||
assert.View("information").Content(Contains("showing output for: git diff branch-a branch-b"))
|
||||
assert.MainView().Content(Contains("+second line"))
|
||||
t.Views().SubCommits().
|
||||
IsFocused().
|
||||
SelectedLine(Contains("update")).
|
||||
Tap(func() {
|
||||
t.Views().Main().Content(Contains("+second line"))
|
||||
}).
|
||||
PressEnter()
|
||||
|
||||
input.Enter()
|
||||
assert.CurrentView().Name("subCommits")
|
||||
assert.MainView().Content(Contains("+second line"))
|
||||
assert.CurrentView().SelectedLine(Contains("update"))
|
||||
input.Enter()
|
||||
assert.CurrentView().Name("commitFiles").SelectedLine(Contains("file1"))
|
||||
assert.MainView().Content(Contains("+second line"))
|
||||
t.Views().CommitFiles().
|
||||
IsFocused().
|
||||
SelectedLine(Contains("file1")).
|
||||
Tap(func() {
|
||||
t.Views().Main().Content(Contains("+second line"))
|
||||
}).
|
||||
PressEscape()
|
||||
|
||||
input.Press(keys.Universal.Return)
|
||||
input.Press(keys.Universal.Return)
|
||||
assert.CurrentView().Name("localBranches")
|
||||
t.Views().SubCommits().PressEscape()
|
||||
|
||||
input.Press(keys.Universal.DiffingMenu)
|
||||
input.Menu(Equals("Diffing"), Contains("reverse diff direction"))
|
||||
assert.View("information").Content(Contains("showing output for: git diff branch-a branch-b -R"))
|
||||
assert.MainView().Content(Contains("-second line"))
|
||||
t.Views().Branches().
|
||||
IsFocused().
|
||||
Press(keys.Universal.DiffingMenu)
|
||||
|
||||
t.ExpectPopup().Menu().Title(Equals("Diffing")).Select(Contains("reverse diff direction")).Confirm()
|
||||
t.Views().Information().Content(Contains("showing output for: git diff branch-a branch-b -R"))
|
||||
t.Views().Main().Content(Contains("-second line"))
|
||||
},
|
||||
})
|
||||
|
@ -21,48 +21,58 @@ var DiffAndApplyPatch = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
|
||||
shell.Checkout("branch-a")
|
||||
},
|
||||
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
|
||||
input.SwitchToBranchesView()
|
||||
assert.CurrentView().Lines(
|
||||
Contains("branch-a"),
|
||||
Contains("branch-b"),
|
||||
)
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Branches().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("branch-a"),
|
||||
Contains("branch-b"),
|
||||
).
|
||||
Press(keys.Universal.DiffingMenu)
|
||||
|
||||
input.Press(keys.Universal.DiffingMenu)
|
||||
t.ExpectPopup().Menu().Title(Equals("Diffing")).Select(Equals("diff branch-a")).Confirm()
|
||||
|
||||
input.Menu(Equals("Diffing"), Equals("diff branch-a"))
|
||||
t.Views().Information().Content(Contains("showing output for: git diff branch-a branch-a"))
|
||||
|
||||
assert.CurrentView().Name("localBranches")
|
||||
t.Views().Branches().
|
||||
IsFocused().
|
||||
SelectNextItem().
|
||||
Tap(func() {
|
||||
t.Views().Information().Content(Contains("showing output for: git diff branch-a branch-b"))
|
||||
t.Views().Main().Content(Contains("+second line"))
|
||||
}).
|
||||
PressEnter()
|
||||
|
||||
assert.View("information").Content(Contains("showing output for: git diff branch-a branch-a"))
|
||||
input.NextItem()
|
||||
assert.View("information").Content(Contains("showing output for: git diff branch-a branch-b"))
|
||||
assert.MainView().Content(Contains("+second line"))
|
||||
t.Views().SubCommits().
|
||||
IsFocused().
|
||||
SelectedLine(Contains("update")).
|
||||
Tap(func() {
|
||||
t.Views().Main().Content(Contains("+second line"))
|
||||
}).
|
||||
PressEnter()
|
||||
|
||||
input.Enter()
|
||||
assert.CurrentView().Name("subCommits")
|
||||
assert.MainView().Content(Contains("+second line"))
|
||||
assert.CurrentView().SelectedLine(Contains("update"))
|
||||
input.Enter()
|
||||
assert.CurrentView().Name("commitFiles")
|
||||
assert.CurrentView().SelectedLine(Contains("file1"))
|
||||
assert.MainView().Content(Contains("+second line"))
|
||||
t.Views().CommitFiles().
|
||||
IsFocused().
|
||||
SelectedLine(Contains("file1")).
|
||||
Tap(func() {
|
||||
t.Views().Main().Content(Contains("+second line"))
|
||||
}).
|
||||
PressPrimaryAction(). // add the file to the patch
|
||||
Press(keys.Universal.DiffingMenu).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Menu().Title(Equals("Diffing")).Select(Contains("exit diff mode")).Confirm()
|
||||
|
||||
// add the file to the patch
|
||||
input.PrimaryAction()
|
||||
t.Views().Information().Content(DoesNotContain("building patch"))
|
||||
}).
|
||||
Press(keys.Universal.CreatePatchOptionsMenu)
|
||||
|
||||
input.Press(keys.Universal.DiffingMenu)
|
||||
input.Menu(Equals("Diffing"), Contains("exit diff mode"))
|
||||
|
||||
assert.View("information").Content(DoesNotContain("building patch"))
|
||||
|
||||
input.Press(keys.Universal.CreatePatchOptionsMenu)
|
||||
// adding the regex '$' here to distinguish the menu item from the 'apply patch in reverse' item
|
||||
input.Menu(Equals("Patch Options"), MatchesRegexp("apply patch$"))
|
||||
t.ExpectPopup().Menu().Title(Equals("Patch Options")).Select(MatchesRegexp("apply patch$")).Confirm()
|
||||
|
||||
input.SwitchToFilesView()
|
||||
t.Views().Files().
|
||||
Focus().
|
||||
SelectedLine(Contains("file1"))
|
||||
|
||||
assert.CurrentView().SelectedLine(Contains("file1"))
|
||||
assert.MainView().Content(Contains("+second line"))
|
||||
t.Views().Main().Content(Contains("+second line"))
|
||||
},
|
||||
})
|
||||
|
@ -18,37 +18,38 @@ var DiffCommits = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
shell.UpdateFileAndAdd("file1", "first line\nsecond line\nthird line\n")
|
||||
shell.Commit("third commit")
|
||||
},
|
||||
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
|
||||
input.SwitchToCommitsView()
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("third commit").IsSelected(),
|
||||
Contains("second commit"),
|
||||
Contains("first commit"),
|
||||
).
|
||||
Press(keys.Universal.DiffingMenu).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Menu().Title(Equals("Diffing")).Select(MatchesRegexp(`diff \w+`)).Confirm()
|
||||
|
||||
assert.CurrentView().Lines(
|
||||
Contains("third commit"),
|
||||
Contains("second commit"),
|
||||
Contains("first commit"),
|
||||
)
|
||||
t.Views().Information().Content(Contains("showing output for: git diff"))
|
||||
}).
|
||||
SelectNextItem().
|
||||
SelectNextItem().
|
||||
SelectedLine(Contains("first commit")).
|
||||
Tap(func() {
|
||||
t.Views().Main().Content(Contains("-second line\n-third line"))
|
||||
}).
|
||||
Press(keys.Universal.DiffingMenu).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Menu().Title(Equals("Diffing")).Select(Contains("reverse diff direction")).Confirm()
|
||||
|
||||
input.Press(keys.Universal.DiffingMenu)
|
||||
input.Menu(Equals("Diffing"), MatchesRegexp(`diff \w+`))
|
||||
t.Views().Main().Content(Contains("+second line\n+third line"))
|
||||
}).
|
||||
PressEnter()
|
||||
|
||||
assert.NotInPopup()
|
||||
t.Views().CommitFiles().
|
||||
IsFocused().
|
||||
SelectedLine(Contains("file1"))
|
||||
|
||||
assert.View("information").Content(Contains("showing output for: git diff"))
|
||||
|
||||
input.NextItem()
|
||||
input.NextItem()
|
||||
assert.CurrentView().SelectedLine(Contains("first commit"))
|
||||
|
||||
assert.MainView().Content(Contains("-second line\n-third line"))
|
||||
|
||||
input.Press(keys.Universal.DiffingMenu)
|
||||
input.Menu(Equals("Diffing"), Contains("reverse diff direction"))
|
||||
assert.NotInPopup()
|
||||
|
||||
assert.MainView().Content(Contains("+second line\n+third line"))
|
||||
|
||||
input.Enter()
|
||||
|
||||
assert.CurrentView().Name("commitFiles").SelectedLine(Contains("file1"))
|
||||
assert.MainView().Content(Contains("+second line\n+third line"))
|
||||
t.Views().Main().Content(Contains("+second line\n+third line"))
|
||||
},
|
||||
})
|
||||
|
@ -21,10 +21,13 @@ var DirWithUntrackedFile = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
shell.CreateFile("dir/untracked", "bar")
|
||||
shell.UpdateFile("dir/file", "baz")
|
||||
},
|
||||
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
|
||||
assert.CommitCount(1)
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Lines(
|
||||
Contains("first commit"),
|
||||
)
|
||||
|
||||
assert.MainView().
|
||||
t.Views().Main().
|
||||
Content(DoesNotContain("error: Could not access")).
|
||||
// we show baz because it's a modified file but we don't show bar because it's untracked
|
||||
// (though it would be cool if we could show that too)
|
||||
|
@ -71,9 +71,7 @@ var DiscardChanges = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
shell.RunShellCommand(`echo "renamed\nhaha" > renamed2.txt && git add renamed2.txt`)
|
||||
},
|
||||
|
||||
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
|
||||
assert.CommitCount(3)
|
||||
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
type statusFile struct {
|
||||
status string
|
||||
label string
|
||||
@ -82,9 +80,12 @@ var DiscardChanges = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
|
||||
discardOneByOne := func(files []statusFile) {
|
||||
for _, file := range files {
|
||||
assert.CurrentView().SelectedLine(Contains(file.status + " " + file.label))
|
||||
input.Press(keys.Universal.Remove)
|
||||
input.Menu(Equals(file.menuTitle), Contains("discard all changes"))
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
SelectedLine(Contains(file.status + " " + file.label)).
|
||||
Press(keys.Universal.Remove)
|
||||
|
||||
t.ExpectPopup().Menu().Title(Equals(file.menuTitle)).Select(Contains("discard all changes")).Confirm()
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,7 +99,10 @@ var DiscardChanges = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
{status: "DU", label: "deleted-us.txt", menuTitle: "deleted-us.txt"},
|
||||
})
|
||||
|
||||
input.DenyConfirmation(Equals("continue"), Contains("all merge conflicts resolved. Continue?"))
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Equals("continue")).
|
||||
Content(Contains("all merge conflicts resolved. Continue?")).
|
||||
Cancel()
|
||||
|
||||
discardOneByOne([]statusFile{
|
||||
{status: "MD", label: "change-delete.txt", menuTitle: "change-delete.txt"},
|
||||
@ -115,6 +119,6 @@ var DiscardChanges = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
{status: "??", label: "new.txt", menuTitle: "new.txt"},
|
||||
})
|
||||
|
||||
assert.WorkingTreeFileCount(0)
|
||||
t.Views().Files().IsEmpty()
|
||||
},
|
||||
})
|
||||
|
@ -27,23 +27,35 @@ var AmendMerge = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Merge("feature-branch").
|
||||
CreateFileAndAdd(postMergeFilename, postMergeFileContent)
|
||||
},
|
||||
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
|
||||
assert.CommitCount(3)
|
||||
|
||||
input.SwitchToCommitsView()
|
||||
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
mergeCommitMessage := "Merge branch 'feature-branch' into development-branch"
|
||||
assert.HeadCommitMessage(Contains(mergeCommitMessage))
|
||||
|
||||
input.Press(keys.Commits.AmendToCommit)
|
||||
input.AcceptConfirmation(Equals("Amend Commit"), Contains("Are you sure you want to amend this commit with your staged files?"))
|
||||
t.Views().Commits().
|
||||
Lines(
|
||||
Contains(mergeCommitMessage),
|
||||
Contains("new feature commit"),
|
||||
Contains("initial commit"),
|
||||
)
|
||||
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
Press(keys.Commits.AmendToCommit)
|
||||
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Equals("Amend Commit")).
|
||||
Content(Contains("Are you sure you want to amend this commit with your staged files?")).
|
||||
Confirm()
|
||||
|
||||
// assuring we haven't added a brand new commit
|
||||
assert.CommitCount(3)
|
||||
assert.HeadCommitMessage(Contains(mergeCommitMessage))
|
||||
t.Views().Commits().
|
||||
Lines(
|
||||
Contains(mergeCommitMessage),
|
||||
Contains("new feature commit"),
|
||||
Contains("initial commit"),
|
||||
)
|
||||
|
||||
// assuring the post-merge file shows up in the merge commit.
|
||||
assert.MainView().
|
||||
t.Views().Main().
|
||||
Content(Contains(postMergeFilename)).
|
||||
Content(Contains("++" + postMergeFileContent))
|
||||
},
|
||||
|
@ -14,63 +14,58 @@ var One = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
shell.
|
||||
CreateNCommits(5) // these will appears at commit 05, 04, 04, down to 01
|
||||
},
|
||||
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
|
||||
input.SwitchToCommitsView()
|
||||
assert.CurrentView().Lines(
|
||||
Contains("commit 05"),
|
||||
Contains("commit 04"),
|
||||
Contains("commit 03"),
|
||||
Contains("commit 02"),
|
||||
Contains("commit 01"),
|
||||
)
|
||||
|
||||
input.NavigateToListItem(Contains("commit 02"))
|
||||
input.Press(keys.Universal.Edit)
|
||||
|
||||
assert.CurrentView().Lines(
|
||||
MatchesRegexp("pick.*commit 05"),
|
||||
MatchesRegexp("pick.*commit 04"),
|
||||
MatchesRegexp("pick.*commit 03"),
|
||||
MatchesRegexp("YOU ARE HERE.*commit 02"),
|
||||
Contains("commit 01"),
|
||||
)
|
||||
|
||||
input.PreviousItem()
|
||||
input.Press(keys.Commits.MarkCommitAsFixup)
|
||||
assert.CurrentView().Lines(
|
||||
MatchesRegexp("pick.*commit 05"),
|
||||
MatchesRegexp("pick.*commit 04"),
|
||||
MatchesRegexp("fixup.*commit 03"),
|
||||
MatchesRegexp("YOU ARE HERE.*commit 02"),
|
||||
Contains("commit 01"),
|
||||
)
|
||||
|
||||
input.PreviousItem()
|
||||
input.Press(keys.Universal.Remove)
|
||||
assert.CurrentView().Lines(
|
||||
MatchesRegexp("pick.*commit 05"),
|
||||
MatchesRegexp("drop.*commit 04"),
|
||||
MatchesRegexp("fixup.*commit 03"),
|
||||
MatchesRegexp("YOU ARE HERE.*commit 02"),
|
||||
Contains("commit 01"),
|
||||
)
|
||||
|
||||
input.PreviousItem()
|
||||
input.Press(keys.Commits.SquashDown)
|
||||
|
||||
assert.CurrentView().Lines(
|
||||
MatchesRegexp("squash.*commit 05"),
|
||||
MatchesRegexp("drop.*commit 04"),
|
||||
MatchesRegexp("fixup.*commit 03"),
|
||||
MatchesRegexp("YOU ARE HERE.*commit 02"),
|
||||
Contains("commit 01"),
|
||||
)
|
||||
|
||||
input.ContinueRebase()
|
||||
|
||||
assert.CurrentView().Lines(
|
||||
Contains("commit 02"),
|
||||
Contains("commit 01"),
|
||||
)
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("commit 05"),
|
||||
Contains("commit 04"),
|
||||
Contains("commit 03"),
|
||||
Contains("commit 02"),
|
||||
Contains("commit 01"),
|
||||
).
|
||||
NavigateToListItem(Contains("commit 02")).
|
||||
Press(keys.Universal.Edit).
|
||||
Lines(
|
||||
MatchesRegexp("pick.*commit 05"),
|
||||
MatchesRegexp("pick.*commit 04"),
|
||||
MatchesRegexp("pick.*commit 03"),
|
||||
MatchesRegexp("YOU ARE HERE.*commit 02").IsSelected(),
|
||||
Contains("commit 01"),
|
||||
).
|
||||
SelectPreviousItem().
|
||||
Press(keys.Commits.MarkCommitAsFixup).
|
||||
Lines(
|
||||
MatchesRegexp("pick.*commit 05"),
|
||||
MatchesRegexp("pick.*commit 04"),
|
||||
MatchesRegexp("fixup.*commit 03").IsSelected(),
|
||||
MatchesRegexp("YOU ARE HERE.*commit 02"),
|
||||
Contains("commit 01"),
|
||||
).
|
||||
SelectPreviousItem().
|
||||
Press(keys.Universal.Remove).
|
||||
Lines(
|
||||
MatchesRegexp("pick.*commit 05"),
|
||||
MatchesRegexp("drop.*commit 04").IsSelected(),
|
||||
MatchesRegexp("fixup.*commit 03"),
|
||||
MatchesRegexp("YOU ARE HERE.*commit 02"),
|
||||
Contains("commit 01"),
|
||||
).
|
||||
SelectPreviousItem().
|
||||
Press(keys.Commits.SquashDown).
|
||||
Lines(
|
||||
MatchesRegexp("squash.*commit 05").IsSelected(),
|
||||
MatchesRegexp("drop.*commit 04"),
|
||||
MatchesRegexp("fixup.*commit 03"),
|
||||
MatchesRegexp("YOU ARE HERE.*commit 02"),
|
||||
Contains("commit 01"),
|
||||
).
|
||||
Tap(func() {
|
||||
t.Actions().ContinueRebase()
|
||||
}).
|
||||
Lines(
|
||||
Contains("commit 02"),
|
||||
Contains("commit 01"),
|
||||
)
|
||||
},
|
||||
})
|
||||
|
@ -13,10 +13,14 @@ var ConfirmOnQuit = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
config.UserConfig.ConfirmOnQuit = true
|
||||
},
|
||||
SetupRepo: func(shell *Shell) {},
|
||||
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
|
||||
assert.CommitCount(0)
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
Press(keys.Universal.Quit)
|
||||
|
||||
input.Press(keys.Universal.Quit)
|
||||
input.AcceptConfirmation(Equals(""), Contains("Are you sure you want to quit?"))
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Equals("")).
|
||||
Content(Contains("Are you sure you want to quit?")).
|
||||
Confirm()
|
||||
},
|
||||
})
|
||||
|
@ -18,18 +18,18 @@ var Rename = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
CreateFileAndAdd("file-2", "change to stash2").
|
||||
StashWithMessage("bar")
|
||||
},
|
||||
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
|
||||
input.SwitchToStashView()
|
||||
|
||||
assert.CurrentView().Lines(
|
||||
Equals("On master: bar"),
|
||||
Equals("On master: foo"),
|
||||
)
|
||||
input.NextItem()
|
||||
input.Press(keys.Stash.RenameStash)
|
||||
|
||||
input.Prompt(Equals("Rename stash: stash@{1}"), " baz")
|
||||
|
||||
assert.CurrentView().SelectedLine(Equals("On master: foo baz"))
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Stash().
|
||||
Focus().
|
||||
Lines(
|
||||
Equals("On master: bar"),
|
||||
Equals("On master: foo"),
|
||||
).
|
||||
SelectNextItem().
|
||||
Press(keys.Stash.RenameStash).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Prompt().Title(Equals("Rename stash: stash@{1}")).Type(" baz").Confirm()
|
||||
}).
|
||||
SelectedLine(Equals("On master: foo baz"))
|
||||
},
|
||||
})
|
||||
|
@ -15,17 +15,26 @@ var Stash = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
shell.CreateFile("file", "content")
|
||||
shell.GitAddAll()
|
||||
},
|
||||
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
|
||||
assert.StashCount(0)
|
||||
assert.WorkingTreeFileCount(1)
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Stash().
|
||||
IsEmpty()
|
||||
|
||||
input.Press(keys.Files.ViewStashOptions)
|
||||
t.Views().Files().
|
||||
Lines(
|
||||
Contains("file"),
|
||||
).
|
||||
Press(keys.Files.ViewStashOptions)
|
||||
|
||||
input.Menu(Equals("Stash options"), MatchesRegexp("stash all changes$"))
|
||||
t.ExpectPopup().Menu().Title(Equals("Stash options")).Select(MatchesRegexp("stash all changes$")).Confirm()
|
||||
|
||||
input.Prompt(Equals("Stash changes"), "my stashed file")
|
||||
t.ExpectPopup().Prompt().Title(Equals("Stash changes")).Type("my stashed file").Confirm()
|
||||
|
||||
assert.StashCount(1)
|
||||
assert.WorkingTreeFileCount(0)
|
||||
t.Views().Stash().
|
||||
Lines(
|
||||
Contains("my stashed file"),
|
||||
)
|
||||
|
||||
t.Views().Files().
|
||||
IsEmpty()
|
||||
},
|
||||
})
|
||||
|
@ -16,17 +16,27 @@ var StashIncludingUntrackedFiles = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
shell.CreateFile("file_2", "content")
|
||||
shell.GitAdd("file_1")
|
||||
},
|
||||
Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) {
|
||||
assert.StashCount(0)
|
||||
assert.WorkingTreeFileCount(2)
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Stash().
|
||||
IsEmpty()
|
||||
|
||||
input.Press(keys.Files.ViewStashOptions)
|
||||
t.Views().Files().
|
||||
Lines(
|
||||
Contains("file_1"),
|
||||
Contains("file_2"),
|
||||
).
|
||||
Press(keys.Files.ViewStashOptions)
|
||||
|
||||
input.Menu(Equals("Stash options"), Contains("stash all changes including untracked files"))
|
||||
t.ExpectPopup().Menu().Title(Equals("Stash options")).Select(Contains("stash all changes including untracked files")).Confirm()
|
||||
|
||||
input.Prompt(Equals("Stash changes"), "my stashed file")
|
||||
t.ExpectPopup().Prompt().Title(Equals("Stash changes")).Type("my stashed file").Confirm()
|
||||
|
||||
assert.StashCount(1)
|
||||
assert.WorkingTreeFileCount(0)
|
||||
t.Views().Stash().
|
||||
Lines(
|
||||
Contains("my stashed file"),
|
||||
)
|
||||
|
||||
t.Views().Files().
|
||||
IsEmpty()
|
||||
},
|
||||
})
|
||||
|
@ -20,7 +20,6 @@ type GuiDriver interface {
|
||||
PressKey(string)
|
||||
Keys() config.KeybindingConfig
|
||||
CurrentContext() types.Context
|
||||
Model() *types.Model
|
||||
Fail(message string)
|
||||
// These two log methods are for the sake of debugging while testing. There's no need to actually
|
||||
// commit any logging.
|
||||
|
Reference in New Issue
Block a user