Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ require (
github.com/charmbracelet/bubbletea v1.3.10 // MIT
github.com/charmbracelet/huh v1.0.0 // MIT
github.com/charmbracelet/lipgloss v1.1.0 // MIT
github.com/charmbracelet/x/ansi v0.11.6 // MIT
github.com/databricks/databricks-sdk-go v0.126.0 // Apache-2.0
github.com/fatih/color v1.19.0 // MIT
github.com/google/jsonschema-go v0.4.2 // MIT
Expand Down Expand Up @@ -55,7 +56,6 @@ require (
github.com/catppuccin/go v0.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charmbracelet/colorprofile v0.4.1 // indirect
github.com/charmbracelet/x/ansi v0.11.6 // indirect
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect
Expand Down
108 changes: 108 additions & 0 deletions libs/apps/prompt/prompt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,27 @@ package prompt
import (
"context"
"errors"
"strings"
"testing"
"time"

tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/huh"
"github.com/charmbracelet/x/ansi"
"github.com/databricks/cli/libs/apps/manifest"
"github.com/databricks/cli/libs/cmdio"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// keys creates a tea.KeyMsg for simulating keyboard input in tests.
func keys(runes ...rune) tea.KeyMsg {
return tea.KeyMsg{
Type: tea.KeyRunes,
Runes: runes,
}
}

func TestValidateProjectName(t *testing.T) {
tests := []struct {
name string
Expand Down Expand Up @@ -310,3 +322,99 @@ func TestMaxAppNameLength(t *testing.T) {
assert.Len(t, invalidName, 27)
assert.Error(t, ValidateProjectName(invalidName))
}

// initForm runs the form's Init command and feeds the resulting message back
// through Update so the form picks up its initial layout (window size, focus).
// f.Update(f.Init()) silently swallows the cmd because Init returns a tea.Cmd
// (a function), not a tea.Msg, so the form never receives the WindowSizeMsg
// it needs to render.
func initForm(t *testing.T, f *huh.Form) {
t.Helper()
cmd := f.Init()
if cmd != nil {
if msg := cmd(); msg != nil {
f.Update(msg)
}
}
// huh derives layout from a window size; without one the help line
// and titles can be clipped or omitted.
f.Update(tea.WindowSizeMsg{Width: 80, Height: 24})
}

// newWarehouseSelect builds a huh.Select identical in shape to the one
// constructed inside promptFromListWithLabel. Production and tests share this
// builder so that future regressions to the production prompt's title or
// description show up in test output.
func newWarehouseSelect(title, description string, options ...string) *huh.Select[string] {
return huh.NewSelect[string]().
Options(huh.NewOptions(options...)...).
Title(title).
Description(description).
Height(8)
}

// TestSelectTitleVisibleWithoutFiltering verifies that a Select field renders
// its Title on the initial view when Filtering is not activated. This is the
// behavior after the fix: the Title is always visible.
func TestSelectTitleVisibleWithoutFiltering(t *testing.T) {
field := newWarehouseSelect(
"Select SQL Warehouse",
"3 available — press / to filter",
"Warehouse A", "Warehouse B", "Warehouse C",
)

f := huh.NewForm(huh.NewGroup(field))
initForm(t, f)

view := ansi.Strip(f.View())

assert.Contains(t, view, "Select SQL Warehouse", "Title should be visible in initial render")
assert.Contains(t, view, "press / to filter", "Description should be visible")
assert.Contains(t, view, "Warehouse A", "First option should be visible")
}

// TestSelectSlashKeyActivatesFilter verifies that pressing '/' activates
// filtering even without Filtering(true), and that it filters options. The
// title remains visible after activation, which is the regression this PR
// guards against.
func TestSelectSlashKeyActivatesFilter(t *testing.T) {
field := newWarehouseSelect("Select fruit", "", "Apple", "Apricot", "Banana")

f := huh.NewForm(huh.NewGroup(field))
initForm(t, f)

// Title visible before filtering.
view := ansi.Strip(f.View())
assert.Contains(t, view, "Select fruit")
assert.Contains(t, view, "Banana")

// Press '/' to start filtering, then type 'B'.
m, _ := f.Update(keys('/'))
f = m.(*huh.Form)
m, _ = f.Update(keys('B'))
f = m.(*huh.Form)

view = ansi.Strip(f.View())

// Once filter mode is active huh replaces the title with the filter input
// — that is upstream behavior. The fix this PR enforces is that the title
// is visible BEFORE the user presses '/', not after.
assert.Contains(t, view, "Banana", "Banana should match filter 'B'")
assert.NotContains(t, view, "Apple", "Apple should be filtered out")
}

// TestSelectHelpShowsFilterHint verifies the help text includes a filter hint.
func TestSelectHelpShowsFilterHint(t *testing.T) {
field := newWarehouseSelect("Pick", "", "A", "B")

f := huh.NewForm(huh.NewGroup(field))
initForm(t, f)

view := ansi.Strip(f.View())

// huh's default keymap shows "/ filter" in the help line.
assert.True(t,
strings.Contains(view, "/ filter") || strings.Contains(view, "filter"),
"Help text should mention filtering is available via '/'",
)
}