Files
kportal/internal/ui/wizard_state_test.go
T
lukaszraczylo 7df161aee0 bugfixes nov2025 (#3)
* Fix enter misbehaving.
* Cleanup after previous tui implementation.
* Fix race condition and improve logging
* Add filtering of the namespaces by text input in the wizard UI
2025-11-24 11:09:23 +00:00

351 lines
7.9 KiB
Go

package ui
import (
"testing"
"github.com/nvm/kportal/internal/k8s"
"github.com/stretchr/testify/assert"
)
func TestFilterStrings(t *testing.T) {
tests := []struct {
name string
items []string
filter string
expected []string
}{
{
name: "empty filter returns all items",
items: []string{"namespace-1", "namespace-2", "namespace-3"},
filter: "",
expected: []string{"namespace-1", "namespace-2", "namespace-3"},
},
{
name: "filter matches multiple items",
items: []string{"prod-api", "prod-db", "staging-api", "dev-api"},
filter: "prod",
expected: []string{"prod-api", "prod-db"},
},
{
name: "filter matches single item",
items: []string{"namespace-1", "namespace-2", "namespace-3"},
filter: "2",
expected: []string{"namespace-2"},
},
{
name: "filter matches no items",
items: []string{"namespace-1", "namespace-2", "namespace-3"},
filter: "xyz",
expected: []string{},
},
{
name: "case insensitive matching",
items: []string{"Production", "Staging", "Development"},
filter: "prod",
expected: []string{"Production"},
},
{
name: "partial string matching",
items: []string{"my-app-frontend", "my-app-backend", "other-service"},
filter: "app",
expected: []string{"my-app-frontend", "my-app-backend"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := filterStrings(tt.items, tt.filter)
assert.Equal(t, tt.expected, result)
})
}
}
func TestMatchesFilter(t *testing.T) {
tests := []struct {
name string
item string
filter string
expected bool
}{
{
name: "empty filter matches everything",
item: "namespace-1",
filter: "",
expected: true,
},
{
name: "exact match",
item: "namespace-1",
filter: "namespace-1",
expected: true,
},
{
name: "partial match",
item: "production-api",
filter: "prod",
expected: true,
},
{
name: "no match",
item: "namespace-1",
filter: "xyz",
expected: false,
},
{
name: "case insensitive match",
item: "Production",
filter: "prod",
expected: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := matchesFilter(tt.item, tt.filter)
assert.Equal(t, tt.expected, result)
})
}
}
func TestGetFilteredContexts(t *testing.T) {
wizard := &AddWizardState{
contexts: []string{"prod-cluster", "staging-cluster", "dev-cluster", "test-cluster"},
}
tests := []struct {
name string
filter string
expected []string
}{
{
name: "no filter returns all",
filter: "",
expected: []string{"prod-cluster", "staging-cluster", "dev-cluster", "test-cluster"},
},
{
name: "filter by 'prod'",
filter: "prod",
expected: []string{"prod-cluster"},
},
{
name: "filter by 'cluster'",
filter: "cluster",
expected: []string{"prod-cluster", "staging-cluster", "dev-cluster", "test-cluster"},
},
{
name: "filter by 'staging'",
filter: "staging",
expected: []string{"staging-cluster"},
},
{
name: "filter with no matches",
filter: "xyz",
expected: []string{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
wizard.searchFilter = tt.filter
result := wizard.getFilteredContexts()
assert.Equal(t, tt.expected, result)
})
}
}
func TestGetFilteredNamespaces(t *testing.T) {
wizard := &AddWizardState{
namespaces: []string{
"kube-system", "kube-public", "default",
"prod-api", "prod-db", "staging-api", "staging-db",
"monitoring", "logging",
},
}
tests := []struct {
name string
filter string
expected []string
}{
{
name: "no filter returns all",
filter: "",
expected: []string{
"kube-system", "kube-public", "default",
"prod-api", "prod-db", "staging-api", "staging-db",
"monitoring", "logging",
},
},
{
name: "filter by 'prod'",
filter: "prod",
expected: []string{"prod-api", "prod-db"},
},
{
name: "filter by 'kube'",
filter: "kube",
expected: []string{"kube-system", "kube-public"},
},
{
name: "filter by 'api'",
filter: "api",
expected: []string{"prod-api", "staging-api"},
},
{
name: "filter by 'ing' (partial match)",
filter: "ing",
expected: []string{"staging-api", "staging-db", "monitoring", "logging"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
wizard.searchFilter = tt.filter
result := wizard.getFilteredNamespaces()
assert.Equal(t, tt.expected, result)
})
}
}
func TestGetFilteredServices(t *testing.T) {
wizard := &AddWizardState{
services: []k8s.ServiceInfo{
{Name: "api-gateway"},
{Name: "api-backend"},
{Name: "database"},
{Name: "redis-cache"},
{Name: "postgres-db"},
},
}
tests := []struct {
name string
filter string
expected []string
}{
{
name: "no filter returns all",
filter: "",
expected: []string{"api-gateway", "api-backend", "database", "redis-cache", "postgres-db"},
},
{
name: "filter by 'api'",
filter: "api",
expected: []string{"api-gateway", "api-backend"},
},
{
name: "filter by 'db'",
filter: "db",
expected: []string{"postgres-db"},
},
{
name: "filter by 'base'",
filter: "base",
expected: []string{"database"},
},
{
name: "filter by 'redis'",
filter: "redis",
expected: []string{"redis-cache"},
},
{
name: "filter with no matches",
filter: "xyz",
expected: []string{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
wizard.searchFilter = tt.filter
result := wizard.getFilteredServices()
resultNames := make([]string, len(result))
for i, svc := range result {
resultNames[i] = svc.Name
}
assert.Equal(t, tt.expected, resultNames)
})
}
}
func TestClearSearchFilter(t *testing.T) {
wizard := &AddWizardState{
searchFilter: "test",
cursor: 5,
scrollOffset: 10,
}
wizard.clearSearchFilter()
assert.Equal(t, "", wizard.searchFilter, "searchFilter should be cleared")
assert.Equal(t, 0, wizard.cursor, "cursor should be reset to 0")
assert.Equal(t, 0, wizard.scrollOffset, "scrollOffset should be reset to 0")
}
func TestMoveCursorWithFilteredLists(t *testing.T) {
tests := []struct {
name string
step AddWizardStep
contexts []string
namespaces []string
searchFilter string
initialCursor int
delta int
expectedCursor int
}{
{
name: "move down in filtered contexts",
step: StepSelectContext,
contexts: []string{"prod-1", "prod-2", "staging-1", "dev-1"},
searchFilter: "prod",
initialCursor: 0,
delta: 1,
expectedCursor: 1,
},
{
name: "cannot move beyond filtered list",
step: StepSelectContext,
contexts: []string{"prod-1", "prod-2", "staging-1", "dev-1"},
searchFilter: "prod",
initialCursor: 1,
delta: 1,
expectedCursor: 1, // Should stay at 1 (last item in filtered list)
},
{
name: "move up in filtered list",
step: StepSelectNamespace,
namespaces: []string{"ns-1", "ns-2", "ns-3", "other"},
searchFilter: "ns",
initialCursor: 2,
delta: -1,
expectedCursor: 1,
},
{
name: "cannot move above 0",
step: StepSelectNamespace,
namespaces: []string{"ns-1", "ns-2", "ns-3"},
searchFilter: "ns",
initialCursor: 0,
delta: -1,
expectedCursor: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
wizard := &AddWizardState{
step: tt.step,
inputMode: InputModeList,
cursor: tt.initialCursor,
contexts: tt.contexts,
namespaces: tt.namespaces,
searchFilter: tt.searchFilter,
}
wizard.moveCursor(tt.delta)
assert.Equal(t, tt.expectedCursor, wizard.cursor)
})
}
}