feat: update tui
This commit is contained in:
@@ -73,14 +73,6 @@ func parseReleaseCLIArgs(args []string) ([]string, release.ExecutionOptions, boo
|
||||
switch arg {
|
||||
case "select":
|
||||
selectMode = true
|
||||
case "--dry-run":
|
||||
execution.DryRun = true
|
||||
case "--commit":
|
||||
execution.Commit = true
|
||||
case "--tag":
|
||||
execution.Tag = true
|
||||
case "--push":
|
||||
execution.Push = true
|
||||
default:
|
||||
if strings.HasPrefix(arg, "--") {
|
||||
return nil, release.ExecutionOptions{}, false, fmt.Errorf("unknown flag %q", arg)
|
||||
|
||||
@@ -256,13 +256,16 @@ func TestRunnerExecutesReleaseFlow(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunnerLeavesChangesUncommittedByDefault(t *testing.T) {
|
||||
func TestRunnerAlwaysCommitsTagsAndPushes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
root := t.TempDir()
|
||||
remote := filepath.Join(t.TempDir(), "remote.git")
|
||||
mustRun(t, root, "git", "init")
|
||||
mustRun(t, root, "git", "config", "user.name", "Release Test")
|
||||
mustRun(t, root, "git", "config", "user.email", "release-test@example.com")
|
||||
mustRun(t, root, "git", "config", "commit.gpgsign", "false")
|
||||
mustRun(t, root, "git", "config", "tag.gpgsign", "false")
|
||||
if err := os.WriteFile(filepath.Join(root, "flake.nix"), []byte("{ outputs = { self }: {}; }\n"), 0o644); err != nil {
|
||||
t.Fatalf("WriteFile(flake.nix): %v", err)
|
||||
}
|
||||
@@ -271,6 +274,9 @@ func TestRunnerLeavesChangesUncommittedByDefault(t *testing.T) {
|
||||
}
|
||||
mustRun(t, root, "git", "add", "-A")
|
||||
mustRun(t, root, "git", "commit", "-m", "init")
|
||||
mustRun(t, root, "git", "init", "--bare", remote)
|
||||
mustRun(t, root, "git", "remote", "add", "origin", remote)
|
||||
mustRun(t, root, "git", "push", "-u", "origin", "HEAD")
|
||||
|
||||
binDir := t.TempDir()
|
||||
if err := os.MkdirAll(binDir, 0o755); err != nil {
|
||||
@@ -296,58 +302,20 @@ func TestRunnerLeavesChangesUncommittedByDefault(t *testing.T) {
|
||||
|
||||
assertFileEquals(t, filepath.Join(root, "VERSION"), "1.0.1\nstable\n0\n")
|
||||
status := strings.TrimSpace(mustOutput(t, root, "git", "status", "--short"))
|
||||
if status != "M VERSION" {
|
||||
t.Fatalf("git status --short = %q, want %q", status, "M VERSION")
|
||||
}
|
||||
|
||||
tagList := strings.TrimSpace(mustOutput(t, root, "git", "tag", "--list"))
|
||||
if tagList != "" {
|
||||
t.Fatalf("git tag --list = %q, want empty", tagList)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunnerDryRunDoesNotModifyRepo(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
root := t.TempDir()
|
||||
mustRun(t, root, "git", "init")
|
||||
mustRun(t, root, "git", "config", "user.name", "Release Test")
|
||||
mustRun(t, root, "git", "config", "user.email", "release-test@example.com")
|
||||
if err := os.WriteFile(filepath.Join(root, "flake.nix"), []byte("{ outputs = { self }: {}; }\n"), 0o644); err != nil {
|
||||
t.Fatalf("WriteFile(flake.nix): %v", err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(root, "VERSION"), []byte("1.0.0\nstable\n0\n"), 0o644); err != nil {
|
||||
t.Fatalf("WriteFile(VERSION): %v", err)
|
||||
}
|
||||
mustRun(t, root, "git", "add", "-A")
|
||||
mustRun(t, root, "git", "commit", "-m", "init")
|
||||
|
||||
var stdout strings.Builder
|
||||
r := &Runner{
|
||||
Config: Config{
|
||||
RootDir: root,
|
||||
AllowedChannels: []string{"alpha", "beta", "rc", "internal"},
|
||||
Execution: ExecutionOptions{
|
||||
DryRun: true,
|
||||
Commit: true,
|
||||
Tag: true,
|
||||
Push: true,
|
||||
},
|
||||
Stdout: &stdout,
|
||||
},
|
||||
}
|
||||
|
||||
if err := r.Run([]string{"patch"}); err != nil {
|
||||
t.Fatalf("Runner.Run: %v", err)
|
||||
}
|
||||
|
||||
assertFileEquals(t, filepath.Join(root, "VERSION"), "1.0.0\nstable\n0\n")
|
||||
status := strings.TrimSpace(mustOutput(t, root, "git", "status", "--short"))
|
||||
if status != "" {
|
||||
t.Fatalf("git status --short = %q, want empty", status)
|
||||
}
|
||||
if !strings.Contains(stdout.String(), "Dry run: 1.0.1") {
|
||||
t.Fatalf("dry-run output missing next version:\n%s", stdout.String())
|
||||
|
||||
tagList := strings.TrimSpace(mustOutput(t, root, "git", "tag", "--list", "v1.0.1"))
|
||||
if tagList != "v1.0.1" {
|
||||
t.Fatalf("git tag --list v1.0.1 = %q, want v1.0.1", tagList)
|
||||
}
|
||||
|
||||
branch := strings.TrimSpace(mustOutput(t, root, "git", "branch", "--show-current"))
|
||||
remoteHead := strings.TrimSpace(mustOutput(t, root, "git", "rev-parse", "origin/"+branch))
|
||||
localHead := strings.TrimSpace(mustOutput(t, root, "git", "rev-parse", "HEAD"))
|
||||
if remoteHead != localHead {
|
||||
t.Fatalf("origin/%s = %q, want %q", branch, remoteHead, localHead)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ type Config struct {
|
||||
}
|
||||
|
||||
type ExecutionOptions struct {
|
||||
DryRun bool
|
||||
Commit bool
|
||||
Tag bool
|
||||
Push bool
|
||||
@@ -31,13 +30,11 @@ type Runner struct {
|
||||
}
|
||||
|
||||
func (o ExecutionOptions) Normalize() ExecutionOptions {
|
||||
if o.Push {
|
||||
o.Commit = true
|
||||
return ExecutionOptions{
|
||||
Commit: true,
|
||||
Tag: true,
|
||||
Push: true,
|
||||
}
|
||||
if o.Tag {
|
||||
o.Commit = true
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
func (r *Runner) Run(args []string) error {
|
||||
@@ -60,11 +57,6 @@ func (r *Runner) Run(args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if execution.DryRun {
|
||||
printReleasePlan(stdout, nextVersion, execution, strings.TrimSpace(r.Config.ReleaseStepsJSON) != "", strings.TrimSpace(r.Config.PostVersion) != "")
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := requireCleanGit(rootDir); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -114,10 +106,6 @@ func (r *Runner) finalizeRelease(rootDir string, version Version, execution Exec
|
||||
return err
|
||||
}
|
||||
|
||||
if !execution.Commit {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := runCommand(rootDir, r.Config.Env, stdout, stderr, "git", "add", "-A"); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -127,34 +115,15 @@ func (r *Runner) finalizeRelease(rootDir string, version Version, execution Exec
|
||||
return err
|
||||
}
|
||||
|
||||
if execution.Tag {
|
||||
if _, err := runCommand(rootDir, r.Config.Env, stdout, stderr, "git", "tag", version.Tag()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !execution.Push {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := runCommand(rootDir, r.Config.Env, stdout, stderr, "git", "push"); err != nil {
|
||||
return err
|
||||
}
|
||||
if execution.Tag {
|
||||
if _, err := runCommand(rootDir, r.Config.Env, stdout, stderr, "git", "push", "--tags"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func printReleasePlan(stdout io.Writer, version Version, execution ExecutionOptions, hasReleaseSteps bool, hasPostVersion bool) {
|
||||
fmt.Fprintf(stdout, "Dry run: %s\n", version.String())
|
||||
fmt.Fprintf(stdout, "Tag: %s\n", version.Tag())
|
||||
fmt.Fprintf(stdout, "Release steps: %s\n", yesNo(hasReleaseSteps))
|
||||
fmt.Fprintf(stdout, "Post-version: %s\n", yesNo(hasPostVersion))
|
||||
fmt.Fprintf(stdout, "nix fmt: yes\n")
|
||||
fmt.Fprintf(stdout, "git commit: %s\n", yesNo(execution.Commit))
|
||||
fmt.Fprintf(stdout, "git tag: %s\n", yesNo(execution.Tag))
|
||||
fmt.Fprintf(stdout, "git push: %s\n", yesNo(execution.Push))
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func SelectCommand(config Config) ([]string, bool, error) {
|
||||
return nil, false, fmt.Errorf("no release commands available for current version %s", versionFile.Version.String())
|
||||
}
|
||||
|
||||
model := newCommandPickerModel(versionFile.Version, options)
|
||||
model := newCommandPickerModel(config, versionFile.Version)
|
||||
finalModel, err := tea.NewProgram(model, tea.WithAltScreen()).Run()
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
@@ -111,18 +111,6 @@ func formatReleaseCommand(args []string) string {
|
||||
func formatReleaseCommandWithExecution(args []string, execution ExecutionOptions) string {
|
||||
var parts []string
|
||||
parts = append(parts, "release")
|
||||
if execution.DryRun {
|
||||
parts = append(parts, "--dry-run")
|
||||
}
|
||||
if execution.Commit {
|
||||
parts = append(parts, "--commit")
|
||||
}
|
||||
if execution.Tag {
|
||||
parts = append(parts, "--tag")
|
||||
}
|
||||
if execution.Push {
|
||||
parts = append(parts, "--push")
|
||||
}
|
||||
if len(args) == 0 {
|
||||
return strings.Join(parts, " ")
|
||||
}
|
||||
@@ -199,7 +187,6 @@ func buildPreview(config Config, current Version, args []string, next Version) s
|
||||
" git commit: "+yesNo(execution.Commit),
|
||||
" git tag: "+yesNo(execution.Tag),
|
||||
" git push: "+yesNo(execution.Push),
|
||||
" dry run: "+yesNo(execution.DryRun),
|
||||
)
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
@@ -224,19 +211,32 @@ func capitalize(s string) string {
|
||||
}
|
||||
|
||||
type commandPickerModel struct {
|
||||
config Config
|
||||
current Version
|
||||
options []CommandOption
|
||||
cursor int
|
||||
width int
|
||||
height int
|
||||
focusSection int
|
||||
focusIndex int
|
||||
bumpOptions []selectionOption
|
||||
channelOptions []selectionOption
|
||||
bumpCursor int
|
||||
channelCursor int
|
||||
confirmed bool
|
||||
selected CommandOption
|
||||
err string
|
||||
}
|
||||
|
||||
func newCommandPickerModel(current Version, options []CommandOption) commandPickerModel {
|
||||
type selectionOption struct {
|
||||
Label string
|
||||
Value string
|
||||
}
|
||||
|
||||
func newCommandPickerModel(config Config, current Version) commandPickerModel {
|
||||
return commandPickerModel{
|
||||
config: config,
|
||||
current: current,
|
||||
options: options,
|
||||
bumpOptions: buildBumpOptions(current),
|
||||
channelOptions: buildChannelOptions(current, config.AllowedChannels),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,17 +253,20 @@ func (m commandPickerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg.String() {
|
||||
case "ctrl+c", "q", "esc":
|
||||
return m, tea.Quit
|
||||
case "up", "k":
|
||||
if m.cursor > 0 {
|
||||
m.cursor--
|
||||
}
|
||||
case "down", "j":
|
||||
if m.cursor < len(m.options)-1 {
|
||||
m.cursor++
|
||||
}
|
||||
case "up", "k", "shift+tab":
|
||||
m.moveFocus(-1)
|
||||
case "down", "j", "tab":
|
||||
m.moveFocus(1)
|
||||
case " ":
|
||||
m.selectFocused()
|
||||
case "enter":
|
||||
option, err := m.selectedOption()
|
||||
if err != nil {
|
||||
m.err = err.Error()
|
||||
return m, nil
|
||||
}
|
||||
m.confirmed = true
|
||||
m.selected = m.options[m.cursor]
|
||||
m.selected = option
|
||||
return m, tea.Quit
|
||||
}
|
||||
}
|
||||
@@ -271,28 +274,180 @@ func (m commandPickerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
|
||||
func (m commandPickerModel) View() string {
|
||||
if len(m.options) == 0 {
|
||||
if len(m.bumpOptions) == 0 || len(m.channelOptions) == 0 {
|
||||
return "No release commands available.\n"
|
||||
}
|
||||
|
||||
preview := m.options[m.cursor].Preview
|
||||
header := fmt.Sprintf("Release command picker\nCurrent version: %s\nUse up/down or j/k to choose, Enter to run, q to cancel.\n", m.current.String())
|
||||
|
||||
listLines := make([]string, 0, len(m.options)+1)
|
||||
listLines = append(listLines, "Commands")
|
||||
for i, option := range m.options {
|
||||
cursor := " "
|
||||
if i == m.cursor {
|
||||
cursor = "> "
|
||||
}
|
||||
listLines = append(listLines, fmt.Sprintf("%s%s\n %s", cursor, option.Command, option.Description))
|
||||
}
|
||||
list := strings.Join(listLines, "\n")
|
||||
preview := m.preview()
|
||||
header := fmt.Sprintf("Release command picker\nCurrent version: %s\nUse up/down to move through options, Space to select, Enter to run, q to cancel.\n", m.current.String())
|
||||
sections := strings.Join([]string{
|
||||
m.renderSection("Bump type", m.bumpOptions, m.bumpCursor, m.focusSection == 0, m.focusedOptionIndex()),
|
||||
"",
|
||||
m.renderSection("Channel", m.channelOptions, m.channelCursor, m.focusSection == 1, m.focusedOptionIndex()),
|
||||
}, "\n")
|
||||
|
||||
if m.width >= 100 {
|
||||
return header + "\n" + renderColumns(list, preview, m.width)
|
||||
return header + "\n" + renderColumns(sections, preview, m.width)
|
||||
}
|
||||
return header + "\n" + list + "\n\n" + preview + "\n"
|
||||
return header + "\n" + sections + "\n\n" + preview + "\n"
|
||||
}
|
||||
|
||||
func buildBumpOptions(current Version) []selectionOption {
|
||||
options := []selectionOption{
|
||||
{Label: "Patch", Value: "patch"},
|
||||
{Label: "Minor", Value: "minor"},
|
||||
{Label: "Major", Value: "major"},
|
||||
}
|
||||
if current.Channel != "stable" {
|
||||
options = append(options, selectionOption{
|
||||
Label: "None",
|
||||
Value: "",
|
||||
})
|
||||
return options
|
||||
}
|
||||
options = append(options, selectionOption{
|
||||
Label: "None",
|
||||
Value: "",
|
||||
})
|
||||
return options
|
||||
}
|
||||
|
||||
func buildChannelOptions(current Version, allowedChannels []string) []selectionOption {
|
||||
options := []selectionOption{{
|
||||
Label: "Current",
|
||||
Value: current.Channel,
|
||||
}}
|
||||
if current.Channel != "stable" {
|
||||
options = append(options, selectionOption{
|
||||
Label: "Stable",
|
||||
Value: "stable",
|
||||
})
|
||||
}
|
||||
for _, channel := range allowedChannels {
|
||||
if channel == current.Channel {
|
||||
continue
|
||||
}
|
||||
options = append(options, selectionOption{
|
||||
Label: capitalize(channel),
|
||||
Value: channel,
|
||||
})
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func (m *commandPickerModel) moveFocus(delta int) {
|
||||
total := len(m.bumpOptions) + len(m.channelOptions)
|
||||
if total == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
index := wrapIndex(m.focusIndex+delta, total)
|
||||
m.focusIndex = index
|
||||
|
||||
if index < len(m.bumpOptions) {
|
||||
m.focusSection = 0
|
||||
return
|
||||
}
|
||||
|
||||
m.focusSection = 1
|
||||
}
|
||||
|
||||
func (m *commandPickerModel) selectFocused() {
|
||||
if m.focusSection == 0 {
|
||||
m.bumpCursor = m.focusedOptionIndex()
|
||||
return
|
||||
}
|
||||
m.channelCursor = m.focusedOptionIndex()
|
||||
}
|
||||
|
||||
func wrapIndex(idx int, size int) int {
|
||||
if size == 0 {
|
||||
return 0
|
||||
}
|
||||
for idx < 0 {
|
||||
idx += size
|
||||
}
|
||||
return idx % size
|
||||
}
|
||||
|
||||
func (m commandPickerModel) focusedOptionIndex() int {
|
||||
if m.focusSection == 0 {
|
||||
return m.focusIndex
|
||||
}
|
||||
return m.focusIndex - len(m.bumpOptions)
|
||||
}
|
||||
|
||||
func (m commandPickerModel) renderSection(title string, options []selectionOption, cursor int, focused bool, focusedIndex int) string {
|
||||
lines := []string{title}
|
||||
for i, option := range options {
|
||||
pointer := " "
|
||||
if focused && i == focusedIndex {
|
||||
pointer = ">"
|
||||
}
|
||||
radio := "( )"
|
||||
if i == cursor {
|
||||
radio = "(*)"
|
||||
}
|
||||
lines = append(lines, fmt.Sprintf("%s %s %s", pointer, radio, option.Label))
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func (m commandPickerModel) selectedArgs() []string {
|
||||
bump := m.bumpOptions[m.bumpCursor].Value
|
||||
channel := m.channelOptions[m.channelCursor].Value
|
||||
|
||||
if bump == "" {
|
||||
if channel == "stable" {
|
||||
return []string{"stable"}
|
||||
}
|
||||
if channel == m.current.Channel {
|
||||
if channel == "stable" {
|
||||
return nil
|
||||
}
|
||||
return []string{channel}
|
||||
}
|
||||
return []string{channel}
|
||||
}
|
||||
|
||||
if channel == m.current.Channel || (channel == "stable" && m.current.Channel == "stable") {
|
||||
return []string{bump}
|
||||
}
|
||||
return []string{bump, channel}
|
||||
}
|
||||
|
||||
func (m commandPickerModel) selectedOption() (CommandOption, error) {
|
||||
args := m.selectedArgs()
|
||||
next, err := ResolveNextVersion(m.current, args, m.config.AllowedChannels)
|
||||
if err != nil {
|
||||
return CommandOption{}, err
|
||||
}
|
||||
return CommandOption{
|
||||
Title: titleForArgs(args),
|
||||
Description: descriptionForArgs(m.current, args, next),
|
||||
Command: formatReleaseCommand(args),
|
||||
Args: append([]string(nil), args...),
|
||||
NextVersion: next,
|
||||
Preview: buildPreview(m.config, m.current, args, next),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m commandPickerModel) preview() string {
|
||||
option, err := m.selectedOption()
|
||||
if err != nil {
|
||||
lines := []string{
|
||||
"Command",
|
||||
" " + formatReleaseCommand(m.selectedArgs()),
|
||||
"",
|
||||
"Selection",
|
||||
" " + err.Error(),
|
||||
}
|
||||
if m.err != "" {
|
||||
lines = append(lines, "", "Error", " "+m.err)
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
return option.Preview
|
||||
}
|
||||
|
||||
func renderColumns(left string, right string, width int) string {
|
||||
|
||||
@@ -3,6 +3,8 @@ package release
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
func TestBuildCommandOptionsForStableVersion(t *testing.T) {
|
||||
@@ -81,6 +83,125 @@ func TestBuildCommandOptionsForPrereleaseVersion(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandPickerSelectionForStableVersion(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
model := newCommandPickerModel(Config{
|
||||
AllowedChannels: []string{"alpha", "beta"},
|
||||
}, MustParseVersion(t, "1.2.3"))
|
||||
|
||||
model.bumpCursor = 1
|
||||
model.channelCursor = 0
|
||||
|
||||
option, err := model.selectedOption()
|
||||
if err != nil {
|
||||
t.Fatalf("selectedOption(): %v", err)
|
||||
}
|
||||
if got := strings.Join(option.Args, " "); got != "minor" {
|
||||
t.Fatalf("selected args = %q, want %q", got, "minor")
|
||||
}
|
||||
if option.NextVersion.String() != "1.3.0" {
|
||||
t.Fatalf("next version = %q, want %q", option.NextVersion.String(), "1.3.0")
|
||||
}
|
||||
|
||||
model.bumpCursor = 3
|
||||
model.channelCursor = 1
|
||||
|
||||
option, err = model.selectedOption()
|
||||
if err != nil {
|
||||
t.Fatalf("selectedOption(channel only): %v", err)
|
||||
}
|
||||
if got := strings.Join(option.Args, " "); got != "alpha" {
|
||||
t.Fatalf("selected args = %q, want %q", got, "alpha")
|
||||
}
|
||||
if option.NextVersion.String() != "1.2.4-alpha.1" {
|
||||
t.Fatalf("next version = %q, want %q", option.NextVersion.String(), "1.2.4-alpha.1")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandPickerSelectionForPrereleaseVersion(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
model := newCommandPickerModel(Config{
|
||||
AllowedChannels: []string{"alpha", "beta", "rc"},
|
||||
}, MustParseVersion(t, "1.2.3-beta.2"))
|
||||
|
||||
model.bumpCursor = 3
|
||||
model.channelCursor = 0
|
||||
|
||||
option, err := model.selectedOption()
|
||||
if err != nil {
|
||||
t.Fatalf("selectedOption(current prerelease): %v", err)
|
||||
}
|
||||
if got := strings.Join(option.Args, " "); got != "beta" {
|
||||
t.Fatalf("selected args = %q, want %q", got, "beta")
|
||||
}
|
||||
if option.NextVersion.String() != "1.2.3-beta.3" {
|
||||
t.Fatalf("next version = %q, want %q", option.NextVersion.String(), "1.2.3-beta.3")
|
||||
}
|
||||
|
||||
model.channelCursor = 1
|
||||
|
||||
option, err = model.selectedOption()
|
||||
if err != nil {
|
||||
t.Fatalf("selectedOption(promote): %v", err)
|
||||
}
|
||||
if got := strings.Join(option.Args, " "); got != "stable" {
|
||||
t.Fatalf("selected args = %q, want %q", got, "stable")
|
||||
}
|
||||
if option.NextVersion.String() != "1.2.3" {
|
||||
t.Fatalf("next version = %q, want %q", option.NextVersion.String(), "1.2.3")
|
||||
}
|
||||
|
||||
model.bumpCursor = 0
|
||||
|
||||
if _, err := model.selectedOption(); err == nil {
|
||||
t.Fatalf("selectedOption(patch stable from prerelease) succeeded, want error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandPickerFocusMovesAcrossSections(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
model := newCommandPickerModel(Config{
|
||||
AllowedChannels: []string{"alpha", "beta"},
|
||||
}, MustParseVersion(t, "1.2.3"))
|
||||
|
||||
next, _ := model.Update(tea.KeyMsg{Type: tea.KeyDown})
|
||||
model = next.(commandPickerModel)
|
||||
if model.focusSection != 0 || model.focusIndex != 1 || model.bumpCursor != 0 {
|
||||
t.Fatalf("after first down: focusSection=%d focusIndex=%d bumpCursor=%d", model.focusSection, model.focusIndex, model.bumpCursor)
|
||||
}
|
||||
|
||||
next, _ = model.Update(tea.KeyMsg{Type: tea.KeyDown})
|
||||
model = next.(commandPickerModel)
|
||||
next, _ = model.Update(tea.KeyMsg{Type: tea.KeyDown})
|
||||
model = next.(commandPickerModel)
|
||||
next, _ = model.Update(tea.KeyMsg{Type: tea.KeyDown})
|
||||
model = next.(commandPickerModel)
|
||||
if model.focusSection != 1 || model.focusIndex != 4 || model.channelCursor != 0 {
|
||||
t.Fatalf("after moving into channel section: focusSection=%d focusIndex=%d channelCursor=%d", model.focusSection, model.focusIndex, model.channelCursor)
|
||||
}
|
||||
|
||||
next, _ = model.Update(tea.KeyMsg{Type: tea.KeySpace})
|
||||
model = next.(commandPickerModel)
|
||||
if model.channelCursor != 0 {
|
||||
t.Fatalf("space changed selection unexpectedly: channelCursor=%d", model.channelCursor)
|
||||
}
|
||||
|
||||
next, _ = model.Update(tea.KeyMsg{Type: tea.KeyUp})
|
||||
model = next.(commandPickerModel)
|
||||
if model.focusSection != 0 || model.focusIndex != 3 || model.bumpCursor != 0 {
|
||||
t.Fatalf("after moving back up: focusSection=%d focusIndex=%d bumpCursor=%d", model.focusSection, model.focusIndex, model.bumpCursor)
|
||||
}
|
||||
|
||||
next, _ = model.Update(tea.KeyMsg{Type: tea.KeySpace})
|
||||
model = next.(commandPickerModel)
|
||||
if model.bumpCursor != 3 {
|
||||
t.Fatalf("space did not update bump selection: bumpCursor=%d", model.bumpCursor)
|
||||
}
|
||||
}
|
||||
|
||||
func findOptionByCommand(options []CommandOption, command string) (CommandOption, bool) {
|
||||
for _, option := range options {
|
||||
if option.Command == command {
|
||||
|
||||
Reference in New Issue
Block a user