From 78b495f50a080822121852dfdf27b481dbbd8879 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Tue, 27 Dec 2022 21:35:36 +1100 Subject: rename input to t --- pkg/integration/README.md | 26 +-- pkg/integration/components/alert_asserter.go | 6 +- .../components/commit_message_panel_asserter.go | 8 +- .../components/confirmation_asserter.go | 6 +- pkg/integration/components/input.go | 237 --------------------- pkg/integration/components/menu_asserter.go | 6 +- pkg/integration/components/prompt_asserter.go | 20 +- pkg/integration/components/test.go | 10 +- pkg/integration/components/test_driver.go | 237 +++++++++++++++++++++ pkg/integration/components/test_test.go | 20 +- pkg/integration/components/view.go | 51 ++--- pkg/integration/components/views.go | 18 +- pkg/integration/tests/bisect/basic.go | 20 +- pkg/integration/tests/bisect/from_other_branch.go | 14 +- pkg/integration/tests/branch/checkout_by_name.go | 8 +- pkg/integration/tests/branch/delete.go | 8 +- pkg/integration/tests/branch/rebase.go | 22 +- pkg/integration/tests/branch/rebase_and_drop.go | 26 +-- pkg/integration/tests/branch/reset.go | 10 +- pkg/integration/tests/branch/suggestions.go | 8 +- pkg/integration/tests/cherry_pick/cherry_pick.go | 18 +- .../tests/cherry_pick/cherry_pick_conflicts.go | 32 +-- pkg/integration/tests/commit/commit.go | 10 +- pkg/integration/tests/commit/commit_multiline.go | 16 +- pkg/integration/tests/commit/new_branch.go | 10 +- pkg/integration/tests/commit/revert.go | 12 +- pkg/integration/tests/commit/staged.go | 32 +-- .../tests/commit/staged_without_hooks.go | 22 +- pkg/integration/tests/commit/unstaged.go | 24 +-- pkg/integration/tests/config/remote_named_star.go | 4 +- pkg/integration/tests/custom_commands/basic.go | 6 +- .../tests/custom_commands/form_prompts.go | 18 +- .../tests/custom_commands/menu_from_command.go | 16 +- .../custom_commands/menu_from_commands_output.go | 14 +- .../tests/custom_commands/multiple_prompts.go | 18 +- pkg/integration/tests/diff/diff.go | 32 +-- pkg/integration/tests/diff/diff_and_apply_patch.go | 32 +-- pkg/integration/tests/diff/diff_commits.go | 18 +- .../tests/file/dir_with_untracked_file.go | 6 +- pkg/integration/tests/file/discard_changes.go | 12 +- .../tests/interactive_rebase/amend_merge.go | 16 +- pkg/integration/tests/interactive_rebase/one.go | 6 +- pkg/integration/tests/misc/confirm_on_quit.go | 8 +- pkg/integration/tests/stash/rename.go | 6 +- pkg/integration/tests/stash/stash.go | 16 +- .../tests/stash/stash_including_untracked_files.go | 16 +- 46 files changed, 579 insertions(+), 602 deletions(-) delete mode 100644 pkg/integration/components/input.go create mode 100644 pkg/integration/components/test_driver.go diff --git a/pkg/integration/README.md b/pkg/integration/README.md index fe4f43ac1..2da3b5f81 100644 --- a/pkg/integration/README.md +++ b/pkg/integration/README.md @@ -24,16 +24,15 @@ 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 three arguments passed in: 1. `shell` -2. `input` -3. `assert` +2. `t` (the test driver) 4. `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. +`t` 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. @@ -43,32 +42,21 @@ 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) -input.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") +t.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")) +t.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?")) +t.AcceptConfirmation(Equals("Are you sure?"), Equals("Are you REALLY sure?")) ``` ## Running tests diff --git a/pkg/integration/components/alert_asserter.go b/pkg/integration/components/alert_asserter.go index abc2f1171..2010af3b8 100644 --- a/pkg/integration/components/alert_asserter.go +++ b/pkg/integration/components/alert_asserter.go @@ -1,13 +1,13 @@ package components type AlertAsserter struct { - input *Input + t *TestDriver hasCheckedTitle bool hasCheckedContent bool } func (self *AlertAsserter) getViewAsserter() *View { - return self.input.Views().Confirmation() + return self.t.Views().Confirmation() } // asserts that the alert view has the expected title @@ -42,6 +42,6 @@ func (self *AlertAsserter) Cancel() { func (self *AlertAsserter) checkNecessaryChecksCompleted() { if !self.hasCheckedContent || !self.hasCheckedTitle { - self.input.Fail("You must both check the content and title of a confirmation popup by calling Title()/Content() before calling Confirm()/Cancel().") + self.t.Fail("You must both check the content and title of a confirmation popup by calling Title()/Content() before calling Confirm()/Cancel().") } } diff --git a/pkg/integration/components/commit_message_panel_asserter.go b/pkg/integration/components/commit_message_panel_asserter.go index 9aacacf89..aad82720f 100644 --- a/pkg/integration/components/commit_message_panel_asserter.go +++ b/pkg/integration/components/commit_message_panel_asserter.go @@ -1,11 +1,11 @@ package components type CommitMessagePanelAsserter struct { - input *Input + t *TestDriver } func (self *CommitMessagePanelAsserter) getViewAsserter() *View { - return self.input.Views().CommitMessage() + return self.t.Views().CommitMessage() } // asserts on the text initially present in the prompt @@ -16,13 +16,13 @@ func (self *CommitMessagePanelAsserter) InitialText(expected *matcher) *CommitMe } func (self *CommitMessagePanelAsserter) Type(value string) *CommitMessagePanelAsserter { - self.input.typeContent(value) + self.t.typeContent(value) return self } func (self *CommitMessagePanelAsserter) AddNewline() *CommitMessagePanelAsserter { - self.input.press(self.input.keys.Universal.AppendNewline) + self.t.press(self.t.keys.Universal.AppendNewline) return self } diff --git a/pkg/integration/components/confirmation_asserter.go b/pkg/integration/components/confirmation_asserter.go index 2d3d87ba0..b226f0885 100644 --- a/pkg/integration/components/confirmation_asserter.go +++ b/pkg/integration/components/confirmation_asserter.go @@ -1,13 +1,13 @@ package components type ConfirmationAsserter struct { - input *Input + t *TestDriver hasCheckedTitle bool hasCheckedContent bool } func (self *ConfirmationAsserter) getViewAsserter() *View { - return self.input.Views().Confirmation() + return self.t.Views().Confirmation() } // asserts that the confirmation view has the expected title @@ -42,6 +42,6 @@ func (self *ConfirmationAsserter) Cancel() { func (self *ConfirmationAsserter) checkNecessaryChecksCompleted() { if !self.hasCheckedContent || !self.hasCheckedTitle { - self.input.Fail("You must both check the content and title of a confirmation popup by calling Title()/Content() before calling Confirm()/Cancel().") + self.t.Fail("You must both check the content and title of a confirmation popup by calling Title()/Content() before calling Confirm()/Cancel().") } } diff --git a/pkg/integration/components/input.go b/pkg/integration/components/input.go deleted file mode 100644 index d5db33518..000000000 --- a/pkg/integration/components/input.go +++ /dev/null @@ -1,237 +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" - "github.com/samber/lo" -) - -type Input struct { - gui integrationTypes.GuiDriver - keys config.KeybindingConfig - pushKeyDelay int - *assertionHelper -} - -func NewInput(gui integrationTypes.GuiDriver, keys config.KeybindingConfig, pushKeyDelay int) *Input { - return &Input{ - gui: gui, - keys: keys, - pushKeyDelay: pushKeyDelay, - assertionHelper: &assertionHelper{gui: gui}, - } -} - -// key is something like 'w' or ''. 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(keyStr string) { - self.Wait(self.pushKeyDelay) - - self.gui.PressKey(keyStr) -} - -func (self *Input) typeContent(content string) { - for _, char := range content { - self.press(string(char)) - } -} - -func (self *Input) ContinueMerge() { - self.Views().current().Press(self.keys.Universal.CreateRebaseOptionsMenu) - - self.ExpectMenu(). - Title(Equals("Rebase Options")). - Select(Contains("continue")). - 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.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 *Input) 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 *Input) ExpectConfirmation() *ConfirmationAsserter { - self.inConfirm() - - return &ConfirmationAsserter{input: self} -} - -func (self *Input) 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 *Input) ExpectPrompt() *PromptAsserter { - self.inPrompt() - - return &PromptAsserter{input: self} -} - -func (self *Input) 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 *Input) ExpectAlert() *AlertAsserter { - self.inAlert() - - return &AlertAsserter{input: self} -} - -func (self *Input) 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 *Input) ExpectMenu() *MenuAsserter { - self.inMenu() - - return &MenuAsserter{input: self} -} - -func (self *Input) inMenu() { - self.assertWithRetries(func() (bool, string) { - return self.gui.CurrentContext().GetView().Name() == "menu", "Expected popup menu to be focused" - }) -} - -func (self *Input) ExpectCommitMessagePanel() *CommitMessagePanelAsserter { - self.inCommitMessagePanel() - - return &CommitMessagePanelAsserter{input: self} -} - -func (self *Input) inCommitMessagePanel() { - self.assertWithRetries(func() (bool, string) { - currentView := self.gui.CurrentContext().GetView() - return currentView.Name() == "commitMessage", "Expected commit message panel to be focused" - }) -} - -func (self *Input) 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) - }) -} - -// for making assertions on lazygit views -func (self *Input) Views() *Views { - return &Views{input: self} -} - -// for making assertions on the lazygit model -func (self *Input) Model() *Model { - return &Model{assertionHelper: self.assertionHelper, gui: self.gui} -} - -// for making assertions on the file system -func (self *Input) 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 *Input) Fail(message string) { - self.assertionHelper.fail(message) -} - -func (self *Input) NotInPopup() { - self.assertWithRetries(func() (bool, string) { - viewName := self.gui.CurrentContext().GetView().Name() - return !lo.Contains([]string{"menu", "confirmation", "commitMessage"}, viewName), fmt.Sprintf("Unexpected popup view present: %s view", viewName) - }) -} diff --git a/pkg/integration/components/menu_asserter.go b/pkg/integration/components/menu_asserter.go index 4f1fd035a..42e31a610 100644 --- a/pkg/integration/components/menu_asserter.go +++ b/pkg/integration/components/menu_asserter.go @@ -1,12 +1,12 @@ package components type MenuAsserter struct { - input *Input + t *TestDriver hasCheckedTitle bool } func (self *MenuAsserter) getViewAsserter() *View { - return self.input.Views().Menu() + return self.t.Views().Menu() } // asserts that the popup has the expected title @@ -38,6 +38,6 @@ func (self *MenuAsserter) Select(option *matcher) *MenuAsserter { func (self *MenuAsserter) checkNecessaryChecksCompleted() { if !self.hasCheckedTitle { - self.input.Fail("You must check the title of a menu popup by calling Title() before calling Confirm()/Cancel().") + self.t.Fail("You must check the title of a menu popup by calling Title() before calling Confirm()/Cancel().") } } diff --git a/pkg/integration/components/prompt_asserter.go b/pkg/integration/components/prompt_asserter.go index 343d9d54b..9fea5bdf2 100644 --- a/pkg/integration/components/prompt_asserter.go +++ b/pkg/integration/components/prompt_asserter.go @@ -1,12 +1,12 @@ package components type PromptAsserter struct { - input *Input + t *TestDriver hasCheckedTitle bool } func (self *PromptAsserter) getViewAsserter() *View { - return self.input.Views().Confirmation() + return self.t.Views().Confirmation() } // asserts that the popup has the expected title @@ -26,7 +26,7 @@ func (self *PromptAsserter) InitialText(expected *matcher) *PromptAsserter { } func (self *PromptAsserter) Type(value string) *PromptAsserter { - self.input.typeContent(value) + self.t.typeContent(value) return self } @@ -49,25 +49,25 @@ func (self *PromptAsserter) Cancel() { func (self *PromptAsserter) checkNecessaryChecksCompleted() { if !self.hasCheckedTitle { - self.input.Fail("You must check the title of a prompt popup by calling Title() before calling Confirm()/Cancel().") + 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.input.Views().Suggestions().Lines(matchers...) + self.t.Views().Suggestions().Lines(matchers...) return self } func (self *PromptAsserter) SuggestionTopLines(matchers ...*matcher) *PromptAsserter { - self.input.Views().Suggestions().TopLines(matchers...) + self.t.Views().Suggestions().TopLines(matchers...) return self } func (self *PromptAsserter) SelectFirstSuggestion() *PromptAsserter { - self.input.press(self.input.keys.Universal.TogglePanel) - self.input.Views().Suggestions(). + self.t.press(self.t.keys.Universal.TogglePanel) + self.t.Views().Suggestions(). IsFocused(). SelectedLineIdx(0) @@ -75,8 +75,8 @@ func (self *PromptAsserter) SelectFirstSuggestion() *PromptAsserter { } func (self *PromptAsserter) SelectSuggestion(matcher *matcher) *PromptAsserter { - self.input.press(self.input.keys.Universal.TogglePanel) - self.input.Views().Suggestions(). + self.t.press(self.t.keys.Universal.TogglePanel) + self.t.Views().Suggestions(). IsFocused(). NavigateToListItem(matcher) diff --git a/pkg/integration/components/test.go b/pkg/integration/components/test.go index 705323ecb..927dfb36c 100644 --- a/pkg/integration/components/test.go +++ b/pkg/integration/components/test.go @@ -25,7 +25,7 @@ type IntegrationTest struct { setupConfig func(config *config.AppConfig) run func( shell *Shell, - input *Input, + testController *TestDriver, keys config.KeybindingConfig, ) } @@ -40,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, keys config.KeybindingConfig) + Run func(shell *Shell, t *TestDriver, keys config.KeybindingConfig) // additional args passed to lazygit ExtraCmdArgs string // for when a test is flakey @@ -94,13 +94,13 @@ func (self *IntegrationTest) SetupRepo(shell *Shell) { func (self *IntegrationTest) Run(gui integrationTypes.GuiDriver) { shell := NewShell("/tmp/lazygit-test") keys := gui.Keys() - input := NewInput(gui, keys, KeyPressDelay()) + testController := NewTestController(gui, keys, KeyPressDelay()) - self.run(shell, input, keys) + self.run(shell, testController, keys) if KeyPressDelay() > 0 { // the dev would want to see the final state if they're running in slow mode - input.Wait(2000) + testController.Wait(2000) } } diff --git a/pkg/integration/components/test_driver.go b/pkg/integration/components/test_driver.go new file mode 100644 index 000000000..7b3fb0889 --- /dev/null +++ b/pkg/integration/components/test_driver.go @@ -0,0 +1,237 @@ +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" + "github.com/samber/lo" +) + +type TestDriver struct { + gui integrationTypes.GuiDriver + keys config.KeybindingConfig + pushKeyDelay int + *assertionHelper +} + +func NewTestController(gui integrationTypes.GuiDriver, keys config.KeybindingConfig, pushKeyDelay int) *TestDriver { + return &TestDriver{ + gui: gui, + keys: keys, + pushKeyDelay: pushKeyDelay, + assertionHelper: &assertionHelper{gui: gui}, + } +} + +// key is something like 'w' or ''. 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) ContinueMerge() { + self.Views().current().Press(self.keys.Universal.CreateRebaseOptionsMenu) + + self.ExpectMenu(). + Title(Equals("Rebase Options")). + Select(Contains("continue")). + Confirm() +} + +func (self *TestDriver) ContinueRebase() { + self.ContinueMerge() +} + +// 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) +} + +// 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()) + }) +} + +func (self *TestDriver) ExpectConfirmation() *ConfirmationAsserter { + self.inConfirm() + + return &ConfirmationAsserter{t: self} +} + +func (self *TestDriver) 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 *TestDriver) ExpectPrompt() *PromptAsserter { + self.inPrompt() + + return &PromptAsserter{t: self} +} + +func (self *TestDriver) 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 *TestDriver) ExpectAlert() *AlertAsserter { + self.inAlert() + + return &AlertAsserter{t: self} +} + +func (self *TestDriver) 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 *TestDriver) ExpectMenu() *MenuAsserter { + self.inMenu() + + return &MenuAsserter{t: self} +} + +func (self *TestDriver) inMenu() { + self.assertWithRetries(func() (bool, string) { + return self.gui.CurrentContext().GetView().Name() == "menu", "Expected popup menu to be focused" + }) +} + +func (self *TestDriver) ExpectCommitMessagePanel() *CommitMessagePanelAsserter { + self.inCommitMessagePanel() + + return &CommitMessagePanelAsserter{t: self} +} + +func (self *TestDriver) inCommitMessagePanel() { + self.assertWithRetries(func() (bool, string) { + currentView := self.gui.CurrentContext().GetView() + return currentView.Name() == "commitMessage", "Expected commit message panel to be focused" + }) +} + +func (self *TestDriver) 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) + }) +} + +// for making assertions on lazygit views +func (self *TestDriver) Views() *Views { + return &Views{t: self} +} + +// for making assertions on the lazygit model +func (self *TestDriver) Model() *Model { + return &Model{assertionHelper: self.assertionHelper, gui: self.gui} +} + +// 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) +} + +func (self *TestDriver) NotInPopup() { + self.assertWithRetries(func() (bool, string) { + viewName := self.gui.CurrentContext().GetView().Name() + return !lo.Contains([]string{"menu", "confirmation", "commitMessage"}, viewName), fmt.Sprintf("Unexpected popup view present: %s view", viewName) + }) +} diff --git a/pkg/integration/components/test_test.go b/pkg/integration/components/test_test.go index 1356fbf3b..79d2b3955 100644 --- a/pkg/integration/components/test_test.go +++ b/pkg/integration/components/test_test.go @@ -63,10 +63,10 @@ func (self *fakeGuiDriver) View(viewName string) *gocui.View { func TestAssertionFailure(t *testing.T) { test := NewIntegrationTest(NewIntegrationTestArgs{ Description: unitTestDescription, - Run: func(shell *Shell, input *Input, keys config.KeybindingConfig) { - input.press("a") - input.press("b") - input.Model().CommitCount(2) + Run: func(shell *Shell, t *TestDriver, keys config.KeybindingConfig) { + t.press("a") + t.press("b") + t.Model().CommitCount(2) }, }) driver := &fakeGuiDriver{} @@ -78,8 +78,8 @@ func TestAssertionFailure(t *testing.T) { func TestManualFailure(t *testing.T) { test := NewIntegrationTest(NewIntegrationTestArgs{ Description: unitTestDescription, - Run: func(shell *Shell, input *Input, keys config.KeybindingConfig) { - input.Fail("blah") + Run: func(shell *Shell, t *TestDriver, keys config.KeybindingConfig) { + t.Fail("blah") }, }) driver := &fakeGuiDriver{} @@ -90,10 +90,10 @@ func TestManualFailure(t *testing.T) { func TestSuccess(t *testing.T) { test := NewIntegrationTest(NewIntegrationTestArgs{ Description: unitTestDescription, - Run: func(shell *Shell, input *Input, keys config.KeybindingConfig) { - input.press("a") - input.press("b") - input.Model().CommitCount(0) + Run: func(shell *Shell, t *TestDriver, keys config.KeybindingConfig) { + t.press("a") + t.press("b") + t.Model().CommitCount(0) }, }) driver := &fakeGuiDriver{} diff --git a/pkg/integration/components/view.go b/pkg/integration/components/view.go index cda5ff4cb..479c419cc 100644 --- a/pkg/integration/components/view.go +++ b/pkg/integration/components/view.go @@ -10,23 +10,12 @@ type View struct { // context is prepended to any error messages e.g. 'context: "current view"' context string getView func() *gocui.View - input *Input -} - -// asserts that the view has the expected name. This is typically used in tandem with the CurrentView method i.e.; -// input.CurrentView().Name("commits") to assert that the current view is the commits view. -func (self *View) Name(expected string) *View { - self.input.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 + t *TestDriver } // asserts that the view has the expected title func (self *View) Title(expected *matcher) *View { - self.input.assertWithRetries(func() (bool, string) { + self.t.assertWithRetries(func() (bool, string) { actual := self.getView().Title return expected.context(fmt.Sprintf("%s title", self.context)).test(actual) }) @@ -39,7 +28,7 @@ func (self *View) Title(expected *matcher) *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 { - self.input.assertWithRetries(func() (bool, string) { + 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)) }) @@ -50,7 +39,7 @@ func (self *View) TopLines(matchers ...*matcher) *View { // 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.input.assertWithRetries(func() (bool, string) { + self.t.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)) }) @@ -64,14 +53,14 @@ func (self *View) assertLines(matchers ...*matcher) *View { for i, matcher := range matchers { checkIsSelected, matcher := matcher.checkIsSelected() - self.input.matchString(matcher, fmt.Sprintf("Unexpected content in view '%s'.", view.Name()), + self.t.matchString(matcher, fmt.Sprintf("Unexpected content in view '%s'.", view.Name()), func() string { return view.BufferLines()[i] }, ) if checkIsSelected { - self.input.assertWithRetries(func() (bool, string) { + 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) }) @@ -83,7 +72,7 @@ func (self *View) assertLines(matchers ...*matcher) *View { // asserts on the content of the view i.e. the stuff within the view's frame. func (self *View) Content(matcher *matcher) *View { - self.input.matchString(matcher, fmt.Sprintf("%s: Unexpected content.", self.context), + self.t.matchString(matcher, fmt.Sprintf("%s: Unexpected content.", self.context), func() string { return self.getView().Buffer() }, @@ -94,7 +83,7 @@ func (self *View) Content(matcher *matcher) *View { // asserts on the selected line of the view func (self *View) SelectedLine(matcher *matcher) *View { - self.input.matchString(matcher, fmt.Sprintf("%s: Unexpected selected line.", self.context), + self.t.matchString(matcher, fmt.Sprintf("%s: Unexpected selected line.", self.context), func() string { return self.getView().SelectedLine() }, @@ -105,7 +94,7 @@ func (self *View) SelectedLine(matcher *matcher) *View { // 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.input.assertWithRetries(func() (bool, string) { + 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) }) @@ -132,10 +121,10 @@ func (self *View) Focus() *View { index, ok := windowIndexMap[viewName] if !ok { - self.input.fail(fmt.Sprintf("Cannot focus view %s: Focus() method not implemented", viewName)) + self.t.fail(fmt.Sprintf("Cannot focus view %s: Focus() method not implemented", viewName)) } - self.input.press(self.input.keys.Universal.JumpToBlock[index]) + self.t.press(self.t.keys.Universal.JumpToBlock[index]) // assert that we land in the expected view self.IsFocused() @@ -145,9 +134,9 @@ func (self *View) Focus() *View { // asserts that the view is focused func (self *View) IsFocused() *View { - self.input.assertWithRetries(func() (bool, string) { + self.t.assertWithRetries(func() (bool, string) { expected := self.getView().Name() - actual := self.input.gui.CurrentContext().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) }) @@ -157,40 +146,40 @@ func (self *View) IsFocused() *View { func (self *View) Press(keyStr string) *View { self.IsFocused() - self.input.press(keyStr) + self.t.press(keyStr) return self } // i.e. pressing down arrow func (self *View) SelectNextItem() *View { - return self.Press(self.input.keys.Universal.NextItem) + return self.Press(self.t.keys.Universal.NextItem) } // i.e. pressing up arrow func (self *View) SelectPreviousItem() *View { - return self.Press(self.input.keys.Universal.PrevItem) + return self.Press(self.t.keys.Universal.PrevItem) } // i.e. pressing space func (self *View) PressPrimaryAction() *View { - return self.Press(self.input.keys.Universal.Select) + return self.Press(self.t.keys.Universal.Select) } // i.e. pressing space func (self *View) PressEnter() *View { - return self.Press(self.input.keys.Universal.Confirm) + return self.Press(self.t.keys.Universal.Confirm) } // i.e. pressing escape func (self *View) PressEscape() *View { - return self.Press(self.input.keys.Universal.Return) + return self.Press(self.t.keys.Universal.Return) } func (self *View) NavigateToListItem(matcher *matcher) *View { self.IsFocused() - self.input.navigateToListItem(matcher) + self.t.navigateToListItem(matcher) return self } diff --git a/pkg/integration/components/views.go b/pkg/integration/components/views.go index 9efe7cdcf..4a95e5e0f 100644 --- a/pkg/integration/components/views.go +++ b/pkg/integration/components/views.go @@ -7,7 +7,7 @@ import ( ) type Views struct { - input *Input + t *TestDriver } // not exporting this because I want the test to always be explicit about what @@ -15,32 +15,32 @@ type Views struct { func (self *Views) current() *View { return &View{ context: "current view", - getView: func() *gocui.View { return self.input.gui.CurrentContext().GetView() }, - input: self.input, + 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.input.gui.MainView() }, - input: self.input, + 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.input.gui.SecondaryView() }, - input: self.input, + 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.input.gui.View(viewName) }, - input: self.input, + getView: func() *gocui.View { return self.t.gui.View(viewName) }, + t: self.t, } } diff --git a/pkg/integration/tests/bisect/basic.go b/pkg/integration/tests/bisect/basic.go index ac4953095..cbb37aac7 100644 --- a/pkg/integration/tests/bisect/basic.go +++ b/pkg/integration/tests/bisect/basic.go @@ -16,33 +16,33 @@ var Basic = NewIntegrationTest(NewIntegrationTestArgs{ SetupConfig: func(cfg *config.AppConfig) {}, Run: func( shell *Shell, - input *Input, + t *TestDriver, keys config.KeybindingConfig, ) { markCommitAsBad := func() { - input.Views().Commits(). + t.Views().Commits(). Press(keys.Commits.ViewBisectOptions) - input.ExpectMenu().Title(Equals("Bisect")).Select(MatchesRegexp(`mark .* as bad`)).Confirm() + t.ExpectMenu().Title(Equals("Bisect")).Select(MatchesRegexp(`mark .* as bad`)).Confirm() } markCommitAsGood := func() { - input.Views().Commits(). + t.Views().Commits(). Press(keys.Commits.ViewBisectOptions) - input.ExpectMenu().Title(Equals("Bisect")).Select(MatchesRegexp(`mark .* as good`)).Confirm() + t.ExpectMenu().Title(Equals("Bisect")).Select(MatchesRegexp(`mark .* as good`)).Confirm() } - input.Model().AtLeastOneCommit() + t.Model().AtLeastOneCommit() - input.Views().Commits(). + t.Views().Commits(). Focus(). SelectedLine(Contains("commit 10")). NavigateToListItem(Contains("commit 09")). Tap(func() { markCommitAsBad() - input.Views().Information().Content(Contains("bisecting")) + t.Views().Information().Content(Contains("bisecting")) }). SelectedLine(Contains("<-- bad")). NavigateToListItem(Contains("commit 02")). @@ -55,11 +55,11 @@ var Basic = NewIntegrationTest(NewIntegrationTestArgs{ markCommitAsGood() // commit 5 is the culprit because we marked 4 as good and 5 as bad. - input.ExpectAlert().Title(Equals("Bisect complete")).Content(MatchesRegexp("(?s)commit 05.*Do you want to reset")).Confirm() + t.ExpectAlert().Title(Equals("Bisect complete")).Content(MatchesRegexp("(?s)commit 05.*Do you want to reset")).Confirm() }). IsFocused(). Content(Contains("commit 04")) - input.Views().Information().Content(DoesNotContain("bisecting")) + t.Views().Information().Content(DoesNotContain("bisecting")) }, }) diff --git a/pkg/integration/tests/bisect/from_other_branch.go b/pkg/integration/tests/bisect/from_other_branch.go index 20f0fb646..8759e28f3 100644 --- a/pkg/integration/tests/bisect/from_other_branch.go +++ b/pkg/integration/tests/bisect/from_other_branch.go @@ -20,14 +20,14 @@ var FromOtherBranch = NewIntegrationTest(NewIntegrationTestArgs{ SetupConfig: func(cfg *config.AppConfig) {}, Run: func( shell *Shell, - input *Input, + t *TestDriver, keys config.KeybindingConfig, ) { - input.Views().Information().Content(Contains("bisecting")) + t.Views().Information().Content(Contains("bisecting")) - input.Model().AtLeastOneCommit() + t.Model().AtLeastOneCommit() - input.Views().Commits(). + t.Views().Commits(). Focus(). TopLines( MatchesRegexp(`<-- bad.*commit 08`), @@ -38,11 +38,11 @@ var FromOtherBranch = NewIntegrationTest(NewIntegrationTestArgs{ SelectNextItem(). Press(keys.Commits.ViewBisectOptions). Tap(func() { - input.ExpectMenu().Title(Equals("Bisect")).Select(MatchesRegexp(`mark .* as good`)).Confirm() + t.ExpectMenu().Title(Equals("Bisect")).Select(MatchesRegexp(`mark .* as good`)).Confirm() - input.ExpectAlert().Title(Equals("Bisect complete")).Content(MatchesRegexp("(?s)commit 08.*Do you want to reset")).Confirm() + t.ExpectAlert().Title(Equals("Bisect complete")).Content(MatchesRegexp("(?s)commit 08.*Do you want to reset")).Confirm() - input.Views().Information().Content(DoesNotContain("bisecting")) + t.Views().Information().Content(DoesNotContain("bisecting")) }). // back in master branch which just had the one commit Lines( diff --git a/pkg/integration/tests/branch/checkout_by_name.go b/pkg/integration/tests/branch/checkout_by_name.go index 2d5e6e098..7a8733d26 100644 --- a/pkg/integration/tests/branch/checkout_by_name.go +++ b/pkg/integration/tests/branch/checkout_by_name.go @@ -17,8 +17,8 @@ var CheckoutByName = NewIntegrationTest(NewIntegrationTestArgs{ Checkout("master"). EmptyCommit("blah") }, - Run: func(shell *Shell, input *Input, keys config.KeybindingConfig) { - input.Views().Branches(). + Run: func(shell *Shell, t *TestDriver, keys config.KeybindingConfig) { + t.Views().Branches(). Focus(). Lines( Contains("master").IsSelected(), @@ -27,9 +27,9 @@ var CheckoutByName = NewIntegrationTest(NewIntegrationTestArgs{ SelectNextItem(). Press(keys.Branches.CheckoutBranchByName). Tap(func() { - input.ExpectPrompt().Title(Equals("Branch name:")).Type("new-branch").Confirm() + t.ExpectPrompt().Title(Equals("Branch name:")).Type("new-branch").Confirm() - input.ExpectAlert().Title(Equals("Branch not found")).Content(Equals("Branch not found. Create a new branch named new-branch?")).Confirm() + t.ExpectAlert().Title(Equals("Branch not found")).Content(Equals("Branch not found. Create a new branch named new-branch?")).Confirm() }). Lines( MatchesRegexp(`\*.*new-branch`).IsSelected(), diff --git a/pkg/integration/tests/branch/delete.go b/pkg/integration/tests/branch/delete.go index f0921a82e..6410576e8 100644 --- a/pkg/integration/tests/branch/delete.go +++ b/pkg/integration/tests/branch/delete.go @@ -16,8 +16,8 @@ var Delete = NewIntegrationTest(NewIntegrationTestArgs{ NewBranch("branch-one"). NewBranch("branch-two") }, - Run: func(shell *Shell, input *Input, keys config.KeybindingConfig) { - input.Views().Branches(). + Run: func(shell *Shell, t *TestDriver, keys config.KeybindingConfig) { + t.Views().Branches(). Focus(). Lines( MatchesRegexp(`\*.*branch-two`).IsSelected(), @@ -26,12 +26,12 @@ var Delete = NewIntegrationTest(NewIntegrationTestArgs{ ). Press(keys.Universal.Remove). Tap(func() { - input.ExpectAlert().Title(Equals("Error")).Content(Contains("You cannot delete the checked out branch!")).Confirm() + t.ExpectAlert().Title(Equals("Error")).Content(Contains("You cannot delete the checked out branch!")).Confirm() }). SelectNextItem(). Press(keys.Universal.Remove). Tap(func() { - input.ExpectConfirmation(). + t.ExpectConfirmation(). Title(Equals("Delete Branch")). Content(Contains("Are you sure you want to delete the branch 'branch-one'?")). Confirm() diff --git a/pkg/integration/tests/branch/rebase.go b/pkg/integration/tests/branch/rebase.go index 541493458..b90810b82 100644 --- a/pkg/integration/tests/branch/rebase.go +++ b/pkg/integration/tests/branch/rebase.go @@ -14,13 +14,13 @@ var Rebase = NewIntegrationTest(NewIntegrationTestArgs{ SetupRepo: func(shell *Shell) { shared.MergeConflictsSetup(shell) }, - Run: func(shell *Shell, input *Input, keys config.KeybindingConfig) { - input.Views().Commits().TopLines( + Run: func(shell *Shell, t *TestDriver, keys config.KeybindingConfig) { + t.Views().Commits().TopLines( Contains("first change"), Contains("original"), ) - input.Views().Branches(). + t.Views().Branches(). Focus(). Lines( Contains("first-change-branch"), @@ -30,35 +30,35 @@ var Rebase = NewIntegrationTest(NewIntegrationTestArgs{ SelectNextItem(). Press(keys.Branches.RebaseBranch) - input.ExpectConfirmation(). + t.ExpectConfirmation(). Title(Equals("Rebasing")). Content(Contains("Are you sure you want to rebase 'first-change-branch' on top of 'second-change-branch'?")). Confirm() - input.ExpectConfirmation(). + t.ExpectConfirmation(). Title(Equals("Auto-merge failed")). Content(Contains("Conflicts!")). Confirm() - input.Views().Files(). + t.Views().Files(). IsFocused(). SelectedLine(Contains("file")). PressEnter() - input.Views().MergeConflicts(). + t.Views().MergeConflicts(). IsFocused(). PressPrimaryAction() - input.Views().Information().Content(Contains("rebasing")) + t.Views().Information().Content(Contains("rebasing")) - input.ExpectConfirmation(). + t.ExpectConfirmation(). Title(Equals("continue")). Content(Contains("all merge conflicts resolved. Continue?")). Confirm() - input.Views().Information().Content(DoesNotContain("rebasing")) + t.Views().Information().Content(DoesNotContain("rebasing")) - input.Views().Commits().TopLines( + t.Views().Commits().TopLines( Contains("second-change-branch unrelated change"), Contains("second change"), Contains("original"), diff --git a/pkg/integration/tests/branch/rebase_and_drop.go b/pkg/integration/tests/branch/rebase_and_drop.go index 53a718618..610bb1c19 100644 --- a/pkg/integration/tests/branch/rebase_and_drop.go +++ b/pkg/integration/tests/branch/rebase_and_drop.go @@ -17,8 +17,8 @@ var RebaseAndDrop = NewIntegrationTest(NewIntegrationTestArgs{ shell.EmptyCommit("to remove") shell.EmptyCommit("to keep") }, - Run: func(shell *Shell, input *Input, keys config.KeybindingConfig) { - input.Views().Branches(). + Run: func(shell *Shell, t *TestDriver, keys config.KeybindingConfig) { + t.Views().Branches(). Focus(). Lines( Contains("first-change-branch"), @@ -26,7 +26,7 @@ var RebaseAndDrop = NewIntegrationTest(NewIntegrationTestArgs{ Contains("original-branch"), ) - input.Views().Commits(). + t.Views().Commits(). TopLines( Contains("to keep").IsSelected(), Contains("to remove"), @@ -36,22 +36,22 @@ var RebaseAndDrop = NewIntegrationTest(NewIntegrationTestArgs{ SelectNextItem(). Press(keys.Branches.RebaseBranch) - input.ExpectConfirmation(). + t.ExpectConfirmation(). Title(Equals("Rebasing")). Content(Contains("Are you sure you want to rebase 'first-change-branch' on top of 'second-change-branch'?")). Confirm() - input.Views().Information().Content(Contains("rebasing")) + t.Views().Information().Content(Contains("rebasing")) - input.ExpectConfirmation(). + t.ExpectConfirmation(). Title(Equals("Auto-merge failed")). Content(Contains("Conflicts!")). Confirm() - input.Views().Files().IsFocused(). + t.Views().Files().IsFocused(). SelectedLine(MatchesRegexp("UU.*file")) - input.Views().Commits(). + t.Views().Commits(). Focus(). TopLines( MatchesRegexp(`pick.*to keep`).IsSelected(), @@ -70,22 +70,22 @@ var RebaseAndDrop = NewIntegrationTest(NewIntegrationTestArgs{ MatchesRegexp("original"), ) - input.Views().Files(). + t.Views().Files(). Focus(). PressEnter() - input.Views().MergeConflicts(). + t.Views().MergeConflicts(). IsFocused(). PressPrimaryAction() - input.ExpectConfirmation(). + t.ExpectConfirmation(). Title(Equals("continue")). Content(Contains("all merge conflicts resolved. Continue?")). Confirm() - input.Views().Information().Content(DoesNotContain("rebasing")) + t.Views().Information().Content(DoesNotContain("rebasing")) - input.Views().Commits().TopLines( + t.Views().Commits().TopLines( Contains("to keep"), Contains("second-change-branch unrelated change").IsSelected(), Contains("second change"), diff --git a/pkg/integration/tests/branch/reset.go b/pkg/integration/tests/branch/reset.go index f96a48b56..bfd1b950f 100644 --- a/pkg/integration/tests/branch/reset.go +++ b/pkg/integration/tests/branch/reset.go @@ -20,13 +20,13 @@ var Reset = NewIntegrationTest(NewIntegrationTestArgs{ shell.Checkout("current-branch") shell.EmptyCommit("current-branch commit") }, - Run: func(shell *Shell, input *Input, keys config.KeybindingConfig) { - input.Views().Commits().Lines( + Run: func(shell *Shell, t *TestDriver, keys config.KeybindingConfig) { + t.Views().Commits().Lines( Contains("current-branch commit"), Contains("root commit"), ) - input.Views().Branches(). + t.Views().Branches(). Focus(). Lines( Contains("current-branch"), @@ -35,10 +35,10 @@ var Reset = NewIntegrationTest(NewIntegrationTestArgs{ SelectNextItem(). Press(keys.Commits.ViewResetOptions) - input.ExpectMenu().Title(Contains("reset to other-branch")).Select(Contains("hard reset")).Confirm() + t.ExpectMenu().Title(Contains("reset to other-branch")).Select(Contains("hard reset")).Confirm() // assert that we now have the expected commits in the commit panel - input.Views().Commits(). + t.Views().Commits(). Lines( Contains("other-branch commit"), Contains("root commit"), diff --git a/pkg/integration/tests/branch/suggestions.go b/pkg/integration/tests/branch/suggestions.go index 9999947d9..438bbf9be 100644 --- a/pkg/integration/tests/branch/suggestions.go +++ b/pkg/integration/tests/branch/suggestions.go @@ -20,20 +20,20 @@ var Suggestions = NewIntegrationTest(NewIntegrationTestArgs{ NewBranch("other-new-branch-2"). NewBranch("other-new-branch-3") }, - Run: func(shell *Shell, input *Input, keys config.KeybindingConfig) { - input.Views().Branches(). + Run: func(shell *Shell, 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.ExpectPrompt(). + t.ExpectPrompt(). Title(Equals("Branch name:")). Type("branch-to"). SuggestionTopLines(Contains("branch-to-checkout")). SelectFirstSuggestion(). Confirm() - input.Model().CurrentBranchName("branch-to-checkout") + t.Model().CurrentBranchName("branch-to-checkout") }, }) diff --git a/pkg/integration/tests/cherry_pick/cherry_pick.go b/pkg/integration/tests/cherry_pick/cherry_pick.go index de9e0a645..5fa2d47ac 100644 --- a/pkg/integration/tests/cherry_pick/cherry_pick.go +++ b/pkg/integration/tests/cherry_pick/cherry_pick.go @@ -23,8 +23,8 @@ var CherryPick = NewIntegrationTest(NewIntegrationTestArgs{ EmptyCommit("four"). Checkout("first-branch") }, - Run: func(shell *Shell, input *Input, keys config.KeybindingConfig) { - input.Views().Branches(). + Run: func(shell *Shell, t *TestDriver, keys config.KeybindingConfig) { + t.Views().Branches(). Focus(). Lines( Contains("first-branch"), @@ -34,7 +34,7 @@ var CherryPick = NewIntegrationTest(NewIntegrationTestArgs{ SelectNextItem(). PressEnter() - input.Views().SubCommits(). + t.Views().SubCommits(). IsFocused(). Lines( Contains("four").IsSelected(), @@ -44,14 +44,14 @@ var CherryPick = NewIntegrationTest(NewIntegrationTestArgs{ // copy commits 'four' and 'three' Press(keys.Commits.CherryPickCopy). Tap(func() { - input.Views().Information().Content(Contains("1 commit copied")) + t.Views().Information().Content(Contains("1 commit copied")) }). SelectNextItem(). Press(keys.Commits.CherryPickCopy) - input.Views().Information().Content(Contains("2 commits copied")) + t.Views().Information().Content(Contains("2 commits copied")) - input.Views().Commits(). + t.Views().Commits(). Focus(). Lines( Contains("two").IsSelected(), @@ -60,7 +60,7 @@ var CherryPick = NewIntegrationTest(NewIntegrationTestArgs{ ). Press(keys.Commits.PasteCommits). Tap(func() { - input.ExpectAlert(). + t.ExpectAlert(). Title(Equals("Cherry-Pick")). Content(Contains("Are you sure you want to cherry-pick the copied commits onto this branch?")). Confirm() @@ -74,11 +74,11 @@ var CherryPick = NewIntegrationTest(NewIntegrationTestArgs{ ). Tap(func() { // we need to manually exit out of cherry pick mode - input.Views().Information().Content(Contains("2 commits copied")) + t.Views().Information().Content(Contains("2 commits copied")) }). PressEscape(). Tap(func() { - input.Views().Information().Content(DoesNotContain("commits copied")) + t.Views().Information().Content(DoesNotContain("commits copied")) }) }, }) diff --git a/pkg/integration/tests/cherry_pick/cherry_pick_conflicts.go b/pkg/integration/tests/cherry_pick/cherry_pick_conflicts.go index 9c02cc8d0..5de63baa6 100644 --- a/pkg/integration/tests/cherry_pick/cherry_pick_conflicts.go +++ b/pkg/integration/tests/cherry_pick/cherry_pick_conflicts.go @@ -14,8 +14,8 @@ var CherryPickConflicts = NewIntegrationTest(NewIntegrationTestArgs{ SetupRepo: func(shell *Shell) { shared.MergeConflictsSetup(shell) }, - Run: func(shell *Shell, input *Input, keys config.KeybindingConfig) { - input.Views().Branches(). + Run: func(shell *Shell, t *TestDriver, keys config.KeybindingConfig) { + t.Views().Branches(). Focus(). Lines( Contains("first-change-branch"), @@ -25,7 +25,7 @@ var CherryPickConflicts = NewIntegrationTest(NewIntegrationTestArgs{ SelectNextItem(). PressEnter() - input.Views().SubCommits(). + t.Views().SubCommits(). IsFocused(). TopLines( Contains("second-change-branch unrelated change"), @@ -33,46 +33,46 @@ var CherryPickConflicts = NewIntegrationTest(NewIntegrationTestArgs{ ). Press(keys.Commits.CherryPickCopy). Tap(func() { - input.Views().Information().Content(Contains("1 commit copied")) + t.Views().Information().Content(Contains("1 commit copied")) }). SelectNextItem(). Press(keys.Commits.CherryPickCopy) - input.Views().Information().Content(Contains("2 commits copied")) + t.Views().Information().Content(Contains("2 commits copied")) - input.Views().Commits(). + t.Views().Commits(). Focus(). TopLines( Contains("first change"), ). Press(keys.Commits.PasteCommits) - input.ExpectAlert().Title(Equals("Cherry-Pick")).Content(Contains("Are you sure you want to cherry-pick the copied commits onto this branch?")).Confirm() + t.ExpectAlert().Title(Equals("Cherry-Pick")).Content(Contains("Are you sure you want to cherry-pick the copied commits onto this branch?")).Confirm() - input.ExpectConfirmation(). + t.ExpectConfirmation(). Title(Equals("Auto-merge failed")). Content(Contains("Conflicts!")). Confirm() - input.Views().Files(). + t.Views().Files(). IsFocused(). SelectedLine(Contains("file")). PressEnter() - input.Views().MergeConflicts(). + t.Views().MergeConflicts(). IsFocused(). // picking 'Second change' SelectNextItem(). PressPrimaryAction() - input.ExpectConfirmation(). + t.ExpectConfirmation(). Title(Equals("continue")). Content(Contains("all merge conflicts resolved. Continue?")). Confirm() - input.Model().WorkingTreeFileCount(0) + t.Model().WorkingTreeFileCount(0) - input.Views().Commits(). + t.Views().Commits(). Focus(). TopLines( Contains("second-change-branch unrelated change"), @@ -84,15 +84,15 @@ var CherryPickConflicts = NewIntegrationTest(NewIntegrationTestArgs{ // 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' - input.Views().Main(). + t.Views().Main(). Content(Contains("-First Change")). Content(Contains("+Second Change")) - input.Views().Information().Content(Contains("2 commits copied")) + t.Views().Information().Content(Contains("2 commits copied")) }). PressEscape(). Tap(func() { - input.Views().Information().Content(DoesNotContain("commits copied")) + t.Views().Information().Content(DoesNotContain("commits copied")) }) }, }) diff --git a/pkg/integration/tests/commit/commit.go b/pkg/integration/tests/commit/commit.go index c9bc91fdd..951529cea 100644 --- a/pkg/integration/tests/commit/commit.go +++ b/pkg/integration/tests/commit/commit.go @@ -14,10 +14,10 @@ var Commit = NewIntegrationTest(NewIntegrationTestArgs{ shell.CreateFile("myfile", "myfile content") shell.CreateFile("myfile2", "myfile2 content") }, - Run: func(shell *Shell, input *Input, keys config.KeybindingConfig) { - input.Model().CommitCount(0) + Run: func(shell *Shell, t *TestDriver, keys config.KeybindingConfig) { + t.Model().CommitCount(0) - input.Views().Files(). + t.Views().Files(). IsFocused(). PressPrimaryAction(). // stage file SelectNextItem(). @@ -26,9 +26,9 @@ var Commit = NewIntegrationTest(NewIntegrationTestArgs{ commitMessage := "my commit message" - input.ExpectCommitMessagePanel().Type(commitMessage).Confirm() + t.ExpectCommitMessagePanel().Type(commitMessage).Confirm() - input.Model(). + t.Model(). CommitCount(1). HeadCommitMessage(Equals(commitMessage)) }, diff --git a/pkg/integration/tests/commit/commit_multiline.go b/pkg/integration/tests/commit/commit_multiline.go index 4f80adbe1..823412f68 100644 --- a/pkg/integration/tests/commit/commit_multiline.go +++ b/pkg/integration/tests/commit/commit_multiline.go @@ -13,20 +13,20 @@ var CommitMultiline = NewIntegrationTest(NewIntegrationTestArgs{ SetupRepo: func(shell *Shell) { shell.CreateFile("myfile", "myfile content") }, - Run: func(shell *Shell, input *Input, keys config.KeybindingConfig) { - input.Model().CommitCount(0) + Run: func(shell *Shell, t *TestDriver, keys config.KeybindingConfig) { + t.Model().CommitCount(0) - input.Views().Files(). + t.Views().Files(). IsFocused(). PressPrimaryAction(). Press(keys.Files.CommitChanges) - input.ExpectCommitMessagePanel().Type("first line").AddNewline().AddNewline().Type("third line").Confirm() + t.ExpectCommitMessagePanel().Type("first line").AddNewline().AddNewline().Type("third line").Confirm() - input.Model().CommitCount(1) - input.Model().HeadCommitMessage(Equals("first line")) + t.Model().CommitCount(1) + t.Model().HeadCommitMessage(Equals("first line")) - input.Views().Commits().Focus() - input.Views().Main().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")) }, }) diff --git a/pkg/integration/tests/commit/new_branch.go b/pkg/integration/tests/commit/new_branch.go index 758edb558..f2deecdc9 100644 --- a/pkg/integration/tests/commit/new_branch.go +++ b/pkg/integration/tests/commit/new_branch.go @@ -16,10 +16,10 @@ var NewBranch = NewIntegrationTest(NewIntegrationTestArgs{ EmptyCommit("commit 2"). EmptyCommit("commit 3") }, - Run: func(shell *Shell, input *Input, keys config.KeybindingConfig) { - input.Model().CommitCount(3) + Run: func(shell *Shell, t *TestDriver, keys config.KeybindingConfig) { + t.Model().CommitCount(3) - input.Views().Commits(). + t.Views().Commits(). Focus(). SelectNextItem(). Lines( @@ -30,9 +30,9 @@ var NewBranch = NewIntegrationTest(NewIntegrationTestArgs{ Press(keys.Universal.New). Tap(func() { branchName := "my-branch-name" - input.ExpectPrompt().Title(Contains("New Branch Name")).Type(branchName).Confirm() + t.ExpectPrompt().Title(Contains("New Branch Name")).Type(branchName).Confirm() - input.Model().CurrentBranchName(branchName) + t.Model().CurrentBranchName(branchName) }). Lines( Contains("commit 2"), diff --git a/pkg/integration/tests/commit/revert.go b/pkg/integration/tests/commit/revert.go index 23cb5cb8e..c2f3b9a52 100644 --- a/pkg/integration/tests/commit/revert.go +++ b/pkg/integration/tests/commit/revert.go @@ -15,17 +15,17 @@ var Revert = NewIntegrationTest(NewIntegrationTestArgs{ shell.GitAddAll() shell.Commit("first commit") }, - Run: func(shell *Shell, input *Input, keys config.KeybindingConfig) { - input.Model().CommitCount(1) + Run: func(shell *Shell, t *TestDriver, keys config.KeybindingConfig) { + t.Model().CommitCount(1) - input.Views().Commits(). + t.Views().Commits(). Focus(). Lines( Contains("first commit"), ). Press(keys.Commits.RevertCommit). Tap(func() { - input.ExpectConfirmation(). + t.ExpectConfirmation(). Title(Equals("Revert commit")). Content(MatchesRegexp(`Are you sure you want to revert \w+?`)). Confirm() @@ -35,7 +35,7 @@ var Revert = NewIntegrationTest