-
Notifications
You must be signed in to change notification settings - Fork 7
fix(tui): esc back-navigation; multi-select Enter stops force-selecting #264
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| package components | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| tea "github.com/charmbracelet/bubbletea" | ||
| ) | ||
|
|
||
| func twoItemSelect() MultiSelect { | ||
| return MultiSelect{Items: []MultiSelectItem{ | ||
| {Label: "alpha", Value: "alpha"}, | ||
| {Label: "beta", Value: "beta"}, | ||
| }} | ||
| } | ||
|
|
||
| // TestMultiSelect_EnterConfirmsEmpty pins the fix: pressing Enter with | ||
| // nothing toggled confirms an EMPTY selection instead of silently | ||
| // auto-selecting the highlighted row. | ||
| func TestMultiSelect_EnterConfirmsEmpty(t *testing.T) { | ||
| m := twoItemSelect() | ||
| m, _ = m.Update(tea.KeyMsg{Type: tea.KeyEnter}) | ||
| if !m.Done() { | ||
| t.Fatal("Enter should confirm the selection") | ||
| } | ||
| if vals := m.SelectedValues(); len(vals) != 0 { | ||
| t.Errorf("Enter with nothing toggled must select nothing, got %v", vals) | ||
| } | ||
| } | ||
|
|
||
| // TestMultiSelect_SpaceThenEnterSelects confirms Space still toggles the | ||
| // cursor row and Enter confirms it. | ||
| func TestMultiSelect_SpaceThenEnterSelects(t *testing.T) { | ||
| m := twoItemSelect() | ||
| m, _ = m.Update(tea.KeyMsg{Type: tea.KeySpace}) // toggle cursor row (alpha) | ||
| m, _ = m.Update(tea.KeyMsg{Type: tea.KeyEnter}) | ||
| vals := m.SelectedValues() | ||
| if len(vals) != 1 || vals[0] != "alpha" { | ||
| t.Errorf("Space+Enter should select 'alpha', got %v", vals) | ||
| } | ||
| } | ||
|
|
||
| // TestMultiSelect_MoveThenSpaceSelectsCorrectRow guards that toggling | ||
| // follows the cursor, not row 0. | ||
| func TestMultiSelect_MoveThenSpaceSelectsCorrectRow(t *testing.T) { | ||
| m := twoItemSelect() | ||
| m, _ = m.Update(tea.KeyMsg{Type: tea.KeyDown}) // cursor -> beta | ||
| m, _ = m.Update(tea.KeyMsg{Type: tea.KeySpace}) // toggle beta | ||
| m, _ = m.Update(tea.KeyMsg{Type: tea.KeyEnter}) | ||
| vals := m.SelectedValues() | ||
| if len(vals) != 1 || vals[0] != "beta" { | ||
| t.Errorf("expected only 'beta', got %v", vals) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| package steps | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/initializ/forge/forge-cli/internal/tui" | ||
| ) | ||
|
|
||
| // TestStepInitResetsComplete pins the re-entry contract from the #264 review: | ||
| // every step's Init() must reset `complete` to false, because the wizard calls | ||
| // Init() (not Prepare) on BACK navigation and every step's Update short- | ||
| // circuits on `if s.complete`. Without the reset, esc-back lands on an inert | ||
| // step that ignores all input and soft-locks the wizard. | ||
| // | ||
| // The test drives REAL steps (a stateless mock can't catch the soft-lock — | ||
| // see the wizard_nav_test note): construct the step, force it complete, call | ||
| // Init(), and assert it's usable again. | ||
| func TestStepInitResetsComplete(t *testing.T) { | ||
| styles := tui.NewStyleSet(tui.DarkTheme) | ||
| noKey := func(string, string) error { return nil } | ||
|
|
||
| t.Run("name", func(t *testing.T) { | ||
| s := NewNameStep(styles, "") // no prefill | ||
| s.complete = true | ||
| s.Init() | ||
| if s.complete { | ||
| t.Error("NameStep.Init() must reset complete") | ||
| } | ||
| }) | ||
| t.Run("provider", func(t *testing.T) { | ||
| s := NewProviderStep(styles, noKey) | ||
| s.complete = true | ||
| s.Init() | ||
| if s.complete { | ||
| t.Error("ProviderStep.Init() must reset complete") | ||
| } | ||
| }) | ||
| t.Run("channel", func(t *testing.T) { | ||
| s := NewChannelStep(styles) | ||
| s.complete = true | ||
| s.Init() | ||
| if s.complete { | ||
| t.Error("ChannelStep.Init() must reset complete") | ||
| } | ||
| }) | ||
| t.Run("web_search", func(t *testing.T) { | ||
| s := NewWebSearchStep(styles, noKey) | ||
| s.complete = true | ||
| s.Init() | ||
| if s.complete { | ||
| t.Error("WebSearchStep.Init() must reset complete") | ||
| } | ||
| }) | ||
| t.Run("fallback", func(t *testing.T) { | ||
| s := NewFallbackStep(styles, noKey) | ||
| s.complete = true | ||
| s.Init() | ||
| if s.complete { | ||
| t.Error("FallbackStep.Init() must reset complete") | ||
| } | ||
| }) | ||
| t.Run("auth", func(t *testing.T) { | ||
| s := NewAuthStep(styles) | ||
| s.complete = true | ||
| s.Init() | ||
| if s.complete { | ||
| t.Error("AuthStep.Init() must reset complete") | ||
| } | ||
| }) | ||
| t.Run("compression", func(t *testing.T) { | ||
| s := NewCompressionStep(styles) | ||
| s.complete = true | ||
| s.Init() | ||
| if s.complete { | ||
| t.Error("CompressionStep.Init() must reset complete") | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| // TestWebSearchStepApply_ClearsStaleKeys pins finding #2: redoing the | ||
| // web-search step (after back-nav) with "No web search" chosen must not leak | ||
| // the previously-entered provider/key into the context. | ||
| func TestWebSearchStepApply_ClearsStaleKeys(t *testing.T) { | ||
| styles := tui.NewStyleSet(tui.DarkTheme) | ||
| ctx := tui.NewWizardContext() | ||
|
|
||
| // First pass: Tavily chosen with a key. | ||
| s := NewWebSearchStep(styles, func(string, string) error { return nil }) | ||
| s.provider = "tavily" | ||
| s.keyName = "TAVILY_API_KEY" | ||
| s.key = "tvly-x" | ||
| s.Apply(ctx) | ||
| if ctx.EnvVars["TAVILY_API_KEY"] == "" || ctx.EnvVars["WEB_SEARCH_PROVIDER"] == "" { | ||
| t.Fatal("first Apply should have written the web-search env vars") | ||
| } | ||
| if !containsWebSearch(ctx.BuiltinTools) { | ||
| t.Fatal("first Apply should have recorded web_search") | ||
| } | ||
|
|
||
| // Redo: user went back and chose "No web search". | ||
| s.provider = "" | ||
| s.keyName = "" | ||
| s.key = "" | ||
| s.Apply(ctx) | ||
|
|
||
| if _, ok := ctx.EnvVars["TAVILY_API_KEY"]; ok { | ||
| t.Error("stale TAVILY_API_KEY leaked after choosing No web search") | ||
| } | ||
| if _, ok := ctx.EnvVars["WEB_SEARCH_PROVIDER"]; ok { | ||
| t.Error("stale WEB_SEARCH_PROVIDER leaked after choosing No web search") | ||
| } | ||
| if containsWebSearch(ctx.BuiltinTools) { | ||
| t.Error("stale web_search left in BuiltinTools after deselect") | ||
| } | ||
| } | ||
|
|
||
| func containsWebSearch(s []string) bool { | ||
| for _, v := range s { | ||
| if v == "web_search" { | ||
| return true | ||
| } | ||
| } | ||
| return false | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: the review screen now advertises two back keys —
backspace back(step-localStepBackMsg) andesc back(wizard-level). Either drop the backspace hint since wizard-level esc supersedes it, or add a line on why both stay.